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('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"
- >
- } />
-
+
+
{i18n.t('Language')}
+
onChange(e.key),
+ }}
+ >
+
+
+
+
);
}
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.
+
+
+
} size={'small'} onClick={onClick}>
+ Change Language
+
+
} type={'text'} shape={'circle'} onClick={onClose} size={'small'} />
+
+ )
+}
+
+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 : }
+
+
-
-
-
-
+
+
+
+
-
-
+
+
+ >
);
}
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": "语言"
}