Browse Source

Merge branch 'master' into support/114575-v6-migration-readme

Ryoji Shimizu 3 years ago
parent
commit
b1b8adf3e5

+ 2 - 1
packages/app/config/next-i18next.config.ts

@@ -7,8 +7,9 @@ import I18NextLocalStorageBackend from 'i18next-localstorage-backend';
 
 
 const isDev = process.env.NODE_ENV === 'development';
 const isDev = process.env.NODE_ENV === 'development';
 
 
+export const defaultLang = Lang.en_US;
 export const i18n = {
 export const i18n = {
-  defaultLocale: Lang.en_US,
+  defaultLocale: defaultLang,
   locales: AllLang,
   locales: AllLang,
 };
 };
 export const defaultNS = 'translation';
 export const defaultNS = 'translation';

+ 60 - 0
packages/app/src/client/util/locale-utils.ts

@@ -1,9 +1,69 @@
+import type { IncomingHttpHeaders } from 'http';
+
+import { Lang } from '@growi/core';
+
+import * as nextI18NextConfig from '^/config/next-i18next.config';
+
 // https://docs.google.com/spreadsheets/d/1FoYdyEraEQuWofzbYCDPKN7EdKgS_2ZrsDrOA8scgwQ
 // https://docs.google.com/spreadsheets/d/1FoYdyEraEQuWofzbYCDPKN7EdKgS_2ZrsDrOA8scgwQ
 const DIAGRAMS_NET_LANG_MAP = {
 const DIAGRAMS_NET_LANG_MAP = {
   ja_JP: 'ja',
   ja_JP: 'ja',
   zh_CN: 'zh',
   zh_CN: 'zh',
 };
 };
 
 
+const ACCEPT_LANG_MAP = {
+  en: Lang.en_US,
+  ja: Lang.ja_JP,
+  zh: Lang.zh_CN,
+};
+
 export const getDiagramsNetLangCode = (lang) => {
 export const getDiagramsNetLangCode = (lang) => {
   return DIAGRAMS_NET_LANG_MAP[lang];
   return DIAGRAMS_NET_LANG_MAP[lang];
 };
 };
+
+/**
+ * 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 nextI18NextConfig.defaultLang;
+};
+
+/**
+  * Detect locale from browser accept language
+  * @param headers
+  */
+export const detectLocaleFromBrowserAcceptLanguage = (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'];
+
+  if (acceptLanguages == null) {
+    return nextI18NextConfig.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. 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);
+};

+ 4 - 4
packages/app/src/pages/utils/commons.ts

@@ -7,6 +7,7 @@ import type { SSRConfig, UserConfig } from 'next-i18next';
 
 
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 
 
+import { detectLocaleFromBrowserAcceptLanguage } from '~/client/util/locale-utils';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
@@ -99,13 +100,12 @@ export const getNextI18NextConfig = async(
 ): Promise<SSRConfig> => {
 ): Promise<SSRConfig> => {
 
 
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
-  const { crowi, user } = req;
+  const { crowi, user, headers } = req;
   const { configManager } = crowi;
   const { configManager } = crowi;
 
 
   // determine language
   // determine language
-  const locale = user?.lang
-    ?? configManager.getConfig('crowi', 'app:globalLang') as Lang
-    ?? Lang.en_US;
+  const locale = user == null ? detectLocaleFromBrowserAcceptLanguage(headers)
+    : (user.lang ?? configManager.getConfig('crowi', 'app:globalLang') as Lang ?? Lang.en_US);
 
 
   const namespaces = ['commons'];
   const namespaces = ['commons'];
   if (namespacesRequired != null) {
   if (namespacesRequired != null) {