|
|
@@ -1,35 +1,38 @@
|
|
|
-import * as i18nextConfig from "^/config/i18next.config";
|
|
|
-import { Lang } from "@growi/core/dist/interfaces";
|
|
|
-import { enUS, fr, ja, ko, type Locale, zhCN } from "date-fns/locale";
|
|
|
-import type { IncomingHttpHeaders } from "http";
|
|
|
+import { Lang } from '@growi/core/dist/interfaces';
|
|
|
+import type { IncomingHttpHeaders } from 'http';
|
|
|
+import { type Locale } from 'date-fns/locale';
|
|
|
+import {
|
|
|
+ enUS, ja, fr, ko, zhCN,
|
|
|
+} from 'date-fns/locale';
|
|
|
+import * as i18nextConfig from '^/config/i18next.config';
|
|
|
|
|
|
const ACCEPT_LANG_MAP = {
|
|
|
- en: Lang.en_US,
|
|
|
- ja: Lang.ja_JP,
|
|
|
- zh: Lang.zh_CN,
|
|
|
- fr: Lang.fr_FR,
|
|
|
- ko: Lang.ko_KR,
|
|
|
+ en: Lang.en_US,
|
|
|
+ ja: Lang.ja_JP,
|
|
|
+ zh: Lang.zh_CN,
|
|
|
+ fr: Lang.fr_FR,
|
|
|
+ ko: Lang.ko_KR,
|
|
|
};
|
|
|
const DATE_FNS_LOCALE_MAP: Record<string, Locale | undefined> = {
|
|
|
- en: enUS,
|
|
|
- "en-US": enUS,
|
|
|
- en_US: enUS,
|
|
|
+ en: enUS,
|
|
|
+ 'en-US': enUS,
|
|
|
+ en_US: enUS,
|
|
|
|
|
|
- ja,
|
|
|
- "ja-JP": ja,
|
|
|
- ja_JP: ja,
|
|
|
+ ja,
|
|
|
+ 'ja-JP': ja,
|
|
|
+ ja_JP: ja,
|
|
|
|
|
|
- fr,
|
|
|
- "fr-FR": fr,
|
|
|
- fr_FR: fr,
|
|
|
+ fr,
|
|
|
+ 'fr-FR': fr,
|
|
|
+ fr_FR: fr,
|
|
|
|
|
|
- ko,
|
|
|
- "ko-KR": ko,
|
|
|
- ko_KR: ko,
|
|
|
+ ko,
|
|
|
+ 'ko-KR': ko,
|
|
|
+ ko_KR: ko,
|
|
|
|
|
|
- zh: zhCN,
|
|
|
- "zh-CN": zhCN,
|
|
|
- zh_CN: zhCN,
|
|
|
+ zh: zhCN,
|
|
|
+ 'zh-CN': zhCN,
|
|
|
+ zh_CN: zhCN,
|
|
|
};
|
|
|
/**
|
|
|
* Gets the corresponding date-fns Locale object from an i18next language code.
|
|
|
@@ -37,28 +40,29 @@ const DATE_FNS_LOCALE_MAP: Record<string, Locale | undefined> = {
|
|
|
* @returns The date-fns Locale object, defaulting to enUS if not found.
|
|
|
*/
|
|
|
export const getLocale = (langCode: string): Locale => {
|
|
|
- let locale = DATE_FNS_LOCALE_MAP[langCode];
|
|
|
+ let locale = DATE_FNS_LOCALE_MAP[langCode];
|
|
|
|
|
|
- if (!locale) {
|
|
|
- const baseCode = langCode.split(/[-_]/)[0];
|
|
|
- locale = DATE_FNS_LOCALE_MAP[baseCode];
|
|
|
- }
|
|
|
+ if (!locale) {
|
|
|
+ const baseCode = langCode.split(/[-_]/)[0];
|
|
|
+ locale = DATE_FNS_LOCALE_MAP[baseCode];
|
|
|
+ }
|
|
|
|
|
|
- return locale ?? enUS;
|
|
|
+ return locale ?? enUS;
|
|
|
};
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
* It return the first language that matches ACCEPT_LANG_MAP keys from sorted accept languages array
|
|
|
* @param sortedAcceptLanguagesArray
|
|
|
*/
|
|
|
const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
|
|
|
- for (const lang of sortedAcceptLanguagesArray) {
|
|
|
- const matchingLang = Object.keys(ACCEPT_LANG_MAP).find((key) =>
|
|
|
- lang.includes(key),
|
|
|
- );
|
|
|
- if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
|
|
|
- }
|
|
|
- return i18nextConfig.defaultLang;
|
|
|
+ for (const lang of sortedAcceptLanguagesArray) {
|
|
|
+ const matchingLang = Object.keys(ACCEPT_LANG_MAP).find((key) =>
|
|
|
+ lang.includes(key),
|
|
|
+ );
|
|
|
+ if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
|
|
|
+ }
|
|
|
+ return i18nextConfig.defaultLang;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -66,35 +70,35 @@ const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
|
|
|
* @param headers
|
|
|
*/
|
|
|
export const detectLocaleFromBrowserAcceptLanguage = (
|
|
|
- headers: IncomingHttpHeaders,
|
|
|
+ headers: IncomingHttpHeaders,
|
|
|
): Lang => {
|
|
|
- // 1. get the header accept-language
|
|
|
- // ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
|
|
|
- const acceptLanguages = headers["accept-language"];
|
|
|
+ // 1. get the header accept-language
|
|
|
+ // ex. "ja,ar-SA;q=0.8,en;q=0.6,en-CA;q=0.4,en-US;q=0.2"
|
|
|
+ const acceptLanguages = headers['accept-language'];
|
|
|
|
|
|
- if (acceptLanguages == null) {
|
|
|
- return i18nextConfig.defaultLang;
|
|
|
- }
|
|
|
+ if (acceptLanguages == null) {
|
|
|
+ return i18nextConfig.defaultLang;
|
|
|
+ }
|
|
|
|
|
|
- // 1. trim blank spaces.
|
|
|
- // 2. separate by ,.
|
|
|
- // 3. if "lang;q=x", then { 'x', 'lang' } to add to the associative array.
|
|
|
- // if "lang" has no weight x (";q=x"), add it with key = 1.
|
|
|
- // ex. {'1': 'ja','0.8': 'ar-SA','0.6': 'en','0.4': 'en-CA','0.2': 'en-US'}
|
|
|
- const acceptLanguagesDict = acceptLanguages
|
|
|
- .replace(/\s+/g, "")
|
|
|
- .split(",")
|
|
|
- .map((item) => item.split(/\s*;\s*q\s*=\s*/))
|
|
|
- .reduce((acc, [key, value = "1"]) => {
|
|
|
- acc[value] = key;
|
|
|
- return acc;
|
|
|
- }, {});
|
|
|
+ // 1. trim blank spaces.
|
|
|
+ // 2. separate by ,.
|
|
|
+ // 3. if "lang;q=x", then { 'x', 'lang' } to add to the associative array.
|
|
|
+ // if "lang" has no weight x (";q=x"), add it with key = 1.
|
|
|
+ // ex. {'1': 'ja','0.8': 'ar-SA','0.6': 'en','0.4': 'en-CA','0.2': 'en-US'}
|
|
|
+ const acceptLanguagesDict = acceptLanguages
|
|
|
+ .replace(/\s+/g, '')
|
|
|
+ .split(',')
|
|
|
+ .map((item) => item.split(/\s*;\s*q\s*=\s*/))
|
|
|
+ .reduce((acc, [key, value = '1']) => {
|
|
|
+ acc[value] = key;
|
|
|
+ return acc;
|
|
|
+ }, {});
|
|
|
|
|
|
- // 1. create an array of sorted languages in descending order.
|
|
|
- // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
|
|
|
- const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
|
|
|
- .sort((x, y) => y.localeCompare(x))
|
|
|
- .map((item) => acceptLanguagesDict[item]);
|
|
|
+ // 1. create an array of sorted languages in descending order.
|
|
|
+ // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
|
|
|
+ const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
|
|
|
+ .sort((x, y) => y.localeCompare(x))
|
|
|
+ .map((item) => acceptLanguagesDict[item]);
|
|
|
|
|
|
- return getPreferredLanguage(sortedAcceptLanguagesArray);
|
|
|
+ return getPreferredLanguage(sortedAcceptLanguagesArray);
|
|
|
};
|