Skip to content

Commit

Permalink
(web): lazy-load languages
Browse files Browse the repository at this point in the history
  • Loading branch information
cstenglein committed Oct 23, 2024
1 parent 6175088 commit 6221394
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 103 deletions.
82 changes: 38 additions & 44 deletions frontends/web/src/components/language/language.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ 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;
}
languages?: TLanguagesList;
};

const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => {

const { t, i18n } = useTranslation();
const allLanguages = languages || defaultLanguages;

const [selectedIndex, setSelectedIndex] = useState<number>(getSelectedIndex(allLanguages, i18n));
const [activeDialog, setActiveDialog] = useState<boolean>(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) {
Expand All @@ -46,11 +46,7 @@ const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => {

return (
<div>
<button
type="button"
title="Select Language"
className={style.link}
onClick={() => setActiveDialog(true)}>
<button type="button" title="Select Language" className={style.link} onClick={() => setActiveDialog(true)}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
Expand All @@ -60,47 +56,45 @@ const LanguageSwitch = ({ languages }: TLanguageSwitchProps) => {
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
{allLanguages[selectedIndex].code === 'en' ? 'Other languages' : 'English'}
</button>
<Dialog small slim title={t('language.title')} onClose={() => setActiveDialog(false)} open={activeDialog}>
{
allLanguages.map((language, i) => {
const selected = selectedIndex === i;
return (
<button
type="button"
key={language.code}
className={[style.language, selected ? style.selected : ''].join(' ')}
onClick={() => changeLanguage(language.code, i)}
data-testid={`language-selection-${language.code}`}
>
{language.display}
{
selected && (
<svg
className={style.checked}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
)
}
</button>
);
})
}
{allLanguages.map((language, i) => {
const selected = selectedIndex === i;
return (
<button
type="button"
key={language.code}
className={[style.language, selected ? style.selected : ''].join(' ')}
onClick={() => changeLanguage(language.code, i)}
data-testid={`language-selection-${language.code}`}
>
{language.display}
{selected && (
<svg
className={style.checked}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
)}
</button>
);
})}
</Dialog>
</div>
);
Expand Down
118 changes: 60 additions & 58 deletions frontends/web/src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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
Expand All @@ -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 };
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -41,7 +42,7 @@ export const LanguageDropdownSetting = () => {
extraComponent={
<SingleDropdown
options={formattedLanguages}
handleChange={i18n.changeLanguage}
handleChange={changei18nLanguage}
value={{ label: selectedLanguage.display, value: selectedLanguage.code }}
/>
}
Expand Down

0 comments on commit 6221394

Please sign in to comment.