From d77a518cf00f086aed97e10163d2f83340d67c14 Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Mon, 24 Mar 2025 11:08:35 +0100 Subject: [PATCH] ui: change language selection ui --- .../ProfileSettings/ProfileSettings.tsx | 122 ++++++++---------- .../LanguageSwitcher/LanguageSwitcher.tsx | 67 ++++++---- frontend/app/layout/LangBanner.tsx | 29 +++++ frontend/app/layout/TopHeader.tsx | 101 +++++++++------ frontend/app/layout/TopRight.tsx | 10 +- frontend/app/locales/en.json | 5 +- frontend/app/locales/es.json | 5 +- frontend/app/locales/fr.json | 5 +- frontend/app/locales/ru.json | 5 +- frontend/app/locales/zh.json | 5 +- 10 files changed, 208 insertions(+), 146 deletions(-) create mode 100644 frontend/app/layout/LangBanner.tsx diff --git a/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx b/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx index ab7c31460..96d98d081 100644 --- a/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx +++ b/frontend/app/components/Client/ProfileSettings/ProfileSettings.tsx @@ -3,6 +3,7 @@ import withPageTitle from 'HOCs/withPageTitle'; import { PageTitle } from 'UI'; import { observer } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; +import LanguageSwitcher from "App/components/LanguageSwitcher"; import Settings from './Settings'; import ChangePassword from './ChangePassword'; import styles from './profileSettings.module.css'; @@ -20,107 +21,90 @@ function ProfileSettings() { return (
{t('Account')}
} /> -
-
-

{t('Profile')}

-
- {t( - 'Your email address is your identity on OpenReplay and is used to login.', - )} -
-
-
- -
-
+
} + />
{account.hasPassword && ( <> -
-
-

{t('Change Password')}

-
- {t('Updating your password from time to time enhances your account’s security.')} -
-
-
- -
-
+
} + />
)} -
-
-

{t('Organization API Key')}

-
- {t('Your API key gives you access to an extra set of services.')} -
-
-
- -
-
+
} + /> + +
} + /> {isEnterprise && (account.admin || account.superAdmin) && ( <>
-
-
-

{t('Tenant Key')}

-
- {t('For SSO (SAML) authentication.')} -
-
-
- -
-
+
} + /> )} {!isEnterprise && ( <>
-
-
-

{t('Data Collection')}

-
- {t('Enables you to control how OpenReplay captures data on your organization’s usage to improve our product.')} -
-
-
- -
-
+
} + /> )} {account.license && ( <>
- -
-
-

{t('License')}

-
- {t('License key and expiration date.')} -
-
-
- -
-
+
} /> )}
); } +function Section({ title, description, children }: { + title: string; + description: string; + children: React.ReactNode; +}) { + return ( +
+
+

{title}

+
+ {description} +
+
+
+ {children} +
+
+ ) +} + export default withPageTitle('Account - OpenReplay Preferences')( observer(ProfileSettings), ); diff --git a/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx b/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx index 225d2ba3b..e408d6980 100644 --- a/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx +++ b/frontend/app/components/LanguageSwitcher/LanguageSwitcher.tsx @@ -1,9 +1,7 @@ -import { Button, Dropdown, MenuProps, Space, Typography } from 'antd'; -import React, { useCallback, useState } from 'react'; +import { Button, Dropdown, MenuProps, Typography } from 'antd'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { CaretDownOutlined } from '@ant-design/icons'; -import { Languages } from 'lucide-react'; -import { Icon } from '../ui'; +import { ChevronDown } from 'lucide-react'; const langs = [ { code: 'en', label: 'English' }, @@ -12,14 +10,25 @@ const langs = [ { code: 'ru', label: 'Русский' }, { code: 'zh', label: '中國人' }, ]; +const langLabels = { + en: 'English', + fr: 'Français', + es: 'Español', + ru: 'Русский', + zh: '中國人', +} function LanguageSwitcher() { const { i18n } = useTranslation(); + const [selected, setSelected] = React.useState(i18n.language); - const handleChangeLanguage = useCallback((lang: string) => { - i18n.changeLanguage(lang); - localStorage.setItem('i18nextLng', lang); - }, []); + const onChange = (val: string) => { + setSelected(val) + } + const handleChangeLanguage = () => { + void i18n.changeLanguage(selected) + localStorage.setItem('i18nextLng', selected) + } const menuItems: MenuProps['items'] = langs.map((lang) => ({ key: lang.code, @@ -31,21 +40,31 @@ function LanguageSwitcher() { })); return ( - handleChangeLanguage(e.key), - }} - placement="bottomLeft" - > - + + +
); } diff --git a/frontend/app/layout/LangBanner.tsx b/frontend/app/layout/LangBanner.tsx new file mode 100644 index 000000000..04472ce7a --- /dev/null +++ b/frontend/app/layout/LangBanner.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { + Languages, X +} from 'lucide-react' +import { Button } from 'antd'; +import { useHistory } from "react-router-dom"; +import { client } from 'App/routes' + +function LangBanner({ onClose }: { onClose: () => void }) { + const history = useHistory() + + const onClick = () => { + history.push(client('account')) + } + return ( +
+
+ OpenReplay now supports French, Russian, Chinese, and Spanish 🎉. Update your language in settings. +
+
+ +
+ ) +} + +export default LangBanner \ No newline at end of file diff --git a/frontend/app/layout/TopHeader.tsx b/frontend/app/layout/TopHeader.tsx index 07a41bd28..045098637 100644 --- a/frontend/app/layout/TopHeader.tsx +++ b/frontend/app/layout/TopHeader.tsx @@ -1,6 +1,7 @@ import { Layout, Space, Tooltip } from 'antd'; import { observer } from 'mobx-react-lite'; import React, { useEffect } from 'react'; +import LangBanner from './LangBanner'; import { INDEXES } from 'App/constants/zindex'; import Logo from 'App/layout/Logo'; @@ -11,14 +12,27 @@ import { useTranslation } from 'react-i18next'; const { Header } = Layout; +const langBannerClosedKey = '__or__langBannerClosed'; +const getLangBannerClosed = () => localStorage.getItem(langBannerClosedKey) === '1' function TopHeader() { const { userStore, notificationStore, projectsStore, settingsStore } = useStore(); const { account } = userStore; const { siteId } = projectsStore; const { initialDataFetched } = userStore; + const [langBannerClosed, setLangBannerClosed] = React.useState(getLangBannerClosed); const { t } = useTranslation(); + React.useEffect(() => { + const langBannerVal = localStorage.getItem(langBannerClosedKey); + if (langBannerVal === null) { + localStorage.setItem(langBannerClosedKey, '0') + } + if (langBannerVal === '0') { + localStorage.setItem(langBannerClosedKey, '1') + } + }, []) + useEffect(() => { if (!account.id || initialDataFetched) return; Promise.all([ @@ -29,51 +43,58 @@ function TopHeader() { }); }, [account]); + const closeLangBanner = () => { + setLangBannerClosed(true); + localStorage.setItem(langBannerClosedKey, '1'); + } return ( -
- -
{ - settingsStore.updateMenuCollapsed(!settingsStore.menuCollapsed); - }} - style={{ paddingTop: '4px' }} - className="cursor-pointer xl:block hidden" - > - + {langBannerClosed ? null : } +
+ +
{ + settingsStore.updateMenuCollapsed(!settingsStore.menuCollapsed); + }} + style={{ paddingTop: '4px' }} + className="cursor-pointer xl:block hidden" > - - -
+ mouseEnterDelay={1} + > + + +
-
- -
-
+
+ +
+ - -
+ + + ); } diff --git a/frontend/app/layout/TopRight.tsx b/frontend/app/layout/TopRight.tsx index 313a960db..8c0c3da29 100644 --- a/frontend/app/layout/TopRight.tsx +++ b/frontend/app/layout/TopRight.tsx @@ -11,18 +11,13 @@ import ProjectDropdown from 'Shared/ProjectDropdown'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; -interface Props { - account: any; - spotOnly?: boolean; -} - -function TopRight(props: Props) { +function TopRight() { const { userStore } = useStore(); const spotOnly = userStore.scopeState === 1; const { account } = userStore; return ( - {props.spotOnly ? null : ( + {spotOnly ? null : ( <> @@ -30,7 +25,6 @@ function TopRight(props: Props) { {account.name ? : null} - )} diff --git a/frontend/app/locales/en.json b/frontend/app/locales/en.json index 57035348f..80292d6be 100644 --- a/frontend/app/locales/en.json +++ b/frontend/app/locales/en.json @@ -1498,5 +1498,8 @@ "More attribute": "More attribute", "More attributes": "More attributes", "Account settings updated successfully": "Account settings updated successfully", - "Include rage clicks": "Include rage clicks" + "Include rage clicks": "Include rage clicks", + "Interface Language": "Interface Language", + "Select the language in which OpenReplay will appear.": "Select the language in which OpenReplay will appear.", + "Language": "Language" } diff --git a/frontend/app/locales/es.json b/frontend/app/locales/es.json index 65670ecd8..0d2c3c73b 100644 --- a/frontend/app/locales/es.json +++ b/frontend/app/locales/es.json @@ -1498,5 +1498,8 @@ "More attribute": "Más atributos", "More attributes": "Más atributos", "Account settings updated successfully": "Configuración de la cuenta actualizada correctamente", - "Include rage clicks": "Incluir clics de ira" + "Include rage clicks": "Incluir clics de ira", + "Interface Language": "Idioma de la interfaz", + "Select the language in which OpenReplay will appear.": "Selecciona el idioma en el que aparecerá OpenReplay.", + "Language": "Idioma" } diff --git a/frontend/app/locales/fr.json b/frontend/app/locales/fr.json index 3292c2423..91c535204 100644 --- a/frontend/app/locales/fr.json +++ b/frontend/app/locales/fr.json @@ -1498,5 +1498,8 @@ "More attribute": "Plus d'attributs", "More attributes": "Plus d'attributs", "Account settings updated successfully": "Paramètres du compte mis à jour avec succès", - "Include rage clicks": "Inclure les clics de rage" + "Include rage clicks": "Inclure les clics de rage", + "Interface Language": "Langue de l'interface", + "Select the language in which OpenReplay will appear.": "Sélectionnez la langue dans laquelle OpenReplay apparaîtra.", + "Language": "Langue" } diff --git a/frontend/app/locales/ru.json b/frontend/app/locales/ru.json index 80c720f3b..d713686d1 100644 --- a/frontend/app/locales/ru.json +++ b/frontend/app/locales/ru.json @@ -1498,5 +1498,8 @@ "More attribute": "Еще атрибут", "More attributes": "Еще атрибуты", "Account settings updated successfully": "Настройки аккаунта успешно обновлены", - "Include rage clicks": "Включить невыносимые клики" + "Include rage clicks": "Включить невыносимые клики", + "Interface Language": "Язык интерфейса", + "Select the language in which OpenReplay will appear.": "Выберите язык, на котором будет отображаться OpenReplay.", + "Language": "Язык" } diff --git a/frontend/app/locales/zh.json b/frontend/app/locales/zh.json index f60b057f9..236164820 100644 --- a/frontend/app/locales/zh.json +++ b/frontend/app/locales/zh.json @@ -1498,5 +1498,8 @@ "More attributes": "更多属性", "More attribute": "更多属性", "Account settings updated successfully": "帐户设置已成功更新", - "Include rage clicks": "包括点击狂怒" + "Include rage clicks": "包括点击狂怒", + "Interface Language": "界面语言", + "Select the language in which OpenReplay will appear.": "选择 OpenReplay 将显示的语言。", + "Language": "语言" }