From 7fc83a4fcd24aeadb249c946452672aad050d38d Mon Sep 17 00:00:00 2001 From: rvveber Date: Tue, 4 Mar 2025 16:08:39 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20the=20LanguagePicker=20no?= =?UTF-8?q?w=20uses=20config=20as=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config endpoint languages are used as available options for LanguagePicker - updating the language from it, triggers an update on the user via API --- .../e2e/__tests__/app-impress/config.spec.ts | 4 +- .../src/features/language/LanguagePicker.tsx | 48 +++++++++++++------ src/frontend/apps/impress/src/i18n/conf.ts | 7 --- .../apps/impress/src/i18n/initI18n.ts | 23 +++++---- src/frontend/apps/impress/src/pages/_app.tsx | 1 + 5 files changed, 52 insertions(+), 31 deletions(-) delete mode 100644 src/frontend/apps/impress/src/i18n/conf.ts diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts index 7c9639ba..4565cbe9 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts @@ -12,8 +12,8 @@ const config = { MEDIA_BASE_URL: 'http://localhost:8083', LANGUAGES: [ ['en-us', 'English'], - ['fr-fr', 'French'], - ['de-de', 'German'], + ['fr-fr', 'Français'], + ['de-de', 'Deutsch'], ], LANGUAGE_CODE: 'en-us', POSTHOG_KEY: {}, diff --git a/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx b/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx index 2a54f379..6bef7995 100644 --- a/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx +++ b/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx @@ -4,25 +4,45 @@ import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { DropdownMenu, Text } from '@/components/'; -import { LANGUAGES_ALLOWED } from '@/i18n/conf'; +import { useConfig } from '@/core'; + +import { useLanguageSynchronizer } from './hooks/useLanguageSynchronizer'; +import { getMatchingLocales } from './utils/locale'; export const LanguagePicker = () => { const { t, i18n } = useTranslation(); - const { preload: languages } = i18n.options; - const language = i18n.language; + const { data: conf } = useConfig(); + const { synchronizeLanguage } = useLanguageSynchronizer(); + const language = i18n.languages[0]; Settings.defaultLocale = language; + // Compute options for dropdown const optionsPicker = useMemo(() => { - return (languages || []).map((lang) => ({ - label: LANGUAGES_ALLOWED[lang], - isSelected: language === lang, - callback: () => { - i18n.changeLanguage(lang).catch((err) => { - console.error('Error changing language', err); - }); - }, - })); - }, [i18n, language, languages]); + const backendOptions = conf?.LANGUAGES ?? [[language, language]]; + return backendOptions.map(([backendLocale, label]) => { + // Determine if the option is selected + const isSelected = + getMatchingLocales([backendLocale], [language]).length > 0; + // Define callback for updating both frontend and backend languages + const callback = () => { + i18n + .changeLanguage(backendLocale) + .then(() => { + void synchronizeLanguage('toBackend'); + }) + .catch((err) => { + console.error('Error changing language', err); + }); + }; + return { label, isSelected, callback }; + }); + }, [conf, i18n, language, synchronizeLanguage]); + + // Extract current language label for display + const currentLanguageLabel = + conf?.LANGUAGES.find( + ([code]) => getMatchingLocales([code], [language]).length > 0, + )?.[1] || language; return ( { translate - {LANGUAGES_ALLOWED[language]} + {currentLanguageLabel} ); diff --git a/src/frontend/apps/impress/src/i18n/conf.ts b/src/frontend/apps/impress/src/i18n/conf.ts deleted file mode 100644 index dd42827c..00000000 --- a/src/frontend/apps/impress/src/i18n/conf.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const LANGUAGES_ALLOWED: { [key: string]: string } = { - en: 'English', - fr: 'Français', - de: 'Deutsch', -}; -export const LANGUAGE_COOKIE_NAME = 'docs_language'; -export const BASE_LANGUAGE = 'en'; diff --git a/src/frontend/apps/impress/src/i18n/initI18n.ts b/src/frontend/apps/impress/src/i18n/initI18n.ts index cc98586b..40700e64 100644 --- a/src/frontend/apps/impress/src/i18n/initI18n.ts +++ b/src/frontend/apps/impress/src/i18n/initI18n.ts @@ -1,27 +1,34 @@ -import i18n from 'i18next'; +import i18next from 'i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import { initReactI18next } from 'react-i18next'; -import { BASE_LANGUAGE, LANGUAGES_ALLOWED, LANGUAGE_COOKIE_NAME } from './conf'; import resources from './translations.json'; -i18n +export const availableFrontendLanguages: readonly string[] = + Object.keys(resources); + +i18next .use(LanguageDetector) .use(initReactI18next) .init({ resources, - fallbackLng: BASE_LANGUAGE, - supportedLngs: Object.keys(LANGUAGES_ALLOWED), + fallbackLng: 'en', + debug: false, detection: { order: ['cookie', 'navigator'], // detection order caches: ['cookie'], // Use cookies to store the language preference - lookupCookie: LANGUAGE_COOKIE_NAME, + lookupCookie: 'docs_language', cookieMinutes: 525600, // Expires after one year + cookieOptions: { + path: '/', + sameSite: 'lax', + }, }, interpolation: { escapeValue: false, }, - preload: Object.keys(LANGUAGES_ALLOWED), + preload: availableFrontendLanguages, + lowerCaseLng: true, nsSeparator: false, keySeparator: false, }) @@ -29,4 +36,4 @@ i18n throw new Error('i18n initialization failed'); }); -export default i18n; +export default i18next; diff --git a/src/frontend/apps/impress/src/pages/_app.tsx b/src/frontend/apps/impress/src/pages/_app.tsx index bd46e8c3..e93848f3 100644 --- a/src/frontend/apps/impress/src/pages/_app.tsx +++ b/src/frontend/apps/impress/src/pages/_app.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { AppProvider } from '@/core/'; import { useSWRegister } from '@/features/service-worker/'; +import '@/i18n/initI18n'; import { NextPageWithLayout } from '@/types/next'; import './globals.css';