Skip to content

Commit 37f0b95

Browse files
authored
Merge pull request Expensify#68140 from software-mansion-labs/war-in/remove-onyx-connect-from-app-ts
Remove Onyx.connect() for the key: ONYXKEYS.NVP_PREFERRED_LOCALE in src/libs/actions/App.ts
2 parents 33d4361 + 40e96a8 commit 37f0b95

14 files changed

Lines changed: 145 additions & 127 deletions

File tree

src/components/LocaleContextProvider.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import React, {createContext, useEffect, useMemo, useState} from 'react';
2+
import {importEmojiLocale} from '@assets/emojis';
23
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
34
import useOnyx from '@hooks/useOnyx';
45
import DateUtils from '@libs/DateUtils';
6+
import {buildEmojisTrie} from '@libs/EmojiTrie';
57
import {fromLocaleDigit as fromLocaleDigitLocaleDigitUtils, toLocaleDigit as toLocaleDigitLocaleDigitUtils, toLocaleOrdinal as toLocaleOrdinalLocaleDigitUtils} from '@libs/LocaleDigitUtils';
68
import {formatPhoneNumberWithCountryCode} from '@libs/LocalePhoneNumber';
7-
import {translate as translateLocalize} from '@libs/Localize';
9+
import {getDevicePreferredLocale, translate as translateLocalize} from '@libs/Localize';
10+
import localeEventCallback from '@libs/Localize/localeEventCallback';
811
import {format} from '@libs/NumberFormatUtils';
12+
import {setLocale} from '@userActions/App';
13+
import CONST from '@src/CONST';
14+
import {isFullySupportedLocale, isSupportedLocale} from '@src/CONST/LOCALES';
915
import IntlStore from '@src/languages/IntlStore';
1016
import type {TranslationParameters, TranslationPaths} from '@src/languages/types';
1117
import ONYXKEYS from '@src/ONYXKEYS';
1218
import type Locale from '@src/types/onyx/Locale';
1319
import type {SelectedTimezone} from '@src/types/onyx/PersonalDetails';
20+
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
1421

1522
type LocaleContextProviderProps = {
1623
/** Actual content wrapped by this component */
@@ -73,8 +80,37 @@ function LocaleContextProvider({children}: LocaleContextProviderProps) {
7380
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
7481
const [areTranslationsLoading = true] = useOnyx(ONYXKEYS.ARE_TRANSLATIONS_LOADING, {initWithStoredValues: false, canBeMissing: true});
7582
const [countryCodeByIP = 1] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: true});
83+
const [nvpPreferredLocale, nvpPreferredLocaleMetadata] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true});
7684
const [currentLocale, setCurrentLocale] = useState<Locale | undefined>(() => IntlStore.getCurrentLocale());
7785

86+
const localeToApply = useMemo(() => {
87+
if (isLoadingOnyxValue(nvpPreferredLocaleMetadata)) {
88+
return undefined;
89+
}
90+
if (nvpPreferredLocale && isSupportedLocale(nvpPreferredLocale)) {
91+
return nvpPreferredLocale;
92+
}
93+
94+
const deviceLocale = getDevicePreferredLocale();
95+
return isSupportedLocale(deviceLocale) ? deviceLocale : CONST.LOCALES.DEFAULT;
96+
}, [nvpPreferredLocale, nvpPreferredLocaleMetadata]);
97+
98+
useEffect(() => {
99+
if (!localeToApply) {
100+
return;
101+
}
102+
103+
setLocale(localeToApply, nvpPreferredLocale);
104+
IntlStore.load(localeToApply);
105+
localeEventCallback(localeToApply);
106+
107+
// For locales without emoji support, fallback on English
108+
const normalizedLocale = isFullySupportedLocale(localeToApply) ? localeToApply : CONST.LOCALES.DEFAULT;
109+
importEmojiLocale(normalizedLocale).then(() => {
110+
buildEmojisTrie(normalizedLocale);
111+
});
112+
}, [localeToApply, nvpPreferredLocale]);
113+
78114
useEffect(() => {
79115
if (areTranslationsLoading) {
80116
return;

src/components/LocalePicker.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,7 @@ function LocalePicker({size = 'normal'}: LocalePickerProps) {
3737
return (
3838
<Picker
3939
label={size === 'normal' ? translate('languagePage.language') : null}
40-
onInputChange={(locale) => {
41-
if (locale === preferredLocale) {
42-
return;
43-
}
44-
45-
setLocale(locale);
46-
}}
40+
onInputChange={(locale) => setLocale(locale, preferredLocale)}
4741
isDisabled={shouldDisablePicker}
4842
items={locales}
4943
shouldAllowDisabledStyle={false}

src/libs/actions/App.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ import type {AppStateStatus} from 'react-native';
44
import {AppState} from 'react-native';
55
import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
66
import Onyx from 'react-native-onyx';
7-
import {importEmojiLocale} from '@assets/emojis';
87
import * as API from '@libs/API';
98
import type {GetMissingOnyxMessagesParams, HandleRestrictedEventParams, OpenAppParams, OpenOldDotLinkParams, ReconnectAppParams, UpdatePreferredLocaleParams} from '@libs/API/parameters';
109
import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
1110
import * as Browser from '@libs/Browser';
1211
import DateUtils from '@libs/DateUtils';
13-
import {buildEmojisTrie} from '@libs/EmojiTrie';
14-
import localeEventCallback from '@libs/Localize/localeEventCallback';
1512
import Log from '@libs/Log';
1613
import getCurrentUrl from '@libs/Navigation/currentUrl';
1714
import Navigation from '@libs/Navigation/Navigation';
@@ -20,8 +17,6 @@ import {isPublicRoom, isValidReport} from '@libs/ReportUtils';
2017
import {isLoggingInAsNewUser as isLoggingInAsNewUserSessionUtils} from '@libs/SessionUtils';
2118
import {clearSoundAssetsCache} from '@libs/Sound';
2219
import CONST from '@src/CONST';
23-
import {isFullySupportedLocale, isSupportedLocale} from '@src/CONST/LOCALES';
24-
import IntlStore from '@src/languages/IntlStore';
2520
import ONYXKEYS from '@src/ONYXKEYS';
2621
import type {OnyxKey} from '@src/ONYXKEYS';
2722
import type {Route} from '@src/ROUTES';
@@ -55,26 +50,6 @@ Onyx.connect({
5550
initWithStoredValues: false,
5651
});
5752

58-
let preferredLocale: Locale | undefined;
59-
Onyx.connect({
60-
key: ONYXKEYS.NVP_PREFERRED_LOCALE,
61-
callback: (val) => {
62-
if (!val || !isSupportedLocale(val)) {
63-
return;
64-
}
65-
66-
preferredLocale = val;
67-
IntlStore.load(val);
68-
localeEventCallback(val);
69-
70-
// For locales without emoji support, fallback on English
71-
const normalizedLocale = isFullySupportedLocale(val) ? val : CONST.LOCALES.DEFAULT;
72-
importEmojiLocale(normalizedLocale).then(() => {
73-
buildEmojisTrie(normalizedLocale);
74-
});
75-
},
76-
});
77-
7853
let isUsingImportedState: boolean | undefined;
7954
Onyx.connect({
8055
key: ONYXKEYS.IS_USING_IMPORTED_STATE,
@@ -171,8 +146,8 @@ function getNonOptimisticPolicyIDs(policies: OnyxCollection<OnyxTypes.Policy>):
171146
.filter((id): id is string => !!id);
172147
}
173148

174-
function setLocale(locale: OnyxTypes.Locale) {
175-
if (locale === preferredLocale) {
149+
function setLocale(locale: Locale, currentPreferredLocale: Locale | undefined) {
150+
if (locale === currentPreferredLocale) {
176151
return;
177152
}
178153

@@ -198,11 +173,6 @@ function setLocale(locale: OnyxTypes.Locale) {
198173
API.write(WRITE_COMMANDS.UPDATE_PREFERRED_LOCALE, parameters, {optimisticData});
199174
}
200175

201-
function setLocaleAndNavigate(locale: OnyxTypes.Locale) {
202-
setLocale(locale);
203-
Navigation.goBack();
204-
}
205-
206176
function setSidebarLoaded() {
207177
if (isSidebarLoaded) {
208178
return;
@@ -672,7 +642,6 @@ function clearOnyxAndResetApp(shouldNavigateToHomepage?: boolean) {
672642

673643
export {
674644
setLocale,
675-
setLocaleAndNavigate,
676645
setSidebarLoaded,
677646
setUpPoliciesAndNavigate,
678647
redirectThirdPartyDesktopSignIn,

src/libs/actions/Locale.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/libs/actions/__mocks__/App.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ jest.mock('@libs/actions/OnyxUpdateManager/utils/applyUpdates');
99
const AppImplementation = jest.requireActual<typeof AppImport>('@libs/actions/App');
1010
const {
1111
setLocale,
12-
setLocaleAndNavigate,
1312
setSidebarLoaded,
1413
setUpPoliciesAndNavigate,
1514
redirectThirdPartyDesktopSignIn,
@@ -73,7 +72,6 @@ export {
7372

7473
// Actual App implementation
7574
setLocale,
76-
setLocaleAndNavigate,
7775
setSidebarLoaded,
7876
setUpPoliciesAndNavigate,
7977
redirectThirdPartyDesktopSignIn,

src/pages/settings/Preferences/LanguagePage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import SelectionList from '@components/SelectionList';
66
import RadioListItem from '@components/SelectionList/RadioListItem';
77
import useLocalize from '@hooks/useLocalize';
88
import Navigation from '@libs/Navigation/Navigation';
9-
import {setLocaleAndNavigate} from '@userActions/App';
9+
import {setLocale} from '@userActions/App';
1010
import type {ListItem} from '@src/components/SelectionList/types';
1111
import {LOCALE_TO_LANGUAGE_STRING, SORTED_LOCALES} from '@src/CONST/LOCALES';
1212
import type Locale from '@src/types/onyx/Locale';
@@ -33,7 +33,9 @@ function LanguagePage() {
3333
return;
3434
}
3535
isOptionSelected.current = true;
36-
setLocaleAndNavigate(selectedLanguage.value);
36+
37+
setLocale(selectedLanguage.value, preferredLocale);
38+
Navigation.goBack();
3739
};
3840

3941
return (

src/pages/signin/SignInPage.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
1414
import useStyleUtils from '@hooks/useStyleUtils';
1515
import useThemeStyles from '@hooks/useThemeStyles';
1616
import {isClientTheLeader as isClientTheLeaderActiveClientManager} from '@libs/ActiveClientManager';
17-
import {getDevicePreferredLocale} from '@libs/Localize';
1817
import Log from '@libs/Log';
1918
import Navigation from '@libs/Navigation/Navigation';
2019
import Performance from '@libs/Performance';
2120
import Visibility from '@libs/Visibility';
22-
import {setLocale} from '@userActions/App';
2321
import {clearSignInData} from '@userActions/Session';
2422
import CONST from '@src/CONST';
2523
import ONYXKEYS from '@src/ONYXKEYS';
@@ -165,7 +163,6 @@ function SignInPage({shouldEnableMaxHeight = true}: SignInPageInnerProps, ref: F
165163
We use that function to prevent repeating code that checks which client is the leader.
166164
*/
167165
const [activeClients = getEmptyArray<string>()] = useOnyx(ONYXKEYS.ACTIVE_CLIENTS, {canBeMissing: true});
168-
const [preferredLocale] = useOnyx(ONYXKEYS.NVP_PREFERRED_LOCALE, {canBeMissing: true});
169166

170167
/** This state is needed to keep track of if user is using recovery code instead of 2fa code,
171168
* and we need it here since welcome text(`welcomeText`) also depends on it */
@@ -186,12 +183,6 @@ function SignInPage({shouldEnableMaxHeight = true}: SignInPageInnerProps, ref: F
186183

187184
useEffect(() => Performance.measureTTI(), []);
188185

189-
useEffect(() => {
190-
if (preferredLocale) {
191-
return;
192-
}
193-
setLocale(getDevicePreferredLocale());
194-
}, [preferredLocale]);
195186
useEffect(() => {
196187
if (credentials?.login) {
197188
return;

src/setup/index.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {I18nManager} from 'react-native';
22
import Onyx from 'react-native-onyx';
33
import intlPolyfill from '@libs/IntlPolyfill';
44
import {setDeviceID} from '@userActions/Device';
5-
import initLocale from '@userActions/Locale';
65
import initOnyxDerivedValues from '@userActions/OnyxDerived';
76
import CONST from '@src/CONST';
87
import ONYXKEYS from '@src/ONYXKEYS';
@@ -53,9 +52,6 @@ export default function () {
5352
skippableCollectionMemberIDs: CONST.SKIPPABLE_COLLECTION_MEMBER_IDS,
5453
});
5554

56-
// Init locale early to avoid rendering translations keys instead of real translations
57-
initLocale();
58-
5955
initOnyxDerivedValues();
6056

6157
setDeviceID();

tests/ui/PureReportActionItemTest.tsx

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {translateLocal} from '@libs/Localize';
1313
import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
1414
import PureReportActionItem from '@pages/home/report/PureReportActionItem';
1515
import CONST from '@src/CONST';
16+
import type {TranslationPaths} from '@src/languages/types';
1617
import * as ReportActionUtils from '@src/libs/ReportActionsUtils';
1718
import ONYXKEYS from '@src/ONYXKEYS';
1819
import type {ReportAction} from '@src/types/onyx';
@@ -101,40 +102,57 @@ describe('PureReportActionItem', () => {
101102
}
102103

103104
describe('Automatic actions', () => {
104-
it('APPROVED action via workspace rules', async () => {
105-
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.APPROVED, {automaticAction: true});
105+
const testCases = [
106+
{
107+
testTitle: 'APPROVED action via workspace rules',
108+
actionName: CONST.REPORT.ACTIONS.TYPE.APPROVED,
109+
originalMessageExtras: {automaticAction: true},
110+
translationKey: 'iou.automaticallyApproved',
111+
},
112+
{
113+
testTitle: 'FORWARDED action via workspace rules',
114+
actionName: CONST.REPORT.ACTIONS.TYPE.FORWARDED,
115+
originalMessageExtras: {automaticAction: true},
116+
translationKey: 'iou.automaticallyForwarded',
117+
},
118+
{
119+
testTitle: 'SUBMITTED action via harvesting',
120+
actionName: CONST.REPORT.ACTIONS.TYPE.SUBMITTED,
121+
originalMessageExtras: {harvesting: true},
122+
translationKey: 'iou.automaticallySubmitted',
123+
},
124+
{
125+
testTitle: 'SUBMITTED_AND_CLOSED action via harvesting',
126+
actionName: CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED,
127+
originalMessageExtras: {harvesting: true},
128+
translationKey: 'iou.automaticallySubmitted',
129+
},
130+
];
131+
132+
const parseTextWithTrailingLink = (translatedText: string) => {
133+
const match = translatedText.match(/^(.*?)(<a[^>]*>)(.*?)(<\/a>)$/);
134+
if (!match) {
135+
return null;
136+
}
137+
const [, textBeforeLink, , linkText] = match;
138+
return {textBeforeLink, linkText};
139+
};
140+
141+
it.each(testCases)('$testTitle', async ({actionName, originalMessageExtras, translationKey}) => {
142+
const action = createReportAction(actionName, originalMessageExtras);
106143
renderItemWithAction(action);
107144
await waitForBatchedUpdatesWithAct();
108145

109146
expect(screen.getByText(actorEmail)).toBeOnTheScreen();
110-
expect(screen.getByText(translateLocal('iou.automaticallyApproved'))).toBeOnTheScreen();
111-
});
112-
113-
it('FORWARDED action via workspace rules', async () => {
114-
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.FORWARDED, {automaticAction: true});
115-
renderItemWithAction(action);
116-
await waitForBatchedUpdatesWithAct();
117-
118-
expect(screen.getByText(actorEmail)).toBeOnTheScreen();
119-
expect(screen.getByText(translateLocal('iou.automaticallyForwarded'))).toBeOnTheScreen();
120-
});
121-
122-
it('SUBMITTED action via harvesting', async () => {
123-
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.SUBMITTED, {harvesting: true});
124-
renderItemWithAction(action);
125-
await waitForBatchedUpdatesWithAct();
126-
127-
expect(screen.getByText(actorEmail)).toBeOnTheScreen();
128-
expect(screen.getByText(translateLocal('iou.automaticallySubmitted'))).toBeOnTheScreen();
129-
});
130147

131-
it('SUBMITTED_AND_CLOSED action via harvesting', async () => {
132-
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED, {harvesting: true});
133-
renderItemWithAction(action);
134-
await waitForBatchedUpdatesWithAct();
148+
const parsedText = parseTextWithTrailingLink(translateLocal(translationKey as TranslationPaths));
149+
if (!parsedText) {
150+
throw new Error('Text cannot be parsed, translation failed');
151+
}
135152

136-
expect(screen.getByText(actorEmail)).toBeOnTheScreen();
137-
expect(screen.getByText(translateLocal('iou.automaticallySubmitted'))).toBeOnTheScreen();
153+
const {textBeforeLink, linkText} = parsedText;
154+
expect(screen.getByText(textBeforeLink)).toBeOnTheScreen();
155+
expect(screen.getByText(linkText)).toBeOnTheScreen();
138156
});
139157
});
140158

tests/ui/ReportDetailsPageTest.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {Report} from '@src/types/onyx';
1515
import createRandomReportAction from '../utils/collections/reportActions';
1616
import {createRandomReport} from '../utils/collections/reports';
1717
import createRandomTransaction from '../utils/collections/transaction';
18+
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
1819

1920
jest.mock('@src/components/ConfirmedRoute.tsx');
2021

@@ -85,6 +86,7 @@ describe('ReportDetailsPage', () => {
8586
</LocaleContextProvider>
8687
</OnyxListItemProvider>,
8788
);
89+
await waitForBatchedUpdates();
8890

8991
const submitText = translateLocal('actionableMentionTrackExpense.submit');
9092
const categorizeText = translateLocal('actionableMentionTrackExpense.categorize');

0 commit comments

Comments
 (0)