|
@@ -1,6 +1,8 @@
|
|
|
|
|
+import type { IncomingHttpHeaders } from 'http';
|
|
|
|
|
+
|
|
|
import type { ColorScheme, IUserHasId } from '@growi/core';
|
|
import type { ColorScheme, IUserHasId } from '@growi/core';
|
|
|
import {
|
|
import {
|
|
|
- DevidedPagePath, Lang, AllLang,
|
|
|
|
|
|
|
+ DevidedPagePath, Lang, AllLang, acceptLangMap,
|
|
|
} from '@growi/core';
|
|
} from '@growi/core';
|
|
|
import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
|
|
import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
|
|
|
import type { SSRConfig, UserConfig } from 'next-i18next';
|
|
import type { SSRConfig, UserConfig } from 'next-i18next';
|
|
@@ -89,6 +91,55 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
|
|
|
return { props };
|
|
return { props };
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * It return the first language that matches acceptLangMap keys from sorted accept languages array
|
|
|
|
|
+ * @param sortedAcceptLanguagesArray
|
|
|
|
|
+ */
|
|
|
|
|
+const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
|
|
|
|
|
+ for (const lang of sortedAcceptLanguagesArray) {
|
|
|
|
|
+ const matchingLang = Object.keys(acceptLangMap).find(key => lang.includes(key));
|
|
|
|
|
+ if (matchingLang) return acceptLangMap[matchingLang];
|
|
|
|
|
+ }
|
|
|
|
|
+ return nextI18NextConfig.defalutLang;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Detect locale from browser accept language
|
|
|
|
|
+ * @param headers
|
|
|
|
|
+ */
|
|
|
|
|
+const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeaders) => {
|
|
|
|
|
+ // 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 nextI18NextConfig.defalutLang;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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]);
|
|
|
|
|
+
|
|
|
|
|
+ return getPreferredLanguage(sortedAcceptLanguagesArray);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
export const getNextI18NextConfig = async(
|
|
export const getNextI18NextConfig = async(
|
|
|
// 'serverSideTranslations' method should be given from Next.js Page
|
|
// 'serverSideTranslations' method should be given from Next.js Page
|
|
|
// because importing it in this file causes https://github.com/isaachinman/next-i18next/issues/1545
|
|
// because importing it in this file causes https://github.com/isaachinman/next-i18next/issues/1545
|
|
@@ -102,50 +153,8 @@ export const getNextI18NextConfig = async(
|
|
|
const { crowi, user, headers } = req;
|
|
const { crowi, user, headers } = req;
|
|
|
const { configManager } = crowi;
|
|
const { configManager } = crowi;
|
|
|
|
|
|
|
|
- // detect locale from browser accept language
|
|
|
|
|
- const detectLocaleFromBrowserAcceptLanguage = () => {
|
|
|
|
|
- // 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 Lang.en_US;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 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;
|
|
|
|
|
- }, {});
|
|
|
|
|
-
|
|
|
|
|
- // create an array of sorted languages in descending order.
|
|
|
|
|
- // ex. [ 'ja', 'ar-SA', 'en', 'en-CA', 'en-US' ]
|
|
|
|
|
- const sortedAcceptLanguagesArray = Object.keys(acceptLanguagesDict)
|
|
|
|
|
- .sort((a, b) => b.localeCompare(a))
|
|
|
|
|
- .map(item => acceptLanguagesDict[item]);
|
|
|
|
|
-
|
|
|
|
|
- // it return the first language that matches 'en', 'ja' or 'zh.
|
|
|
|
|
- // if no matching language is found, it returns Lang.en_US as a default.
|
|
|
|
|
- for (const lang of sortedAcceptLanguagesArray) {
|
|
|
|
|
- if (lang.includes('en')) return Lang.en_US;
|
|
|
|
|
- if (lang.includes('ja')) return Lang.ja_JP;
|
|
|
|
|
- if (lang.includes('zh')) return Lang.zh_CN;
|
|
|
|
|
- }
|
|
|
|
|
- return Lang.en_US;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const detectLocale = detectLocaleFromBrowserAcceptLanguage();
|
|
|
|
|
-
|
|
|
|
|
// determine language
|
|
// determine language
|
|
|
- const locale = user == null ? detectLocale
|
|
|
|
|
|
|
+ const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
|
|
|
: (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
|
|
: (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
|
|
|
|
|
|
|
|
const namespaces = ['commons'];
|
|
const namespaces = ['commons'];
|