From 56d8c8eba6959266aac6dc3e0838d67ad2fbeef8 Mon Sep 17 00:00:00 2001 From: soroush5 Date: Sun, 13 Sep 2026 15:35:46 +0330 Subject: [PATCH] fix(frontend): persist UI locale so new tabs keep the selected language --- frontend/src/index.js | 5 +-- .../__tests__/i18nPersistence.test.js | 32 +++++++++++++++++++ frontend/src/utilities/i18n.config.js | 18 +++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 frontend/src/utilities/__tests__/i18nPersistence.test.js diff --git a/frontend/src/index.js b/frontend/src/index.js index 899c161024..66ff528485 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -12,7 +12,7 @@ import { client, currentUserVar } from './client' import canada from './theme/canada' import { UserVarProvider, useUserVar } from './utilities/userState' import { REFRESH_TOKENS } from './graphql/mutations' -import { activate, defaultLocale } from './utilities/i18n.config' +import { activate, defaultLocale, getStoredLocale } from './utilities/i18n.config' import { IS_LOGIN_REQUIRED } from './graphql/queries' import { TourProvider } from './userOnboarding/contexts/TourContext' @@ -98,7 +98,8 @@ const I18nApp = () => { const setUpApp = async () => { await activate( - ['en', 'fr'].includes(window.env?.APP_DEFAULT_LANGUAGE) ? window.env?.APP_DEFAULT_LANGUAGE : defaultLocale, + getStoredLocale() ?? + (['en', 'fr'].includes(window.env?.APP_DEFAULT_LANGUAGE) ? window.env?.APP_DEFAULT_LANGUAGE : defaultLocale), ) const root = createRoot(document.getElementById('root')) diff --git a/frontend/src/utilities/__tests__/i18nPersistence.test.js b/frontend/src/utilities/__tests__/i18nPersistence.test.js new file mode 100644 index 0000000000..6b600088b3 --- /dev/null +++ b/frontend/src/utilities/__tests__/i18nPersistence.test.js @@ -0,0 +1,32 @@ +import { i18n } from '@lingui/core' + +import { activate, getStoredLocale } from '../i18n.config' + +describe('locale persistence', () => { + beforeEach(() => { + window.localStorage.clear() + }) + + it('returns null when nothing was stored', () => { + expect(getStoredLocale()).toBeNull() + }) + + it('returns the stored locale', () => { + window.localStorage.setItem('locale', 'fr') + expect(getStoredLocale()).toEqual('fr') + }) + + it('ignores an invalid stored value', () => { + window.localStorage.setItem('locale', 'de') + expect(getStoredLocale()).toBeNull() + }) + + it('persists the activated locale for the next tab', async () => { + await activate('fr') + expect(i18n.locale).toEqual('fr') + expect(window.localStorage.getItem('locale')).toEqual('fr') + + await activate('en') + expect(window.localStorage.getItem('locale')).toEqual('en') + }) +}) diff --git a/frontend/src/utilities/i18n.config.js b/frontend/src/utilities/i18n.config.js index 5a9ec058b2..3cab3a943a 100644 --- a/frontend/src/utilities/i18n.config.js +++ b/frontend/src/utilities/i18n.config.js @@ -9,6 +9,17 @@ export const locales = { fr: 'Français', } +const LOCALE_STORAGE_KEY = 'locale' + +export function getStoredLocale() { + try { + const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY) + return ['en', 'fr'].includes(stored) ? stored : null + } catch (e) { + return null + } +} + export async function activate(locale) { let catalog try { @@ -22,6 +33,13 @@ export async function activate(locale) { i18n.load(locale, catalog.messages) i18n.activate(locale) + + try { + window.localStorage.setItem(LOCALE_STORAGE_KEY, locale) + } catch (e) { + // storage unavailable (e.g. private mode) — app still works, + // the choice just won't survive to the next tab. + } } let defaultLanguage