From 18ffd812b8fb9279a713020c512097353598f7e8 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Fri, 6 Jun 2025 22:04:49 +0530
Subject: [PATCH 01/12] Implemented lazy-load translations

---
 src/Expensify.tsx                             |  4 +-
 src/ONYXKEYS.ts                               |  4 ++
 src/components/LocaleContextProvider.tsx      | 53 ++++++++------
 src/components/Onfido/BaseOnfidoWeb.tsx       |  2 +-
 src/languages/TranslationStore.ts             | 69 ++++++++++++++++++
 src/languages/__mocks__/TranslationStore.ts   | 70 +++++++++++++++++++
 .../{translations.ts => flattenObject.ts}     | 11 +--
 src/libs/CurrencyUtils.ts                     | 14 ++--
 src/libs/DateUtils.ts                         | 34 +++++----
 src/libs/EmojiUtils.tsx                       |  6 +-
 src/libs/LocaleCompare.ts                     | 15 +++-
 src/libs/LocaleDigitUtils.ts                  |  8 +--
 .../LocaleListener/BaseLocaleListener.ts      | 22 +++---
 src/libs/Localize/index.ts                    | 24 ++++---
 src/libs/NumberFormatUtils/index.ts           | 10 +--
 src/libs/PersonalDetailsUtils.ts              |  5 +-
 src/libs/ReportActionsUtils.ts                |  6 +-
 src/libs/TransactionUtils/index.ts            |  8 +--
 src/libs/actions/App.ts                       |  6 +-
 src/libs/actions/Delegate.ts                  |  1 +
 src/libs/actions/OnyxDerived/index.ts         | 23 ++++++
 src/libs/actions/QueuedOnyxUpdates.ts         |  1 +
 src/libs/actions/SignInRedirect.ts            |  1 +
 tests/actions/PolicyTest.ts                   |  4 +-
 tests/unit/TranslateTest.ts                   | 44 ++++--------
 25 files changed, 306 insertions(+), 139 deletions(-)
 create mode 100644 src/languages/TranslationStore.ts
 create mode 100644 src/languages/__mocks__/TranslationStore.ts
 rename src/languages/{translations.ts => flattenObject.ts} (78%)

diff --git a/src/Expensify.tsx b/src/Expensify.tsx
index 867ca68ff280..d14480a63340 100644
--- a/src/Expensify.tsx
+++ b/src/Expensify.tsx
@@ -89,7 +89,7 @@ function Expensify() {
     const [isOnyxMigrated, setIsOnyxMigrated] = useState(false);
     const {splashScreenState, setSplashScreenState} = useContext(SplashScreenStateContext);
     const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false);
-    const {translate} = useLocalize();
+    const {translate, preferredLocale} = useLocalize();
     const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
     const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
     const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE, {canBeMissing: true});
@@ -115,7 +115,7 @@ function Expensify() {
     const isAuthenticated = useIsAuthenticated();
     const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);
 
-    const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom;
+    const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom && !!preferredLocale;
     const isSplashVisible = splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE;
     const isHybridAppReady = splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN && isAuthenticated;
     const shouldHideSplash = shouldInit && (CONFIG.IS_HYBRID_APP ? isHybridAppReady : isSplashVisible);
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 59ef2f6c414e..1a9d2f77ea1e 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -169,6 +169,9 @@ const ONYXKEYS = {
     /** Indicates which locale should be used */
     NVP_PREFERRED_LOCALE: 'nvp_preferredLocale',
 
+    /** Whether the app is currently loading a translation */
+    ARE_TRANSLATIONS_LOADING: 'areTranslationsLoading',
+
     /** Whether the user has tried focus mode yet */
     NVP_TRY_FOCUS_MODE: 'nvp_tryFocusMode',
 
@@ -1064,6 +1067,7 @@ type OnyxValuesMapping = {
     [ONYXKEYS.ONFIDO_TOKEN]: string;
     [ONYXKEYS.ONFIDO_APPLICANT_ID]: string;
     [ONYXKEYS.NVP_PREFERRED_LOCALE]: OnyxTypes.Locale;
+    [ONYXKEYS.ARE_TRANSLATIONS_LOADING]: boolean;
     [ONYXKEYS.NVP_ACTIVE_POLICY_ID]: string;
     [ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS]: OnyxTypes.DismissedReferralBanners;
     [ONYXKEYS.NVP_HAS_SEEN_TRACK_TRAINING]: boolean;
diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 0d8d2fae9703..4c16f41c898a 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -1,4 +1,4 @@
-import React, {createContext, useMemo} from 'react';
+import React, {createContext, useEffect, useMemo, useState} from 'react';
 import {useOnyx} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
@@ -7,7 +7,8 @@ import {fromLocaleDigit as fromLocaleDigitLocaleDigitUtils, toLocaleDigit as toL
 import {formatPhoneNumber as formatPhoneNumberLocalePhoneNumber} from '@libs/LocalePhoneNumber';
 import {translate as translateLocalize} from '@libs/Localize';
 import {format} from '@libs/NumberFormatUtils';
-import CONST from '@src/CONST';
+import type CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {TranslationParameters, TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {SelectedTimezone} from '@src/types/onyx/PersonalDetails';
@@ -51,8 +52,8 @@ type LocaleContextProps = {
     /** Gets the standard digit corresponding to a locale digit */
     fromLocaleDigit: (digit: string) => string;
 
-    /** The user's preferred locale e.g. 'en', 'es-ES' */
-    preferredLocale: Locale;
+    /** The user's preferred locale e.g. 'en', 'es' */
+    preferredLocale: Locale | undefined;
 };
 
 const LocaleContext = createContext<LocaleContextProps>({
@@ -66,53 +67,61 @@ const LocaleContext = createContext<LocaleContextProps>({
     toLocaleDigit: () => '',
     toLocaleOrdinal: () => '',
     fromLocaleDigit: () => '',
-    preferredLocale: CONST.LOCALES.DEFAULT,
+    preferredLocale: undefined,
 });
 
 function LocaleContextProvider({children}: LocaleContextProviderProps) {
-    const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true});
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
-    const locale = preferredLocale ?? CONST.LOCALES.DEFAULT;
+    const [areTranslationsLoading = true] = useOnyx(ONYXKEYS.ARE_TRANSLATIONS_LOADING, {initWithStoredValues: false, canBeMissing: true});
+    const [currentLocale, setCurrentLocale] = useState<Locale | undefined>(undefined);
+
+    useEffect(() => {
+        if (areTranslationsLoading) {
+            return;
+        }
+
+        setCurrentLocale(TranslationStore.getCurrentLocale());
+    }, [areTranslationsLoading]);
 
     const selectedTimezone = useMemo(() => currentUserPersonalDetails?.timezone?.selected, [currentUserPersonalDetails]);
 
     const translate = useMemo<LocaleContextProps['translate']>(
         () =>
             (path, ...parameters) =>
-                translateLocalize(locale, path, ...parameters),
-        [locale],
+                translateLocalize(currentLocale, path, ...parameters),
+        [currentLocale],
     );
 
-    const numberFormat = useMemo<LocaleContextProps['numberFormat']>(() => (number, options) => format(locale, number, options), [locale]);
+    const numberFormat = useMemo<LocaleContextProps['numberFormat']>(() => (number, options) => format(currentLocale, number, options), [currentLocale]);
 
     const getLocalDateFromDatetime = useMemo<LocaleContextProps['getLocalDateFromDatetime']>(
-        () => (datetime, currentSelectedTimezone) => DateUtils.getLocalDateFromDatetime(locale, datetime, currentSelectedTimezone ?? selectedTimezone),
-        [locale, selectedTimezone],
+        () => (datetime, currentSelectedTimezone) => DateUtils.getLocalDateFromDatetime(currentLocale, datetime, currentSelectedTimezone ?? selectedTimezone),
+        [currentLocale, selectedTimezone],
     );
 
-    const datetimeToRelative = useMemo<LocaleContextProps['datetimeToRelative']>(() => (datetime) => DateUtils.datetimeToRelative(locale, datetime), [locale]);
+    const datetimeToRelative = useMemo<LocaleContextProps['datetimeToRelative']>(() => (datetime) => DateUtils.datetimeToRelative(currentLocale, datetime), [currentLocale]);
 
     const datetimeToCalendarTime = useMemo<LocaleContextProps['datetimeToCalendarTime']>(
         () =>
             (datetime, includeTimezone, isLowercase = false) =>
-                DateUtils.datetimeToCalendarTime(locale, datetime, includeTimezone, selectedTimezone, isLowercase),
-        [locale, selectedTimezone],
+                DateUtils.datetimeToCalendarTime(currentLocale, datetime, includeTimezone, selectedTimezone, isLowercase),
+        [currentLocale, selectedTimezone],
     );
 
-    const updateLocale = useMemo<LocaleContextProps['updateLocale']>(() => () => DateUtils.setLocale(locale), [locale]);
+    const updateLocale = useMemo<LocaleContextProps['updateLocale']>(() => () => DateUtils.setLocale(currentLocale), [currentLocale]);
 
     const formatPhoneNumber = useMemo<LocaleContextProps['formatPhoneNumber']>(() => (phoneNumber) => formatPhoneNumberLocalePhoneNumber(phoneNumber), []);
 
-    const toLocaleDigit = useMemo<LocaleContextProps['toLocaleDigit']>(() => (digit) => toLocaleDigitLocaleDigitUtils(locale, digit), [locale]);
+    const toLocaleDigit = useMemo<LocaleContextProps['toLocaleDigit']>(() => (digit) => toLocaleDigitLocaleDigitUtils(currentLocale, digit), [currentLocale]);
 
     const toLocaleOrdinal = useMemo<LocaleContextProps['toLocaleOrdinal']>(
         () =>
             (number, writtenOrdinals = false) =>
-                toLocaleOrdinalLocaleDigitUtils(locale, number, writtenOrdinals),
-        [locale],
+                toLocaleOrdinalLocaleDigitUtils(currentLocale, number, writtenOrdinals),
+        [currentLocale],
     );
 
-    const fromLocaleDigit = useMemo<LocaleContextProps['fromLocaleDigit']>(() => (localeDigit) => fromLocaleDigitLocaleDigitUtils(locale, localeDigit), [locale]);
+    const fromLocaleDigit = useMemo<LocaleContextProps['fromLocaleDigit']>(() => (localeDigit) => fromLocaleDigitLocaleDigitUtils(currentLocale, localeDigit), [currentLocale]);
 
     const contextValue = useMemo<LocaleContextProps>(
         () => ({
@@ -126,7 +135,7 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
             toLocaleDigit,
             toLocaleOrdinal,
             fromLocaleDigit,
-            preferredLocale: locale,
+            preferredLocale: currentLocale,
         }),
         [
             translate,
@@ -139,7 +148,7 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
             toLocaleDigit,
             toLocaleOrdinal,
             fromLocaleDigit,
-            locale,
+            currentLocale,
         ],
     );
 
diff --git a/src/components/Onfido/BaseOnfidoWeb.tsx b/src/components/Onfido/BaseOnfidoWeb.tsx
index c5ccf2e36929..3c2e58d40d2a 100644
--- a/src/components/Onfido/BaseOnfidoWeb.tsx
+++ b/src/components/Onfido/BaseOnfidoWeb.tsx
@@ -106,7 +106,7 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo
         },
         language: {
             // We need to use ES_ES as locale key because the key `ES` is not a valid config key for Onfido
-            locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : preferredLocale,
+            locale: preferredLocale === CONST.LOCALES.ES ? CONST.LOCALES.ES_ES_ONFIDO : (preferredLocale ?? CONST.LOCALES.DEFAULT),
 
             // Provide a custom phrase for the back button so that the first letter is capitalized,
             // and translate the phrase while we're at it. See the issue and documentation for more context.
diff --git a/src/languages/TranslationStore.ts b/src/languages/TranslationStore.ts
new file mode 100644
index 000000000000..765118c8c86c
--- /dev/null
+++ b/src/languages/TranslationStore.ts
@@ -0,0 +1,69 @@
+import Onyx from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import flattenObject from './flattenObject';
+import type {FlatTranslationsObject, TranslationPaths} from './types';
+
+// This function was added here to avoid circular dependencies
+function setAreTranslationsLoading(areTranslationsLoading: boolean) {
+    // eslint-disable-next-line rulesdir/prefer-actions-set-data
+    Onyx.set(ONYXKEYS.ARE_TRANSLATIONS_LOADING, areTranslationsLoading);
+}
+
+class TranslationStore {
+    private static currentLocale: ValueOf<typeof CONST.LOCALES> | undefined = undefined;
+
+    private static localeCache = new Map<ValueOf<typeof CONST.LOCALES>, FlatTranslationsObject>();
+
+    private static loaders: Partial<Record<ValueOf<typeof CONST.LOCALES>, () => Promise<void>>> = {
+        [CONST.LOCALES.EN]: () => {
+            if (this.localeCache.has(CONST.LOCALES.EN)) {
+                return Promise.resolve();
+            }
+            return import('./en').then((module) => {
+                const flattened = flattenObject(module.default);
+                this.localeCache.set(CONST.LOCALES.EN, flattened);
+            });
+        },
+        [CONST.LOCALES.ES]: () => {
+            if (this.localeCache.has(CONST.LOCALES.ES)) {
+                return Promise.resolve();
+            }
+            return import('./es').then((module) => {
+                const flattened = flattenObject(module.default);
+                this.localeCache.set(CONST.LOCALES.ES, flattened);
+            });
+        },
+    };
+
+    static getCurrentLocale() {
+        return this.currentLocale;
+    }
+
+    static load(locale: ValueOf<typeof CONST.LOCALES>) {
+        if (this.currentLocale === locale) {
+            return Promise.resolve();
+        }
+        const loaderPromise = this.loaders[locale] ?? (() => Promise.resolve());
+        setAreTranslationsLoading(true);
+        return loaderPromise()
+            .then(() => {
+                this.currentLocale = locale;
+            })
+            .then(() => {
+                setAreTranslationsLoading(false);
+            });
+    }
+
+    static get<TPath extends TranslationPaths>(key: TPath, locale?: ValueOf<typeof CONST.LOCALES>) {
+        const localeToUse = locale && this.localeCache.has(locale) ? locale : this.currentLocale;
+        if (!localeToUse) {
+            return null;
+        }
+        const translations = this.localeCache.get(localeToUse);
+        return translations?.[key] ?? null;
+    }
+}
+
+export default TranslationStore;
diff --git a/src/languages/__mocks__/TranslationStore.ts b/src/languages/__mocks__/TranslationStore.ts
new file mode 100644
index 000000000000..72c7ca88880d
--- /dev/null
+++ b/src/languages/__mocks__/TranslationStore.ts
@@ -0,0 +1,70 @@
+import type {ValueOf} from 'type-fest';
+import CONST from '@src/CONST';
+import flattenObject from '@src/languages/flattenObject';
+import type {FlatTranslationsObject, TranslationPaths} from '@src/languages/types';
+
+class TranslationStore {
+    private static currentLocale: ValueOf<typeof CONST.LOCALES> | undefined = 'en';
+
+    private static localeCache = new Map<ValueOf<typeof CONST.LOCALES>, FlatTranslationsObject>([
+        [
+            CONST.LOCALES.EN,
+            flattenObject({
+                testKey1: 'English',
+                testKey2: 'Test Word 2',
+                testKeyGroup: {
+                    testFunction: ({testVariable}: {testVariable: string}) => `With variable ${testVariable}`,
+                },
+                pluralizationGroup: {
+                    countWithoutPluralRules: ({count}: {count: number}) => `Count value is ${count}`,
+                    countWithNoCorrespondingRule: ({count}: {count: number}) => ({
+                        one: 'One file is being downloaded.',
+                        other: `Other ${count} files are being downloaded.`,
+                    }),
+                },
+            }),
+        ],
+        [
+            CONST.LOCALES.ES,
+            flattenObject({
+                testKey1: 'Spanish',
+                testKey2: 'Spanish Word 2',
+                pluralizationGroup: {
+                    couthWithCorrespondingRule: ({count}: {count: number}) => ({
+                        one: 'Un artículo',
+                        other: `${count} artículos`,
+                    }),
+                },
+            }),
+        ],
+    ]);
+
+    private static loaders: Partial<Record<ValueOf<typeof CONST.LOCALES>, () => Promise<void>>> = {
+        [CONST.LOCALES.EN]: () => {
+            return Promise.resolve();
+        },
+        [CONST.LOCALES.ES]: () => {
+            return Promise.resolve();
+        },
+    };
+
+    static getCurrentLocale() {
+        return this.currentLocale;
+    }
+
+    static load() {
+        return Promise.resolve();
+    }
+
+    static get<TPath extends TranslationPaths>(key: TPath, locale?: ValueOf<typeof CONST.LOCALES>) {
+        console.log(`TranslationStore.get called with key: ${key}, locale: ${locale}`);
+        const localeToUse = locale && this.localeCache.has(locale) ? locale : this.currentLocale;
+        if (!localeToUse) {
+            return null;
+        }
+        const translations = this.localeCache.get(localeToUse);
+        return translations?.[key] ?? null;
+    }
+}
+
+export default TranslationStore;
diff --git a/src/languages/translations.ts b/src/languages/flattenObject.ts
similarity index 78%
rename from src/languages/translations.ts
rename to src/languages/flattenObject.ts
index 9be57805cf04..407391b02707 100644
--- a/src/languages/translations.ts
+++ b/src/languages/flattenObject.ts
@@ -1,5 +1,3 @@
-import en from './en';
-import es from './es';
 import type {FlatTranslationsObject, TranslationDeepObject} from './types';
 
 /**
@@ -9,9 +7,7 @@ import type {FlatTranslationsObject, TranslationDeepObject} from './types';
  * Input: { common: { yes: "Yes", no: "No" }}
  * Output: { "common.yes": "Yes", "common.no": "No" }
  */
-// Necessary to export so that it is accessible to the unit tests
-// eslint-disable-next-line rulesdir/no-inline-named-export
-export function flattenObject<TTranslations>(obj: TranslationDeepObject<TTranslations>): FlatTranslationsObject {
+function flattenObject<TTranslations>(obj: TranslationDeepObject<TTranslations>): FlatTranslationsObject {
     const result: Record<string, unknown> = {};
 
     const recursive = (data: TranslationDeepObject, key: string): void => {
@@ -41,7 +37,4 @@ export function flattenObject<TTranslations>(obj: TranslationDeepObject<TTransla
     return result as FlatTranslationsObject;
 }
 
-export default {
-    en: flattenObject(en),
-    es: flattenObject(es),
-};
+export default flattenObject;
diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts
index cb5363225a4c..d7d1ee0d1505 100644
--- a/src/libs/CurrencyUtils.ts
+++ b/src/libs/CurrencyUtils.ts
@@ -1,9 +1,9 @@
 import Onyx from 'react-native-onyx';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {OnyxValues} from '@src/ONYXKEYS';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Currency} from '@src/types/onyx';
-import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener';
 import {format, formatToParts} from './NumberFormatUtils';
 
 let currencyList: OnyxValues[typeof ONYXKEYS.CURRENCY_LIST] = {};
@@ -50,7 +50,7 @@ function getCurrencyUnit(currency: string = CONST.CURRENCY.USD): number {
  * Get localized currency symbol for currency(ISO 4217) Code
  */
 function getLocalizedCurrencySymbol(currencyCode: string): string | undefined {
-    const parts = formatToParts(BaseLocaleListener.getPreferredLocale(), 0, {
+    const parts = formatToParts(TranslationStore.getCurrentLocale(), 0, {
         style: 'currency',
         currency: currencyCode,
     });
@@ -68,7 +68,7 @@ function getCurrencySymbol(currencyCode: string): string | undefined {
  * Whether the currency symbol is left-to-right.
  */
 function isCurrencySymbolLTR(currencyCode: string): boolean {
-    const parts = formatToParts(BaseLocaleListener.getPreferredLocale(), 0, {
+    const parts = formatToParts(TranslationStore.getCurrentLocale(), 0, {
         style: 'currency',
         currency: currencyCode,
     });
@@ -127,7 +127,7 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR
     if (!currency) {
         currencyWithFallback = CONST.CURRENCY.USD;
     }
-    return format(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
+    return format(TranslationStore.getCurrentLocale(), convertedAmount, {
         style: 'currency',
         currency: currencyWithFallback,
 
@@ -149,7 +149,7 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR
 function convertToShortDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string {
     const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency);
 
-    return format(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
+    return format(TranslationStore.getCurrentLocale(), convertedAmount, {
         style: 'currency',
         currency,
 
@@ -167,7 +167,7 @@ function convertToShortDisplayString(amountInCents = 0, currency: string = CONST
  */
 function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRENCY.USD): string {
     const convertedAmount = amount / 100.0;
-    return format(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
+    return format(TranslationStore.getCurrentLocale(), convertedAmount, {
         style: 'currency',
         currency,
         minimumFractionDigits: CONST.MIN_TAX_RATE_DECIMAL_PLACES,
@@ -180,7 +180,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE
  */
 function convertToDisplayStringWithoutCurrency(amountInCents: number, currency: string = CONST.CURRENCY.USD) {
     const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency);
-    return formatToParts(BaseLocaleListener.getPreferredLocale(), convertedAmount, {
+    return formatToParts(TranslationStore.getCurrentLocale(), convertedAmount, {
         style: 'currency',
         currency,
 
diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index b31b36d812d6..057e287ef23e 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -39,13 +39,13 @@ import Onyx from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import type {LocaleContextProps} from '@components/LocaleContextProvider';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import {timezoneBackwardMap} from '@src/TIMEZONES';
 import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
 import {setCurrentDate} from './actions/CurrentDate';
 import {setNetworkLastOffline} from './actions/Network';
 import {translate, translateLocal} from './Localize';
-import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener';
 import Log from './Log';
 
 type CustomStatusTypes = ValueOf<typeof CONST.CUSTOM_STATUS_TYPES>;
@@ -85,23 +85,19 @@ Onyx.connect({
 });
 
 let networkTimeSkew = 0;
-Onyx.connect({
-    key: ONYXKEYS.NETWORK,
-    callback: (value) => (networkTimeSkew = value?.timeSkew ?? 0),
-});
-
 let isOffline: boolean | undefined;
 
 Onyx.connect({
     key: ONYXKEYS.NETWORK,
     callback: (val) => {
+        networkTimeSkew = val?.timeSkew ?? 0;
         if (!val?.lastOfflineAt) {
-            setNetworkLastOffline(getLocalDateFromDatetime(BaseLocaleListener.getPreferredLocale()));
+            setNetworkLastOffline(getLocalDateFromDatetime(TranslationStore.getCurrentLocale()));
         }
 
         const newIsOffline = val?.isOffline ?? val?.shouldForceOffline;
         if (newIsOffline && isOffline === false) {
-            setNetworkLastOffline(getLocalDateFromDatetime(BaseLocaleListener.getPreferredLocale()));
+            setNetworkLastOffline(getLocalDateFromDatetime(TranslationStore.getCurrentLocale()));
         }
         isOffline = newIsOffline;
     },
@@ -130,7 +126,7 @@ function getWeekEndsOn(): WeekDay {
 /**
  * Gets the locale string and setting default locale for date-fns
  */
-function setLocale(localeString: Locale) {
+function setLocale(localeString: Locale | undefined) {
     switch (localeString) {
         case CONST.LOCALES.EN:
             setDefaultOptions({locale: enGB});
@@ -147,8 +143,10 @@ function setLocale(localeString: Locale) {
  * Gets the user's stored time zone NVP and returns a localized
  * Date object for the given ISO-formatted datetime string
  */
-function getLocalDateFromDatetime(locale: Locale, datetime?: string, currentSelectedTimezone: SelectedTimezone = timezone.selected): Date {
-    setLocale(locale);
+function getLocalDateFromDatetime(locale: Locale | undefined, datetime?: string, currentSelectedTimezone: SelectedTimezone = timezone.selected): Date {
+    if (locale) {
+        setLocale(locale);
+    }
     if (!datetime) {
         const res = toZonedTime(new Date(), currentSelectedTimezone);
         if (Number.isNaN(res.getTime())) {
@@ -222,7 +220,13 @@ function isYesterday(date: Date, timeZone: SelectedTimezone): boolean {
  * Jan 20 at 5:30 PM          within the past year
  * Jan 20, 2019 at 5:30 PM    anything over 1 year ago
  */
-function datetimeToCalendarTime(locale: Locale, datetime: string, includeTimeZone = false, currentSelectedTimezone: SelectedTimezone = timezone.selected, isLowercase = false): string {
+function datetimeToCalendarTime(
+    locale: Locale | undefined,
+    datetime: string,
+    includeTimeZone = false,
+    currentSelectedTimezone: SelectedTimezone = timezone.selected,
+    isLowercase = false,
+): string {
     const date = getLocalDateFromDatetime(locale, datetime, currentSelectedTimezone);
     const tz = includeTimeZone ? ' [UTC]Z' : '';
     let todayAt = translate(locale, 'common.todayAt');
@@ -268,7 +272,7 @@ function datetimeToCalendarTime(locale: Locale, datetime: string, includeTimeZon
  * Jan 20               within the past year
  * Jan 20, 2019         anything over 1 year
  */
-function datetimeToRelative(locale: Locale, datetime: string): string {
+function datetimeToRelative(locale: Locale | undefined, datetime: string): string {
     const date = getLocalDateFromDatetime(locale, datetime);
     const now = getLocalDateFromDatetime(locale);
     return formatDistance(date, now, {addSuffix: true, locale: locale === CONST.LOCALES.EN ? enGB : es});
@@ -349,7 +353,7 @@ function getCurrentTimezone(): Required<Timezone> {
 /**
  * @returns [January, February, March, April, May, June, July, August, ...]
  */
-function getMonthNames(preferredLocale: Locale): string[] {
+function getMonthNames(preferredLocale: Locale = CONST.LOCALES.DEFAULT): string[] {
     if (preferredLocale) {
         setLocale(preferredLocale);
     }
@@ -365,7 +369,7 @@ function getMonthNames(preferredLocale: Locale): string[] {
 /**
  * @returns [Monday, Tuesday, Wednesday, ...]
  */
-function getDaysOfWeek(preferredLocale: Locale): string[] {
+function getDaysOfWeek(preferredLocale: Locale = CONST.LOCALES.DEFAULT): string[] {
     if (preferredLocale) {
         setLocale(preferredLocale);
     }
diff --git a/src/libs/EmojiUtils.tsx b/src/libs/EmojiUtils.tsx
index f73b9c5808db..5c7eecfaa533 100644
--- a/src/libs/EmojiUtils.tsx
+++ b/src/libs/EmojiUtils.tsx
@@ -408,10 +408,14 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone: OnyxEntry<numb
  * Suggest emojis when typing emojis prefix after colon
  * @param [limit] - matching emojis limit
  */
-function suggestEmojis(text: string, lang: Locale, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
+function suggestEmojis(text: string, lang?: Locale, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
     // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it
     const emojisTrie = require<EmojiTrieModule>('./EmojiTrie').default;
 
+    if (!lang) {
+        return [];
+    }
+
     const trie = emojisTrie[lang as SupportedLanguage];
     if (!trie) {
         return [];
diff --git a/src/libs/LocaleCompare.ts b/src/libs/LocaleCompare.ts
index 817f617523f3..6c8c211bbffc 100644
--- a/src/libs/LocaleCompare.ts
+++ b/src/libs/LocaleCompare.ts
@@ -1,5 +1,6 @@
 import Onyx from 'react-native-onyx';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 
 const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'variant', numeric: true, caseFirst: 'upper'};
@@ -7,9 +8,17 @@ const COLLATOR_OPTIONS: Intl.CollatorOptions = {usage: 'sort', sensitivity: 'var
 let collator = new Intl.Collator(CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS);
 
 Onyx.connect({
-    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    callback: (locale) => {
-        collator = new Intl.Collator(locale ?? CONST.LOCALES.DEFAULT, COLLATOR_OPTIONS);
+    key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
+    initWithStoredValues: false,
+    callback: (areTranslationsLoading) => {
+        if (areTranslationsLoading ?? true) {
+            return;
+        }
+        const locale = TranslationStore.getCurrentLocale();
+        if (!locale) {
+            return;
+        }
+        collator = new Intl.Collator(locale, COLLATOR_OPTIONS);
     },
 });
 
diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts
index e6e91d58b273..7d23c926747d 100644
--- a/src/libs/LocaleDigitUtils.ts
+++ b/src/libs/LocaleDigitUtils.ts
@@ -14,7 +14,7 @@ const INDEX_MINUS_SIGN = 11;
 const INDEX_GROUP = 12;
 
 const getLocaleDigits = memoize(
-    (locale: Locale): string[] => {
+    (locale: Locale | undefined): string[] => {
         const localeDigits = [...STANDARD_DIGITS];
         for (let i = 0; i <= 9; i++) {
             localeDigits[i] = NumberFormatUtils.format(locale, i);
@@ -47,7 +47,7 @@ const getLocaleDigits = memoize(
  *
  * @throws If `digit` is not a valid standard digit.
  */
-function toLocaleDigit(locale: Locale, digit: string): string {
+function toLocaleDigit(locale: Locale | undefined, digit: string): string {
     const index = STANDARD_DIGITS.indexOf(digit);
     if (index < 0) {
         throw new Error(`"${digit}" must be in ${JSON.stringify(STANDARD_DIGITS)}`);
@@ -63,7 +63,7 @@ function toLocaleDigit(locale: Locale, digit: string): string {
  *
  * @throws If `localeDigit` is not a valid locale digit.
  */
-function fromLocaleDigit(locale: Locale, localeDigit: string): string {
+function fromLocaleDigit(locale: Locale | undefined, localeDigit: string): string {
     const index = getLocaleDigits(locale).indexOf(localeDigit);
     if (index < 0) {
         throw new Error(`"${localeDigit}" must be in ${JSON.stringify(getLocaleDigits(locale))}`);
@@ -77,7 +77,7 @@ function fromLocaleDigit(locale: Locale, localeDigit: string): string {
  * @param number - The number to format
  * @param writtenOrdinals - If true, returns the written ordinal (e.g. "first", "second") for numbers 1-10
  */
-function toLocaleOrdinal(locale: Locale, number: number, writtenOrdinals = false): string {
+function toLocaleOrdinal(locale: Locale | undefined, number: number, writtenOrdinals = false): string {
     // Defaults to "other" suffix or "th" in English
     let suffixKey: TranslationPaths = 'workflowsPage.frequencies.ordinals.other';
 
diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts
index 4294172775c9..c19e3927a79a 100644
--- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts
+++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts
@@ -1,35 +1,31 @@
 import Onyx from 'react-native-onyx';
-import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {LocaleListenerConnect} from './types';
-import type BaseLocale from './types';
-
-let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT;
 
 /**
  * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx.
  */
 const connect: LocaleListenerConnect = (callbackAfterChange = () => {}) => {
     Onyx.connect({
-        key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+        key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
+        initWithStoredValues: false,
         callback: (val) => {
-            if (!val) {
+            if (val ?? true) {
                 return;
             }
 
-            preferredLocale = val;
-            callbackAfterChange(val);
+            const preferredLocale = TranslationStore.getCurrentLocale();
+            if (!preferredLocale) {
+                return;
+            }
+            callbackAfterChange(preferredLocale);
         },
     });
 };
 
-function getPreferredLocale(): BaseLocale {
-    return preferredLocale;
-}
-
 const BaseLocaleListener = {
     connect,
-    getPreferredLocale,
 };
 
 export default BaseLocaleListener;
diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts
index 92fa6972e6dd..40e7c953f02a 100644
--- a/src/libs/Localize/index.ts
+++ b/src/libs/Localize/index.ts
@@ -5,13 +5,12 @@ import memoize from '@libs/memoize';
 import type {MessageElementBase, MessageTextElement} from '@libs/MessageElement';
 import Config from '@src/CONFIG';
 import CONST from '@src/CONST';
-import translations from '@src/languages/translations';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {PluralForm, TranslationParameters, TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Locale} from '@src/types/onyx';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import LocaleListener from './LocaleListener';
-import BaseLocaleListener from './LocaleListener/BaseLocaleListener';
 
 // Current user mail is needed for handling missing translations
 let userEmail = '';
@@ -33,10 +32,10 @@ LocaleListener.connect();
 let CONJUNCTION_LIST_FORMATS_FOR_LOCALES: Record<string, Intl.ListFormat>;
 function init() {
     CONJUNCTION_LIST_FORMATS_FOR_LOCALES = Object.values(CONST.LOCALES).reduce((memo: Record<string, Intl.ListFormat>, locale) => {
-        // This is not a supported locale, so we'll use ES_ES instead
+        // This is not a supported locale, so we'll use ES instead
         if (locale === CONST.LOCALES.ES_ES_ONFIDO) {
             // eslint-disable-next-line no-param-reassign
-            memo[locale] = new Intl.ListFormat(CONST.LOCALES.ES_ES, {style: 'long', type: 'conjunction'});
+            memo[locale] = new Intl.ListFormat(CONST.LOCALES.ES, {style: 'long', type: 'conjunction'});
             return memo;
         }
 
@@ -60,7 +59,7 @@ function init() {
  * the translated value.
  */
 function getTranslatedPhrase<TKey extends TranslationPaths>(language: 'en' | 'es', phraseKey: TKey, ...parameters: TranslationParameters<TKey>): string | null {
-    const translatedPhrase = translations?.[language]?.[phraseKey];
+    const translatedPhrase = TranslationStore.get(phraseKey, language);
 
     if (translatedPhrase) {
         if (typeof translatedPhrase === 'function') {
@@ -117,10 +116,14 @@ const memoizedGetTranslatedPhrase = memoize(getTranslatedPhrase, {
 /**
  * Return translated string for given locale and phrase
  *
- * @param [desiredLanguage] eg 'en', 'es-ES'
+ * @param [desiredLanguage] eg 'en', 'es'
  * @param [parameters] Parameters to supply if the phrase is a template literal.
  */
-function translate<TPath extends TranslationPaths>(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', path: TPath, ...parameters: TranslationParameters<TPath>): string {
+function translate<TPath extends TranslationPaths>(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES' | undefined, path: TPath, ...parameters: TranslationParameters<TPath>): string {
+    if (!desiredLanguage) {
+        // If no language is provided, return the path as is
+        return Array.isArray(path) ? path.join('.') : path;
+    }
     // Search phrase in full locale e.g. es-ES
     const language = ([CONST.LOCALES.ES_ES_ONFIDO, CONST.LOCALES.ES_ES] as string[]).includes(desiredLanguage) ? CONST.LOCALES.ES : (desiredLanguage as 'en' | 'es');
 
@@ -139,14 +142,15 @@ function translate<TPath extends TranslationPaths>(desiredLanguage: 'en' | 'es'
         }
         return phraseString;
     }
-    throw new Error(`${path} was not found in the default language`);
+    throw new Error(`${path} was not found in the ${desiredLanguage} language`);
 }
 
 /**
  * Uses the locale in this file updated by the Onyx subscriber.
  */
 function translateLocal<TPath extends TranslationPaths>(phrase: TPath, ...parameters: TranslationParameters<TPath>) {
-    return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...parameters);
+    const currentLocale = TranslationStore.getCurrentLocale();
+    return translate(currentLocale, phrase, ...parameters);
 }
 
 function getPreferredListFormat(): Intl.ListFormat {
@@ -154,7 +158,7 @@ function getPreferredListFormat(): Intl.ListFormat {
         init();
     }
 
-    return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[BaseLocaleListener.getPreferredLocale()];
+    return CONJUNCTION_LIST_FORMATS_FOR_LOCALES[TranslationStore.getCurrentLocale() ?? CONST.LOCALES.DEFAULT];
 }
 
 /**
diff --git a/src/libs/NumberFormatUtils/index.ts b/src/libs/NumberFormatUtils/index.ts
index 204f0a80b5d9..b9aba78898a7 100644
--- a/src/libs/NumberFormatUtils/index.ts
+++ b/src/libs/NumberFormatUtils/index.ts
@@ -1,18 +1,18 @@
 import type {ValueOf} from 'type-fest';
 import memoize from '@libs/memoize';
-import type CONST from '@src/CONST';
+import CONST from '@src/CONST';
 import initPolyfill from './intlPolyfill';
 
 initPolyfill();
 
 const MemoizedNumberFormat = memoize(Intl.NumberFormat, {maxSize: 10, monitoringName: 'NumberFormatUtils'});
 
-function format(locale: ValueOf<typeof CONST.LOCALES>, number: number, options?: Intl.NumberFormatOptions): string {
-    return new MemoizedNumberFormat(locale, options).format(number);
+function format(locale: ValueOf<typeof CONST.LOCALES> | undefined, number: number, options?: Intl.NumberFormatOptions): string {
+    return new MemoizedNumberFormat(locale ?? CONST.LOCALES.DEFAULT, options).format(number);
 }
 
-function formatToParts(locale: ValueOf<typeof CONST.LOCALES>, number: number, options?: Intl.NumberFormatOptions): Intl.NumberFormatPart[] {
-    return new MemoizedNumberFormat(locale, options).formatToParts(number);
+function formatToParts(locale: ValueOf<typeof CONST.LOCALES> | undefined, number: number, options?: Intl.NumberFormatOptions): Intl.NumberFormatPart[] {
+    return new MemoizedNumberFormat(locale ?? CONST.LOCALES.DEFAULT, options).formatToParts(number);
 }
 
 export {format, formatToParts};
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 564f0a09760f..4dfe2bf7f07b 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -39,9 +39,10 @@ let hiddenTranslation = '';
 let youTranslation = '';
 
 Onyx.connect({
-    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+    key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
+    initWithStoredValues: false,
     callback: (value) => {
-        if (!value) {
+        if (value ?? true) {
             return;
         }
         hiddenTranslation = translateLocal('common.hidden');
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index b4137bd4bb22..dc950255a211 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -7,6 +7,7 @@ import Onyx from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import usePrevious from '@hooks/usePrevious';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -26,7 +27,6 @@ import {isReportMessageAttachment} from './isReportMessageAttachment';
 import {toLocaleOrdinal} from './LocaleDigitUtils';
 import {formatPhoneNumber} from './LocalePhoneNumber';
 import {formatMessageElementList, translateLocal} from './Localize';
-import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener';
 import Log from './Log';
 import type {MessageElementBase, MessageTextElement} from './MessageElement';
 import Parser from './Parser';
@@ -2216,7 +2216,7 @@ function getWorkspaceUpdateFieldMessage(action: ReportAction): string {
                 return translateLocal('workflowsPage.frequencies.lastBusinessDayOfMonth');
             }
             if (typeof autoReportingOffset === 'number') {
-                return toLocaleOrdinal(BaseLocaleListener.getPreferredLocale(), autoReportingOffset, false);
+                return toLocaleOrdinal(TranslationStore.getCurrentLocale(), autoReportingOffset, false);
             }
             return '';
         };
@@ -2438,7 +2438,7 @@ function wasActionCreatedWhileOffline(action: ReportAction, isOffline: boolean,
 /**
  * Whether a message is NOT from the active user, and it was received while the user was offline.
  */
-function wasMessageReceivedWhileOffline(action: ReportAction, isOffline: boolean, lastOfflineAt: Date | undefined, lastOnlineAt: Date | undefined, locale: Locale) {
+function wasMessageReceivedWhileOffline(action: ReportAction, isOffline: boolean, lastOfflineAt: Date | undefined, lastOnlineAt: Date | undefined, locale: Locale = CONST.LOCALES.DEFAULT) {
     const wasByCurrentUser = wasActionTakenByCurrentUser(action);
     const wasCreatedOffline = wasActionCreatedWhileOffline(action, isOffline, lastOfflineAt, lastOnlineAt, locale);
 
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index c0f3ab3db54c..ad88fe4f19fe 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -15,7 +15,6 @@ import DateUtils from '@libs/DateUtils';
 import DistanceRequestUtils from '@libs/DistanceRequestUtils';
 import {toLocaleDigit} from '@libs/LocaleDigitUtils';
 import {translateLocal} from '@libs/Localize';
-import BaseLocaleListener from '@libs/Localize/LocaleListener/BaseLocaleListener';
 import {rand64, roundToTwoDecimalPlaces} from '@libs/NumberUtils';
 import {
     getCleanedTagName,
@@ -42,6 +41,7 @@ import {
 import type {IOURequestType} from '@userActions/IOU';
 import CONST from '@src/CONST';
 import type {IOUType} from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {OnyxInputOrEntry, Policy, RecentWaypoint, Report, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx';
 import type {Attendee, Participant, SplitExpense} from '@src/types/onyx/IOU';
@@ -435,7 +435,7 @@ function getUpdatedTransaction({
             const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0);
             const updatedAmount = isFromExpenseReport ? -amount : amount;
             const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, transaction.currency, translateLocal, (digit) =>
-                toLocaleDigit(BaseLocaleListener.getPreferredLocale(), digit),
+                toLocaleDigit(TranslationStore.getCurrentLocale(), digit),
             );
 
             updatedTransaction.amount = updatedAmount;
@@ -475,7 +475,7 @@ function getUpdatedTransaction({
             const updatedAmount = isFromExpenseReport ? -amount : amount;
             const updatedCurrency = updatedMileageRate.currency ?? CONST.CURRENCY.USD;
             const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, updatedCurrency, translateLocal, (digit) =>
-                toLocaleDigit(BaseLocaleListener.getPreferredLocale(), digit),
+                toLocaleDigit(TranslationStore.getCurrentLocale(), digit),
             );
 
             updatedTransaction.amount = updatedAmount;
@@ -664,7 +664,7 @@ function getMerchant(transaction: OnyxInputOrEntry<Transaction>, policyParam: On
         const {unit, rate} = mileageRate;
         const distanceInMeters = getDistanceInMeters(transaction, unit);
         return DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, transaction.currency, translateLocal, (digit) =>
-            toLocaleDigit(BaseLocaleListener.getPreferredLocale(), digit),
+            toLocaleDigit(TranslationStore.getCurrentLocale(), digit),
         );
     }
     return transaction?.modifiedMerchant ? transaction.modifiedMerchant : (transaction?.merchant ?? '');
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index 8426468d9e84..e49802f73689 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -20,6 +20,7 @@ import {isPublicRoom, isValidReport} from '@libs/ReportUtils';
 import {isLoggingInAsNewUser as isLoggingInAsNewUserSessionUtils} from '@libs/SessionUtils';
 import {clearSoundAssetsCache} from '@libs/Sound';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {OnyxKey} from '@src/ONYXKEYS';
 import type {Route} from '@src/ROUTES';
@@ -62,6 +63,7 @@ Onyx.connect({
     callback: (val) => {
         preferredLocale = val;
         if (preferredLocale) {
+            TranslationStore.load(preferredLocale as Locale);
             importEmojiLocale(preferredLocale as Locale).then(() => {
                 buildEmojisTrie(preferredLocale as Locale);
             });
@@ -197,10 +199,6 @@ function setLocale(locale: Locale) {
         value: locale,
     };
 
-    importEmojiLocale(locale).then(() => {
-        buildEmojisTrie(locale);
-    });
-
     API.write(WRITE_COMMANDS.UPDATE_PREFERRED_LOCALE, parameters, {optimisticData});
 }
 
diff --git a/src/libs/actions/Delegate.ts b/src/libs/actions/Delegate.ts
index bf1f9469703d..7dc3cf0d03b1 100644
--- a/src/libs/actions/Delegate.ts
+++ b/src/libs/actions/Delegate.ts
@@ -64,6 +64,7 @@ const KEYS_TO_PRESERVE_DELEGATE_ACCESS = [
     ONYXKEYS.NVP_TRY_FOCUS_MODE,
     ONYXKEYS.PREFERRED_THEME,
     ONYXKEYS.NVP_PREFERRED_LOCALE,
+    ONYXKEYS.ARE_TRANSLATIONS_LOADING,
     ONYXKEYS.SESSION,
     ONYXKEYS.STASHED_SESSION,
     ONYXKEYS.IS_LOADING_APP,
diff --git a/src/libs/actions/OnyxDerived/index.ts b/src/libs/actions/OnyxDerived/index.ts
index abbcd8a20048..dbefc11a6ae5 100644
--- a/src/libs/actions/OnyxDerived/index.ts
+++ b/src/libs/actions/OnyxDerived/index.ts
@@ -8,6 +8,8 @@
 import Onyx from 'react-native-onyx';
 import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
 import Log from '@libs/Log';
+import TranslationStore from '@src/languages/TranslationStore';
+import ONYXKEYS from '@src/ONYXKEYS';
 import ObjectUtils from '@src/types/utils/ObjectUtils';
 import ONYX_DERIVED_VALUES from './ONYX_DERIVED_VALUES';
 import type {DerivedValueContext} from './types';
@@ -97,6 +99,27 @@ function init() {
                             recomputeDerivedValue(dependencyOnyxKey, sourceValue, dependencyIndex);
                         },
                     });
+                } else if (dependencyOnyxKey === ONYXKEYS.NVP_PREFERRED_LOCALE) {
+                    // Special case for locale, we want to recompute derived values when the locale change actually loads.
+                    Onyx.connect({
+                        key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
+                        initWithStoredValues: false,
+                        callback: (value) => {
+                            if (value ?? true) {
+                                Log.info(`[OnyxDerived] translations are still loading, not recomputing derived value for ${key}`);
+                                return;
+                            }
+                            Log.info(`[OnyxDerived] translations loaded, recomputing derived value for ${key}`);
+                            const localeValue = TranslationStore.getCurrentLocale();
+                            if (!localeValue) {
+                                Log.info(`[OnyxDerived] No locale found for derived key ${key}, skipping recompute`);
+                                return;
+                            }
+                            Log.info(`[OnyxDerived] dependency ${dependencyOnyxKey} for derived key ${key} changed, recomputing`);
+                            setDependencyValue(dependencyIndex, localeValue as Parameters<typeof compute>[0][typeof dependencyIndex]);
+                            recomputeDerivedValue(dependencyOnyxKey, localeValue, dependencyIndex);
+                        },
+                    });
                 } else {
                     Onyx.connect({
                         key: dependencyOnyxKey,
diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts
index 67c58bb1f7f9..ef3f2c9c8ad8 100644
--- a/src/libs/actions/QueuedOnyxUpdates.ts
+++ b/src/libs/actions/QueuedOnyxUpdates.ts
@@ -35,6 +35,7 @@ function flushQueue(): Promise<void> {
             ONYXKEYS.NVP_TRY_FOCUS_MODE,
             ONYXKEYS.PREFERRED_THEME,
             ONYXKEYS.NVP_PREFERRED_LOCALE,
+            ONYXKEYS.ARE_TRANSLATIONS_LOADING,
             ONYXKEYS.SESSION,
             ONYXKEYS.IS_LOADING_APP,
             ONYXKEYS.HAS_LOADED_APP,
diff --git a/src/libs/actions/SignInRedirect.ts b/src/libs/actions/SignInRedirect.ts
index d0cfc5352156..626641927b68 100644
--- a/src/libs/actions/SignInRedirect.ts
+++ b/src/libs/actions/SignInRedirect.ts
@@ -21,6 +21,7 @@ function clearStorageAndRedirect(errorMessage?: string): Promise<void> {
     // flashes of unwanted default state.
     const keysToPreserve: OnyxKey[] = [];
     keysToPreserve.push(ONYXKEYS.NVP_PREFERRED_LOCALE);
+    keysToPreserve.push(ONYXKEYS.ARE_TRANSLATIONS_LOADING);
     keysToPreserve.push(ONYXKEYS.PREFERRED_THEME);
     keysToPreserve.push(ONYXKEYS.ACTIVE_CLIENTS);
     keysToPreserve.push(ONYXKEYS.DEVICE_ID);
diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts
index 3c8357d1c1df..9e32b8749984 100644
--- a/tests/actions/PolicyTest.ts
+++ b/tests/actions/PolicyTest.ts
@@ -2,10 +2,10 @@ import {Str} from 'expensify-common';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import {translateLocal} from '@libs/Localize';
-import BaseLocaleListener from '@libs/Localize/LocaleListener/BaseLocaleListener';
 // eslint-disable-next-line no-restricted-syntax
 import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
 import * as Policy from '@src/libs/actions/Policy/Policy';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -735,7 +735,7 @@ describe('actions/Policy', () => {
 
         it('should generate a workspace name with an incremented number even if previous workspaces were created in english lang', async () => {
             await Onyx.set(ONYXKEYS.COLLECTION.POLICY, {});
-            jest.spyOn(BaseLocaleListener, 'getPreferredLocale').mockReturnValue('es');
+            await TranslationStore.load(CONST.LOCALES.ES);
             const existingPolicies = {
                 ...createRandomPolicy(0, CONST.POLICY.TYPE.PERSONAL, `${TEST_DISPLAY_NAME}'s Workspace`),
                 ...createRandomPolicy(0, CONST.POLICY.TYPE.PERSONAL, `${TEST_DISPLAY_NAME}'s Workspace 1`),
diff --git a/tests/unit/TranslateTest.ts b/tests/unit/TranslateTest.ts
index 7ab8c40a4e26..23ceefeed7ff 100644
--- a/tests/unit/TranslateTest.ts
+++ b/tests/unit/TranslateTest.ts
@@ -1,39 +1,19 @@
 /* eslint-disable @typescript-eslint/naming-convention */
 import CONFIG from '@src/CONFIG';
 import CONST from '@src/CONST';
-import * as translations from '@src/languages/translations';
+import en from '@src/languages/en';
+import es from '@src/languages/es';
+import flattenObject from '@src/languages/flattenObject';
 import type {FlatTranslationsObject, TranslationDeepObject, TranslationPaths} from '@src/languages/types';
 import {translate} from '@src/libs/Localize';
 import asMutable from '@src/types/utils/asMutable';
 import arrayDifference from '@src/utils/arrayDifference';
 
-const originalTranslations = {...translations};
-
-asMutable(translations).default = {
-    [CONST.LOCALES.EN]: translations.flattenObject({
-        testKey1: 'English',
-        testKey2: 'Test Word 2',
-        testKeyGroup: {
-            testFunction: ({testVariable}: {testVariable: string}) => `With variable ${testVariable}`,
-        },
-        pluralizationGroup: {
-            countWithoutPluralRules: ({count}: {count: number}) => `Count value is ${count}`,
-            countWithNoCorrespondingRule: ({count}: {count: number}) => ({
-                one: 'One file is being downloaded.',
-                other: `Other ${count} files are being downloaded.`,
-            }),
-        },
-    }),
-    [CONST.LOCALES.ES]: translations.flattenObject({
-        testKey1: 'Spanish',
-        testKey2: 'Spanish Word 2',
-        pluralizationGroup: {
-            couthWithCorrespondingRule: ({count}: {count: number}) => ({
-                one: 'Un artículo',
-                other: `${count} artículos`,
-            }),
-        },
-    }),
+jest.mock('@src/languages/TranslationStore');
+
+const originalTranslations = {
+    [CONST.LOCALES.EN]: flattenObject(en),
+    [CONST.LOCALES.ES]: flattenObject(es),
 };
 
 describe('translate', () => {
@@ -92,12 +72,12 @@ describe('Translation Keys', () => {
     }
 
     const excludeLanguages = [CONST.LOCALES.EN];
-    const languages = Object.keys(originalTranslations.default).filter((ln) => !excludeLanguages.some((excludeLanguage) => excludeLanguage === ln));
-    const mainLanguage = originalTranslations.default.en;
+    const languages = Object.keys(originalTranslations).filter((ln) => !excludeLanguages.some((excludeLanguage) => excludeLanguage === ln));
+    const mainLanguage = originalTranslations.en;
     const mainLanguageKeys = traverseKeyPath(mainLanguage);
 
     languages.forEach((ln) => {
-        const languageKeys = traverseKeyPath(originalTranslations.default[ln as keyof typeof originalTranslations.default]);
+        const languageKeys = traverseKeyPath(originalTranslations[ln as keyof typeof originalTranslations]);
 
         it(`Does ${ln} locale have all the keys`, () => {
             const hasAllKeys = arrayDifference(mainLanguageKeys, languageKeys);
@@ -147,7 +127,7 @@ describe('flattenObject', () => {
             },
         };
 
-        const result = translations.flattenObject(simpleObject as TranslationDeepObject<typeof simpleObject>);
+        const result = flattenObject(simpleObject as TranslationDeepObject<typeof simpleObject>);
         expect(result).toStrictEqual({
             'common.yes': 'Yes',
             'common.no': 'No',

From bd102a352b53167be2e9849226e08faf7f4b6897 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Fri, 6 Jun 2025 22:09:40 +0530
Subject: [PATCH 02/12] Fix lint

---
 src/languages/__mocks__/TranslationStore.ts |  1 -
 src/libs/LocaleDigitUtils.ts                | 12 ++++++------
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/languages/__mocks__/TranslationStore.ts b/src/languages/__mocks__/TranslationStore.ts
index 72c7ca88880d..12c0dad98c37 100644
--- a/src/languages/__mocks__/TranslationStore.ts
+++ b/src/languages/__mocks__/TranslationStore.ts
@@ -57,7 +57,6 @@ class TranslationStore {
     }
 
     static get<TPath extends TranslationPaths>(key: TPath, locale?: ValueOf<typeof CONST.LOCALES>) {
-        console.log(`TranslationStore.get called with key: ${key}, locale: ${locale}`);
         const localeToUse = locale && this.localeCache.has(locale) ? locale : this.currentLocale;
         if (!localeToUse) {
             return null;
diff --git a/src/libs/LocaleDigitUtils.ts b/src/libs/LocaleDigitUtils.ts
index 7d23c926747d..8ba628ede798 100644
--- a/src/libs/LocaleDigitUtils.ts
+++ b/src/libs/LocaleDigitUtils.ts
@@ -1,9 +1,9 @@
 import type {ValueOf} from 'type-fest';
 import type CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
-import * as Localize from './Localize';
+import {translate} from './Localize';
 import memoize from './memoize';
-import * as NumberFormatUtils from './NumberFormatUtils';
+import {format, formatToParts} from './NumberFormatUtils';
 
 type Locale = ValueOf<typeof CONST.LOCALES>;
 
@@ -17,9 +17,9 @@ const getLocaleDigits = memoize(
     (locale: Locale | undefined): string[] => {
         const localeDigits = [...STANDARD_DIGITS];
         for (let i = 0; i <= 9; i++) {
-            localeDigits[i] = NumberFormatUtils.format(locale, i);
+            localeDigits[i] = format(locale, i);
         }
-        NumberFormatUtils.formatToParts(locale, 1000000.5).forEach((part) => {
+        formatToParts(locale, 1000000.5).forEach((part) => {
             switch (part.type) {
                 case 'decimal':
                     localeDigits[INDEX_DECIMAL] = part.value;
@@ -88,7 +88,7 @@ function toLocaleOrdinal(locale: Locale | undefined, number: number, writtenOrdi
     const lastTwoDigits = number % 100;
 
     if (writtenOrdinals && number >= 1 && number <= 10) {
-        return Localize.translate(locale, `workflowsPage.frequencies.ordinals.${number}` as TranslationPaths);
+        return translate(locale, `workflowsPage.frequencies.ordinals.${number}` as TranslationPaths);
     }
 
     if (lastDigit === 1 && lastTwoDigits !== 11) {
@@ -99,7 +99,7 @@ function toLocaleOrdinal(locale: Locale | undefined, number: number, writtenOrdi
         suffixKey = 'workflowsPage.frequencies.ordinals.few';
     }
 
-    const suffix = Localize.translate(locale, suffixKey);
+    const suffix = translate(locale, suffixKey);
 
     return `${number}${suffix}`;
 }

From 8038e172e13625b02bc7755b6135bd78e52d113e Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Fri, 6 Jun 2025 22:21:23 +0530
Subject: [PATCH 03/12] Fix lint

---
 src/pages/ReimbursementAccount/USD/BankInfo/ExampleCheck.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/ReimbursementAccount/USD/BankInfo/ExampleCheck.tsx b/src/pages/ReimbursementAccount/USD/BankInfo/ExampleCheck.tsx
index 47bb59af1050..0d3fd158d4bf 100644
--- a/src/pages/ReimbursementAccount/USD/BankInfo/ExampleCheck.tsx
+++ b/src/pages/ReimbursementAccount/USD/BankInfo/ExampleCheck.tsx
@@ -9,7 +9,7 @@ function ExampleCheckImage() {
     const styles = useThemeStyles();
     const illustrations = useThemeIllustrations();
     const {preferredLocale} = useLocalize();
-    const isSpanish = (preferredLocale || CONST.LOCALES.DEFAULT) === CONST.LOCALES.ES;
+    const isSpanish = (preferredLocale ?? CONST.LOCALES.DEFAULT) === CONST.LOCALES.ES;
 
     return (
         <Image

From 46fba66d43d853536ebda9d308152a3da9eda5f2 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Sat, 7 Jun 2025 15:18:19 +0530
Subject: [PATCH 04/12] Fix test part 1

---
 src/components/DotIndicatorMessage.tsx   | 11 +++++------
 src/components/LocaleContextProvider.tsx |  2 +-
 src/languages/TranslationStore.ts        | 10 ++++++++--
 tests/actions/PolicyTest.ts              |  1 +
 tests/ui/DebugReportActionsTest.tsx      |  1 +
 tests/ui/LHNItemsPresence.tsx            |  1 +
 tests/ui/WorkspaceCategoriesTest.tsx     |  5 ++++-
 tests/unit/ReportUtilsTest.ts            | 15 ++++++---------
 tests/unit/Search/SearchUIUtilsTest.ts   |  4 ++++
 tests/unit/SidebarTest.ts                |  1 +
 tests/unit/TaxOptionsListUtilsTest.ts    |  4 ++++
 11 files changed, 36 insertions(+), 19 deletions(-)

diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx
index a357bd96a665..dd270f5e6ca6 100644
--- a/src/components/DotIndicatorMessage.tsx
+++ b/src/components/DotIndicatorMessage.tsx
@@ -9,7 +9,6 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {isReceiptError} from '@libs/ErrorUtils';
 import fileDownload from '@libs/fileDownload';
-import {translateLocal} from '@libs/Localize';
 import handleRetryPress from '@libs/ReceiptUploadRetryHandler';
 import type {ReceiptError} from '@src/types/onyx/Transaction';
 import ConfirmModal from './ConfirmModal';
@@ -71,24 +70,24 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles, dismissErr
                         key={index}
                         style={styles.offlineFeedback.text}
                     >
-                        <Text style={[StyleUtils.getDotIndicatorTextStyles(isErrorMessage)]}>{translateLocal('iou.error.receiptFailureMessage')}</Text>
+                        <Text style={[StyleUtils.getDotIndicatorTextStyles(isErrorMessage)]}>{translate('iou.error.receiptFailureMessage')}</Text>
                         <TextLink
                             style={[StyleUtils.getDotIndicatorTextStyles(), styles.link]}
                             onPress={() => handleRetryPress(message, dismissError, setShouldShowErrorModal)}
                         >
-                            {translateLocal('iou.error.tryAgainMessage')}
+                            {translate('iou.error.tryAgainMessage')}
                         </TextLink>
-                        <Text style={[StyleUtils.getDotIndicatorTextStyles(isErrorMessage)]}>{translateLocal('common.or')}</Text>
+                        <Text style={[StyleUtils.getDotIndicatorTextStyles(isErrorMessage)]}>{translate('common.or')}</Text>
                         <TextLink
                             style={[StyleUtils.getDotIndicatorTextStyles(), styles.link]}
                             onPress={() => {
                                 fileDownload(message.source, message.filename).finally(() => dismissError());
                             }}
                         >
-                            {translateLocal('iou.error.saveFileMessage')}
+                            {translate('iou.error.saveFileMessage')}
                         </TextLink>
 
-                        <Text style={[StyleUtils.getDotIndicatorTextStyles(isErrorMessage)]}>{translateLocal('iou.error.uploadLaterMessage')}</Text>
+                        <Text style={[StyleUtils.getDotIndicatorTextStyles(isErrorMessage)]}>{translate('iou.error.uploadLaterMessage')}</Text>
                     </Text>
 
                     <ConfirmModal
diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 4c16f41c898a..95612718218c 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -73,7 +73,7 @@ const LocaleContext = createContext<LocaleContextProps>({
 function LocaleContextProvider({children}: LocaleContextProviderProps) {
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
     const [areTranslationsLoading = true] = useOnyx(ONYXKEYS.ARE_TRANSLATIONS_LOADING, {initWithStoredValues: false, canBeMissing: true});
-    const [currentLocale, setCurrentLocale] = useState<Locale | undefined>(undefined);
+    const [currentLocale, setCurrentLocale] = useState<Locale | undefined>(() => TranslationStore.getCurrentLocale());
 
     useEffect(() => {
         if (areTranslationsLoading) {
diff --git a/src/languages/TranslationStore.ts b/src/languages/TranslationStore.ts
index 765118c8c86c..a90f05c7590f 100644
--- a/src/languages/TranslationStore.ts
+++ b/src/languages/TranslationStore.ts
@@ -22,7 +22,10 @@ class TranslationStore {
                 return Promise.resolve();
             }
             return import('./en').then((module) => {
-                const flattened = flattenObject(module.default);
+                // it is needed because in jest test the modules are imported in double nested default object
+                const flattened = flattenObject(
+                    (module.default as Record<string, unknown>).default ? ((module.default as Record<string, unknown>).default as typeof module.default) : module.default,
+                );
                 this.localeCache.set(CONST.LOCALES.EN, flattened);
             });
         },
@@ -31,7 +34,10 @@ class TranslationStore {
                 return Promise.resolve();
             }
             return import('./es').then((module) => {
-                const flattened = flattenObject(module.default);
+                // it is needed because in jest test the modules are imported in double nested default object
+                const flattened = flattenObject(
+                    (module.default as Record<string, unknown>).default ? ((module.default as Record<string, unknown>).default as typeof module.default) : module.default,
+                );
                 this.localeCache.set(CONST.LOCALES.ES, flattened);
             });
         },
diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts
index 9e32b8749984..48e1338df909 100644
--- a/tests/actions/PolicyTest.ts
+++ b/tests/actions/PolicyTest.ts
@@ -35,6 +35,7 @@ describe('actions/Policy', () => {
     beforeEach(() => {
         global.fetch = TestHelper.getGlobalFetchMock();
         mockFetch = fetch as MockFetch;
+        TranslationStore.load(CONST.LOCALES.EN);
         return Onyx.clear().then(waitForBatchedUpdates);
     });
 
diff --git a/tests/ui/DebugReportActionsTest.tsx b/tests/ui/DebugReportActionsTest.tsx
index 09fbe5e430bc..85cf6157efcb 100644
--- a/tests/ui/DebugReportActionsTest.tsx
+++ b/tests/ui/DebugReportActionsTest.tsx
@@ -31,6 +31,7 @@ describe('DebugReportActions', () => {
             keys: ONYXKEYS,
             evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
         });
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
     });
 
     afterEach(async () => {
diff --git a/tests/ui/LHNItemsPresence.tsx b/tests/ui/LHNItemsPresence.tsx
index 3aee5b03da33..8ee56193a41e 100644
--- a/tests/ui/LHNItemsPresence.tsx
+++ b/tests/ui/LHNItemsPresence.tsx
@@ -126,6 +126,7 @@ describe('SidebarLinksData', () => {
         wrapOnyxWithWaitForBatchedUpdates(Onyx);
         // Initialize the network key for OfflineWithFeedback
         Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false});
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
         signUpWithTestUser();
     });
 
diff --git a/tests/ui/WorkspaceCategoriesTest.tsx b/tests/ui/WorkspaceCategoriesTest.tsx
index b8fbf36f5ef9..9d86551ed371 100644
--- a/tests/ui/WorkspaceCategoriesTest.tsx
+++ b/tests/ui/WorkspaceCategoriesTest.tsx
@@ -54,7 +54,10 @@ describe('WorkspaceCategories', () => {
         });
     });
 
-    beforeEach(() => {
+    beforeEach(async () => {
+        await act(async () => {
+            await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
+        });
         jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({
             isSmallScreenWidth: false,
             shouldUseNarrowLayout: false,
diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts
index 6d52363074fe..e7fa2940fab2 100644
--- a/tests/unit/ReportUtilsTest.ts
+++ b/tests/unit/ReportUtilsTest.ts
@@ -60,6 +60,7 @@ import {buildOptimisticTransaction} from '@libs/TransactionUtils';
 import initOnyxDerivedValues from '@userActions/OnyxDerived';
 import type {OnboardingTaskLinks} from '@src/CONST';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Beta, OnyxInputOrEntry, PersonalDetailsList, Policy, PolicyEmployeeList, Report, ReportAction, Transaction} from '@src/types/onyx';
 import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon';
@@ -263,7 +264,7 @@ describe('ReportUtils', () => {
         });
         return waitForBatchedUpdates();
     });
-    beforeEach(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.DEFAULT).then(waitForBatchedUpdates));
+    beforeEach(() => TranslationStore.load(CONST.LOCALES.DEFAULT).then(waitForBatchedUpdates));
 
     describe('prepareOnboardingOnyxData', () => {
         it('provides test drive url to task title', () => {
@@ -499,7 +500,7 @@ describe('ReportUtils', () => {
 
                 expect(getReportName(baseAdminsRoom)).toBe('#admins (archived)');
 
-                return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(getReportName(baseAdminsRoom)).toBe('#admins (archivado)'));
+                return TranslationStore.load(CONST.LOCALES.ES).then(() => expect(getReportName(baseAdminsRoom)).toBe('#admins (archivado)'));
             });
         });
 
@@ -531,7 +532,7 @@ describe('ReportUtils', () => {
 
                 expect(getReportName(archivedPolicyRoom)).toBe('#VikingsChat (archived)');
 
-                return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(getReportName(archivedPolicyRoom)).toBe('#VikingsChat (archivado)'));
+                return TranslationStore.load(CONST.LOCALES.ES).then(() => expect(getReportName(archivedPolicyRoom)).toBe('#VikingsChat (archivado)'));
             });
         });
 
@@ -585,9 +586,7 @@ describe('ReportUtils', () => {
 
                     expect(getReportName(memberArchivedPolicyExpenseChat)).toBe(`Ragnar Lothbrok's expenses (archived)`);
 
-                    return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() =>
-                        expect(getReportName(memberArchivedPolicyExpenseChat)).toBe(`Ragnar Lothbrok's gastos (archivado)`),
-                    );
+                    return TranslationStore.load(CONST.LOCALES.ES).then(() => expect(getReportName(memberArchivedPolicyExpenseChat)).toBe(`Ragnar Lothbrok's gastos (archivado)`));
                 });
 
                 test('as admin', async () => {
@@ -598,9 +597,7 @@ describe('ReportUtils', () => {
 
                     expect(getReportName(adminArchivedPolicyExpenseChat)).toBe(`Ragnar Lothbrok's expenses (archived)`);
 
-                    return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() =>
-                        expect(getReportName(adminArchivedPolicyExpenseChat)).toBe(`Ragnar Lothbrok's gastos (archivado)`),
-                    );
+                    return TranslationStore.load(CONST.LOCALES.ES).then(() => expect(getReportName(adminArchivedPolicyExpenseChat)).toBe(`Ragnar Lothbrok's gastos (archivado)`));
                 });
             });
         });
diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts
index 1368e092f83c..d2b1e78e5df9 100644
--- a/tests/unit/Search/SearchUIUtilsTest.ts
+++ b/tests/unit/Search/SearchUIUtilsTest.ts
@@ -5,6 +5,7 @@ import TransactionListItem from '@components/SelectionList/Search/TransactionLis
 import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
 import * as Expensicons from '@src/components/Icon/Expensicons';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import * as SearchUIUtils from '@src/libs/SearchUIUtils';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type * as OnyxTypes from '@src/types/onyx';
@@ -927,6 +928,9 @@ const reportsListItems = [
 ] as ReportListItemType[];
 
 describe('SearchUIUtils', () => {
+    beforeAll(async () => {
+        await TranslationStore.load('en');
+    });
     describe('Test getAction', () => {
         test('Should return `Submit` action for transaction on policy with delayed submission and no violations', () => {
             let action = SearchUIUtils.getAction(searchResults.data, {}, `report_${reportID}`);
diff --git a/tests/unit/SidebarTest.ts b/tests/unit/SidebarTest.ts
index a3ea31bcf283..92dd3dc1e49f 100644
--- a/tests/unit/SidebarTest.ts
+++ b/tests/unit/SidebarTest.ts
@@ -34,6 +34,7 @@ describe('Sidebar', () => {
     beforeEach(() => {
         // Wrap Onyx each onyx action with waitForBatchedUpdates
         wrapOnyxWithWaitForBatchedUpdates(Onyx);
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
         // Initialize the network key for OfflineWithFeedback
         return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN).then(() => Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}));
     });
diff --git a/tests/unit/TaxOptionsListUtilsTest.ts b/tests/unit/TaxOptionsListUtilsTest.ts
index 6bdba29d4c08..fdc85528ff00 100644
--- a/tests/unit/TaxOptionsListUtilsTest.ts
+++ b/tests/unit/TaxOptionsListUtilsTest.ts
@@ -1,8 +1,12 @@
 import type {Section} from '@libs/OptionsListUtils';
 import {getTaxRatesSection} from '@libs/TaxOptionsListUtils';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {Policy, TaxRatesWithDefault, Transaction} from '@src/types/onyx';
 
 describe('TaxOptionsListUtils', () => {
+    beforeAll(() => {
+        TranslationStore.load('en');
+    });
     it('getTaxRatesSection()', () => {
         const search = 'rate';
         const emptySearch = '';

From a2527c219b57e7273b9f006fe436d9e05bfef155 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Tue, 10 Jun 2025 09:17:46 +0530
Subject: [PATCH 05/12] Fix test part 2

---
 tests/actions/IOUTest.ts                 | 2 ++
 tests/unit/CardFeedUtilsTest.ts          | 5 +++++
 tests/unit/ModifiedExpenseMessageTest.ts | 4 ++++
 3 files changed, 11 insertions(+)

diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index f81d40bef8f0..bcf79a7499a5 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -57,6 +57,7 @@ import type {OptimisticChatReport} from '@libs/ReportUtils';
 import {buildOptimisticTransaction, getValidWaypoints, isDistanceRequest as isDistanceRequestUtil} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import type {IOUAction} from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
 import * as API from '@src/libs/API';
 import DateUtils from '@src/libs/DateUtils';
@@ -145,6 +146,7 @@ describe('actions/IOU', () => {
                 [ONYXKEYS.PERSONAL_DETAILS_LIST]: {[RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL}},
             },
         });
+        TranslationStore.load(CONST.LOCALES.EN);
     });
 
     let mockFetch: MockFetch;
diff --git a/tests/unit/CardFeedUtilsTest.ts b/tests/unit/CardFeedUtilsTest.ts
index a96d526d9ea0..00dfd216d0c2 100644
--- a/tests/unit/CardFeedUtilsTest.ts
+++ b/tests/unit/CardFeedUtilsTest.ts
@@ -1,5 +1,7 @@
 import {getCardFeedNamesWithType, getSelectedCardsFromFeeds} from '@libs/CardFeedUtils';
 import {translateLocal} from '@libs/Localize';
+import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {WorkspaceCardsList} from '@src/types/onyx';
 
 /* eslint-disable @typescript-eslint/naming-convention */
@@ -57,6 +59,9 @@ const fakeWorkspace: Record<string, WorkspaceCardsList> = {
 /* eslint-enable @typescript-eslint/naming-convention */
 
 describe('Card Feed Utils', () => {
+    beforeAll(() => {
+        TranslationStore.load(CONST.LOCALES.EN);
+    });
     it('returns display name of workspace & domain cards', () => {
         const cardFeedNamesWithType = getCardFeedNamesWithType({workspaceCardFeeds: fakeWorkspace, translate: translateLocal});
         expect(Object.keys(cardFeedNamesWithType).length).toBe(2);
diff --git a/tests/unit/ModifiedExpenseMessageTest.ts b/tests/unit/ModifiedExpenseMessageTest.ts
index b5d3df206c6b..320489f8d348 100644
--- a/tests/unit/ModifiedExpenseMessageTest.ts
+++ b/tests/unit/ModifiedExpenseMessageTest.ts
@@ -1,6 +1,7 @@
 import Onyx from 'react-native-onyx';
 import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import {translate} from '@src/libs/Localize';
 import ONYXKEYS from '@src/ONYXKEYS';
 import createRandomReportAction from '../utils/collections/reportActions';
@@ -8,6 +9,9 @@ import createRandomReport from '../utils/collections/reports';
 import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
 
 describe('ModifiedExpenseMessage', () => {
+    beforeAll(() => {
+        TranslationStore.load(CONST.LOCALES.EN);
+    });
     describe('getForAction', () => {
         const report = createRandomReport(1);
         describe('when the amount is changed', () => {

From 795e220fbc401b2df81147b46700e49c461c476f Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Tue, 10 Jun 2025 10:06:24 +0530
Subject: [PATCH 06/12] Fix test part 3

---
 src/libs/ReportUtils.ts                | 18 +++++++++++++++---
 tests/ui/BottomTabBarTest.tsx          |  3 ++-
 tests/ui/WorkspaceTagsTest.tsx         |  5 ++++-
 tests/unit/CardUtilsTest.ts            |  4 ++++
 tests/unit/LocalizeTests.ts            | 13 +++++++++----
 tests/unit/OptionsListUtilsTest.ts     |  4 ++++
 tests/unit/SidebarOrderTest.ts         |  2 ++
 tests/unit/SidebarUtilsTest.ts         |  3 ++-
 tests/unit/TagsOptionsListUtilsTest.ts |  5 +++++
 tests/unit/TransactionUtilsTest.ts     |  2 ++
 10 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 1db647ebf39e..355261ad4b04 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -1082,6 +1082,21 @@ Onyx.connect({
     },
 });
 
+let hiddenTranslation = '';
+let unavailableTranslation = '';
+
+Onyx.connect({
+    key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
+    initWithStoredValues: false,
+    callback: (value) => {
+        if (value ?? true) {
+            return;
+        }
+        hiddenTranslation = translateLocal('common.hidden');
+        unavailableTranslation = translateLocal('workspace.common.unavailable');
+    },
+});
+
 function getCurrentUserAvatar(): AvatarSource | undefined {
     return currentUserPersonalDetails?.avatar;
 }
@@ -1223,7 +1238,6 @@ function getPolicyType(report: OnyxInputOrEntry<Report>, policies: OnyxCollectio
     return policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.type ?? '';
 }
 
-const unavailableTranslation = translateLocal('workspace.common.unavailable');
 /**
  * Get the policy name from a given report
  */
@@ -2653,8 +2667,6 @@ function getPersonalDetailsOrDefault(personalDetails: Partial<PersonalDetails> |
     return personalDetails ?? {isOptimisticPersonalDetail: true};
 }
 
-const hiddenTranslation = translateLocal('common.hidden');
-
 const phoneNumberCache: Record<string, string> = {};
 
 /**
diff --git a/tests/ui/BottomTabBarTest.tsx b/tests/ui/BottomTabBarTest.tsx
index 214cbf19016f..2ac209ac5911 100644
--- a/tests/ui/BottomTabBarTest.tsx
+++ b/tests/ui/BottomTabBarTest.tsx
@@ -18,9 +18,10 @@ describe('NavigationTabBar', () => {
     beforeAll(() => {
         Onyx.init({keys: ONYXKEYS});
         initOnyxDerivedValues();
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
     });
     beforeEach(() => {
-        Onyx.clear();
+        Onyx.clear([ONYXKEYS.NVP_PREFERRED_LOCALE]);
     });
     describe('Home tab', () => {
         describe('Debug mode enabled', () => {
diff --git a/tests/ui/WorkspaceTagsTest.tsx b/tests/ui/WorkspaceTagsTest.tsx
index c3611ed1591b..9bedd6970069 100644
--- a/tests/ui/WorkspaceTagsTest.tsx
+++ b/tests/ui/WorkspaceTagsTest.tsx
@@ -72,7 +72,10 @@ describe('WorkspaceTags', () => {
         });
     });
 
-    beforeEach(() => {
+    beforeEach(async () => {
+        await act(async () => {
+            await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
+        });
         jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({
             isSmallScreenWidth: true,
             shouldUseNarrowLayout: true,
diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts
index a47dd0206103..fa2853a66e62 100644
--- a/tests/unit/CardUtilsTest.ts
+++ b/tests/unit/CardUtilsTest.ts
@@ -2,6 +2,7 @@ import type {OnyxCollection} from 'react-native-onyx';
 import type IllustrationsType from '@styles/theme/illustrations/types';
 import type * as Illustrations from '@src/components/Icon/Illustrations';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import {
     checkIfFeedConnectionIsBroken,
     filterInactiveCards,
@@ -440,6 +441,9 @@ describe('CardUtils', () => {
     });
 
     describe('getCustomOrFormattedFeedName', () => {
+        beforeAll(() => {
+            TranslationStore.load(CONST.LOCALES.EN);
+        });
         it('Should return custom name if exists', () => {
             const feed = CONST.COMPANY_CARD.FEED_BANK_NAME.VISA;
             const companyCardNicknames = cardFeedsCollection.FAKE_ID_1?.settings?.companyCardNicknames;
diff --git a/tests/unit/LocalizeTests.ts b/tests/unit/LocalizeTests.ts
index 7693a0a4a88d..b0688897733d 100644
--- a/tests/unit/LocalizeTests.ts
+++ b/tests/unit/LocalizeTests.ts
@@ -1,4 +1,5 @@
 import Onyx from 'react-native-onyx';
+import TranslationStore from '@src/languages/TranslationStore';
 import CONST from '../../src/CONST';
 import * as Localize from '../../src/libs/Localize';
 import ONYXKEYS from '../../src/ONYXKEYS';
@@ -7,8 +8,10 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
 describe('localize', () => {
     beforeAll(() => {
         Onyx.init({
-            keys: {NVP_PREFERRED_LOCALE: ONYXKEYS.NVP_PREFERRED_LOCALE},
-            initialKeyStates: {[ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.DEFAULT},
+            keys: {
+                NVP_PREFERRED_LOCALE: ONYXKEYS.NVP_PREFERRED_LOCALE,
+                ARE_TRANSLATIONS_LOADING: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
+            },
         });
         return waitForBatchedUpdates();
     });
@@ -52,9 +55,11 @@ describe('localize', () => {
                     [CONST.LOCALES.ES]: 'rory, vit e ionatan',
                 },
             ],
-        ])('formatList(%s)', (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => {
+        ])('formatList(%s)', async (input, {[CONST.LOCALES.DEFAULT]: expectedOutput, [CONST.LOCALES.ES]: expectedOutputES}) => {
+            await TranslationStore.load(CONST.LOCALES.EN);
             expect(Localize.formatList(input)).toBe(expectedOutput);
-            return Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(Localize.formatList(input)).toBe(expectedOutputES));
+            await TranslationStore.load(CONST.LOCALES.ES);
+            expect(Localize.formatList(input)).toBe(expectedOutputES);
         });
     });
 });
diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts
index 471e8e61f1a0..bea0aed94b5b 100644
--- a/tests/unit/OptionsListUtilsTest.ts
+++ b/tests/unit/OptionsListUtilsTest.ts
@@ -23,6 +23,7 @@ import {
 import {canCreateTaskInReport, canUserPerformWriteAction, isCanceledTaskReport, isExpensifyOnlyParticipantInReport} from '@libs/ReportUtils';
 import type {OptionData} from '@libs/ReportUtils';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {PersonalDetails, Policy, Report} from '@src/types/onyx';
 import {getFakeAdvancedReportAction} from '../utils/LHNTestUtils';
@@ -43,6 +44,9 @@ jest.mock('@react-native-community/geolocation', () => ({
 type PersonalDetailsList = Record<string, PersonalDetails & OptionData>;
 
 describe('OptionsListUtils', () => {
+    beforeAll(() => {
+        TranslationStore.load(CONST.LOCALES.EN);
+    });
     const policyID = 'ABC123';
 
     const POLICY: Policy = {
diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts
index 5940c9c16f0d..1c4575e38a67 100644
--- a/tests/unit/SidebarOrderTest.ts
+++ b/tests/unit/SidebarOrderTest.ts
@@ -6,6 +6,7 @@ import DateUtils from '@libs/DateUtils';
 import {translateLocal} from '@libs/Localize';
 import initOnyxDerivedValues from '@userActions/OnyxDerived';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type * as OnyxTypes from '@src/types/onyx';
 import type {ReportCollectionDataSet} from '@src/types/onyx/Report';
@@ -35,6 +36,7 @@ describe('Sidebar', () => {
             evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
         });
         initOnyxDerivedValues();
+        TranslationStore.load(CONST.LOCALES.EN);
     });
 
     beforeEach(() => {
diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts
index 039ea1128dc9..cbf5a7210ff5 100644
--- a/tests/unit/SidebarUtilsTest.ts
+++ b/tests/unit/SidebarUtilsTest.ts
@@ -9,6 +9,7 @@ import {getAllReportErrors} from '@libs/ReportUtils';
 import SidebarUtils from '@libs/SidebarUtils';
 import initOnyxDerivedValues from '@userActions/OnyxDerived';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx';
 import type {ReportCollectionDataSet} from '@src/types/onyx/Report';
@@ -25,7 +26,7 @@ describe('SidebarUtils', () => {
             keys: ONYXKEYS,
             evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
         });
-        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
+        TranslationStore.load(CONST.LOCALES.EN);
         initOnyxDerivedValues();
     });
 
diff --git a/tests/unit/TagsOptionsListUtilsTest.ts b/tests/unit/TagsOptionsListUtilsTest.ts
index 77587dad0687..b3c9b89d9cd1 100644
--- a/tests/unit/TagsOptionsListUtilsTest.ts
+++ b/tests/unit/TagsOptionsListUtilsTest.ts
@@ -1,8 +1,13 @@
 import type {Section} from '@libs/OptionsListUtils';
 import type {SelectedTagOption} from '@libs/TagsOptionsListUtils';
 import {getTagListSections, sortTags} from '@libs/TagsOptionsListUtils';
+import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 
 describe('TagsOptionsListUtils', () => {
+    beforeAll(() => {
+        TranslationStore.load(CONST.LOCALES.EN);
+    });
     it('getTagListSections()', () => {
         const search = 'ing';
         const emptySearch = '';
diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts
index 1c4fb29bd23c..fdc8146753ed 100644
--- a/tests/unit/TransactionUtilsTest.ts
+++ b/tests/unit/TransactionUtilsTest.ts
@@ -1,6 +1,7 @@
 import Onyx from 'react-native-onyx';
 import {shouldShowBrokenConnectionViolation, shouldShowBrokenConnectionViolationForMultipleTransactions} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Attendee} from '@src/types/onyx/IOU';
 import type {ReportCollectionDataSet} from '@src/types/onyx/Report';
@@ -78,6 +79,7 @@ describe('TransactionUtils', () => {
                 ...reportCollectionDataSet,
             },
         });
+        TranslationStore.load(CONST.LOCALES.EN);
     });
 
     describe('getCreated', () => {

From b2172ffae6863be212edcb4c8952271f010604ab Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Tue, 10 Jun 2025 11:18:38 +0530
Subject: [PATCH 07/12] Fix test part 4

---
 tests/ui/AddressPageTest.tsx              |  2 ++
 tests/ui/TransactionItemRowRBRTest.tsx    |  9 +++++----
 tests/unit/CategoryOptionListUtilsTest.ts |  5 +++++
 tests/unit/CurrencyUtilsTest.ts           | 13 +++++++++----
 tests/unit/DateUtilsTest.ts               |  6 ++++++
 tests/unit/ReportActionsUtilsTest.ts      |  2 ++
 tests/unit/WorkspaceSettingsUtilsTest.ts  |  5 ++++-
 7 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/tests/ui/AddressPageTest.tsx b/tests/ui/AddressPageTest.tsx
index 01c9c7975a94..878bbe9c6f4d 100644
--- a/tests/ui/AddressPageTest.tsx
+++ b/tests/ui/AddressPageTest.tsx
@@ -10,6 +10,7 @@ import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
 import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
 import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
 import PersonalAddressPage from '@pages/settings/Profile/PersonalDetails/PersonalAddressPage';
+import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import SCREENS from '@src/SCREENS';
 import * as TestHelper from '../utils/TestHelper';
@@ -51,6 +52,7 @@ describe('AddressPageTest', () => {
         Onyx.init({
             keys: ONYXKEYS,
         });
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
     });
     it('should not reset state', async () => {
         await TestHelper.signInWithTestUser();
diff --git a/tests/ui/TransactionItemRowRBRTest.tsx b/tests/ui/TransactionItemRowRBRTest.tsx
index 0f9ff48d9194..b2a617af741b 100644
--- a/tests/ui/TransactionItemRowRBRTest.tsx
+++ b/tests/ui/TransactionItemRowRBRTest.tsx
@@ -89,16 +89,17 @@ const createErrorReportAction = () =>
     });
 
 describe('TransactionItemRowRBRWithOnyx', () => {
-    beforeAll(() =>
+    beforeAll(() => {
         Onyx.init({
             keys: ONYXKEYS,
             evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
-        }),
-    );
+        });
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.DEFAULT);
+    });
 
     beforeEach(() => {
         jest.clearAllMocks();
-        return Onyx.clear().then(waitForBatchedUpdates);
+        return Onyx.clear([ONYXKEYS.NVP_PREFERRED_LOCALE]).then(waitForBatchedUpdates);
     });
 
     it('should display RBR message for transaction with single violation', async () => {
diff --git a/tests/unit/CategoryOptionListUtilsTest.ts b/tests/unit/CategoryOptionListUtilsTest.ts
index a2ddfc50d0ec..83c94901f3bb 100644
--- a/tests/unit/CategoryOptionListUtilsTest.ts
+++ b/tests/unit/CategoryOptionListUtilsTest.ts
@@ -1,10 +1,15 @@
 /* eslint-disable @typescript-eslint/naming-convention */
 import {getCategoryListSections, getCategoryOptionTree, sortCategories} from '@libs/CategoryOptionListUtils';
 import type {Category, CategoryTreeSection} from '@libs/CategoryOptionListUtils';
+import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {PolicyCategories} from '@src/types/onyx';
 import type {PendingAction} from '@src/types/onyx/OnyxCommon';
 
 describe('CategoryOptionListUtils', () => {
+    beforeAll(() => {
+        TranslationStore.load(CONST.LOCALES.DEFAULT);
+    });
     it('getCategoryListSections()', () => {
         const search = 'Food';
         const emptySearch = '';
diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts
index ab84a3ee0a32..cc01a594e76c 100644
--- a/tests/unit/CurrencyUtilsTest.ts
+++ b/tests/unit/CurrencyUtilsTest.ts
@@ -1,5 +1,6 @@
 import Onyx from 'react-native-onyx';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import * as CurrencyUtils from '@src/libs/CurrencyUtils';
 import LocaleListener from '@src/libs/Localize/LocaleListener';
 import ONYXKEYS from '@src/ONYXKEYS';
@@ -31,11 +32,15 @@ describe('CurrencyUtils', () => {
         return waitForBatchedUpdates();
     });
 
+    beforeEach(() => {
+        TranslationStore.load(CONST.LOCALES.DEFAULT);
+    });
+
     afterEach(() => Onyx.clear());
 
     describe('getLocalizedCurrencySymbol', () => {
         test.each(AVAILABLE_LOCALES)('Returns non empty string for all currencyCode with preferredLocale %s', (preferredLocale) =>
-            Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, preferredLocale).then(() => {
+            TranslationStore.load(preferredLocale).then(() => {
                 currencyCodeList.forEach((currencyCode: string) => {
                     const localizedSymbol = CurrencyUtils.getLocalizedCurrencySymbol(currencyCode);
 
@@ -50,7 +55,7 @@ describe('CurrencyUtils', () => {
             [true, CONST.LOCALES.EN, 'USD'],
             [false, CONST.LOCALES.ES, 'USD'],
         ])('Returns %s for preferredLocale %s and currencyCode %s', (isLeft, locale, currencyCode) =>
-            Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, locale).then(() => {
+            TranslationStore.load(locale).then(() => {
                 const isSymbolLeft = CurrencyUtils.isCurrencySymbolLTR(currencyCode);
                 expect(isSymbolLeft).toBe(isLeft);
             }),
@@ -167,7 +172,7 @@ describe('CurrencyUtils', () => {
             ['EUR', 250000, '2500,00\xa0€'],
             ['EUR', 250000000, '2.500.000,00\xa0€'],
         ])('Correctly displays %s in ES locale', (currency, amount, expectedResult) =>
-            Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToDisplayString(amount, currency)).toBe(expectedResult)),
+            TranslationStore.load(CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToDisplayString(amount, currency)).toBe(expectedResult)),
         );
     });
 
@@ -194,7 +199,7 @@ describe('CurrencyUtils', () => {
             ['EUR', 250000, '2500\xa0€'],
             ['EUR', 250000000, '2.500.000\xa0€'],
         ])('Correctly displays %s in ES locale', (currency, amount, expectedResult) =>
-            Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToShortDisplayString(amount, currency)).toBe(expectedResult)),
+            TranslationStore.load(CONST.LOCALES.ES).then(() => expect(CurrencyUtils.convertToShortDisplayString(amount, currency)).toBe(expectedResult)),
         );
     });
 });
diff --git a/tests/unit/DateUtilsTest.ts b/tests/unit/DateUtilsTest.ts
index 8764931207c2..3d2e5a0ce4f3 100644
--- a/tests/unit/DateUtilsTest.ts
+++ b/tests/unit/DateUtilsTest.ts
@@ -4,6 +4,7 @@ import {toZonedTime, format as tzFormat} from 'date-fns-tz';
 import Onyx from 'react-native-onyx';
 import DateUtils from '@libs/DateUtils';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {SelectedTimezone} from '@src/types/onyx/PersonalDetails';
 import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
@@ -34,6 +35,11 @@ describe('DateUtils', () => {
         return waitForBatchedUpdates();
     });
 
+    beforeEach(() => {
+        TranslationStore.load(LOCALE);
+        DateUtils.setLocale(LOCALE);
+    });
+
     afterEach(() => {
         jest.restoreAllMocks();
         jest.useRealTimers();
diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts
index 9aa8fde27534..eb4a34a4738e 100644
--- a/tests/unit/ReportActionsUtilsTest.ts
+++ b/tests/unit/ReportActionsUtilsTest.ts
@@ -1,6 +1,7 @@
 import type {KeyValueMapping} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import {isExpenseReport} from '@libs/ReportUtils';
+import TranslationStore from '@src/languages/TranslationStore';
 import {actionR14932 as mockIOUAction, originalMessageR14932 as mockOriginalMessage} from '../../__mocks__/reportData/actions';
 import {chatReportR14932 as mockChatReport, iouReportR14932 as mockIOUReport} from '../../__mocks__/reportData/reports';
 import CONST from '../../src/CONST';
@@ -24,6 +25,7 @@ describe('ReportActionsUtils', () => {
     beforeEach(() => {
         // Wrap Onyx each onyx action with waitForBatchedUpdates
         wrapOnyxWithWaitForBatchedUpdates(Onyx);
+        TranslationStore.load(CONST.LOCALES.DEFAULT);
         // Initialize the network key for OfflineWithFeedback
         return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false});
     });
diff --git a/tests/unit/WorkspaceSettingsUtilsTest.ts b/tests/unit/WorkspaceSettingsUtilsTest.ts
index 731e49deec74..3baca728bedf 100644
--- a/tests/unit/WorkspaceSettingsUtilsTest.ts
+++ b/tests/unit/WorkspaceSettingsUtilsTest.ts
@@ -2,6 +2,8 @@ import type {OnyxCollection} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import {getBrickRoadForPolicy} from '@libs/WorkspacesSettingsUtils';
 import initOnyxDerivedValues from '@userActions/OnyxDerived';
+import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Report, ReportActions, Transaction, TransactionViolations} from '@src/types/onyx';
 import type {ReportCollectionDataSet} from '@src/types/onyx/Report';
@@ -19,7 +21,8 @@ describe('WorkspacesSettingsUtils', () => {
 
     beforeEach(() => {
         global.fetch = TestHelper.getGlobalFetchMock();
-        return Onyx.clear().then(waitForBatchedUpdates);
+        Onyx.clear([ONYXKEYS.NVP_PREFERRED_LOCALE]).then(waitForBatchedUpdates);
+        TranslationStore.load(CONST.LOCALES.EN);
     });
     describe('getBrickRoadForPolicy', () => {
         it('Should return "error"', async () => {

From 179e254be6b940b7ecb4d9b7704f79c7b3beb9b8 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Tue, 10 Jun 2025 14:20:41 +0530
Subject: [PATCH 08/12] Fix code after merge conflict

---
 src/libs/DateUtils.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index d9dc5b9bac14..c7ae919d5a38 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -241,6 +241,7 @@ const fallbackToSupportedTimezone = memoize((timezoneInput: SelectedTimezone): S
 function datetimeToCalendarTime(
     locale: Locale | undefined,
     datetime: string,
+    includeTimeZone = false,
     currentSelectedTimezone: SelectedTimezone = timezone.selected,
     isLowercase = false,
 ): string {

From ab5c513619d3b61288ebcb56ffc2dbb0d002154a Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Tue, 10 Jun 2025 19:49:43 +0530
Subject: [PATCH 09/12] Fix translate use in components

---
 src/pages/settings/Subscription/PaymentCard/index.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/pages/settings/Subscription/PaymentCard/index.tsx b/src/pages/settings/Subscription/PaymentCard/index.tsx
index 08887ebbae44..881cdd697ccf 100644
--- a/src/pages/settings/Subscription/PaymentCard/index.tsx
+++ b/src/pages/settings/Subscription/PaymentCard/index.tsx
@@ -19,7 +19,6 @@ import useSubscriptionPrice from '@hooks/useSubscriptionPrice';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {getMCardNumberString, getMonthFromExpirationDateString, getYearFromExpirationDateString} from '@libs/CardUtils';
 import {convertToShortDisplayString} from '@libs/CurrencyUtils';
-import {translateLocal} from '@libs/Localize';
 import CardAuthenticationModal from '@pages/settings/Subscription/CardAuthenticationModal';
 import {addSubscriptionPaymentCard, clearPaymentCardFormErrorAndSubmit, continueSetup} from '@userActions/PaymentMethods';
 import CONST from '@src/CONST';
@@ -42,7 +41,7 @@ function AddPaymentCard() {
 
     const subscriptionPricingInfo =
         hasTeam2025Pricing && isCollect
-            ? translateLocal('subscription.yourPlan.pricePerMemberPerMonth', {price: convertToShortDisplayString(subscriptionPrice, preferredCurrency)})
+            ? translate('subscription.yourPlan.pricePerMemberPerMonth', {price: convertToShortDisplayString(subscriptionPrice, preferredCurrency)})
             : translate(`subscription.yourPlan.${isCollect ? 'collect' : 'control'}.${isAnnual ? 'priceAnnual' : 'pricePayPerUse'}`, {
                   lower: convertToShortDisplayString(subscriptionPrice, preferredCurrency),
                   upper: convertToShortDisplayString(subscriptionPrice * CONST.SUBSCRIPTION_PRICE_FACTOR, preferredCurrency),

From 5083310a7382670ac9f429e63fef2449720596f7 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Sat, 14 Jun 2025 09:35:22 +0530
Subject: [PATCH 10/12] Make sure undefined locale does not get into
 currentLocale

---
 src/components/LocaleContextProvider.tsx | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 95612718218c..181aa9acd158 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -80,7 +80,12 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
             return;
         }
 
-        setCurrentLocale(TranslationStore.getCurrentLocale());
+        const locale = TranslationStore.getCurrentLocale();
+        if (!locale) {
+            return;
+        }
+
+        setCurrentLocale(locale);
     }, [areTranslationsLoading]);
 
     const selectedTimezone = useMemo(() => currentUserPersonalDetails?.timezone?.selected, [currentUserPersonalDetails]);

From 1421b935066b415a5cf2bf3ac3b8b7cc7e676381 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Sat, 14 Jun 2025 15:31:19 +0530
Subject: [PATCH 11/12] Fix translate bug in desktop

---
 desktop/main.ts                               | 11 ++++---
 .../LocaleListener/BaseLocaleListener.ts      | 31 -------------------
 .../Localize/LocaleListener/index.desktop.ts  | 18 -----------
 src/libs/Localize/LocaleListener/index.ts     | 10 ------
 src/libs/Localize/LocaleListener/types.ts     | 13 --------
 src/libs/Localize/index.ts                    |  8 ++---
 .../localeEventCallback/index.desktop.ts      |  9 ++++++
 .../Localize/localeEventCallback/index.ts     |  5 +++
 .../Localize/localeEventCallback/types.ts     |  9 ++++++
 src/libs/actions/App.ts                       |  2 ++
 tests/unit/CurrencyUtilsTest.ts               |  2 --
 11 files changed, 34 insertions(+), 84 deletions(-)
 delete mode 100644 src/libs/Localize/LocaleListener/BaseLocaleListener.ts
 delete mode 100644 src/libs/Localize/LocaleListener/index.desktop.ts
 delete mode 100644 src/libs/Localize/LocaleListener/index.ts
 delete mode 100644 src/libs/Localize/LocaleListener/types.ts
 create mode 100644 src/libs/Localize/localeEventCallback/index.desktop.ts
 create mode 100644 src/libs/Localize/localeEventCallback/index.ts
 create mode 100644 src/libs/Localize/localeEventCallback/types.ts

diff --git a/desktop/main.ts b/desktop/main.ts
index 82c6fdac2418..b7b67fd8acb3 100644
--- a/desktop/main.ts
+++ b/desktop/main.ts
@@ -14,6 +14,7 @@ import CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
 import type PlatformSpecificUpdater from '@src/setup/platformSetup/types';
 import type {Locale} from '@src/types/onyx';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {CreateDownloadQueueModule, DownloadItem} from './createDownloadQueue';
 import serve from './electron-serve';
 import ELECTRON_EVENTS from './ELECTRON_EVENTS';
@@ -665,10 +666,12 @@ const mainWindow = (): Promise<void> => {
                 // because the only way code can be shared between the main and renderer processes at runtime is via the context bridge
                 // So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED
                 ipcMain.on(ELECTRON_EVENTS.LOCALE_UPDATED, (event, updatedLocale: Locale) => {
-                    preferredLocale = updatedLocale;
-                    Menu.setApplicationMenu(Menu.buildFromTemplate(localizeMenuItems(initialMenuTemplate, updatedLocale)));
-                    disposeContextMenu?.();
-                    disposeContextMenu = createContextMenu(updatedLocale);
+                    TranslationStore.load(updatedLocale).then(() => {
+                        preferredLocale = updatedLocale;
+                        Menu.setApplicationMenu(Menu.buildFromTemplate(localizeMenuItems(initialMenuTemplate, updatedLocale)));
+                        disposeContextMenu?.();
+                        disposeContextMenu = createContextMenu(updatedLocale);
+                    });
                 });
 
                 ipcMain.on(ELECTRON_EVENTS.REQUEST_VISIBILITY, (event) => {
diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts
deleted file mode 100644
index c19e3927a79a..000000000000
--- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import Onyx from 'react-native-onyx';
-import TranslationStore from '@src/languages/TranslationStore';
-import ONYXKEYS from '@src/ONYXKEYS';
-import type {LocaleListenerConnect} from './types';
-
-/**
- * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx.
- */
-const connect: LocaleListenerConnect = (callbackAfterChange = () => {}) => {
-    Onyx.connect({
-        key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
-        initWithStoredValues: false,
-        callback: (val) => {
-            if (val ?? true) {
-                return;
-            }
-
-            const preferredLocale = TranslationStore.getCurrentLocale();
-            if (!preferredLocale) {
-                return;
-            }
-            callbackAfterChange(preferredLocale);
-        },
-    });
-};
-
-const BaseLocaleListener = {
-    connect,
-};
-
-export default BaseLocaleListener;
diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts
deleted file mode 100644
index 3695917c6f01..000000000000
--- a/src/libs/Localize/LocaleListener/index.desktop.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
-import BaseLocaleListener from './BaseLocaleListener';
-import type {LocaleListener, LocaleListenerConnect} from './types';
-
-const localeListenerConnect: LocaleListenerConnect = (callbackAfterChange = () => {}) =>
-    BaseLocaleListener.connect((val) => {
-        // Send the updated locale to the Electron main process
-        window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val);
-
-        // Then execute the callback provided for the renderer process
-        callbackAfterChange(val);
-    });
-
-const localeListener: LocaleListener = {
-    connect: localeListenerConnect,
-};
-
-export default localeListener;
diff --git a/src/libs/Localize/LocaleListener/index.ts b/src/libs/Localize/LocaleListener/index.ts
deleted file mode 100644
index c08eb129934a..000000000000
--- a/src/libs/Localize/LocaleListener/index.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import BaseLocaleListener from './BaseLocaleListener';
-import type {LocaleListener, LocaleListenerConnect} from './types';
-
-const localeListenerConnect: LocaleListenerConnect = BaseLocaleListener.connect;
-
-const localizeListener: LocaleListener = {
-    connect: localeListenerConnect,
-};
-
-export default localizeListener;
diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts
deleted file mode 100644
index 453e8c7f442a..000000000000
--- a/src/libs/Localize/LocaleListener/types.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type {ValueOf} from 'type-fest';
-import type CONST from '@src/CONST';
-
-type BaseLocale = ValueOf<typeof CONST.LOCALES>;
-
-type LocaleListenerConnect = (callbackAfterChange?: (locale?: BaseLocale) => void) => void;
-
-type LocaleListener = {
-    connect: LocaleListenerConnect;
-};
-
-export type {LocaleListenerConnect, LocaleListener};
-export default BaseLocale;
diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts
index 40e7c953f02a..c7e2deada8e9 100644
--- a/src/libs/Localize/index.ts
+++ b/src/libs/Localize/index.ts
@@ -10,7 +10,6 @@ import type {PluralForm, TranslationParameters, TranslationPaths} from '@src/lan
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Locale} from '@src/types/onyx';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import LocaleListener from './LocaleListener';
 
 // Current user mail is needed for handling missing translations
 let userEmail = '';
@@ -24,9 +23,6 @@ Onyx.connect({
     },
 });
 
-// Listener when an update in Onyx happens so we use the updated locale when translating/localizing items.
-LocaleListener.connect();
-
 // Note: This has to be initialized inside a function and not at the top level of the file, because Intl is polyfilled,
 // and if React Native executes this code upon import, then the polyfill will not be available yet and it will barf
 let CONJUNCTION_LIST_FORMATS_FOR_LOCALES: Record<string, Intl.ListFormat>;
@@ -136,13 +132,13 @@ function translate<TPath extends TranslationPaths>(desiredLanguage: 'en' | 'es'
     // on development throw an error
     if (Config.IS_IN_PRODUCTION || Config.IS_IN_STAGING) {
         const phraseString = Array.isArray(path) ? path.join('.') : path;
-        Log.alert(`${phraseString} was not found in the en locale`);
+        Log.alert(`${phraseString} was not found in the ${language} locale`);
         if (userEmail.includes(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) {
             return CONST.MISSING_TRANSLATION;
         }
         return phraseString;
     }
-    throw new Error(`${path} was not found in the ${desiredLanguage} language`);
+    throw new Error(`${path} was not found in the ${language} locale`);
 }
 
 /**
diff --git a/src/libs/Localize/localeEventCallback/index.desktop.ts b/src/libs/Localize/localeEventCallback/index.desktop.ts
new file mode 100644
index 000000000000..4d257b14dbd2
--- /dev/null
+++ b/src/libs/Localize/localeEventCallback/index.desktop.ts
@@ -0,0 +1,9 @@
+import ELECTRON_EVENTS from '@desktop/ELECTRON_EVENTS';
+import type {LocaleEventCallback} from './types';
+
+const localeEventCallback: LocaleEventCallback = (value) => {
+    // Send the updated locale to the Electron main process
+    window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, value);
+};
+
+export default localeEventCallback;
diff --git a/src/libs/Localize/localeEventCallback/index.ts b/src/libs/Localize/localeEventCallback/index.ts
new file mode 100644
index 000000000000..805cefdbc878
--- /dev/null
+++ b/src/libs/Localize/localeEventCallback/index.ts
@@ -0,0 +1,5 @@
+import type {LocaleEventCallback} from './types';
+
+const localeEventCallback: LocaleEventCallback = () => {};
+
+export default localeEventCallback;
diff --git a/src/libs/Localize/localeEventCallback/types.ts b/src/libs/Localize/localeEventCallback/types.ts
new file mode 100644
index 000000000000..2ebd4c9f2260
--- /dev/null
+++ b/src/libs/Localize/localeEventCallback/types.ts
@@ -0,0 +1,9 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+
+type BaseLocale = ValueOf<typeof CONST.LOCALES>;
+
+type LocaleEventCallback = (locale?: BaseLocale) => void;
+
+export type {LocaleEventCallback};
+export default BaseLocale;
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index e49802f73689..30330822910f 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -27,6 +27,7 @@ import type {Route} from '@src/ROUTES';
 import ROUTES from '@src/ROUTES';
 import type * as OnyxTypes from '@src/types/onyx';
 import type {OnyxData} from '@src/types/onyx/Request';
+import localeEventCallback from '@libs/Localize/localeEventCallback';
 import {setShouldForceOffline} from './Network';
 import {getAll, rollbackOngoingRequest, save} from './PersistedRequests';
 import {createDraftInitialWorkspace, createWorkspace, generatePolicyID} from './Policy/Policy';
@@ -67,6 +68,7 @@ Onyx.connect({
             importEmojiLocale(preferredLocale as Locale).then(() => {
                 buildEmojisTrie(preferredLocale as Locale);
             });
+            localeEventCallback(val);
         }
     },
 });
diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts
index cc01a594e76c..811a6a095c5e 100644
--- a/tests/unit/CurrencyUtilsTest.ts
+++ b/tests/unit/CurrencyUtilsTest.ts
@@ -2,7 +2,6 @@ import Onyx from 'react-native-onyx';
 import CONST from '@src/CONST';
 import TranslationStore from '@src/languages/TranslationStore';
 import * as CurrencyUtils from '@src/libs/CurrencyUtils';
-import LocaleListener from '@src/libs/Localize/LocaleListener';
 import ONYXKEYS from '@src/ONYXKEYS';
 import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
 // This file can get outdated. In that case, you can follow these steps to update it:
@@ -28,7 +27,6 @@ describe('CurrencyUtils', () => {
                 [ONYXKEYS.CURRENCY_LIST]: currencyList,
             },
         });
-        LocaleListener.connect();
         return waitForBatchedUpdates();
     });
 

From f5f24d313f53f779a9f0a32deefbcdd67b1dad4c Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Sat, 14 Jun 2025 15:35:48 +0530
Subject: [PATCH 12/12] Fix prettier

---
 desktop/main.ts         | 2 +-
 src/libs/actions/App.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/desktop/main.ts b/desktop/main.ts
index b7b67fd8acb3..98f30549e8b4 100644
--- a/desktop/main.ts
+++ b/desktop/main.ts
@@ -11,10 +11,10 @@ import {translate} from '@libs/Localize';
 import Log from '@libs/Log';
 import CONFIG from '@src/CONFIG';
 import CONST from '@src/CONST';
+import TranslationStore from '@src/languages/TranslationStore';
 import type {TranslationPaths} from '@src/languages/types';
 import type PlatformSpecificUpdater from '@src/setup/platformSetup/types';
 import type {Locale} from '@src/types/onyx';
-import TranslationStore from '@src/languages/TranslationStore';
 import type {CreateDownloadQueueModule, DownloadItem} from './createDownloadQueue';
 import serve from './electron-serve';
 import ELECTRON_EVENTS from './ELECTRON_EVENTS';
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index 30330822910f..18ed4c3d6d9a 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -12,6 +12,7 @@ import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
 import * as Browser from '@libs/Browser';
 import DateUtils from '@libs/DateUtils';
 import {buildEmojisTrie} from '@libs/EmojiTrie';
+import localeEventCallback from '@libs/Localize/localeEventCallback';
 import Log from '@libs/Log';
 import getCurrentUrl from '@libs/Navigation/currentUrl';
 import Navigation from '@libs/Navigation/Navigation';
@@ -27,7 +28,6 @@ import type {Route} from '@src/ROUTES';
 import ROUTES from '@src/ROUTES';
 import type * as OnyxTypes from '@src/types/onyx';
 import type {OnyxData} from '@src/types/onyx/Request';
-import localeEventCallback from '@libs/Localize/localeEventCallback';
 import {setShouldForceOffline} from './Network';
 import {getAll, rollbackOngoingRequest, save} from './PersistedRequests';
 import {createDraftInitialWorkspace, createWorkspace, generatePolicyID} from './Policy/Policy';
