1 import { DEFAULT_LOCALE } from '../constants';
3 export const getNormalizedLocale = (locale = '') => {
4 return locale.toLowerCase().replace(/-/g, '_');
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');
13 * Takes the first portion, e.g. nl_NL => nl, kab_KAB => kab
15 export const getLanguageCode = (locale = '') => {
16 return getNormalizedLocale(locale).split('_')[0];
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 **
23 export const getNaiveCountryCode = (locale = '') => {
24 return getNormalizedLocale(locale).split('_')[1];
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)
31 export const getIntlLocale = (locale = '') => {
32 return getNormalizedLocale(locale).replace(/_/g, '-');
35 export const getBrowserLanguageTags = (): string[] => {
36 const tags = window.navigator?.languages;
41 * Gets the first specified locale from the browser, if any.
43 * If the first locale does not have a region and the second is a regional variant of the first, take it instead.
45 export const getBrowserLocale = () => {
46 const first = window.navigator?.languages?.[0];
47 const second = window.navigator?.languages?.[1];
49 if (!/[_-]/.test(first) && /[_-]/.test(second) && getLanguageCode(first) === getLanguageCode(second)) {
57 * Give a higher score to locales with higher chances to be a proper fallback languages
58 * when there is no exact match.
60 const getLanguagePriority = (locale: string) => {
61 const parts = locale.toLowerCase().split(/[_-]/);
63 // Prefer language (en) over language + region (en_US)
64 if (parts.length === 1) {
68 // Prefer region matching language (fr_FR, it_IT, de_DE) over other regions (fr_CA, it_CH, de_AU)
69 return parts[0] === parts[1] ? 1 : 0;
73 * Get the closest matching locale from an object of locales.
75 export const getClosestLocaleMatch = (locale = '', locales = {}) => {
76 const localeKeys = [DEFAULT_LOCALE, ...Object.keys(locales)].sort((first, second) => {
77 if (first === second) {
81 const firstPriority = getLanguagePriority(first);
82 const secondPriority = getLanguagePriority(second);
84 if (firstPriority > secondPriority) {
88 if (firstPriority < secondPriority) {
92 return first > second ? 1 : -1;
94 const normalizedLocaleKeys = localeKeys.map(getNormalizedLocale);
95 const normalizedLocale = getNormalizedLocale(locale);
97 // First by language and country code.
98 const fullMatchIndex = normalizedLocaleKeys.findIndex((key) => key === normalizedLocale);
99 if (fullMatchIndex >= 0) {
100 return localeKeys[fullMatchIndex];
104 const language = getLanguageCode(normalizedLocale);
105 const languageMatchIndex = normalizedLocaleKeys.findIndex((key) => {
106 return getLanguageCode(key) === language;
108 if (languageMatchIndex >= 0) {
109 return localeKeys[languageMatchIndex];
113 export const getClosestLocaleCode = (locale: string | undefined, locales: { [key: string]: any }) => {
115 return DEFAULT_LOCALE;
117 return getClosestLocaleMatch(locale, locales) || DEFAULT_LOCALE;