From c2505b305ee881b065ddef387a02c5571704ba9b Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Sun, 1 Jun 2025 19:19:18 +0530
Subject: [PATCH 1/2] Refactored components/functions to use centralized
 preferred locale

---
 .../LHNOptionsList/OptionRowLHNData.tsx       |  1 -
 src/components/LocalePicker.tsx               | 31 ++++-----------
 .../Reactions/MiniQuickEmojiReactions.tsx     |  3 +-
 .../BaseQuickEmojiReactions.tsx               |  3 +-
 src/hooks/useSidePanel.ts                     |  5 ++-
 src/libs/DateUtils.ts                         | 17 ++-------
 src/libs/OptionsListUtils.ts                  | 38 +++++++------------
 src/libs/ReportActionsUtils.ts                | 15 +-------
 src/libs/SidebarUtils.ts                      | 15 +++-----
 src/libs/TransactionUtils/index.ts            | 38 +++++++------------
 .../settings/Preferences/PreferencesPage.tsx  |  5 ++-
 tests/perf-test/SidebarUtils.perf-test.ts     |  3 +-
 tests/unit/SidebarUtilsTest.ts                | 15 +-------
 13 files changed, 57 insertions(+), 132 deletions(-)

diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx
index 8da4d87d6280..641c071112fc 100644
--- a/src/components/LHNOptionsList/OptionRowLHNData.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx
@@ -46,7 +46,6 @@ function OptionRowLHNData({
             oneTransactionThreadReport,
             reportNameValuePairs,
             personalDetails,
-            preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT,
             policy,
             parentReportAction,
             lastMessageTextFromReport,
diff --git a/src/components/LocalePicker.tsx b/src/components/LocalePicker.tsx
index b6e59643e499..d25cf7f3b393 100644
--- a/src/components/LocalePicker.tsx
+++ b/src/components/LocalePicker.tsx
@@ -1,34 +1,26 @@
 import React from 'react';
-import type {OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
 import useLocalize from '@hooks/useLocalize';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import AccountUtils from '@libs/AccountUtils';
-import * as App from '@userActions/App';
+import {setLocale} from '@userActions/App';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {Account, Locale} from '@src/types/onyx';
 import Picker from './Picker';
 import type {PickerSize} from './Picker/types';
 
-type LocalePickerOnyxProps = {
-    /** The details about the account that the user is signing in with */
-    account: OnyxEntry<Account>;
-
-    /** Indicates which locale the user currently has selected */
-    preferredLocale: OnyxEntry<Locale>;
-};
-
-type LocalePickerProps = LocalePickerOnyxProps & {
+type LocalePickerProps = {
     /** Indicates size of a picker component and whether to render the label or not */
     size?: PickerSize;
 };
 
-function LocalePicker({account, preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}: LocalePickerProps) {
+function LocalePicker({size = 'normal'}: LocalePickerProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const {translate} = useLocalize();
+    const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true});
+    const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
     const localesToLanguages = CONST.LANGUAGES.map((language) => ({
         value: language,
         label: translate(`languagePage.languages.${language}.label`),
@@ -45,7 +37,7 @@ function LocalePicker({account, preferredLocale = CONST.LOCALES.DEFAULT, size =
                     return;
                 }
 
-                App.setLocale(locale);
+                setLocale(locale);
             }}
             isDisabled={shouldDisablePicker}
             items={localesToLanguages}
@@ -61,11 +53,4 @@ function LocalePicker({account, preferredLocale = CONST.LOCALES.DEFAULT, size =
 
 LocalePicker.displayName = 'LocalePicker';
 
-export default withOnyx<LocalePickerProps, LocalePickerOnyxProps>({
-    account: {
-        key: ONYXKEYS.ACCOUNT,
-    },
-    preferredLocale: {
-        key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    },
-})(LocalePicker);
+export default LocalePicker;
diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx
index 7c8b02412df9..f6a98a3daeee 100644
--- a/src/components/Reactions/MiniQuickEmojiReactions.tsx
+++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx
@@ -36,8 +36,7 @@ function MiniQuickEmojiReactions({reportAction, reportActionID, onEmojiSelected,
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
     const ref = useRef<View>(null);
-    const {translate} = useLocalize();
-    const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {initialValue: CONST.LOCALES.DEFAULT});
+    const {translate, preferredLocale} = useLocalize();
     const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
     const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, {initialValue: {}});
 
diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
index 246c9a5dd0d5..730c5cd002da 100644
--- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
+++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
@@ -5,6 +5,7 @@ import type {Emoji} from '@assets/emojis/types';
 import AddReactionBubble from '@components/Reactions/AddReactionBubble';
 import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble';
 import Tooltip from '@components/Tooltip';
+import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {getLocalizedEmojiName, getPreferredEmojiCode} from '@libs/EmojiUtils';
 import {callFunctionIfActionIsAllowed} from '@userActions/Session';
@@ -21,7 +22,7 @@ function BaseQuickEmojiReactions({
     setIsEmojiPickerActive,
 }: BaseQuickEmojiReactionsProps) {
     const styles = useThemeStyles();
-    const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {initialValue: CONST.LOCALES.DEFAULT});
+    const {preferredLocale} = useLocalize();
     const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
     const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, {initialValue: {}});
 
diff --git a/src/hooks/useSidePanel.ts b/src/hooks/useSidePanel.ts
index f1c3c96a1fe6..9116fb0de43f 100644
--- a/src/hooks/useSidePanel.ts
+++ b/src/hooks/useSidePanel.ts
@@ -10,6 +10,7 @@ import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import KeyboardUtils from '@src/utils/keyboard';
+import useLocalize from './useLocalize';
 import usePrevious from './usePrevious';
 import useResponsiveLayout from './useResponsiveLayout';
 import useWindowDimensions from './useWindowDimensions';
@@ -19,8 +20,8 @@ import useWindowDimensions from './useWindowDimensions';
  */
 function useSidePanelDisplayStatus() {
     const {isExtraLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
+    const {preferredLocale} = useLocalize();
     const [sidePanelNVP] = useOnyx(ONYXKEYS.NVP_SIDE_PANEL, {canBeMissing: true});
-    const [language] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true});
     const [isModalCenteredVisible = false] = useOnyx(ONYXKEYS.MODAL, {
         canBeMissing: true,
         selector: (modal) =>
@@ -30,7 +31,7 @@ function useSidePanelDisplayStatus() {
             modal?.type === CONST.MODAL.MODAL_TYPE.CENTERED,
     });
 
-    const isLanguageUnsupported = language !== CONST.LOCALES.EN;
+    const isLanguageUnsupported = preferredLocale !== CONST.LOCALES.EN;
     const isSidePanelVisible = isExtraLargeScreenWidth ? sidePanelNVP?.open : sidePanelNVP?.openNarrowScreen;
 
     // The Side Panel is hidden when:
diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index e339b23430c2..b31b36d812d6 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -45,6 +45,7 @@ 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>;
@@ -91,28 +92,16 @@ Onyx.connect({
 
 let isOffline: boolean | undefined;
 
-let preferredLocaleFromOnyx: Locale;
-
-Onyx.connect({
-    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    callback: (value) => {
-        if (!value) {
-            return;
-        }
-        preferredLocaleFromOnyx = value;
-    },
-});
-
 Onyx.connect({
     key: ONYXKEYS.NETWORK,
     callback: (val) => {
         if (!val?.lastOfflineAt) {
-            setNetworkLastOffline(getLocalDateFromDatetime(preferredLocaleFromOnyx));
+            setNetworkLastOffline(getLocalDateFromDatetime(BaseLocaleListener.getPreferredLocale()));
         }
 
         const newIsOffline = val?.isOffline ?? val?.shouldForceOffline;
         if (newIsOffline && isOffline === false) {
-            setNetworkLastOffline(getLocalDateFromDatetime(preferredLocaleFromOnyx));
+            setNetworkLastOffline(getLocalDateFromDatetime(BaseLocaleListener.getPreferredLocale()));
         }
         isOffline = newIsOffline;
     },
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index a22a35a01c72..310c013bf62f 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -33,14 +33,13 @@ import type {
 } from '@src/types/onyx';
 import type {Attendee, Participant} from '@src/types/onyx/IOU';
 import type {Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
-import type DeepValueOf from '@src/types/utils/DeepValueOf';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import Timing from './actions/Timing';
 import {getEnabledCategoriesCount} from './CategoryUtils';
 import filterArrayByMatch from './filterArrayByMatch';
 import {isReportMessageAttachment} from './isReportMessageAttachment';
 import {formatPhoneNumber} from './LocalePhoneNumber';
-import {translate, translateLocal} from './Localize';
+import {translateLocal} from './Localize';
 import {appendCountryCode, getPhoneNumberWithoutSpecialChars} from './LoginUtils';
 import ModifiedExpenseMessage from './ModifiedExpenseMessage';
 import Navigation from './Navigation/Navigation';
@@ -326,17 +325,6 @@ Onyx.connect({
     callback: (value) => (allPersonalDetails = isEmptyObject(value) ? {} : value),
 });
 
-let preferredLocale: DeepValueOf<typeof CONST.LOCALES> = CONST.LOCALES.DEFAULT;
-Onyx.connect({
-    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    callback: (value) => {
-        if (!value) {
-            return;
-        }
-        preferredLocale = value;
-    },
-});
-
 const policies: OnyxCollection<Policy> = {};
 Onyx.connect({
     key: ONYXKEYS.COLLECTION.POLICY,
@@ -617,11 +605,11 @@ function getAlternateText(option: OptionData, {showChatPreviewLine = false, forc
     const formattedLastMessageTextWithPrefix = reportPrefix + formattedLastMessageText;
 
     if (isExpenseThread || option.isMoneyRequestReport) {
-        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translate(preferredLocale, 'iou.expense');
+        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateLocal('iou.expense');
     }
 
     if (option.isThread) {
-        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translate(preferredLocale, 'threads.thread');
+        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateLocal('threads.thread');
     }
 
     if (option.isChatRoom && !isAdminRoom && !isAnnounceRoom) {
@@ -633,11 +621,11 @@ function getAlternateText(option: OptionData, {showChatPreviewLine = false, forc
     }
 
     if (option.isTaskReport) {
-        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translate(preferredLocale, 'task.task');
+        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateLocal('task.task');
     }
 
     if (isGroupChat) {
-        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translate(preferredLocale, 'common.group');
+        return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateLocal('common.group');
     }
 
     return showChatPreviewLine && formattedLastMessageText
@@ -725,18 +713,18 @@ function getLastMessageTextForReport(
             case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED:
             case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY:
             case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: {
-                lastMessageTextFromReport = translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, {
+                lastMessageTextFromReport = translateLocal(`reportArchiveReasons.${archiveReason}`, {
                     displayName: formatPhoneNumber(getDisplayNameOrDefault(lastActorDetails)),
                     policyName: getPolicyName({report, policy}),
                 });
                 break;
             }
             case CONST.REPORT.ARCHIVE_REASON.BOOKING_END_DATE_HAS_PASSED: {
-                lastMessageTextFromReport = translate(preferredLocale, `reportArchiveReasons.${archiveReason}`);
+                lastMessageTextFromReport = translateLocal(`reportArchiveReasons.${archiveReason}`);
                 break;
             }
             default: {
-                lastMessageTextFromReport = translate(preferredLocale, `reportArchiveReasons.default`);
+                lastMessageTextFromReport = translateLocal(`reportArchiveReasons.default`);
             }
         }
     } else if (isMoneyRequestAction(lastReportAction)) {
@@ -2021,22 +2009,22 @@ function getHeaderMessage(hasSelectableOptions: boolean, hasUserToInvite: boolea
     const isValidEmail = Str.isValidEmail(searchValue);
 
     if (searchValue && CONST.REGEX.DIGITS_AND_PLUS.test(searchValue) && !isValidPhone && !hasSelectableOptions) {
-        return translate(preferredLocale, 'messages.errorMessageInvalidPhone');
+        return translateLocal('messages.errorMessageInvalidPhone');
     }
 
     // Without a search value, it would be very confusing to see a search validation message.
     // Therefore, this skips the validation when there is no search value.
     if (searchValue && !hasSelectableOptions && !hasUserToInvite) {
         if (/^\d+$/.test(searchValue) && !isValidPhone) {
-            return translate(preferredLocale, 'messages.errorMessageInvalidPhone');
+            return translateLocal('messages.errorMessageInvalidPhone');
         }
         if (/@/.test(searchValue) && !isValidEmail) {
-            return translate(preferredLocale, 'messages.errorMessageInvalidEmail');
+            return translateLocal('messages.errorMessageInvalidEmail');
         }
         if (hasMatchedParticipant && (isValidEmail || isValidPhone)) {
             return '';
         }
-        return translate(preferredLocale, 'common.noResultsFound');
+        return translateLocal('common.noResultsFound');
     }
 
     return '';
@@ -2047,7 +2035,7 @@ function getHeaderMessage(hasSelectableOptions: boolean, hasUserToInvite: boolea
  */
 function getHeaderMessageForNonUserList(hasSelectableOptions: boolean, searchValue: string): string {
     if (searchValue && !hasSelectableOptions) {
-        return translate(preferredLocale, 'common.noResultsFound');
+        return translateLocal('common.noResultsFound');
     }
     return '';
 }
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index a2f3503d036d..16015c361184 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -17,7 +17,6 @@ import type Report from '@src/types/onyx/Report';
 import type ReportAction from '@src/types/onyx/ReportAction';
 import type {Message, OldDotReportAction, OriginalMessage, ReportActions} from '@src/types/onyx/ReportAction';
 import type ReportActionName from '@src/types/onyx/ReportActionName';
-import type DeepValueOf from '@src/types/utils/DeepValueOf';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import {convertToDisplayString} from './CurrencyUtils';
 import DateUtils from './DateUtils';
@@ -27,6 +26,7 @@ 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';
@@ -67,17 +67,6 @@ Onyx.connect({
     },
 });
 
-let preferredLocale: DeepValueOf<typeof CONST.LOCALES> = CONST.LOCALES.DEFAULT;
-Onyx.connect({
-    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    callback: (value) => {
-        if (!value) {
-            return;
-        }
-        preferredLocale = value;
-    },
-});
-
 let allReports: OnyxCollection<Report>;
 Onyx.connect({
     key: ONYXKEYS.COLLECTION.REPORT,
@@ -2193,7 +2182,7 @@ function getWorkspaceUpdateFieldMessage(action: ReportAction): string {
                 return translateLocal('workflowsPage.frequencies.lastBusinessDayOfMonth');
             }
             if (typeof autoReportingOffset === 'number') {
-                return toLocaleOrdinal(preferredLocale, autoReportingOffset, false);
+                return toLocaleOrdinal(BaseLocaleListener.getPreferredLocale(), autoReportingOffset, false);
             }
             return '';
         };
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 936851e731fa..8b0a5920b9c0 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -13,13 +13,12 @@ import type Policy from '@src/types/onyx/Policy';
 import type PriorityMode from '@src/types/onyx/PriorityMode';
 import type Report from '@src/types/onyx/Report';
 import type ReportAction from '@src/types/onyx/ReportAction';
-import type DeepValueOf from '@src/types/utils/DeepValueOf';
 import {getExpensifyCardFromReportAction} from './CardMessageUtils';
 import {extractCollectionItemID} from './CollectionUtils';
 import {hasValidDraftComment} from './DraftCommentUtils';
 import localeCompare from './LocaleCompare';
 import {formatPhoneNumber} from './LocalePhoneNumber';
-import {translate, translateLocal} from './Localize';
+import {translateLocal} from './Localize';
 import {getLastActorDisplayName, getLastMessageTextForReport, getPersonalDetailsForAccountIDs, shouldShowLastActorDisplayName} from './OptionsListUtils';
 import Parser from './Parser';
 import Performance from './Performance';
@@ -414,7 +413,6 @@ function getOptionData({
     oneTransactionThreadReport,
     reportNameValuePairs,
     personalDetails,
-    preferredLocale,
     policy,
     parentReportAction,
     lastMessageTextFromReport: lastMessageTextFromReportProp,
@@ -424,7 +422,6 @@ function getOptionData({
     oneTransactionThreadReport: OnyxEntry<Report>;
     reportNameValuePairs: OnyxEntry<ReportNameValuePairs>;
     personalDetails: OnyxEntry<PersonalDetailsList>;
-    preferredLocale: DeepValueOf<typeof CONST.LOCALES>;
     policy: OnyxEntry<Policy> | undefined;
     parentReportAction: OnyxEntry<ReportAction> | undefined;
     lastMessageTextFromReport?: string;
@@ -587,16 +584,16 @@ function getOptionData({
             const targetAccountIDsLength = targetAccountIDs.length !== 0 ? targetAccountIDs.length : (report.lastMessageHtml?.match(/<mention-user[^>]*><\/mention-user>/g)?.length ?? 0);
             const verb =
                 lastActionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM || lastActionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM
-                    ? translate(preferredLocale, 'workspace.invite.invited')
-                    : translate(preferredLocale, 'workspace.invite.removed');
-            const users = translate(preferredLocale, targetAccountIDsLength > 1 ? 'common.members' : 'common.member')?.toLocaleLowerCase();
+                    ? translateLocal('workspace.invite.invited')
+                    : translateLocal('workspace.invite.removed');
+            const users = translateLocal(targetAccountIDsLength > 1 ? 'common.members' : 'common.member')?.toLocaleLowerCase();
             result.alternateText = formatReportLastMessageText(`${actorDisplayName ?? lastActorDisplayName} ${verb} ${targetAccountIDsLength} ${users}`);
             const roomName = getReportName(allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${lastActionOriginalMessage?.reportID}`]) || lastActionOriginalMessage?.roomName;
             if (roomName) {
                 const preposition =
                     lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM || lastAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM
-                        ? ` ${translate(preferredLocale, 'workspace.invite.to')}`
-                        : ` ${translate(preferredLocale, 'workspace.invite.from')}`;
+                        ? ` ${translateLocal('workspace.invite.to')}`
+                        : ` ${translateLocal('workspace.invite.from')}`;
                 result.alternateText += `${preposition} ${roomName}`;
             }
         } else if (isActionOfType(lastAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.UPDATE_NAME)) {
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 31e3d6b7f4d7..afe90c1cd752 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -14,8 +14,9 @@ import {convertToBackendAmount, getCurrencyDecimals} from '@libs/CurrencyUtils';
 import DateUtils from '@libs/DateUtils';
 import DistanceRequestUtils from '@libs/DistanceRequestUtils';
 import {toLocaleDigit} from '@libs/LocaleDigitUtils';
-import * as Localize from '@libs/Localize';
-import * as NumberUtils from '@libs/NumberUtils';
+import {translateLocal} from '@libs/Localize';
+import BaseLocaleListener from '@libs/Localize/LocaleListener/BaseLocaleListener';
+import {rand64, roundToTwoDecimalPlaces} from '@libs/NumberUtils';
 import {
     getCleanedTagName,
     getDistanceRateCustomUnitRate,
@@ -47,7 +48,6 @@ import type {Attendee, Participant, SplitExpense} from '@src/types/onyx/IOU';
 import type {PendingAction} from '@src/types/onyx/OnyxCommon';
 import type {SearchPolicy, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults';
 import type {Comment, Receipt, TransactionChanges, TransactionCustomUnit, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
-import type DeepValueOf from '@src/types/utils/DeepValueOf';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import getDistanceInMeters from './getDistanceInMeters';
 
@@ -120,17 +120,6 @@ Onyx.connect({
     callback: (value) => (allTransactionViolations = value),
 });
 
-let preferredLocale: DeepValueOf<typeof CONST.LOCALES> = CONST.LOCALES.DEFAULT;
-Onyx.connect({
-    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    callback: (value) => {
-        if (!value) {
-            return;
-        }
-        preferredLocale = value;
-    },
-});
-
 let currentUserEmail = '';
 let currentUserAccountID = -1;
 Onyx.connect({
@@ -268,7 +257,7 @@ function buildOptimisticTransaction(params: BuildOptimisticTransactionParams): T
     } = transactionParams;
     // transactionIDs are random, positive, 64-bit numeric strings.
     // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID)
-    const transactionID = existingTransactionID ?? NumberUtils.rand64();
+    const transactionID = existingTransactionID ?? rand64();
 
     const commentJSON: Comment = {comment, attendees};
     if (source) {
@@ -433,7 +422,7 @@ function getUpdatedTransaction({
             // The waypoints were changed, but there is no route – it is pending from the BE and we should mark the fields as pending
             updatedTransaction.amount = CONST.IOU.DEFAULT_AMOUNT;
             updatedTransaction.modifiedAmount = CONST.IOU.DEFAULT_AMOUNT;
-            updatedTransaction.modifiedMerchant = Localize.translateLocal('iou.fieldPending');
+            updatedTransaction.modifiedMerchant = translateLocal('iou.fieldPending');
         } else {
             const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy});
             const {unit, rate} = mileageRate;
@@ -441,8 +430,8 @@ function getUpdatedTransaction({
             const distanceInMeters = getDistanceInMeters(transaction, unit);
             const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0);
             const updatedAmount = isFromExpenseReport ? -amount : amount;
-            const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, transaction.currency, Localize.translateLocal, (digit) =>
-                toLocaleDigit(preferredLocale, digit),
+            const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, transaction.currency, translateLocal, (digit) =>
+                toLocaleDigit(BaseLocaleListener.getPreferredLocale(), digit),
             );
 
             updatedTransaction.amount = updatedAmount;
@@ -465,7 +454,7 @@ function getUpdatedTransaction({
         // If the distanceUnit is set and the rate is changed to one that has a different unit, convert the distance to the new unit
         if (existingDistanceUnit && newDistanceUnit !== existingDistanceUnit) {
             const conversionFactor = existingDistanceUnit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS : CONST.CUSTOM_UNITS.KILOMETERS_TO_MILES;
-            const distance = NumberUtils.roundToTwoDecimalPlaces((transaction?.comment?.customUnit?.quantity ?? 0) * conversionFactor);
+            const distance = roundToTwoDecimalPlaces((transaction?.comment?.customUnit?.quantity ?? 0) * conversionFactor);
             lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance);
         }
 
@@ -481,8 +470,8 @@ function getUpdatedTransaction({
             const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0);
             const updatedAmount = isFromExpenseReport ? -amount : amount;
             const updatedCurrency = updatedMileageRate.currency ?? CONST.CURRENCY.USD;
-            const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, updatedCurrency, Localize.translateLocal, (digit) =>
-                toLocaleDigit(preferredLocale, digit),
+            const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, updatedCurrency, translateLocal, (digit) =>
+                toLocaleDigit(BaseLocaleListener.getPreferredLocale(), digit),
             );
 
             updatedTransaction.amount = updatedAmount;
@@ -670,7 +659,9 @@ function getMerchant(transaction: OnyxInputOrEntry<Transaction>, policyParam: On
         const mileageRate = DistanceRequestUtils.getRate({transaction, policy});
         const {unit, rate} = mileageRate;
         const distanceInMeters = getDistanceInMeters(transaction, unit);
-        return DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, transaction.currency, Localize.translateLocal, (digit) => toLocaleDigit(preferredLocale, digit));
+        return DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, transaction.currency, translateLocal, (digit) =>
+            toLocaleDigit(BaseLocaleListener.getPreferredLocale(), digit),
+        );
     }
     return transaction?.modifiedMerchant ? transaction.modifiedMerchant : (transaction?.merchant ?? '');
 }
@@ -1248,8 +1239,7 @@ function transformedTaxRates(policy: OnyxEntry<Policy> | undefined, transaction?
         return policy && getDefaultTaxCode(policy, transaction);
     };
 
-    const getModifiedName = (data: TaxRate, code: string) =>
-        `${data.name} (${data.value})${defaultTaxCode() === code ? ` ${CONST.DOT_SEPARATOR} ${Localize.translateLocal('common.default')}` : ''}`;
+    const getModifiedName = (data: TaxRate, code: string) => `${data.name} (${data.value})${defaultTaxCode() === code ? ` ${CONST.DOT_SEPARATOR} ${translateLocal('common.default')}` : ''}`;
     const taxes = Object.fromEntries(Object.entries(taxRates?.taxes ?? {}).map(([code, data]) => [code, {...data, code, modifiedName: getModifiedName(data, code), name: data.name}]));
     return taxes;
 }
diff --git a/src/pages/settings/Preferences/PreferencesPage.tsx b/src/pages/settings/Preferences/PreferencesPage.tsx
index 53ff9f71c643..f85596fbb949 100755
--- a/src/pages/settings/Preferences/PreferencesPage.tsx
+++ b/src/pages/settings/Preferences/PreferencesPage.tsx
@@ -32,12 +32,13 @@ function PreferencesPage() {
     const isPlatformMuted = mutedPlatforms[platform];
     const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
     const [preferredTheme] = useOnyx(ONYXKEYS.PREFERRED_THEME, {canBeMissing: true});
+    const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true});
     const personalPolicy = usePolicy(getPersonalPolicy()?.id);
 
     const paymentCurrency = personalPolicy?.outputCurrency ?? CONST.CURRENCY.USD;
 
     const styles = useThemeStyles();
-    const {translate, preferredLocale} = useLocalize();
+    const {translate} = useLocalize();
     const {shouldUseNarrowLayout} = useResponsiveLayout();
 
     return (
@@ -97,7 +98,7 @@ function PreferencesPage() {
                             />
                             <MenuItemWithTopDescription
                                 shouldShowRightIcon
-                                title={translate(`languagePage.languages.${LocaleUtils.getLanguageFromLocale(preferredLocale)}.label`)}
+                                title={preferredLocale ? translate(`languagePage.languages.${LocaleUtils.getLanguageFromLocale(preferredLocale)}.label`) : undefined}
                                 description={translate('languagePage.language')}
                                 onPress={() => Navigation.navigate(ROUTES.SETTINGS_LANGUAGE)}
                                 wrapperStyle={styles.sectionMenuItemTopDescription}
diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts
index 6f6bcd94aad9..cd01913cbc4f 100644
--- a/tests/perf-test/SidebarUtils.perf-test.ts
+++ b/tests/perf-test/SidebarUtils.perf-test.ts
@@ -58,6 +58,7 @@ describe('SidebarUtils', () => {
 
         Onyx.multiSet({
             [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails,
+            [ONYXKEYS.NVP_PREFERRED_LOCALE]: 'en',
         });
     });
 
@@ -67,7 +68,6 @@ describe('SidebarUtils', () => {
 
     test('[SidebarUtils] getOptionData', async () => {
         const report = createRandomReport(1);
-        const preferredLocale = 'en';
         const policy = createRandomPolicy(1);
         const parentReportAction = createRandomReportAction(1);
         const reportNameValuePairs = {};
@@ -80,7 +80,6 @@ describe('SidebarUtils', () => {
                 reportAttributes: undefined,
                 reportNameValuePairs,
                 personalDetails,
-                preferredLocale,
                 policy,
                 parentReportAction,
                 oneTransactionThreadReport: undefined,
diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts
index 0d768ea601ef..039ea1128dc9 100644
--- a/tests/unit/SidebarUtilsTest.ts
+++ b/tests/unit/SidebarUtilsTest.ts
@@ -25,6 +25,7 @@ describe('SidebarUtils', () => {
             keys: ONYXKEYS,
             evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
         });
+        Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN);
         initOnyxDerivedValues();
     });
 
@@ -310,7 +311,6 @@ describe('SidebarUtils', () => {
                 reportAttributes: undefined,
                 reportNameValuePairs: {},
                 personalDetails: {},
-                preferredLocale: CONST.LOCALES.DEFAULT,
                 policy: undefined,
                 parentReportAction: undefined,
                 oneTransactionThreadReport: undefined,
@@ -320,7 +320,6 @@ describe('SidebarUtils', () => {
                 reportAttributes: undefined,
                 reportNameValuePairs: {},
                 personalDetails: {},
-                preferredLocale: CONST.LOCALES.DEFAULT,
                 policy: undefined,
                 parentReportAction: undefined,
                 oneTransactionThreadReport: undefined,
@@ -783,7 +782,6 @@ describe('SidebarUtils', () => {
                 personalDetails: {},
                 policy: undefined,
                 parentReportAction: undefined,
-                preferredLocale: CONST.LOCALES.EN,
                 oneTransactionThreadReport: undefined,
             });
 
@@ -841,7 +839,6 @@ describe('SidebarUtils', () => {
                 personalDetails: {},
                 policy: undefined,
                 parentReportAction: undefined,
-                preferredLocale: CONST.LOCALES.EN,
                 oneTransactionThreadReport: undefined,
             });
 
@@ -855,7 +852,6 @@ describe('SidebarUtils', () => {
                 await waitForBatchedUpdates();
             });
             it('The text should not contain the policy name at prefix if the report is not related to a workspace', async () => {
-                const preferredLocale = 'en';
                 const policy: Policy = {
                     ...createRandomPolicy(1),
                     role: CONST.POLICY.ROLE.ADMIN,
@@ -880,7 +876,6 @@ describe('SidebarUtils', () => {
                     reportAttributes: undefined,
                     reportNameValuePairs,
                     personalDetails: {},
-                    preferredLocale,
                     policy,
                     parentReportAction: undefined,
                     lastMessageTextFromReport: 'test message',
@@ -890,7 +885,6 @@ describe('SidebarUtils', () => {
                 expect(optionData?.alternateText).toBe(`test message`);
             });
             it("The text should not contain the last actor's name at prefix if the report is archived.", async () => {
-                const preferredLocale = 'en';
                 const policy: Policy = {
                     ...createRandomPolicy(1),
                     role: CONST.POLICY.ROLE.ADMIN,
@@ -915,7 +909,6 @@ describe('SidebarUtils', () => {
                     reportAttributes: undefined,
                     reportNameValuePairs,
                     personalDetails: LHNTestUtils.fakePersonalDetails,
-                    preferredLocale,
                     policy,
                     parentReportAction: undefined,
                     lastMessageTextFromReport: 'test message',
@@ -925,7 +918,6 @@ describe('SidebarUtils', () => {
                 expect(optionData?.alternateText).toBe(`test message`);
             });
             it('The text should not contain the policy name at prefix if we only have a workspace', async () => {
-                const preferredLocale = 'en';
                 const policy: Policy = {
                     ...createRandomPolicy(1),
                     role: CONST.POLICY.ROLE.ADMIN,
@@ -947,7 +939,6 @@ describe('SidebarUtils', () => {
                     reportAttributes: undefined,
                     reportNameValuePairs,
                     personalDetails: {},
-                    preferredLocale,
                     policy,
                     parentReportAction: undefined,
                     lastMessageTextFromReport: 'test message',
@@ -957,7 +948,6 @@ describe('SidebarUtils', () => {
                 expect(optionData?.alternateText).toBe(`test message`);
             });
             it('The text should contain the policy name at prefix if we have multiple workspace and the report is related to a workspace', async () => {
-                const preferredLocale = 'en';
                 const policy: Policy = {
                     ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM),
                     role: CONST.POLICY.ROLE.ADMIN,
@@ -983,7 +973,6 @@ describe('SidebarUtils', () => {
                     reportAttributes: undefined,
                     reportNameValuePairs,
                     personalDetails: {},
-                    preferredLocale,
                     policy,
                     parentReportAction: undefined,
                     lastMessageTextFromReport: 'test message',
@@ -1051,7 +1040,6 @@ describe('SidebarUtils', () => {
                     personalDetails: {},
                     policy: undefined,
                     parentReportAction: undefined,
-                    preferredLocale: CONST.LOCALES.EN,
                     oneTransactionThreadReport: undefined,
                 });
 
@@ -1093,7 +1081,6 @@ describe('SidebarUtils', () => {
                     personalDetails: {},
                     policy: undefined,
                     parentReportAction: undefined,
-                    preferredLocale: CONST.LOCALES.EN,
                     oneTransactionThreadReport: undefined,
                 });
 

From 6f19431278d0989f80f685af6cb8503fa693b698 Mon Sep 17 00:00:00 2001
From: Shubham Agrawal <shubham.agra1206@gmail.com>
Date: Sun, 1 Jun 2025 20:35:17 +0530
Subject: [PATCH 2/2] Fix tests

---
 src/components/Reactions/MiniQuickEmojiReactions.tsx          | 4 ++--
 .../Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx | 4 ++--
 tests/unit/DateUtilsTest.ts                                   | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx
index f6a98a3daeee..ef0519f3bc1e 100644
--- a/src/components/Reactions/MiniQuickEmojiReactions.tsx
+++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx
@@ -37,8 +37,8 @@ function MiniQuickEmojiReactions({reportAction, reportActionID, onEmojiSelected,
     const StyleUtils = useStyleUtils();
     const ref = useRef<View>(null);
     const {translate, preferredLocale} = useLocalize();
-    const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
-    const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, {initialValue: {}});
+    const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {canBeMissing: true, initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
+    const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, {canBeMissing: true, initialValue: {}});
 
     const openEmojiPicker = () => {
         onPressOpenPicker();
diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
index 730c5cd002da..fb16f7cf4a1c 100644
--- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
+++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
@@ -23,8 +23,8 @@ function BaseQuickEmojiReactions({
 }: BaseQuickEmojiReactionsProps) {
     const styles = useThemeStyles();
     const {preferredLocale} = useLocalize();
-    const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
-    const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, {initialValue: {}});
+    const [preferredSkinTone] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, {canBeMissing: true, initialValue: CONST.EMOJI_DEFAULT_SKIN_TONE});
+    const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, {canBeMissing: true, initialValue: {}});
 
     return (
         <View style={styles.quickReactionsContainer}>
diff --git a/tests/unit/DateUtilsTest.ts b/tests/unit/DateUtilsTest.ts
index f1f94a131e52..8764931207c2 100644
--- a/tests/unit/DateUtilsTest.ts
+++ b/tests/unit/DateUtilsTest.ts
@@ -45,7 +45,7 @@ describe('DateUtils', () => {
 
     it('getZoneAbbreviation should show zone abbreviation from the datetime', () => {
         const zoneAbbreviation = DateUtils.getZoneAbbreviation(datetime, timezone);
-        expect(zoneAbbreviation).toBe('PST');
+        expect(zoneAbbreviation).toBe('GMT-8');
     });
 
     it('formatToLongDateWithWeekday should return a long date with a weekday', () => {
