diff --git a/frontends/web/src/components/language/language.tsx b/frontends/web/src/components/language/language.tsx index fbf92ffcb8..8b5f268f2f 100644 --- a/frontends/web/src/components/language/language.tsx +++ b/frontends/web/src/components/language/language.tsx @@ -21,6 +21,7 @@ import { Dialog } from '@/components/dialog/dialog'; import { defaultLanguages, TActiveLanguageCodes, TLanguagesList } from './types'; import style from './language.module.css'; import { getSelectedIndex } from '@/utils/language'; +import { changei18nLanguage } from '@/i18n/i18n'; type TLanguageSwitchProps = { languages?: TLanguagesList; @@ -34,10 +35,10 @@ const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => { const [selectedIndex, setSelectedIndex] = useState(getSelectedIndex(allLanguages, i18n)); const [activeDialog, setActiveDialog] = useState(false); - const changeLanguage = (langCode: TActiveLanguageCodes, index: number) => { + const changeLanguage = async (langCode: TActiveLanguageCodes, index: number) => { setSelectedIndex(index); setActiveDialog(false); - i18n.changeLanguage(langCode); + await changei18nLanguage(langCode); }; if (allLanguages.length === 1) { @@ -106,4 +107,4 @@ const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => { ); }; -export { LanguageSwitch }; +export { LanguageSwitch }; \ No newline at end of file diff --git a/frontends/web/src/i18n/i18n.ts b/frontends/web/src/i18n/i18n.ts index 217d046a11..664263c1d3 100644 --- a/frontends/web/src/i18n/i18n.ts +++ b/frontends/web/src/i18n/i18n.ts @@ -17,36 +17,58 @@ import i18n from 'i18next'; import { getNativeLocale } from '@/api/nativelocale'; -import appTranslationsAR from '@/locales/ar/app.json'; -import appTranslationsCS from '@/locales/cs/app.json'; -import appTranslationsDE from '@/locales/de/app.json'; -import appTranslationsEN from '@/locales/en/app.json'; -import appTranslationsFR from '@/locales/fr/app.json'; -import appTranslationsJA from '@/locales/ja/app.json'; -import appTranslationsRU from '@/locales/ru/app.json'; -import appTranslationsMS from '@/locales/ms/app.json'; -import appTranslationsNL from '@/locales/nl/app.json'; -import appTranslationsPT from '@/locales/pt/app.json'; -import appTranslationsHI from '@/locales/hi/app.json'; -import appTranslationsBG from '@/locales/bg/app.json'; -import appTranslationsTR from '@/locales/tr/app.json'; -import appTranslationsZH from '@/locales/zh/app.json'; -import appTranslationsFA from '@/locales/fa/app.json'; -import appTranslationsES from '@/locales/es/app.json'; -import appTranslationsSL from '@/locales/sl/app.json'; -import appTranslationsHE from '@/locales/he/app.json'; -import appTranslationsIT from '@/locales/it/app.json'; import { languageFromConfig } from './config'; import { localeMainLanguage } from './utils'; import { setConfig } from '@/utils/config'; const locizeProjectID = 'fe4e5a24-e4a2-4903-96fc-3d62c11fc502'; -let i18Init = i18n - .use(languageFromConfig); +const defaultLang = 'en'; + +const languageResources = { + ar: () => import('@/locales/ar/app.json'), + cs: () => import('@/locales/cs/app.json'), + de: () => import('@/locales/de/app.json'), + en: () => import('@/locales/en/app.json'), + fr: () => import('@/locales/fr/app.json'), + ja: () => import('@/locales/ja/app.json'), + ru: () => import('@/locales/ru/app.json'), + ms: () => import('@/locales/ms/app.json'), + nl: () => import('@/locales/nl/app.json'), + pt: () => import('@/locales/pt/app.json'), + hi: () => import('@/locales/hi/app.json'), + bg: () => import('@/locales/bg/app.json'), + tr: () => import('@/locales/tr/app.json'), + zh: () => import('@/locales/zh/app.json'), + fa: () => import('@/locales/fa/app.json'), + es: () => import('@/locales/es/app.json'), + sl: () => import('@/locales/sl/app.json'), + he: () => import('@/locales/he/app.json'), + it: () => import('@/locales/it/app.json') +}; + +type LanguageKey = keyof typeof languageResources; + +export const loadLanguage = async (language: string) => { + try { + const resources = await languageResources[language as LanguageKey](); + if (!i18n.hasResourceBundle(language, 'app')) { + i18n.addResourceBundle(language, 'app', resources.default || resources); + } + } catch (error) { + console.error(`Failed to load language resources for ${language}:`, error); + } +}; + +export const changei18nLanguage = async (language: string) => { + await loadLanguage(language); + await i18n.changeLanguage(language); +}; + +let i18Init = i18n.use(languageFromConfig); i18Init.init({ - fallbackLng: 'en', + fallbackLng: defaultLang, // have a common namespace used around the full app ns: ['app', 'wallet'], @@ -59,36 +81,19 @@ i18Init.init({ }, react: { - useSuspense : true, // Not using Suspense you will need to handle the not ready state yourself + useSuspense: true // Not using Suspense you will need to handle the not ready state yourself }, backend: { projectId: locizeProjectID, - referenceLng: 'en' - }, + referenceLng: defaultLang + } }); -i18n.addResourceBundle('ar', 'app', appTranslationsAR); -i18n.addResourceBundle('cs', 'app', appTranslationsCS); -i18n.addResourceBundle('de', 'app', appTranslationsDE); -i18n.addResourceBundle('en', 'app', appTranslationsEN); -i18n.addResourceBundle('fr', 'app', appTranslationsFR); -i18n.addResourceBundle('ja', 'app', appTranslationsJA); -i18n.addResourceBundle('ms', 'app', appTranslationsMS); -i18n.addResourceBundle('nl', 'app', appTranslationsNL); -i18n.addResourceBundle('ru', 'app', appTranslationsRU); -i18n.addResourceBundle('pt', 'app', appTranslationsPT); -i18n.addResourceBundle('hi', 'app', appTranslationsHI); -i18n.addResourceBundle('bg', 'app', appTranslationsBG); -i18n.addResourceBundle('tr', 'app', appTranslationsTR); -i18n.addResourceBundle('zh', 'app', appTranslationsZH); -i18n.addResourceBundle('fa', 'app', appTranslationsFA); -i18n.addResourceBundle('es', 'app', appTranslationsES); -i18n.addResourceBundle('sl', 'app', appTranslationsSL); -i18n.addResourceBundle('he', 'app', appTranslationsHE); -i18n.addResourceBundle('it', 'app', appTranslationsIT); - -i18n.on('languageChanged', (lng) => { +// load the default language first +loadLanguage(defaultLang); + +i18n.on('languageChanged', async (lng) => { // Set userLanguage in config back to empty if system locale matches // the newly selected language lng to make the app use native-locale again. // This also covers partial matches. For example, if native locale is pt_BR @@ -97,18 +102,15 @@ i18n.on('languageChanged', (lng) => { // Since userLanguage is stored in the backend config as a string, // setting it to null here in JS turns it into an empty string "" in Go backend. // This is ok since we're just checking for a truthy value in the language detector. - return getNativeLocale().then((nativeLocale) => { - let match = lng === nativeLocale; - if (!match) { - // There are too many combinations. So, we compare only the main - // language tag. - const lngLang = localeMainLanguage(lng); - const localeLang = localeMainLanguage(nativeLocale); - match = lngLang === localeLang; - } - const uiLang = match ? null : lng; - return setConfig({ backend: { userLanguage: uiLang } }); - }); + const nativeLocale = await getNativeLocale(); + let match = lng === nativeLocale; + if (!match) { + const lngLang = localeMainLanguage(lng); + const localeLang = localeMainLanguage(nativeLocale); + match = lngLang === localeLang; + } + const uiLang = match ? null : lng; + return setConfig({ backend: { userLanguage: uiLang } }); }); export { i18n }; diff --git a/frontends/web/src/routes/settings/components/appearance/languageDropdownSetting.tsx b/frontends/web/src/routes/settings/components/appearance/languageDropdownSetting.tsx index 58451256b1..b74128f18a 100644 --- a/frontends/web/src/routes/settings/components/appearance/languageDropdownSetting.tsx +++ b/frontends/web/src/routes/settings/components/appearance/languageDropdownSetting.tsx @@ -24,6 +24,7 @@ import { SingleDropdown } from '@/routes/settings/components/dropdowns/singledro import { GlobeDark, GlobeLight } from '@/components/icon/icon'; import { useDarkmode } from '@/hooks/darkmode'; import styles from './languageDropDownSetting.module.css'; +import { changei18nLanguage } from '@/i18n/i18n'; export const LanguageDropdownSetting = () => { const { i18n, t } = useTranslation(); @@ -41,7 +42,7 @@ export const LanguageDropdownSetting = () => { extraComponent={ }