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

Merge pull request #9379 from weseek/fix/i18n-for-security-settings

fix: i18n for security settings
mergify[bot] 1 год назад
Родитель
Сommit
032d3495aa

+ 4 - 6
apps/app/config/i18next.config.js

@@ -1,6 +1,6 @@
-const { Lang, AllLang } = require('@growi/core');
+const { Lang, AllLang } = require('@growi/core/dist/interfaces');
 
 
-/** @type {Lang} */
+/** @type {import('@growi/core/dist/interfaces').Lang} */
 const defaultLang = Lang.en_US;
 const defaultLang = Lang.en_US;
 
 
 /** @type {import('i18next').InitOptions} */
 /** @type {import('i18next').InitOptions} */
@@ -10,7 +10,5 @@ const initOptions = {
   defaultNS: 'translation',
   defaultNS: 'translation',
 };
 };
 
 
-module.exports = {
-  defaultLang,
-  initOptions,
-};
+exports.defaultLang = defaultLang;
+exports.initOptions = initOptions;

+ 5 - 5
apps/app/package.json

@@ -129,7 +129,7 @@
     "hastscript": "^8.0.0",
     "hastscript": "^8.0.0",
     "helmet": "^4.6.0",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
     "http-errors": "^2.0.0",
-    "i18next": "^23.10.1",
+    "i18next": "^23.16.5",
     "i18next-resources-to-backend": "^1.2.1",
     "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",
@@ -159,7 +159,7 @@
     "mustache": "^4.2.0",
     "mustache": "^4.2.0",
     "next": "^14.2.13",
     "next": "^14.2.13",
     "next-dynamic-loading-props": "^0.1.1",
     "next-dynamic-loading-props": "^0.1.1",
-    "next-i18next": "^15.2.0",
+    "next-i18next": "^15.3.1",
     "next-superjson": "^0.0.4",
     "next-superjson": "^0.0.4",
     "next-themes": "^0.2.1",
     "next-themes": "^0.2.1",
     "nocache": "^4.0.0",
     "nocache": "^4.0.0",
@@ -185,7 +185,7 @@
     "react-disable": "^0.1.1",
     "react-disable": "^0.1.1",
     "react-dom": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-error-boundary": "^3.1.4",
     "react-error-boundary": "^3.1.4",
-    "react-i18next": "^14.1.0",
+    "react-i18next": "^15.1.1",
     "react-image-crop": "^8.3.0",
     "react-image-crop": "^8.3.0",
     "react-markdown": "^9.0.1",
     "react-markdown": "^9.0.1",
     "react-multiline-clamp": "^2.0.0",
     "react-multiline-clamp": "^2.0.0",
@@ -284,8 +284,8 @@
     "handsontable": "=6.2.2",
     "handsontable": "=6.2.2",
     "happy-dom": "^15.7.4",
     "happy-dom": "^15.7.4",
     "i18next-chained-backend": "^4.6.2",
     "i18next-chained-backend": "^4.6.2",
-    "i18next-hmr": "^3.0.4",
-    "i18next-http-backend": "^2.5.0",
+    "i18next-hmr": "^3.1.3",
+    "i18next-http-backend": "^2.6.2",
     "i18next-localstorage-backend": "^4.2.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",

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

@@ -170,8 +170,8 @@
     },
     },
     "message": {
     "message": {
       "error_message": "Some values ​​are incorrect",
       "error_message": "Some values ​​are incorrect",
-      "required": "%s is required",
-      "invalid_syntax": "The syntax of %s is invalid.",
+      "required": "'{{param}}' is required",
+      "invalid_syntax": "The syntax of {{syntax}} is invalid.",
       "title_required": "Title is required.",
       "title_required": "Title is required.",
       "field_required": "{{target}} is required"
       "field_required": "{{target}} is required"
     }
     }

+ 2 - 2
apps/app/public/static/locales/fr_FR/translation.json

@@ -170,8 +170,8 @@
     },
     },
     "message": {
     "message": {
       "error_message": "Des champs sont invalides",
       "error_message": "Des champs sont invalides",
-      "required": "%s est requis",
-      "invalid_syntax": "La syntaxe de %s est invalide.",
+      "required": "'{{param}}' est requis",
+      "invalid_syntax": "La syntaxe de {{syntax}} est invalide.",
       "title_required": "Titre requis.",
       "title_required": "Titre requis.",
       "field_required": "{{target}} est requis"
       "field_required": "{{target}} est requis"
     }
     }

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

@@ -171,8 +171,8 @@
     },
     },
     "message": {
     "message": {
       "error_message": "いくつかの値が設定されていません",
       "error_message": "いくつかの値が設定されていません",
-      "required": "%sに値を入力してください",
-      "invalid_syntax": "%sの構文が不正です",
+      "required": "'{{param}}' に値を入力してください",
+      "invalid_syntax": "{{syntax}} の構文が不正です",
       "title_required": "タイトルを入力してください",
       "title_required": "タイトルを入力してください",
       "field_required": "{{target}}に値を入力してください"
       "field_required": "{{target}}に値を入力してください"
     }
     }

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

@@ -177,8 +177,8 @@
     },
     },
     "message": {
     "message": {
       "error_message": "有些值不正确",
       "error_message": "有些值不正确",
-      "required": "%s 是必需的",
-      "invalid_syntax": "%s的语法无效。",
+      "required": "'{{param}}' 是必需的",
+      "invalid_syntax": "{{syntax}} 的语法无效。",
       "title_required": "标题是必需的。",
       "title_required": "标题是必需的。",
       "field_required": "{{target}} 是必需的"
       "field_required": "{{target}} 是必需的"
     }
     }

+ 7 - 1
apps/app/src/client/components/Admin/Security/SamlSecuritySettingContents.jsx

@@ -33,12 +33,18 @@ class SamlSecurityManagementContents extends React.Component {
 
 
     try {
     try {
       await adminSamlSecurityContainer.updateSamlSetting();
       await adminSamlSecurityContainer.updateSamlSetting();
-      await adminGeneralSecurityContainer.retrieveSetupStratedies();
       toastSuccess(t('security_settings.SAML.updated_saml'));
       toastSuccess(t('security_settings.SAML.updated_saml'));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
+
+    try {
+      await adminGeneralSecurityContainer.retrieveSetupStratedies();
+    }
+    catch (err) {
+      toastError(err);
+    }
   }
   }
 
 
   render() {
   render() {

+ 1 - 1
apps/app/src/client/components/InstallerForm.tsx

@@ -32,7 +32,7 @@ const InstallerForm = memo((props: Props): JSX.Element => {
 
 
   const tWithOpt = useTWithOpt();
   const tWithOpt = useTWithOpt();
 
 
-  const isSupportedLang = AllLang.includes(i18n.language);
+  const isSupportedLang = AllLang.includes(i18n.language as Lang);
 
 
   const [isLoading, setIsLoading] = useState(false);
   const [isLoading, setIsLoading] = useState(false);
   const [currentLocale, setCurrentLocale] = useState(isSupportedLang ? i18n.language : Lang.en_US);
   const [currentLocale, setCurrentLocale] = useState(isSupportedLang ? i18n.language : Lang.en_US);

+ 4 - 0
apps/app/src/client/util/apiv3-client.ts

@@ -17,10 +17,14 @@ const apiv3ErrorHandler = (_err: any): any[] => {
   // extract api errors from general 400 err
   // extract api errors from general 400 err
   const err = _err.response ? _err.response.data.errors : _err;
   const err = _err.response ? _err.response.data.errors : _err;
   const errs = toArrayIfNot(err);
   const errs = toArrayIfNot(err);
+  const errorInfo = _err.response ? _err.response.data.info : undefined;
 
 
   for (const err of errs) {
   for (const err of errs) {
     logger.error(err.message);
     logger.error(err.message);
   }
   }
+  if (errorInfo != null) {
+    logger.error('additional info:', errorInfo);
+  }
 
 
   return errs;
   return errs;
 };
 };

+ 1 - 1
apps/app/src/features/openai/server/services/replace-annotation-with-page-link.ts

@@ -17,7 +17,7 @@ export const replaceAnnotationWithPageLink = async(messageContentDelta: MessageC
           .populate<{page: Pick<IPageHasId, 'path' | '_id'>}>('page', 'path');
           .populate<{page: Pick<IPageHasId, 'path' | '_id'>}>('page', 'path');
 
 
         if (vectorStoreFileRelation != null) {
         if (vectorStoreFileRelation != null) {
-          const { t } = await getTranslation(lang);
+          const { t } = await getTranslation({ lang });
           messageContentDelta.text.value = messageContentDelta.text.value?.replace(
           messageContentDelta.text.value = messageContentDelta.text.value?.replace(
             annotation.text,
             annotation.text,
             ` [${t('source')}: [${vectorStoreFileRelation.page.path}](/${vectorStoreFileRelation.page._id})]`,
             ` [${t('source')}: [${vectorStoreFileRelation.page.path}](/${vectorStoreFileRelation.page._id})]`,

+ 1 - 1
apps/app/src/pages/_app.page.tsx

@@ -1,7 +1,7 @@
 import type { ReactElement, ReactNode } from 'react';
 import type { ReactElement, ReactNode } from 'react';
 import React, { useEffect } from 'react';
 import React, { useEffect } from 'react';
 
 
-import type { Locale } from '@growi/core';
+import type { Locale } from '@growi/core/dist/interfaces';
 import type { NextPage } from 'next';
 import type { NextPage } from 'next';
 import { appWithTranslation } from 'next-i18next';
 import { appWithTranslation } from 'next-i18next';
 import type { AppContext, AppProps } from 'next/app';
 import type { AppContext, AppProps } from 'next/app';

+ 1 - 1
apps/app/src/pages/_document.page.tsx

@@ -1,7 +1,7 @@
 /* eslint-disable @next/next/google-font-display */
 /* eslint-disable @next/next/google-font-display */
 import React from 'react';
 import React from 'react';
 
 
-import type { Locale } from '@growi/core';
+import type { Locale } from '@growi/core/dist/interfaces';
 import type { DocumentContext, DocumentInitialProps } from 'next/document';
 import type { DocumentContext, DocumentInitialProps } from 'next/document';
 import Document, {
 import Document, {
   Html, Head, Main, NextScript,
   Html, Head, Main, NextScript,

+ 1 - 1
apps/app/src/server/routes/apiv3/app-settings.js

@@ -569,7 +569,7 @@ module.exports = (crowi) => {
    *            description: Succeeded to send test mail for smtp
    *            description: Succeeded to send test mail for smtp
    */
    */
   router.post('/smtp-test', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
   router.post('/smtp-test', loginRequiredStrictly, adminRequired, addActivity, async(req, res) => {
-    const { t } = await getTranslation(req.user.lang);
+    const { t } = await getTranslation({ lang: req.user.lang });
 
 
     try {
     try {
       await sendTestEmail(req.user.email);
       await sendTestEmail(req.user.email);

+ 4 - 4
apps/app/src/server/routes/apiv3/security-settings/index.js

@@ -932,7 +932,7 @@ module.exports = (crowi) => {
    *                  $ref: '#/components/schemas/SamlAuthSetting'
    *                  $ref: '#/components/schemas/SamlAuthSetting'
    */
    */
   router.put('/saml', loginRequiredStrictly, adminRequired, addActivity, validator.samlAuth, apiV3FormValidator, async(req, res) => {
   router.put('/saml', loginRequiredStrictly, adminRequired, addActivity, validator.samlAuth, apiV3FormValidator, async(req, res) => {
-    const { t } = await getTranslation(req.user.lang);
+    const { t } = await getTranslation({ lang: req.user.lang, ns: ['translation', 'admin'] });
 
 
     //  For the value of each mandatory items,
     //  For the value of each mandatory items,
     //  check whether it from the environment variables is empty and form value to update it is empty
     //  check whether it from the environment variables is empty and form value to update it is empty
@@ -942,8 +942,8 @@ module.exports = (crowi) => {
       const key = configKey.replace('security:passport-saml:', '');
       const key = configKey.replace('security:passport-saml:', '');
       const formValue = req.body[key];
       const formValue = req.body[key];
       if (configManager.getConfigFromEnvVars('crowi', configKey) === null && formValue == null) {
       if (configManager.getConfigFromEnvVars('crowi', configKey) === null && formValue == null) {
-        const formItemName = t(`security_setting.form_item_name.${key}`);
-        invalidValues.push(t('input_validation.message.required', formItemName));
+        const formItemName = t(`security_settings.form_item_name.${key}`);
+        invalidValues.push(t('input_validation.message.required', { param: formItemName }));
       }
       }
     }
     }
     if (invalidValues.length !== 0) {
     if (invalidValues.length !== 0) {
@@ -958,7 +958,7 @@ module.exports = (crowi) => {
         crowi.passportService.parseABLCRule(rule);
         crowi.passportService.parseABLCRule(rule);
       }
       }
       catch (err) {
       catch (err) {
-        return res.apiv3Err(t('input_validation.message.invalid_syntax', t('security_settings.form_item_name.ABLCRule')), 400);
+        return res.apiv3Err(t('input_validation.message.invalid_syntax', { syntax: t('security_settings.form_item_name.ABLCRule') }), 400);
       }
       }
     }
     }
 
 

+ 1 - 1
apps/app/src/server/routes/login-passport.js

@@ -241,7 +241,7 @@ module.exports = function(crowi, app) {
    * @param {*} res
    * @param {*} res
    */
    */
   const testLdapCredentials = async(req, res) => {
   const testLdapCredentials = async(req, res) => {
-    const { t } = await getTranslation(req.user.lang);
+    const { t } = await getTranslation({ lang: req.user.lang });
 
 
     if (!passportService.isLdapStrategySetup) {
     if (!passportService.isLdapStrategySetup) {
       logger.debug('LdapStrategy has not been set up');
       logger.debug('LdapStrategy has not been set up');

+ 27 - 9
apps/app/src/server/service/i18next.ts

@@ -1,11 +1,11 @@
 import path from 'path';
 import path from 'path';
 
 
 import type { Lang } from '@growi/core';
 import type { Lang } from '@growi/core';
-import type { TFunction, i18n } from 'i18next';
+import type { InitOptions, TFunction, i18n } from 'i18next';
 import { createInstance } from 'i18next';
 import { createInstance } from 'i18next';
 import resourcesToBackend from 'i18next-resources-to-backend';
 import resourcesToBackend from 'i18next-resources-to-backend';
 
 
-import { defaultLang, initOptions } from '^/config/i18next.config';
+import * as i18nextConfig from '^/config/i18next.config';
 
 
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 
 
@@ -14,7 +14,7 @@ import { configManager } from './config-manager';
 
 
 const relativePathToLocalesRoot = path.relative(__dirname, resolveFromRoot('public/static/locales'));
 const relativePathToLocalesRoot = path.relative(__dirname, resolveFromRoot('public/static/locales'));
 
 
-const initI18next = async(fallbackLng: Lang[] = [defaultLang]) => {
+const initI18next = async(overwriteOpts: InitOptions) => {
   const i18nInstance = createInstance();
   const i18nInstance = createInstance();
   await i18nInstance
   await i18nInstance
     .use(
     .use(
@@ -25,8 +25,8 @@ const initI18next = async(fallbackLng: Lang[] = [defaultLang]) => {
       ),
       ),
     )
     )
     .init({
     .init({
-      ...initOptions,
-      fallbackLng,
+      ...i18nextConfig.initOptions,
+      ...overwriteOpts,
     });
     });
   return i18nInstance;
   return i18nInstance;
 };
 };
@@ -36,13 +36,31 @@ type Translation = {
   i18n: i18n
   i18n: i18n
 }
 }
 
 
-export async function getTranslation(lang?: Lang): Promise<Translation> {
+type Opts = {
+  lang?: Lang,
+  ns?: string | readonly string[],
+}
+
+export async function getTranslation(opts?: Opts): Promise<Translation> {
   const globalLang = configManager.getConfig('crowi', 'app:globalLang') as Lang;
   const globalLang = configManager.getConfig('crowi', 'app:globalLang') as Lang;
-  const fixedLang = lang ?? globalLang;
-  const i18nextInstance = await initI18next([fixedLang, defaultLang]);
+  const fixedLang = opts?.lang ?? globalLang;
+
+  const initOptions: InitOptions = {
+    fallbackLng: [fixedLang, i18nextConfig.defaultLang],
+  };
+
+  // set ns if not null
+  // cz: 'ns: unefined' causes
+  //   TypeError: Cannot read properties of undefined (reading 'forEach')
+  //     at /workspace/growi/node_modules/.pnpm/i18next@23.16.5/node_modules/i18next/dist/cjs/i18next.js:1613:18"
+  if (opts?.ns != null) {
+    initOptions.ns = opts.ns;
+  }
+
+  const i18nextInstance = await initI18next(initOptions);
 
 
   return {
   return {
-    t: i18nextInstance.getFixedT(fixedLang),
+    t: i18nextInstance.getFixedT(fixedLang, opts?.ns),
     i18n: i18nextInstance,
     i18n: i18nextInstance,
   };
   };
 }
 }

+ 4 - 4
apps/app/src/server/util/locale-utils.ts

@@ -1,8 +1,8 @@
 import type { IncomingHttpHeaders } from 'http';
 import type { IncomingHttpHeaders } from 'http';
 
 
-import { Lang } from '@growi/core';
+import { Lang } from '@growi/core/dist/interfaces';
 
 
-import { defaultLang } from '^/config/i18next.config';
+import * as i18nextConfig from '^/config/i18next.config';
 
 
 const ACCEPT_LANG_MAP = {
 const ACCEPT_LANG_MAP = {
   en: Lang.en_US,
   en: Lang.en_US,
@@ -20,7 +20,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 defaultLang;
+  return i18nextConfig.defaultLang;
 };
 };
 
 
 /**
 /**
@@ -33,7 +33,7 @@ export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeade
   const acceptLanguages = headers['accept-language'];
   const acceptLanguages = headers['accept-language'];
 
 
   if (acceptLanguages == null) {
   if (acceptLanguages == null) {
-    return defaultLang;
+    return i18nextConfig.defaultLang;
   }
   }
 
 
   // 1. trim blank spaces.
   // 1. trim blank spaces.

+ 1 - 1
packages/editor/package.json

@@ -60,7 +60,7 @@
     "csv-to-markdown-table": "^1.4.1",
     "csv-to-markdown-table": "^1.4.1",
     "emoji-mart": "^5.6.0",
     "emoji-mart": "^5.6.0",
     "eslint-plugin-react-refresh": "^0.4.1",
     "eslint-plugin-react-refresh": "^0.4.1",
-    "i18next": "^23.11.5",
+    "i18next": "^23.16.5",
     "lib0": "^0.2.94",
     "lib0": "^0.2.94",
     "markdown-table": "^3.0.3",
     "markdown-table": "^3.0.3",
     "react-dropzone": "^14.2.3",
     "react-dropzone": "^14.2.3",

+ 34 - 34
pnpm-lock.yaml

@@ -388,8 +388,8 @@ importers:
         specifier: ^2.0.0
         specifier: ^2.0.0
         version: 2.0.0
         version: 2.0.0
       i18next:
       i18next:
-        specifier: ^23.10.1
-        version: 23.11.5
+        specifier: ^23.16.5
+        version: 23.16.5
       i18next-resources-to-backend:
       i18next-resources-to-backend:
         specifier: ^1.2.1
         specifier: ^1.2.1
         version: 1.2.1
         version: 1.2.1
@@ -478,8 +478,8 @@ importers:
         specifier: ^0.1.1
         specifier: ^0.1.1
         version: 0.1.1(react@18.2.0)
         version: 0.1.1(react@18.2.0)
       next-i18next:
       next-i18next:
-        specifier: ^15.2.0
-        version: 15.3.0(i18next@23.11.5)(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
+        specifier: ^15.3.1
+        version: 15.3.1(i18next@23.16.5)(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
       next-superjson:
       next-superjson:
         specifier: ^0.0.4
         specifier: ^0.0.4
         version: 0.0.4(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.5.25(@swc/helpers@0.5.11)))
         version: 0.0.4(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.5.25(@swc/helpers@0.5.11)))
@@ -556,8 +556,8 @@ importers:
         specifier: ^3.1.4
         specifier: ^3.1.4
         version: 3.1.4(react@18.2.0)
         version: 3.1.4(react@18.2.0)
       react-i18next:
       react-i18next:
-        specifier: ^14.1.0
-        version: 14.1.2(i18next@23.11.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+        specifier: ^15.1.1
+        version: 15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       react-image-crop:
       react-image-crop:
         specifier: ^8.3.0
         specifier: ^8.3.0
         version: 8.6.12(react@18.2.0)
         version: 8.6.12(react@18.2.0)
@@ -827,11 +827,11 @@ importers:
         specifier: ^4.6.2
         specifier: ^4.6.2
         version: 4.6.2
         version: 4.6.2
       i18next-hmr:
       i18next-hmr:
-        specifier: ^3.0.4
-        version: 3.1.2
+        specifier: ^3.1.3
+        version: 3.1.3
       i18next-http-backend:
       i18next-http-backend:
-        specifier: ^2.5.0
-        version: 2.5.2(encoding@0.1.13)
+        specifier: ^2.6.2
+        version: 2.6.2(encoding@0.1.13)
       i18next-localstorage-backend:
       i18next-localstorage-backend:
         specifier: ^4.2.0
         specifier: ^4.2.0
         version: 4.2.0
         version: 4.2.0
@@ -1176,8 +1176,8 @@ importers:
         specifier: ^0.4.1
         specifier: ^0.4.1
         version: 0.4.7(eslint@8.41.0)
         version: 0.4.7(eslint@8.41.0)
       i18next:
       i18next:
-        specifier: ^23.11.5
-        version: 23.11.5
+        specifier: ^23.16.5
+        version: 23.16.5
       react-dropzone:
       react-dropzone:
         specifier: ^14.2.3
         specifier: ^14.2.3
         version: 14.2.3(react@18.2.0)
         version: 14.2.3(react@18.2.0)
@@ -7375,14 +7375,14 @@ packages:
   i18next-chained-backend@4.6.2:
   i18next-chained-backend@4.6.2:
     resolution: {integrity: sha512-2P092fR+nAPQlGzPUoIIxbwo7PTBqQYgLxwv1XhSTQUAUoelLo5LkX+FqRxxSDg9WEAsrc8+2WL6mJtMGIa6WQ==}
     resolution: {integrity: sha512-2P092fR+nAPQlGzPUoIIxbwo7PTBqQYgLxwv1XhSTQUAUoelLo5LkX+FqRxxSDg9WEAsrc8+2WL6mJtMGIa6WQ==}
 
 
-  i18next-fs-backend@2.3.1:
-    resolution: {integrity: sha512-tvfXskmG/9o+TJ5Fxu54sSO5OkY6d+uMn+K6JiUGLJrwxAVfer+8V3nU8jq3ts9Pe5lXJv4b1N7foIjJ8Iy2Gg==}
+  i18next-fs-backend@2.3.2:
+    resolution: {integrity: sha512-LIwUlkqDZnUI8lnUxBnEj8K/FrHQTT/Sc+1rvDm9E8YvvY5YxzoEAASNx+W5M9DfD5s77lI5vSAFWeTp26B/3Q==}
 
 
-  i18next-hmr@3.1.2:
-    resolution: {integrity: sha512-N5MDJXH3habVh9rwr+1OvMug/Eo7HAQ2N0q5gtR3xRFBjMoqgpocgHj10KJoqDGy7axdzEhqseQGQtZUBjpmDA==}
+  i18next-hmr@3.1.3:
+    resolution: {integrity: sha512-zoM4B6toVk48rAMl0t9eV+ldEq9HIO9+bek8H1aGSLQZAjPSBQCUggkxdk0vQjEWSKLsssxZqZBAWS+Ow1rcsA==}
 
 
-  i18next-http-backend@2.5.2:
-    resolution: {integrity: sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==}
+  i18next-http-backend@2.6.2:
+    resolution: {integrity: sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==}
 
 
   i18next-localstorage-backend@4.2.0:
   i18next-localstorage-backend@4.2.0:
     resolution: {integrity: sha512-vglEQF0AnLriX7dLA2drHnqAYzHxnLwWQzBDw8YxcIDjOvYZz5rvpal59Dq4In+IHNmGNM32YgF0TDjBT0fHmA==}
     resolution: {integrity: sha512-vglEQF0AnLriX7dLA2drHnqAYzHxnLwWQzBDw8YxcIDjOvYZz5rvpal59Dq4In+IHNmGNM32YgF0TDjBT0fHmA==}
@@ -7390,8 +7390,8 @@ packages:
   i18next-resources-to-backend@1.2.1:
   i18next-resources-to-backend@1.2.1:
     resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==}
     resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==}
 
 
-  i18next@23.11.5:
-    resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==}
+  i18next@23.16.5:
+    resolution: {integrity: sha512-KTlhE3EP9x6pPTAW7dy0WKIhoCpfOGhRQlO+jttQLgzVaoOjWwBWramu7Pp0i+8wDNduuzXfe3kkVbzrKyrbTA==}
 
 
   iconv-lite@0.4.24:
   iconv-lite@0.4.24:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
@@ -9008,8 +9008,8 @@ packages:
     peerDependencies:
     peerDependencies:
       react: '>= 16.9.0'
       react: '>= 16.9.0'
 
 
-  next-i18next@15.3.0:
-    resolution: {integrity: sha512-bq7Cc9XJFcmGOCLnyEtHaeJ3+JJNsI/8Pkj9BaHAnhm4sZ9vNNC4ZsaqYnlRZ7VH5ypSo73fEqLK935jLsmCvQ==}
+  next-i18next@15.3.1:
+    resolution: {integrity: sha512-+pa2pZJb7B6k5PKW3TLVMmAodqkNaOBWVYlpWX56mgcEJz0UMW+MKSdKM9Z72CHp6Bp48g7OWwDnLqxXNp/84w==}
     engines: {node: '>=14'}
     engines: {node: '>=14'}
     peerDependencies:
     peerDependencies:
       i18next: '>= 23.7.13'
       i18next: '>= 23.7.13'
@@ -9914,8 +9914,8 @@ packages:
     peerDependencies:
     peerDependencies:
       react: '>= 0.14.0'
       react: '>= 0.14.0'
 
 
-  react-i18next@14.1.2:
-    resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==}
+  react-i18next@15.1.1:
+    resolution: {integrity: sha512-R/Vg9wIli2P3FfeI8o1eNJUJue5LWpFsQePCHdQDmX0Co3zkr6kdT8gAseb/yGeWbNz1Txc4bKDQuZYsC0kQfw==}
     peerDependencies:
     peerDependencies:
       i18next: '>= 23.2.3'
       i18next: '>= 23.2.3'
       react: '>= 16.8.0'
       react: '>= 16.8.0'
@@ -20089,11 +20089,11 @@ snapshots:
     dependencies:
     dependencies:
       '@babel/runtime': 7.25.4
       '@babel/runtime': 7.25.4
 
 
-  i18next-fs-backend@2.3.1: {}
+  i18next-fs-backend@2.3.2: {}
 
 
-  i18next-hmr@3.1.2: {}
+  i18next-hmr@3.1.3: {}
 
 
-  i18next-http-backend@2.5.2(encoding@0.1.13):
+  i18next-http-backend@2.6.2(encoding@0.1.13):
     dependencies:
     dependencies:
       cross-fetch: 4.0.0(encoding@0.1.13)
       cross-fetch: 4.0.0(encoding@0.1.13)
     transitivePeerDependencies:
     transitivePeerDependencies:
@@ -20107,7 +20107,7 @@ snapshots:
     dependencies:
     dependencies:
       '@babel/runtime': 7.25.4
       '@babel/runtime': 7.25.4
 
 
-  i18next@23.11.5:
+  i18next@23.16.5:
     dependencies:
     dependencies:
       '@babel/runtime': 7.25.4
       '@babel/runtime': 7.25.4
 
 
@@ -22198,17 +22198,17 @@ snapshots:
     dependencies:
     dependencies:
       react: 18.2.0
       react: 18.2.0
 
 
-  next-i18next@15.3.0(i18next@23.11.5)(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
+  next-i18next@15.3.1(i18next@23.16.5)(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
     dependencies:
     dependencies:
       '@babel/runtime': 7.25.4
       '@babel/runtime': 7.25.4
       '@types/hoist-non-react-statics': 3.3.5
       '@types/hoist-non-react-statics': 3.3.5
       core-js: 3.37.1
       core-js: 3.37.1
       hoist-non-react-statics: 3.3.2
       hoist-non-react-statics: 3.3.2
-      i18next: 23.11.5
-      i18next-fs-backend: 2.3.1
+      i18next: 23.16.5
+      i18next-fs-backend: 2.3.2
       next: 14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       next: 14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       react: 18.2.0
       react: 18.2.0
-      react-i18next: 14.1.2(i18next@23.11.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react-i18next: 15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
 
 
   next-superjson@0.0.4(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.5.25(@swc/helpers@0.5.11))):
   next-superjson@0.0.4(next@14.2.13(@babel/core@7.24.6)(@playwright/test@1.46.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.5.25(@swc/helpers@0.5.11))):
     dependencies:
     dependencies:
@@ -23137,11 +23137,11 @@ snapshots:
       prop-types: 15.8.1
       prop-types: 15.8.1
       react: 18.2.0
       react: 18.2.0
 
 
-  react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+  react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
     dependencies:
     dependencies:
       '@babel/runtime': 7.25.4
       '@babel/runtime': 7.25.4
       html-parse-stringify: 3.0.1
       html-parse-stringify: 3.0.1
-      i18next: 23.11.5
+      i18next: 23.16.5
       react: 18.2.0
       react: 18.2.0
     optionalDependencies:
     optionalDependencies:
       react-dom: 18.2.0(react@18.2.0)
       react-dom: 18.2.0(react@18.2.0)