From f2475fee355d83ffc6a7c5af19a2880d6d33e81b Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 29 Sep 2023 11:16:45 +0200 Subject: [PATCH 01/14] Migrate localize lib to TS --- .../{BaseLocaleListener.js => BaseLocaleListener.ts} | 10 +++------- .../{index.desktop.js => index.desktop.ts} | 3 ++- .../Localize/LocaleListener/{index.js => index.ts} | 0 src/libs/Localize/LocaleListener/types.ts | 6 ++++++ src/libs/Localize/{index.js => index.ts} | 4 +--- tsconfig.json | 3 ++- 6 files changed, 14 insertions(+), 12 deletions(-) rename src/libs/Localize/LocaleListener/{BaseLocaleListener.js => BaseLocaleListener.ts} (79%) rename src/libs/Localize/LocaleListener/{index.desktop.js => index.desktop.ts} (80%) rename src/libs/Localize/LocaleListener/{index.js => index.ts} (100%) create mode 100644 src/libs/Localize/LocaleListener/types.ts rename src/libs/Localize/{index.js => index.ts} (98%) diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.js b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts similarity index 79% rename from src/libs/Localize/LocaleListener/BaseLocaleListener.js rename to src/libs/Localize/LocaleListener/BaseLocaleListener.ts index 9d4e62c374b0..8cf8319109b6 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.js +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -1,15 +1,14 @@ import Onyx from 'react-native-onyx'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; +import BaseLocale from './types'; -let preferredLocale = CONST.LOCALES.DEFAULT; +let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT; /** * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx. - * - * @param {Function} [callbackAfterChange] */ -const connect = (callbackAfterChange = () => {}) => { +const connect = (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => { Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => { @@ -23,9 +22,6 @@ const connect = (callbackAfterChange = () => {}) => { }); }; -/* - * @return {String} - */ function getPreferredLocale() { return preferredLocale; } diff --git a/src/libs/Localize/LocaleListener/index.desktop.js b/src/libs/Localize/LocaleListener/index.desktop.ts similarity index 80% rename from src/libs/Localize/LocaleListener/index.desktop.js rename to src/libs/Localize/LocaleListener/index.desktop.ts index 0c0d723122da..a75372207b6a 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.js +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,8 +1,9 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; +import BaseLocale from './types'; export default { - connect: (callbackAfterChange = () => {}) => + connect: (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); diff --git a/src/libs/Localize/LocaleListener/index.js b/src/libs/Localize/LocaleListener/index.ts similarity index 100% rename from src/libs/Localize/LocaleListener/index.js rename to src/libs/Localize/LocaleListener/index.ts diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts new file mode 100644 index 000000000000..bce58a6b9ac9 --- /dev/null +++ b/src/libs/Localize/LocaleListener/types.ts @@ -0,0 +1,6 @@ +import {ValueOf} from 'type-fest'; +import CONST from '../../../CONST'; + +type BaseLocale = ValueOf; + +export default BaseLocale; diff --git a/src/libs/Localize/index.js b/src/libs/Localize/index.ts similarity index 98% rename from src/libs/Localize/index.js rename to src/libs/Localize/index.ts index db371301f43f..5143c2337ad9 100644 --- a/src/libs/Localize/index.js +++ b/src/libs/Localize/index.ts @@ -134,10 +134,8 @@ function arrayToString(anArray) { /** * Returns the user device's preferred language. - * - * @return {String} */ -function getDevicePreferredLocale() { +function getDevicePreferredLocale(): string { return lodashGet(RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES]), 'languageTag', CONST.LOCALES.DEFAULT); } diff --git a/tsconfig.json b/tsconfig.json index 0c88512b9749..c0065dc30c78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "es2021.weakref", "es2022.array", "es2022.object", - "es2022.string" + "es2022.string", + "ES2021.Intl" ], "allowJs": true, "checkJs": false, From ddcbd5279505a754cc55190a4d9c802b9b1057e0 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Mon, 2 Oct 2023 13:26:26 +0200 Subject: [PATCH 02/14] Resolve lodash conflicts --- src/libs/Localize/index.ts | 78 +++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 44 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 5143c2337ad9..c790ea3fa435 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,5 +1,3 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Str from 'expensify-common/lib/str'; import * as RNLocalize from 'react-native-localize'; import Log from '../Log'; @@ -14,49 +12,47 @@ 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; +let CONJUNCTION_LIST_FORMATS_FOR_LOCALES: Record; function init() { - CONJUNCTION_LIST_FORMATS_FOR_LOCALES = _.reduce( - CONST.LOCALES, - (memo, locale) => { - // This is not a supported locale, so we'll use ES_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'}); - return memo; - } - + CONJUNCTION_LIST_FORMATS_FOR_LOCALES = Object.values(CONST.LOCALES).reduce((memo: Record, locale) => { + // This is not a supported locale, so we'll use ES_ES instead + if (locale === CONST.LOCALES.ES_ES_ONFIDO) { // eslint-disable-next-line no-param-reassign - memo[locale] = new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'}); + memo[locale] = new Intl.ListFormat(CONST.LOCALES.ES_ES, {style: 'long', type: 'conjunction'}); return memo; - }, - {}, - ); + } + + // eslint-disable-next-line no-param-reassign + memo[locale] = new Intl.ListFormat(locale, {style: 'long', type: 'conjunction'}); + return memo; + }, {}); } /** * Return translated string for given locale and phrase * - * @param {String} [desiredLanguage] eg 'en', 'es-ES' - * @param {String} phraseKey - * @param {Object} [phraseParameters] Parameters to supply if the phrase is a template literal. - * @returns {String} + * @param [desiredLanguage] eg 'en', 'es-ES' + * @param [phraseParameters] Parameters to supply if the phrase is a template literal. */ -function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phraseParameters = {}) { - const languageAbbreviation = desiredLanguage.substring(0, 2); +function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: string, phraseParameters: unknown = {}): string { + const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; let translatedPhrase; // Search phrase in full locale e.g. es-ES - const desiredLanguageDictionary = translations[desiredLanguage] || {}; - translatedPhrase = desiredLanguageDictionary[phraseKey]; + const desiredLanguageDictionary = translations?.[desiredLanguage as keyof typeof translations] ?? {}; + translatedPhrase = desiredLanguageDictionary?.[phraseKey as keyof typeof desiredLanguageDictionary]; if (translatedPhrase) { + // console.log('1translatedPhrase=', translatedPhrase); + // console.log('1phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in full locale, search it in fallback language e.g. es const fallbackLanguageDictionary = translations[languageAbbreviation] || {}; - translatedPhrase = fallbackLanguageDictionary[phraseKey]; + translatedPhrase = fallbackLanguageDictionary?.[phraseKey as keyof typeof fallbackLanguageDictionary] ?? ''; if (translatedPhrase) { + // console.log('2translatedPhrase=', translatedPhrase); + // console.log('2phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } if (languageAbbreviation !== CONST.LOCALES.DEFAULT) { @@ -67,13 +63,17 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; translatedPhrase = defaultLanguageDictionary[phraseKey]; if (translatedPhrase) { + // console.log('3translatedPhrase=', translatedPhrase); + // console.log('3phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in default language, on production log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION) { - const phraseString = _.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; + // console.log('4translatedPhrase=', translatedPhrase); + // console.log('4phraseParameters=', phraseParameters); + const phraseString = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); return phraseString; } @@ -82,29 +82,22 @@ function translate(desiredLanguage = CONST.LOCALES.DEFAULT, phraseKey, phrasePar /** * Uses the locale in this file updated by the Onyx subscriber. - * - * @param {String|Array} phrase - * @param {Object} [variables] - * @returns {String} */ -function translateLocal(phrase, variables) { +function translateLocal(phrase: string, variables: unknown) { return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables); } /** * Return translated string for given error. - * - * @param {String|Array} message - * @returns {String} */ -function translateIfPhraseKey(message) { - if (_.isEmpty(message)) { +function translateIfPhraseKey(message: string | [string, {isTranslated: boolean}]): string { + if (!message || (Array.isArray(message) && message.length > 0)) { return ''; } try { // check if error message has a variable parameter - const [phrase, variables] = _.isArray(message) ? message : [message]; + const [phrase, variables] = Array.isArray(message) ? message : [message]; // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. @@ -114,17 +107,14 @@ function translateIfPhraseKey(message) { return translateLocal(phrase, variables); } catch (error) { - return message; + return Array.isArray(message) ? message[0] : message; } } /** * Format an array into a string with comma and "and" ("a dog, a cat and a chicken") - * - * @param {Array} anArray - * @return {String} */ -function arrayToString(anArray) { +function arrayToString(anArray: string[]) { if (!CONJUNCTION_LIST_FORMATS_FOR_LOCALES) { init(); } @@ -136,7 +126,7 @@ function arrayToString(anArray) { * Returns the user device's preferred language. */ function getDevicePreferredLocale(): string { - return lodashGet(RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES]), 'languageTag', CONST.LOCALES.DEFAULT); + return RNLocalize.findBestAvailableLanguage([CONST.LOCALES.EN, CONST.LOCALES.ES])?.languageTag ?? CONST.LOCALES.DEFAULT; } export {translate, translateLocal, translateIfPhraseKey, arrayToString, getDevicePreferredLocale}; From caadaeb57f6168f32d36413d35d515b77926839f Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 11 Oct 2023 11:29:29 +0200 Subject: [PATCH 03/14] Migrate localize to TS --- src/libs/Localize/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index c790ea3fa435..0a87a8579f58 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -61,7 +61,7 @@ function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: // Phrase is not translated, search it in default language (en) const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; - translatedPhrase = defaultLanguageDictionary[phraseKey]; + translatedPhrase = defaultLanguageDictionary[phraseKey as keyof typeof defaultLanguageDictionary] ?? ''; if (translatedPhrase) { // console.log('3translatedPhrase=', translatedPhrase); // console.log('3phraseParameters=', phraseParameters); From 645650bb5689f1114f0c6b1efe5a281d3f9b82f6 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 17 Oct 2023 11:05:18 +0200 Subject: [PATCH 04/14] Cleanup --- src/languages/translations.ts | 2 +- src/languages/types.ts | 1 + .../LocaleListener/BaseLocaleListener.ts | 2 +- .../Localize/LocaleListener/index.desktop.ts | 2 +- src/libs/Localize/index.ts | 28 +++++++++---------- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/languages/translations.ts b/src/languages/translations.ts index d228394589b2..4d89f1f529de 100644 --- a/src/languages/translations.ts +++ b/src/languages/translations.ts @@ -46,5 +46,5 @@ export default { en: flattenObject(en), es: flattenObject(es), // eslint-disable-next-line @typescript-eslint/naming-convention - 'es-ES': esES, + 'es-ES': flattenObject(esES), }; diff --git a/src/languages/types.ts b/src/languages/types.ts index 52f2df8b3765..a7d219cc8201 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -239,6 +239,7 @@ export type { EnglishTranslation, TranslationFlatObject, AddressLineParams, + TranslationPaths, CharacterLimitParams, MaxParticipantsReachedParams, ZipCodeExampleFormatParams, diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts index 8cf8319109b6..03b59c470cd7 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -8,7 +8,7 @@ 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 = (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => { +const connect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => { Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => { diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index a75372207b6a..424d65a347b3 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -3,7 +3,7 @@ import BaseLocaleListener from './BaseLocaleListener'; import BaseLocale from './types'; export default { - connect: (callbackAfterChange: (arg1?: BaseLocale) => void = () => {}) => + connect: (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 0a87a8579f58..114a0c09f71a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -6,6 +6,7 @@ import translations from '../../languages/translations'; import CONST from '../../CONST'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; +import {TranslationFlatObject, TranslationPaths} from '../../languages/types'; // Listener when an update in Onyx happens so we use the updated locale when translating/localizing items. LocaleListener.connect(); @@ -28,31 +29,32 @@ function init() { }, {}); } +type PhraseParameters = T extends (arg: infer A) => string ? A : never; + /** * Return translated string for given locale and phrase * * @param [desiredLanguage] eg 'en', 'es-ES' * @param [phraseParameters] Parameters to supply if the phrase is a template literal. */ -function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: string, phraseParameters: unknown = {}): string { +function translate( + desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', + phraseKey: TKey, + phraseParameters: PhraseParameters = {} as PhraseParameters, +): string { const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; - let translatedPhrase; // Search phrase in full locale e.g. es-ES - const desiredLanguageDictionary = translations?.[desiredLanguage as keyof typeof translations] ?? {}; - translatedPhrase = desiredLanguageDictionary?.[phraseKey as keyof typeof desiredLanguageDictionary]; + const desiredLanguageDictionary = translations?.[desiredLanguage]; + let translatedPhrase = desiredLanguageDictionary[phraseKey]; if (translatedPhrase) { - // console.log('1translatedPhrase=', translatedPhrase); - // console.log('1phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in full locale, search it in fallback language e.g. es const fallbackLanguageDictionary = translations[languageAbbreviation] || {}; - translatedPhrase = fallbackLanguageDictionary?.[phraseKey as keyof typeof fallbackLanguageDictionary] ?? ''; + translatedPhrase = fallbackLanguageDictionary?.[phraseKey] ?? ''; if (translatedPhrase) { - // console.log('2translatedPhrase=', translatedPhrase); - // console.log('2phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } if (languageAbbreviation !== CONST.LOCALES.DEFAULT) { @@ -61,18 +63,14 @@ function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: // Phrase is not translated, search it in default language (en) const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; - translatedPhrase = defaultLanguageDictionary[phraseKey as keyof typeof defaultLanguageDictionary] ?? ''; + translatedPhrase = defaultLanguageDictionary[phraseKey] ?? ''; if (translatedPhrase) { - // console.log('3translatedPhrase=', translatedPhrase); - // console.log('3phraseParameters=', phraseParameters); return Str.result(translatedPhrase, phraseParameters); } // Phrase is not found in default language, on production log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION) { - // console.log('4translatedPhrase=', translatedPhrase); - // console.log('4phraseParameters=', phraseParameters); const phraseString = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); return phraseString; @@ -83,7 +81,7 @@ function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: string, variables: unknown) { +function translateLocal(phrase: TKey, variables: PhraseParameters = {} as PhraseParameters) { return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables); } From 6d61ffd80cb1a814c8a2dd1d56e86d6cdaa46f8e Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 18 Oct 2023 18:56:16 +0200 Subject: [PATCH 05/14] New Localize.translate typing --- src/libs/Localize/index.ts | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 5682e19a8ebf..e3ea30bb869e 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,4 +1,3 @@ -import Str from 'expensify-common/lib/str'; import * as RNLocalize from 'react-native-localize'; import Onyx from 'react-native-onyx'; import Log from '../Log'; @@ -14,12 +13,11 @@ import ONYXKEYS from '../../ONYXKEYS'; let userEmail = ''; Onyx.connect({ key: ONYXKEYS.SESSION, - waitForCollectionCallback: true, callback: (val) => { if (!val) { return; } - userEmail = val?.email; + userEmail = val?.email ?? ''; }, }); @@ -44,7 +42,8 @@ function init() { }, {}); } -type PhraseParameters = T extends (arg: infer A) => string ? A : never; +type PhraseParameters = T extends (...args: infer A) => string ? A : never[]; +type Phrase = TranslationFlatObject[TKey] extends (...args: infer A) => unknown ? (...args: A) => string : string; /** * Return translated string for given locale and phrase @@ -52,35 +51,29 @@ type PhraseParameters = T extends (arg: infer A) => string ? A : never; * @param [desiredLanguage] eg 'en', 'es-ES' * @param [phraseParameters] Parameters to supply if the phrase is a template literal. */ -function translate( - desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', - phraseKey: TKey, - phraseParameters: PhraseParameters = {} as PhraseParameters, -): string { - const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; - +function translate(desiredLanguage: 'en' | 'es' | 'es-ES' | 'es_ES', phraseKey: TKey, ...phraseParameters: PhraseParameters>): string { // Search phrase in full locale e.g. es-ES - const desiredLanguageDictionary = translations?.[desiredLanguage]; - let translatedPhrase = desiredLanguageDictionary[phraseKey]; + const language = desiredLanguage === CONST.LOCALES.ES_ES_ONFIDO ? CONST.LOCALES.ES_ES : desiredLanguage; + let translatedPhrase = translations?.[language]?.[phraseKey] as Phrase; if (translatedPhrase) { - return Str.result(translatedPhrase, phraseParameters); + return typeof translatedPhrase === 'function' ? translatedPhrase(...phraseParameters) : translatedPhrase; } // Phrase is not found in full locale, search it in fallback language e.g. es - const fallbackLanguageDictionary = translations[languageAbbreviation] || {}; - translatedPhrase = fallbackLanguageDictionary?.[phraseKey] ?? ''; + const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; + translatedPhrase = translations?.[languageAbbreviation]?.[phraseKey] as Phrase; if (translatedPhrase) { - return Str.result(translatedPhrase, phraseParameters); + return typeof translatedPhrase === 'function' ? translatedPhrase(...phraseParameters) : translatedPhrase; } + if (languageAbbreviation !== CONST.LOCALES.DEFAULT) { Log.alert(`${phraseKey} was not found in the ${languageAbbreviation} locale`); } // Phrase is not translated, search it in default language (en) - const defaultLanguageDictionary = translations[CONST.LOCALES.DEFAULT] || {}; - translatedPhrase = defaultLanguageDictionary[phraseKey] ?? ''; + translatedPhrase = translations?.[CONST.LOCALES.DEFAULT]?.[phraseKey] as Phrase; if (translatedPhrase) { - return Str.result(translatedPhrase, phraseParameters); + return typeof translatedPhrase === 'function' ? translatedPhrase(...phraseParameters) : translatedPhrase; } // Phrase is not found in default language, on production and staging log an alert to server From c66ab48ae28542fd3a1e58832740a4334ebb866d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 20 Oct 2023 10:24:38 +0200 Subject: [PATCH 06/14] Last typecheck fixes --- .../Localize/LocaleListener/index.desktop.ts | 20 ++++++++++--------- src/libs/Localize/LocaleListener/index.ts | 5 ++++- src/libs/Localize/LocaleListener/types.ts | 3 +++ src/libs/Localize/index.ts | 17 ++++++++-------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index 424d65a347b3..8445d6152ea7 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,14 +1,16 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; -import BaseLocale from './types'; +import BaseLocale, {LocaleListenerConnect} from './types'; -export default { - connect: (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => - BaseLocaleListener.connect((val) => { - // Send the updated locale to the Electron main process - window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); +const localeListener: LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => + 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); + }); - // Then execute the callback provided for the renderer process - callbackAfterChange(val); - }), +export default { + connect: localeListener, }; diff --git a/src/libs/Localize/LocaleListener/index.ts b/src/libs/Localize/LocaleListener/index.ts index e5f1ea03f93f..6a725be29662 100644 --- a/src/libs/Localize/LocaleListener/index.ts +++ b/src/libs/Localize/LocaleListener/index.ts @@ -1,5 +1,8 @@ import BaseLocaleListener from './BaseLocaleListener'; +import {LocaleListenerConnect} from './types'; + +const localeListener: LocaleListenerConnect = BaseLocaleListener.connect; export default { - connect: BaseLocaleListener.connect, + connect: localeListener, }; diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index bce58a6b9ac9..44dd3dcc359a 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -3,4 +3,7 @@ import CONST from '../../../CONST'; type BaseLocale = ValueOf; +type LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void) => void; + +export type {LocaleListenerConnect}; export default BaseLocale; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index e3ea30bb869e..1493a9bbf788 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -79,7 +79,7 @@ function translate(desiredLanguage: 'en' | 'es' | // Phrase is not found in default language, on production and staging log an alert to server // on development throw an error if (Config.IS_IN_PRODUCTION || Config.IS_IN_STAGING) { - const phraseString = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; + const phraseString: string = Array.isArray(phraseKey) ? phraseKey.join('.') : phraseKey; Log.alert(`${phraseString} was not found in the en locale`); if (userEmail.includes(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) { return CONST.MISSING_TRANSLATION; @@ -92,14 +92,14 @@ function translate(desiredLanguage: 'en' | 'es' | /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: TKey, variables: PhraseParameters = {} as PhraseParameters) { - return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables); +function translateLocal(phrase: TKey, variables: PhraseParameters>) { + return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } /** * Return translated string for given error. */ -function translateIfPhraseKey(message: string | [string, {isTranslated: boolean}]): string { +function translateIfPhraseKey(message: TKey | [TKey, PhraseParameters> & {isTranslated?: true}]): string { if (!message || (Array.isArray(message) && message.length > 0)) { return ''; } @@ -110,13 +110,14 @@ function translateIfPhraseKey(message: string | [string, {isTranslated: boolean} // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. - if (variables && variables.isTranslated) { - return phrase; + if (variables?.isTranslated) { + return phrase as string; } - return translateLocal(phrase, variables); + return translateLocal(phrase, variables as PhraseParameters>); } catch (error) { - return Array.isArray(message) ? message[0] : message; + const result: string = Array.isArray(message) ? message[0] : message; + return result; } } From e51c0f071facb16fb8b48e813587caee5fc71fac Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Thu, 26 Oct 2023 10:04:06 +0200 Subject: [PATCH 07/14] Resolve typecheck probems --- src/libs/DateUtils.ts | 8 +++++++- src/libs/Localize/index.ts | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index a6f2860310c2..df8f51be8d76 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -133,7 +133,13 @@ function isYesterday(date: Date, timeZone: string): 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: string, datetime: string, includeTimeZone = false, currentSelectedTimezone = timezone.selected, isLowercase = false): string { +function datetimeToCalendarTime( + locale: 'en' | 'es' | 'es-ES' | 'es_ES', + datetime: string, + includeTimeZone = false, + currentSelectedTimezone = timezone.selected, + isLowercase = false, +): string { const date = getLocalDateFromDatetime(locale, datetime, currentSelectedTimezone); const tz = includeTimeZone ? ' [UTC]Z' : ''; let todayAt = Localize.translate(locale, 'common.todayAt'); diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 1493a9bbf788..9c444dd8e2c0 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -92,7 +92,7 @@ function translate(desiredLanguage: 'en' | 'es' | /** * Uses the locale in this file updated by the Onyx subscriber. */ -function translateLocal(phrase: TKey, variables: PhraseParameters>) { +function translateLocal(phrase: TKey, ...variables: PhraseParameters>) { return translate(BaseLocaleListener.getPreferredLocale(), phrase, ...variables); } @@ -114,7 +114,7 @@ function translateIfPhraseKey(message: TKey | [TK return phrase as string; } - return translateLocal(phrase, variables as PhraseParameters>); + return translateLocal(phrase, ...variables); } catch (error) { const result: string = Array.isArray(message) ? message[0] : message; return result; From cc9360548a6374717ca69e0c3a55f875bf3b969d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 26 Oct 2023 10:33:36 +0200 Subject: [PATCH 08/14] Fix type errors --- src/libs/ErrorUtils.ts | 4 ++-- src/libs/Localize/LocaleListener/BaseLocaleListener.ts | 6 +++--- src/libs/Localize/LocaleListener/index.desktop.ts | 4 ++-- src/libs/Localize/LocaleListener/types.ts | 2 +- src/libs/Localize/index.ts | 6 ++---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts index bf4fc0d810a4..bf1d5131f722 100644 --- a/src/libs/ErrorUtils.ts +++ b/src/libs/ErrorUtils.ts @@ -3,7 +3,7 @@ import DateUtils from './DateUtils'; import * as Localize from './Localize'; import Response from '../types/onyx/Response'; import {ErrorFields, Errors} from '../types/onyx/OnyxCommon'; -import {TranslationFlatObject} from '../languages/types'; +import {TranslationFlatObject, TranslationPaths} from '../languages/types'; function getAuthenticateErrorMessage(response: Response): keyof TranslationFlatObject { switch (response.jsonCode) { @@ -93,7 +93,7 @@ type ErrorsList = Record; * @param errorList - An object containing current errors in the form * @param message - Message to assign to the inputID errors */ -function addErrorMessage(errors: ErrorsList, inputID?: string, message?: string) { +function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey) { if (!message || !inputID) { return; } diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts index 03b59c470cd7..d91dccb7156d 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -1,14 +1,14 @@ import Onyx from 'react-native-onyx'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; -import BaseLocale from './types'; +import BaseLocale, {LocaleListenerConnect} 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 = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => { +const connect: LocaleListenerConnect = (callbackAfterChange = () => {}) => { Onyx.connect({ key: ONYXKEYS.NVP_PREFERRED_LOCALE, callback: (val) => { @@ -22,7 +22,7 @@ const connect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) }); }; -function getPreferredLocale() { +function getPreferredLocale(): BaseLocale { return preferredLocale; } diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index 8445d6152ea7..f28cc74cf692 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,8 +1,8 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; -import BaseLocale, {LocaleListenerConnect} from './types'; +import {LocaleListenerConnect} from './types'; -const localeListener: LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void = () => {}) => +const localeListener: LocaleListenerConnect = (callbackAfterChange = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index 44dd3dcc359a..4a8954f2675d 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -3,7 +3,7 @@ import CONST from '../../../CONST'; type BaseLocale = ValueOf; -type LocaleListenerConnect = (callbackAfterChange: (locale?: BaseLocale) => void) => void; +type LocaleListenerConnect = (callbackAfterChange?: (locale?: BaseLocale) => void) => void; export type {LocaleListenerConnect}; export default BaseLocale; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index 9c444dd8e2c0..85177537534b 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -109,15 +109,13 @@ function translateIfPhraseKey(message: TKey | [TK const [phrase, variables] = Array.isArray(message) ? message : [message]; // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. - if (variables?.isTranslated) { return phrase as string; } - return translateLocal(phrase, ...variables); + return translateLocal(phrase, ...(variables as PhraseParameters>)); } catch (error) { - const result: string = Array.isArray(message) ? message[0] : message; - return result; + return Array.isArray(message) ? message[0] : message; } } From 79e367fcbc06c59f5d934a18c84c8b1e069e9c10 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 27 Oct 2023 11:40:36 +0200 Subject: [PATCH 09/14] Move localeListener to separate type --- src/libs/Localize/LocaleListener/index.desktop.ts | 10 ++++++---- src/libs/Localize/LocaleListener/index.ts | 10 ++++++---- src/libs/Localize/LocaleListener/types.ts | 6 +++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/libs/Localize/LocaleListener/index.desktop.ts b/src/libs/Localize/LocaleListener/index.desktop.ts index f28cc74cf692..6974d3ed4879 100644 --- a/src/libs/Localize/LocaleListener/index.desktop.ts +++ b/src/libs/Localize/LocaleListener/index.desktop.ts @@ -1,8 +1,8 @@ import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS'; import BaseLocaleListener from './BaseLocaleListener'; -import {LocaleListenerConnect} from './types'; +import {LocaleListener, LocaleListenerConnect} from './types'; -const localeListener: LocaleListenerConnect = (callbackAfterChange = () => {}) => +const localeListenerConnect: LocaleListenerConnect = (callbackAfterChange = () => {}) => BaseLocaleListener.connect((val) => { // Send the updated locale to the Electron main process window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val); @@ -11,6 +11,8 @@ const localeListener: LocaleListenerConnect = (callbackAfterChange = () => {}) = callbackAfterChange(val); }); -export default { - connect: localeListener, +const localeListener: LocaleListener = { + connect: localeListenerConnect, }; + +export default localeListener; diff --git a/src/libs/Localize/LocaleListener/index.ts b/src/libs/Localize/LocaleListener/index.ts index 6a725be29662..b0dda5d5fabc 100644 --- a/src/libs/Localize/LocaleListener/index.ts +++ b/src/libs/Localize/LocaleListener/index.ts @@ -1,8 +1,10 @@ import BaseLocaleListener from './BaseLocaleListener'; -import {LocaleListenerConnect} from './types'; +import {LocaleListener, LocaleListenerConnect} from './types'; -const localeListener: LocaleListenerConnect = BaseLocaleListener.connect; +const localeListenerConnect: LocaleListenerConnect = BaseLocaleListener.connect; -export default { - connect: localeListener, +const localizeListener: LocaleListener = { + connect: localeListenerConnect, }; + +export default localizeListener; diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index 4a8954f2675d..b932f83f6cfb 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -5,5 +5,9 @@ type BaseLocale = ValueOf; type LocaleListenerConnect = (callbackAfterChange?: (locale?: BaseLocale) => void) => void; -export type {LocaleListenerConnect}; +type LocaleListener = { + connect: LocaleListenerConnect; +}; + +export type {LocaleListenerConnect, LocaleListener}; export default BaseLocale; From a63719ca57577747f54e22c947d01ae030877b1a Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Sun, 29 Oct 2023 19:48:11 +0100 Subject: [PATCH 10/14] Fix lint problems for localize lib --- src/libs/Localize/LocaleListener/BaseLocaleListener.ts | 2 +- src/libs/Localize/LocaleListener/types.ts | 2 +- src/libs/Localize/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts index cf716dc10c05..c5eba18af422 100644 --- a/src/libs/Localize/LocaleListener/BaseLocaleListener.ts +++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.ts @@ -1,7 +1,7 @@ import Onyx from 'react-native-onyx'; -import BaseLocale, {LocaleListenerConnect} from './types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import BaseLocale, {LocaleListenerConnect} from './types'; let preferredLocale: BaseLocale = CONST.LOCALES.DEFAULT; diff --git a/src/libs/Localize/LocaleListener/types.ts b/src/libs/Localize/LocaleListener/types.ts index b932f83f6cfb..4daf90af0483 100644 --- a/src/libs/Localize/LocaleListener/types.ts +++ b/src/libs/Localize/LocaleListener/types.ts @@ -1,5 +1,5 @@ import {ValueOf} from 'type-fest'; -import CONST from '../../../CONST'; +import CONST from '@src/CONST'; type BaseLocale = ValueOf; diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ed77e1c498f2..fe7faaad1148 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -4,10 +4,10 @@ import Log from '@libs/Log'; import Config from '@src/CONFIG'; import CONST from '@src/CONST'; import translations from '@src/languages/translations'; +import {TranslationFlatObject, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; -import {TranslationFlatObject, TranslationPaths} from '../../languages/types'; // Current user mail is needed for handling missing translations let userEmail = ''; From d9d7df4b46664ef14d2f467c847864e43486ea92 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 30 Oct 2023 11:53:51 +0100 Subject: [PATCH 11/14] Fix typecheck --- src/libs/SidebarUtils.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 79d3280e859e..260d02b981db 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -418,10 +418,21 @@ function getOptionData( const reportAction = lastReportActions?.[report.reportID]; if (result.isArchivedRoom) { const archiveReason = (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED && reportAction?.originalMessage?.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; - lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), - policyName: ReportUtils.getPolicyName(report, false, policy), - }); + + switch (archiveReason) { + case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED: + case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY: + case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: { + lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { + policyName: ReportUtils.getPolicyName(report, false, policy), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + }); + break; + } + default: { + lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.default`); + } + } } if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport) && !result.isArchivedRoom) { From 7c68bde38f2485b1fb0c2037776ae2effe021d46 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 31 Oct 2023 12:35:12 +0100 Subject: [PATCH 12/14] Make translateIfPhraseKey much more broad --- src/libs/Localize/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index fe7faaad1148..ba7dda8a76b9 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -99,7 +99,7 @@ function translateLocal(phrase: TKey, ...variable /** * Return translated string for given error. */ -function translateIfPhraseKey(message: TKey | [TKey, PhraseParameters> & {isTranslated?: true}]): string { +function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}]): string { if (!message || (Array.isArray(message) && message.length > 0)) { return ''; } @@ -110,10 +110,10 @@ function translateIfPhraseKey(message: TKey | [TK // This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys. if (variables?.isTranslated) { - return phrase as string; + return phrase; } - return translateLocal(phrase, ...(variables as PhraseParameters>)); + return translateLocal(phrase as TranslationPaths, ...(variables as PhraseParameters>)); } catch (error) { return Array.isArray(message) ? message[0] : message; } From 1a569e420b59f3a50de924689a14f38ca0298be9 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 31 Oct 2023 12:38:41 +0100 Subject: [PATCH 13/14] Fix typecheck --- src/libs/Localize/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index ba7dda8a76b9..cc424113857e 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -113,7 +113,7 @@ function translateIfPhraseKey(message: string | [string, Record return phrase; } - return translateLocal(phrase as TranslationPaths, ...(variables as PhraseParameters>)); + return translateLocal(phrase as TranslationPaths, variables as never); } catch (error) { return Array.isArray(message) ? message[0] : message; } From 30359afe2f4b0f6ba7921707efffac961987be4d Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Sun, 5 Nov 2023 14:39:49 +0100 Subject: [PATCH 14/14] Fix check for empty array --- src/libs/Localize/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index cc424113857e..fd49902af369 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -99,8 +99,8 @@ function translateLocal(phrase: TKey, ...variable /** * Return translated string for given error. */ -function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}]): string { - if (!message || (Array.isArray(message) && message.length > 0)) { +function translateIfPhraseKey(message: string | [string, Record & {isTranslated?: true}] | []): string { + if (!message || (Array.isArray(message) && message.length === 0)) { return ''; }