import { Locale } from 'date-fns'; import { CrowdinLocale } from '../../localization/constants'; import { en } from '../../localization/locales'; import type { LocalizerDictionary } from '../../types/localizer'; import { timeLocaleMap } from './timeLocaleMap'; let mappedBrowserLocaleDisplayed = false; let crowdinLocale: CrowdinLocale | undefined; let translationDictionary: LocalizerDictionary | undefined; /** * Only exported for testing, reset the dictionary to use for translations and the locale set */ export function resetLocaleAndTranslationDict() { translationDictionary = undefined; crowdinLocale = undefined; } /** * Returns the current dictionary to use for translations. */ export function getTranslationDictionary(): LocalizerDictionary { if (translationDictionary) { return translationDictionary; } i18nLog('getTranslationDictionary: dictionary not init yet. Using en.'); return en; } export function getFallbackDictionary(): LocalizerDictionary { return en; } /** * Logs an i18n message to the console. * @param message - The message to log. * @deprecated we want to use a normal logger instead of this function, eventually. */ export function i18nLog(message: string) { if (typeof window !== 'undefined') { // eslint-disable-next-line no-console (window?.log?.error ?? console.log)(`i18n: ${message}`); } else { // eslint-disable-next-line no-console console.log(`i18n: ${message}`); } } export function getTimeLocaleDictionary() { return (timeLocaleMap as Record)[getBrowserLocale()] || timeLocaleMap.en; } /** * Returns the current locale as supported by Session (i.e. one generated by crowdin) */ export function getCrowdinLocale(): CrowdinLocale { if (!crowdinLocale) { i18nLog(`getCrowdinLocale: ${crowdinLocale}`); throw new Error('crowdinLocale is unset'); } return crowdinLocale; } /** * Returns the closest supported locale by the browser. */ export function getBrowserLocale() { const browserLocale = process.env.LANGUAGE || getCrowdinLocale() || 'en'; // supportedLocalesOf will throw if the locales has a '_' instead of a '-' in it. const userLocaleDashed = browserLocale.replaceAll('_', '-'); try { let matchingLocales: Array = []; try { matchingLocales = Intl.DateTimeFormat.supportedLocalesOf(userLocaleDashed); } catch (innerError) { // some users have a locale setup with a ':' in it. // see https://github.com/oxen-io/session-desktop/issues/3221 const semiColonIndex = userLocaleDashed.indexOf(':'); if (semiColonIndex > -1) { matchingLocales = Intl.DateTimeFormat.supportedLocalesOf( userLocaleDashed.substring(0, semiColonIndex) ); } } const mappingTo = matchingLocales?.[0] || 'en'; if (!mappedBrowserLocaleDisplayed) { mappedBrowserLocaleDisplayed = true; i18nLog(`userLocaleDashed: '${userLocaleDashed}', mapping to browser locale: ${mappingTo}`); } return mappingTo; } catch (e) { if (!mappedBrowserLocaleDisplayed) { mappedBrowserLocaleDisplayed = true; i18nLog( `userLocaleDashed: '${userLocaleDashed}' was an invalid locale for supportedLocalesOf(). Falling back to 'en'. Error:${e.message} ` ); } return 'en'; } } export function setInitialLocale(crowdinLocaleArg: CrowdinLocale, dictionary: LocalizerDictionary) { if (translationDictionary || crowdinLocale) { throw new Error('setInitialLocale: translationDictionary or crowdinLocale is already init'); } translationDictionary = dictionary; crowdinLocale = crowdinLocaleArg; } export function isSessionLocaleSet() { return !!crowdinLocale; } export function getStringForCardinalRule( localizedString: string, cardinalRule: Intl.LDMLPluralRule ): string | undefined { // TODO: investigate if this is the best way to handle regex like this // We need block scoped regex to avoid running into this issue: // https://stackoverflow.com/questions/18462784/why-is-javascript-regex-matching-every-second-time const cardinalPluralRegex: Record = { zero: /zero \[(.*?)\]/g, one: /one \[(.*?)\]/g, two: /two \[(.*?)\]/g, few: /few \[(.*?)\]/g, many: /many \[(.*?)\]/g, other: /other \[(.*?)\]/g, }; const regex = cardinalPluralRegex[cardinalRule]; const match = regex.exec(localizedString); return match?.[1] ?? undefined; }