Просмотр исходного кода

Merge pull request #8600 from weseek/feat/141574-143040-apply-locale-to-defualt-page-title

feat: Apply locale to defualt page title
Yuki Takei 1 год назад
Родитель
Сommit
bb85b12076

+ 16 - 0
apps/app/config/i18next.config.js

@@ -0,0 +1,16 @@
+const { Lang, AllLang } = require('@growi/core');
+
+/** @type {Lang} */
+const defaultLang = Lang.en_US;
+
+/** @type {import('i18next').InitOptions} */
+const initOptions = {
+  fallbackLng: defaultLang.toString(),
+  supportedLngs: AllLang,
+  defaultNS: 'translation',
+};
+
+module.exports = {
+  defaultLang,
+  initOptions,
+};

+ 20 - 9
apps/app/config/next-i18next.config.js

@@ -2,31 +2,41 @@ const isDev = process.env.NODE_ENV === 'development';
 
 
 const path = require('path');
 const path = require('path');
 
 
-const { AllLang, Lang } = require('@growi/core');
+const { AllLang } = require('@growi/core');
 const { isServer } = require('@growi/core/dist/utils');
 const { isServer } = require('@growi/core/dist/utils');
-const I18nextChainedBackend = isDev ? require('i18next-chained-backend').default : undefined;
-const I18NextHttpBackend = require('i18next-http-backend').default;
-const I18NextLocalStorageBackend = require('i18next-localstorage-backend').default;
+
+const { defaultLang } = require('./i18next.config');
 
 
 const HMRPlugin = isDev ? require('i18next-hmr/plugin').HMRPlugin : undefined;
 const HMRPlugin = isDev ? require('i18next-hmr/plugin').HMRPlugin : undefined;
 
 
+/** @type {import('next-i18next').UserConfig} */
 module.exports = {
 module.exports = {
-  defaultLang: Lang.en_US,
+  ...require('./i18next.config').initOptions,
+
   i18n: {
   i18n: {
-    defaultLocale: Lang.en_US,
+    defaultLocale: defaultLang.toString(),
     locales: AllLang,
     locales: AllLang,
   },
   },
-  defaultNS: 'translation',
+
   localePath: path.resolve('./public/static/locales'),
   localePath: path.resolve('./public/static/locales'),
   serializeConfig: false,
   serializeConfig: false,
+
   // eslint-disable-next-line no-nested-ternary
   // eslint-disable-next-line no-nested-ternary
   use: isDev
   use: isDev
     ? isServer()
     ? isServer()
       ? [new HMRPlugin({ webpack: { server: true } })]
       ? [new HMRPlugin({ webpack: { server: true } })]
-      : [I18nextChainedBackend, new HMRPlugin({ webpack: { client: true } })]
+      : [
+        require('i18next-chained-backend').default,
+        new HMRPlugin({ webpack: { client: true } }),
+      ]
     : [],
     : [],
   backend: {
   backend: {
-    backends: isServer() ? [] : [I18NextLocalStorageBackend, I18NextHttpBackend],
+    backends: isServer()
+      ? []
+      : [
+        require('i18next-localstorage-backend').default,
+        require('i18next-http-backend').default,
+      ],
     backendOptions: [
     backendOptions: [
       // options for i18next-localstorage-backend
       // options for i18next-localstorage-backend
       { expirationTime: isDev ? 0 : 24 * 60 * 60 * 1000 }, // 1 day in production
       { expirationTime: isDev ? 0 : 24 * 60 * 60 * 1000 }, // 1 day in production
@@ -34,4 +44,5 @@ module.exports = {
       { loadPath: '/static/locales/{{lng}}/{{ns}}.json' },
       { loadPath: '/static/locales/{{lng}}/{{ns}}.json' },
     ],
     ],
   },
   },
+
 };
 };

+ 4 - 3
apps/app/package.json

@@ -125,9 +125,7 @@
     "helmet": "^4.6.0",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
     "http-errors": "^2.0.0",
     "i18next": "^23.10.1",
     "i18next": "^23.10.1",
-    "i18next-chained-backend": "^4.6.2",
-    "i18next-http-backend": "^2.5.0",
-    "i18next-localstorage-backend": "^4.2.0",
+    "i18next-resources-to-backend": "^1.2.1",
     "is-absolute-url": "^4.0.1",
     "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
     "is-iso-date": "^0.0.1",
     "ldapjs": "^3.0.2",
     "ldapjs": "^3.0.2",
@@ -253,7 +251,10 @@
     "fslightbox-react": "^1.7.6",
     "fslightbox-react": "^1.7.6",
     "handsontable": "=6.2.2",
     "handsontable": "=6.2.2",
     "happy-dom": "^13.2.0",
     "happy-dom": "^13.2.0",
+    "i18next-chained-backend": "^4.6.2",
     "i18next-hmr": "^3.0.4",
     "i18next-hmr": "^3.0.4",
+    "i18next-http-backend": "^2.5.0",
+    "i18next-localstorage-backend": "^4.2.0",
     "jest": "^29.5.0",
     "jest": "^29.5.0",
     "jest-date-mock": "^1.0.8",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
     "jest-localstorage-mock": "^2.4.14",

+ 3 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -863,5 +863,8 @@
     "show_wip_page": "Show WIP",
     "show_wip_page": "Show WIP",
     "size_s": "Size: S",
     "size_s": "Size: S",
     "size_l": "Size: L"
     "size_l": "Size: L"
+  },
+  "create_page": {
+    "untitled": "Untitled"
   }
   }
 }
 }

+ 3 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -896,5 +896,8 @@
     "show_wip_page": "WIP を表示",
     "show_wip_page": "WIP を表示",
     "size_s": "サイズ: S",
     "size_s": "サイズ: S",
     "size_l": "サイズ: L"
     "size_l": "サイズ: L"
+  },
+  "create_page": {
+    "untitled": "無題のページ"
   }
   }
 }
 }

+ 3 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -866,5 +866,8 @@
     "show_wip_page": "显示 WIP",
     "show_wip_page": "显示 WIP",
     "size_s": "尺寸: S",
     "size_s": "尺寸: S",
     "size_l": "尺寸: L"
     "size_l": "尺寸: L"
+  },
+  "create_page": {
+    "untitled": "Untitled"
   }
   }
 }
 }

+ 3 - 3
apps/app/src/client/util/locale-utils.ts

@@ -2,7 +2,7 @@ import type { IncomingHttpHeaders } from 'http';
 
 
 import { Lang } from '@growi/core';
 import { Lang } from '@growi/core';
 
 
-import * as nextI18NextConfig from '^/config/next-i18next.config';
+import { defaultLang } from '^/config/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 = {
@@ -31,7 +31,7 @@ const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
     const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
     const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
     if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
     if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
   }
   }
-  return nextI18NextConfig.defaultLang;
+  return defaultLang;
 };
 };
 
 
 /**
 /**
@@ -44,7 +44,7 @@ export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeade
   const acceptLanguages = headers['accept-language'];
   const acceptLanguages = headers['accept-language'];
 
 
   if (acceptLanguages == null) {
   if (acceptLanguages == null) {
-    return nextI18NextConfig.defaultLang;
+    return defaultLang;
   }
   }
 
 
   // 1. trim blank spaces.
   // 1. trim blank spaces.

+ 4 - 2
apps/app/src/server/routes/apiv3/page/create-page.ts

@@ -22,6 +22,7 @@ import {
 import type { PageDocument, PageModel } from '~/server/models/page';
 import type { PageDocument, PageModel } from '~/server/models/page';
 import PageTagRelation from '~/server/models/page-tag-relation';
 import PageTagRelation from '~/server/models/page-tag-relation';
 import { configManager } from '~/server/service/config-manager';
 import { configManager } from '~/server/service/config-manager';
+import { getTranslation } from '~/server/service/i18next';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../../middlewares/apiv3-form-validator';
@@ -43,8 +44,9 @@ async function generateUntitledPath(parentPath: string, basePathname: string, in
 }
 }
 
 
 async function determinePath(_parentPath?: string, _path?: string, optionalParentPath?: string): Promise<string> {
 async function determinePath(_parentPath?: string, _path?: string, optionalParentPath?: string): Promise<string> {
-  // TODO: https://redmine.weseek.co.jp/issues/142729
-  const basePathname = 'Untitled';
+  const { t } = await getTranslation();
+
+  const basePathname = t?.('create_page.untitled') || 'Untitled';
 
 
   if (_path != null) {
   if (_path != null) {
     const path = normalizePath(_path);
     const path = normalizePath(_path);

+ 41 - 0
apps/app/src/server/service/i18next.ts

@@ -0,0 +1,41 @@
+import type { Lang } from '@growi/core';
+import type { TFunction, i18n } from 'i18next';
+import { createInstance } from 'i18next';
+import resourcesToBackend from 'i18next-resources-to-backend';
+
+import { defaultLang, initOptions } from '^/config/i18next.config';
+
+import { configManager } from './config-manager';
+
+
+const initI18next = async(lang: Lang = defaultLang) => {
+  const i18nInstance = createInstance();
+  await i18nInstance
+    .use(
+      resourcesToBackend(
+        (language: string, namespace: string) => {
+          return import(`^/public/static/locales/${language}/${namespace}.json`);
+        },
+      ),
+    )
+    .init({
+      ...initOptions,
+      lng: lang,
+    });
+  return i18nInstance;
+};
+
+type Translation = {
+  t: TFunction,
+  i18n: i18n
+}
+
+export async function getTranslation(lang?: Lang): Promise<Translation> {
+  const globalLang = configManager.getConfig('crowi', 'app:globalLang') as Lang;
+  const i18nextInstance = await initI18next(globalLang);
+
+  return {
+    t: i18nextInstance.getFixedT(lang ?? globalLang),
+    i18n: i18nextInstance,
+  };
+}

+ 7 - 0
yarn.lock

@@ -10061,6 +10061,13 @@ i18next-localstorage-backend@^4.2.0:
   dependencies:
   dependencies:
     "@babel/runtime" "^7.22.15"
     "@babel/runtime" "^7.22.15"
 
 
+i18next-resources-to-backend@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/i18next-resources-to-backend/-/i18next-resources-to-backend-1.2.1.tgz#fded121e63e3139ce839c9901b9449dbbea7351d"
+  integrity sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==
+  dependencies:
+    "@babel/runtime" "^7.23.2"
+
 i18next@^23.10.1:
 i18next@^23.10.1:
   version "23.10.1"
   version "23.10.1"
   resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.10.1.tgz#217ce93b75edbe559ac42be00a20566b53937df6"
   resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.10.1.tgz#217ce93b75edbe559ac42be00a20566b53937df6"