diff --git a/src/libs/Localize/index.ts b/src/libs/Localize/index.ts index bd8a34406846..3f079b49653a 100644 --- a/src/libs/Localize/index.ts +++ b/src/libs/Localize/index.ts @@ -1,7 +1,7 @@ import * as RNLocalize from 'react-native-localize'; import Onyx from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; import Log from '@libs/Log'; +import memoize from '@libs/memoize'; import type {MessageElementBase, MessageTextElement} from '@libs/MessageElement'; import Config from '@src/CONFIG'; import CONST from '@src/CONST'; @@ -9,6 +9,7 @@ import translations from '@src/languages/translations'; import type {PluralForm, TranslationParameters, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Locale} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import LocaleListener from './LocaleListener'; import BaseLocaleListener from './LocaleListener/BaseLocaleListener'; @@ -45,28 +46,6 @@ function init() { }, {}); } -/** - * Map to store translated values for each locale. - * This is used to avoid translating the same phrase multiple times. - * - * The data is stored in the following format: - * - * { - * "en": { - * "name": "Name", - * } - * - * Note: We are not storing any translated values for phrases with variables, - * as they have higher chance of being unique, so we'll end up wasting space - * in our cache. - */ -const translationCache = new Map, Map>( - Object.values(CONST.LOCALES).reduce((cache, locale) => { - cache.push([locale, new Map()]); - return cache; - }, [] as Array<[ValueOf, Map]>), -); - /** * Helper function to get the translated string for given * locale and phrase. This function is used to avoid @@ -86,18 +65,6 @@ function getTranslatedPhrase( fallbackLanguage: 'en' | 'es' | null, ...parameters: TranslationParameters ): string | null { - // Get the cache for the above locale - const cacheForLocale = translationCache.get(language); - - // Directly access and assign the translated value from the cache, instead of - // going through map.has() and map.get() to avoid multiple lookups. - const valueFromCache = cacheForLocale?.get(phraseKey); - - // If the phrase is already translated, return the translated value - if (valueFromCache) { - return valueFromCache; - } - const translatedPhrase = translations?.[language]?.[phraseKey]; if (translatedPhrase) { @@ -138,8 +105,6 @@ function getTranslatedPhrase( return translateResult.other(phraseObject.count); } - // We set the translated value in the cache only for the phrases without parameters. - cacheForLocale?.set(phraseKey, translatedPhrase); return translatedPhrase; } @@ -162,6 +127,13 @@ function getTranslatedPhrase( return getTranslatedPhrase(CONST.LOCALES.DEFAULT, phraseKey, null, ...parameters); } +const memoizedGetTranslatedPhrase = memoize(getTranslatedPhrase, { + maxArgs: 2, + equality: 'shallow', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + skipCache: (params) => !isEmptyObject(params.at(3)), +}); + /** * Return translated string for given locale and phrase * @@ -174,7 +146,7 @@ function translate(desiredLanguage: 'en' | 'es' // Phrase is not found in full locale, search it in fallback language e.g. es const languageAbbreviation = desiredLanguage.substring(0, 2) as 'en' | 'es'; - const translatedPhrase = getTranslatedPhrase(language, path, languageAbbreviation, ...parameters); + const translatedPhrase = memoizedGetTranslatedPhrase(language, path, languageAbbreviation, ...parameters); if (translatedPhrase !== null && translatedPhrase !== undefined) { return translatedPhrase; } diff --git a/src/libs/memoize/index.ts b/src/libs/memoize/index.ts index f02b1adbf5ba..792a7ea82f3a 100644 --- a/src/libs/memoize/index.ts +++ b/src/libs/memoize/index.ts @@ -60,6 +60,17 @@ function memoize; + + statsEntry.trackTime('processingTime', fnTimeStart); + statsEntry.track('didHit', false); + + return result; + } + const truncatedArgs = truncateArgs(args, options.maxArgs); const key = options.transformKey ? options.transformKey(truncatedArgs) : (truncatedArgs as Key); diff --git a/src/libs/memoize/types.ts b/src/libs/memoize/types.ts index 9ee48c9dc790..d47a09f114bf 100644 --- a/src/libs/memoize/types.ts +++ b/src/libs/memoize/types.ts @@ -52,6 +52,13 @@ type Options = { * @returns Key to use for caching */ transformKey?: (truncatedArgs: TakeFirst, MaxArgs>) => Key; + + /** + * Checks if the cache should be skipped for the given arguments. + * @param args Tuple of arguments passed to the memoized function. Does not work with constructable (see description). + * @returns boolean to whether to skip cache lookup and execute the function if true + */ + skipCache?: (args: IsomorphicParameters) => boolean; } & InternalOptions; type ClientOptions = Partial, keyof InternalOptions>>;