Skip to content

Commit

Permalink
Merge pull request #9379 from weseek/fix/i18n-for-security-settings
Browse files Browse the repository at this point in the history
fix: i18n for security settings
  • Loading branch information
mergify[bot] authored Nov 12, 2024
2 parents e5052ad + 7ef9a2a commit 032d349
Show file tree
Hide file tree
Showing 19 changed files with 104 additions and 78 deletions.
10 changes: 4 additions & 6 deletions apps/app/config/i18next.config.js
Original file line number Diff line number Diff line change
@@ -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;

/** @type {import('i18next').InitOptions} */
Expand All @@ -10,7 +10,5 @@ const initOptions = {
defaultNS: 'translation',
};

module.exports = {
defaultLang,
initOptions,
};
exports.defaultLang = defaultLang;
exports.initOptions = initOptions;
10 changes: 5 additions & 5 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
"hastscript": "^8.0.0",
"helmet": "^4.6.0",
"http-errors": "^2.0.0",
"i18next": "^23.10.1",
"i18next": "^23.16.5",
"i18next-resources-to-backend": "^1.2.1",
"is-absolute-url": "^4.0.1",
"is-iso-date": "^0.0.1",
Expand Down Expand Up @@ -159,7 +159,7 @@
"mustache": "^4.2.0",
"next": "^14.2.13",
"next-dynamic-loading-props": "^0.1.1",
"next-i18next": "^15.2.0",
"next-i18next": "^15.3.1",
"next-superjson": "^0.0.4",
"next-themes": "^0.2.1",
"nocache": "^4.0.0",
Expand All @@ -185,7 +185,7 @@
"react-disable": "^0.1.1",
"react-dom": "^18.2.0",
"react-error-boundary": "^3.1.4",
"react-i18next": "^14.1.0",
"react-i18next": "^15.1.1",
"react-image-crop": "^8.3.0",
"react-markdown": "^9.0.1",
"react-multiline-clamp": "^2.0.0",
Expand Down Expand Up @@ -284,8 +284,8 @@
"handsontable": "=6.2.2",
"happy-dom": "^15.7.4",
"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",
"jest": "^29.5.0",
"jest-date-mock": "^1.0.8",
Expand Down
4 changes: 2 additions & 2 deletions apps/app/public/static/locales/en_US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@
},
"message": {
"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.",
"field_required": "{{target}} is required"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/app/public/static/locales/fr_FR/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@
},
"message": {
"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.",
"field_required": "{{target}} est requis"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/app/public/static/locales/ja_JP/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@
},
"message": {
"error_message": "いくつかの値が設定されていません",
"required": "%sに値を入力してください",
"invalid_syntax": "%sの構文が不正です",
"required": "'{{param}}' に値を入力してください",
"invalid_syntax": "{{syntax}} の構文が不正です",
"title_required": "タイトルを入力してください",
"field_required": "{{target}}に値を入力してください"
}
Expand Down
4 changes: 2 additions & 2 deletions apps/app/public/static/locales/zh_CN/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@
},
"message": {
"error_message": "有些值不正确",
"required": "%s 是必需的",
"invalid_syntax": "%s的语法无效",
"required": "'{{param}}' 是必需的",
"invalid_syntax": "{{syntax}} 的语法无效",
"title_required": "标题是必需的。",
"field_required": "{{target}} 是必需的"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,18 @@ class SamlSecurityManagementContents extends React.Component {

try {
await adminSamlSecurityContainer.updateSamlSetting();
await adminGeneralSecurityContainer.retrieveSetupStratedies();
toastSuccess(t('security_settings.SAML.updated_saml'));
}
catch (err) {
toastError(err);
}

try {
await adminGeneralSecurityContainer.retrieveSetupStratedies();
}
catch (err) {
toastError(err);
}
}

render() {
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/client/components/InstallerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const InstallerForm = memo((props: Props): JSX.Element => {

const tWithOpt = useTWithOpt();

const isSupportedLang = AllLang.includes(i18n.language);
const isSupportedLang = AllLang.includes(i18n.language as Lang);

const [isLoading, setIsLoading] = useState(false);
const [currentLocale, setCurrentLocale] = useState(isSupportedLang ? i18n.language : Lang.en_US);
Expand Down
4 changes: 4 additions & 0 deletions apps/app/src/client/util/apiv3-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ const apiv3ErrorHandler = (_err: any): any[] => {
// extract api errors from general 400 err
const err = _err.response ? _err.response.data.errors : _err;
const errs = toArrayIfNot(err);
const errorInfo = _err.response ? _err.response.data.info : undefined;

for (const err of errs) {
logger.error(err.message);
}
if (errorInfo != null) {
logger.error('additional info:', errorInfo);
}

return errs;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const replaceAnnotationWithPageLink = async(messageContentDelta: MessageC
.populate<{page: Pick<IPageHasId, 'path' | '_id'>}>('page', 'path');

if (vectorStoreFileRelation != null) {
const { t } = await getTranslation(lang);
const { t } = await getTranslation({ lang });
messageContentDelta.text.value = messageContentDelta.text.value?.replace(
annotation.text,
` [${t('source')}: [${vectorStoreFileRelation.page.path}](/${vectorStoreFileRelation.page._id})]`,
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/pages/_app.page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReactElement, ReactNode } 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 { appWithTranslation } from 'next-i18next';
import type { AppContext, AppProps } from 'next/app';
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/pages/_document.page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @next/next/google-font-display */
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 Document, {
Html, Head, Main, NextScript,
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/server/routes/apiv3/app-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ module.exports = (crowi) => {
* description: Succeeded to send test mail for smtp
*/
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 {
await sendTestEmail(req.user.email);
Expand Down
8 changes: 4 additions & 4 deletions apps/app/src/server/routes/apiv3/security-settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ module.exports = (crowi) => {
* $ref: '#/components/schemas/SamlAuthSetting'
*/
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,
// check whether it from the environment variables is empty and form value to update it is empty
Expand All @@ -942,8 +942,8 @@ module.exports = (crowi) => {
const key = configKey.replace('security:passport-saml:', '');
const formValue = req.body[key];
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) {
Expand All @@ -958,7 +958,7 @@ module.exports = (crowi) => {
crowi.passportService.parseABLCRule(rule);
}
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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/server/routes/login-passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ module.exports = function(crowi, app) {
* @param {*} res
*/
const testLdapCredentials = async(req, res) => {
const { t } = await getTranslation(req.user.lang);
const { t } = await getTranslation({ lang: req.user.lang });

if (!passportService.isLdapStrategySetup) {
logger.debug('LdapStrategy has not been set up');
Expand Down
36 changes: 27 additions & 9 deletions apps/app/src/server/service/i18next.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import path from 'path';

import type { Lang } from '@growi/core';
import type { TFunction, i18n } from 'i18next';
import type { InitOptions, TFunction, i18n } from 'i18next';
import { createInstance } from 'i18next';
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';

Expand All @@ -14,7 +14,7 @@ import { configManager } from './config-manager';

const relativePathToLocalesRoot = path.relative(__dirname, resolveFromRoot('public/static/locales'));

const initI18next = async(fallbackLng: Lang[] = [defaultLang]) => {
const initI18next = async(overwriteOpts: InitOptions) => {
const i18nInstance = createInstance();
await i18nInstance
.use(
Expand All @@ -25,8 +25,8 @@ const initI18next = async(fallbackLng: Lang[] = [defaultLang]) => {
),
)
.init({
...initOptions,
fallbackLng,
...i18nextConfig.initOptions,
...overwriteOpts,
});
return i18nInstance;
};
Expand All @@ -36,13 +36,31 @@ type Translation = {
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 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 {
t: i18nextInstance.getFixedT(fixedLang),
t: i18nextInstance.getFixedT(fixedLang, opts?.ns),
i18n: i18nextInstance,
};
}
8 changes: 4 additions & 4 deletions apps/app/src/server/util/locale-utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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 = {
en: Lang.en_US,
Expand All @@ -20,7 +20,7 @@ const getPreferredLanguage = (sortedAcceptLanguagesArray: string[]): Lang => {
const matchingLang = Object.keys(ACCEPT_LANG_MAP).find(key => lang.includes(key));
if (matchingLang) return ACCEPT_LANG_MAP[matchingLang];
}
return defaultLang;
return i18nextConfig.defaultLang;
};

/**
Expand All @@ -33,7 +33,7 @@ export const detectLocaleFromBrowserAcceptLanguage = (headers: IncomingHttpHeade
const acceptLanguages = headers['accept-language'];

if (acceptLanguages == null) {
return defaultLang;
return i18nextConfig.defaultLang;
}

// 1. trim blank spaces.
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"csv-to-markdown-table": "^1.4.1",
"emoji-mart": "^5.6.0",
"eslint-plugin-react-refresh": "^0.4.1",
"i18next": "^23.11.5",
"i18next": "^23.16.5",
"lib0": "^0.2.94",
"markdown-table": "^3.0.3",
"react-dropzone": "^14.2.3",
Expand Down
Loading

0 comments on commit 032d349

Please sign in to comment.