Use same lock values as mobile clients
[ProtonMail-WebClient.git] / packages / shared / lib / i18n / helper.ts
blobea2eaaf4d91d8abac66ec22c4677ed376a8b14b0
1 import { DEFAULT_LOCALE } from '../constants';
3 export const getNormalizedLocale = (locale = '') => {
4     return locale.toLowerCase().replace(/-/g, '_');
5 };
7 // Attempts to convert the original locale (en_US) to BCP 47 (en-US) as supported by the lang attribute
8 export const getLangAttribute = (locale: string) => {
9     return locale.replace(/_/g, '-').replace('es-LA', 'es');
12 /**
13  * Takes the first portion, e.g. nl_NL => nl, kab_KAB => kab
14  */
15 export const getLanguageCode = (locale = '') => {
16     return getNormalizedLocale(locale).split('_')[0];
19 /**
20  * Takes the second portion, e.g. nl_NL => nl, fr_CA => ca
21  * ** Only for the locale user setting you are guaranteed to get an ISO_3166-1_alpha-2 country code. You may get undefined for other locale instances **
22  */
23 export const getNaiveCountryCode = (locale = '') => {
24     return getNormalizedLocale(locale).split('_')[1];
27 /**
28  * Transforms a locale string into one that can be passed to Javascript Intl methods.
29  * Basically transforms zh_ZH => zh-ZH, es-es => es-es (Intl cares about the dash, but not about capitalization)
30  */
31 export const getIntlLocale = (locale = '') => {
32     return getNormalizedLocale(locale).replace(/_/g, '-');
35 export const getBrowserLanguageTags = (): string[] => {
36     const tags = window.navigator?.languages;
38     return [...tags] || [];
41 /**
42  * Gets the first specified locale from the browser, if any.
43  *
44  * If the first locale does not have a region and the second is a regional variant of the first, take it instead.
45  */
46 export const getBrowserLocale = () => {
47     const first = window.navigator?.languages?.[0];
48     const second = window.navigator?.languages?.[1];
50     if (!/[_-]/.test(first) && /[_-]/.test(second) && getLanguageCode(first) === getLanguageCode(second)) {
51         return second;
52     }
54     return first;
57 /**
58  * Give a higher score to locales with higher chances to be a proper fallback languages
59  * when there is no exact match.
60  */
61 const getLanguagePriority = (locale: string) => {
62     const parts = locale.toLowerCase().split(/[_-]/);
64     // Prefer language (en) over language + region (en_US)
65     if (parts.length === 1) {
66         return 2;
67     }
69     // Prefer region matching language (fr_FR, it_IT, de_DE) over other regions (fr_CA, it_CH, de_AU)
70     return parts[0] === parts[1] ? 1 : 0;
73 /**
74  * Get the closest matching locale from an object of locales.
75  */
76 export const getClosestLocaleMatch = (locale = '', locales = {}) => {
77     const localeKeys = [DEFAULT_LOCALE, ...Object.keys(locales)].sort((first, second) => {
78         if (first === second) {
79             return 0;
80         }
82         const firstPriority = getLanguagePriority(first);
83         const secondPriority = getLanguagePriority(second);
85         if (firstPriority > secondPriority) {
86             return -1;
87         }
89         if (firstPriority < secondPriority) {
90             return 1;
91         }
93         return first > second ? 1 : -1;
94     });
95     const normalizedLocaleKeys = localeKeys.map(getNormalizedLocale);
96     const normalizedLocale = getNormalizedLocale(locale);
98     // First by language and country code.
99     const fullMatchIndex = normalizedLocaleKeys.findIndex((key) => key === normalizedLocale);
100     if (fullMatchIndex >= 0) {
101         return localeKeys[fullMatchIndex];
102     }
104     // Language code.
105     const language = getLanguageCode(normalizedLocale);
106     const languageMatchIndex = normalizedLocaleKeys.findIndex((key) => {
107         return getLanguageCode(key) === language;
108     });
109     if (languageMatchIndex >= 0) {
110         return localeKeys[languageMatchIndex];
111     }
114 export const getClosestLocaleCode = (locale: string | undefined, locales: { [key: string]: any }) => {
115     if (!locale) {
116         return DEFAULT_LOCALE;
117     }
118     return getClosestLocaleMatch(locale, locales) || DEFAULT_LOCALE;