From 017274faea2f4093fd0a2f4b11160bd3c4c671ab Mon Sep 17 00:00:00 2001 From: yyh Date: Thu, 25 Dec 2025 17:42:07 +0800 Subject: [PATCH] Update i18n configuration to use JSON files for translations and improve documentation --- .../translate-i18n-base-on-english.yml | 6 +- web/i18n-config/DEV.md | 13 ++-- web/i18n-config/README.md | 71 ++++++++----------- web/i18n-config/i18next-config.ts | 2 +- web/i18n-config/server.ts | 1 + 5 files changed, 43 insertions(+), 50 deletions(-) diff --git a/.github/workflows/translate-i18n-base-on-english.yml b/.github/workflows/translate-i18n-base-on-english.yml index 87e24a4f90..76d26b7568 100644 --- a/.github/workflows/translate-i18n-base-on-english.yml +++ b/.github/workflows/translate-i18n-base-on-english.yml @@ -4,7 +4,7 @@ on: push: branches: [main] paths: - - 'web/i18n/en-US/*.ts' + - 'web/i18n/en-US/*.json' permissions: contents: write @@ -28,13 +28,13 @@ jobs: run: | git fetch origin "${{ github.event.before }}" || true git fetch origin "${{ github.sha }}" || true - changed_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'i18n/en-US/*.ts') + changed_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" -- 'i18n/en-US/*.json') echo "Changed files: $changed_files" if [ -n "$changed_files" ]; then echo "FILES_CHANGED=true" >> $GITHUB_ENV file_args="" for file in $changed_files; do - filename=$(basename "$file" .ts) + filename=$(basename "$file" .json) file_args="$file_args --file $filename" done echo "FILE_ARGS=$file_args" >> $GITHUB_ENV diff --git a/web/i18n-config/DEV.md b/web/i18n-config/DEV.md index 4c8c23d51b..c40591a9e3 100644 --- a/web/i18n-config/DEV.md +++ b/web/i18n-config/DEV.md @@ -23,7 +23,7 @@ - `setLocaleOnClient` - `changeLanguage` (defined in i18n/i18next-config, also init i18n resources (side effects)) - is `i18next.changeLanguage` - - all languages text is merge & load in FrontEnd as .js (see i18n/i18next-config) + - loads JSON namespaces for the target locale and merges resource bundles (see i18n/i18next-config) - i18n context - `locale` - current locale code (ex `eu-US`, `zh-Hans`) - `i18n` - useless @@ -32,13 +32,16 @@ ### load i18n resources - client: i18n/i18next-config.ts - - ns = camalCase(filename) + - ns = camelCase(filename) (app-debug -> appDebug) + - keys are flat (dot notation); `keySeparator: false` - ex: `app/components/datasets/create/embedding-process/index.tsx` - - `t('datasetSettings.form.retrievalSetting.title')` + - `const { t } = useTranslation('datasetSettings')` + - `t('form.retrievalSetting.title')` - server: i18n/server.ts - - ns = filename + - ns = filename (kebab-case) mapped to camelCase namespace - ex: `app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx` - - `translate(locale, 'dataset-settings')` + - `const { t } = await getTranslation(locale, 'dataset-settings')` + - `t('form.retrievalSetting.title')` ## TODO diff --git a/web/i18n-config/README.md b/web/i18n-config/README.md index c724d94aa7..c9b9e92fa0 100644 --- a/web/i18n-config/README.md +++ b/web/i18n-config/README.md @@ -2,43 +2,34 @@ ## Introduction -This directory contains the internationalization (i18n) files for this project. +This directory contains i18n tooling and configuration. Translation files live under `web/i18n`. ## File Structure ``` -├── [ 24] README.md -├── [ 704] en-US -│   ├── [2.4K] app-annotation.ts -│   ├── [5.2K] app-api.ts -│   ├── [ 16K] app-debug.ts -│   ├── [2.1K] app-log.ts -│   ├── [5.3K] app-overview.ts -│   ├── [1.9K] app.ts -│   ├── [4.1K] billing.ts -│   ├── [ 17K] common.ts -│   ├── [ 859] custom.ts -│   ├── [5.7K] dataset-creation.ts -│   ├── [ 10K] dataset-documents.ts -│   ├── [ 761] dataset-hit-testing.ts -│   ├── [1.7K] dataset-settings.ts -│   ├── [2.0K] dataset.ts -│   ├── [ 941] explore.ts -│   ├── [ 52] layout.ts -│   ├── [2.3K] login.ts -│   ├── [ 52] register.ts -│   ├── [2.5K] share.ts -│   └── [2.8K] tools.ts -├── [1.6K] i18next-config.ts -├── [ 634] index.ts -├── [4.4K] language.ts +web/i18n +├── en-US +│ ├── app.json +│ ├── app-debug.json +│ ├── common.json +│ └── ... +└── zh-Hans + └── ... + +web/i18n-config +├── auto-gen-i18n.js +├── check-i18n.js +├── i18next-config.ts +└── ... ``` -We use English as the default language. The i18n files are organized by language and then by module. For example, the English translation for the `app` module is in `en-US/app.ts`. +We use English as the default language. Translation files are organized by language and then by module. For example, the English translation for the `app` module is in `web/i18n/en-US/app.json`. -If you want to add a new language or modify an existing translation, you can create a new file for the language or modify the existing file. The file name should be the language code (e.g., `zh-Hans` for Chinese) and the file extension should be `.ts`. +Translation files are JSON with flat keys (dot notation). i18next is configured with `keySeparator: false`, so dots are part of the key. The namespace is the camelCase file name (for example, `app-debug.json` -> `appDebug`), so use `useTranslation('appDebug')` or `t('key', { ns: 'appDebug' })`. -For example, if you want to add french translation, you can create a new folder `fr-FR` and add the translation files in it. +If you want to add a new language or modify an existing translation, create or update the `.json` files in the language folder. + +For example, if you want to add French translation, you can create a new folder `fr-FR` and add the translation files in it. By default we will use `LanguagesSupported` to determine which languages are supported. For example, in login page and settings page, we will use `LanguagesSupported` to determine which languages are supported and display them in the language selection dropdown. @@ -51,13 +42,9 @@ cd web/i18n cp -r en-US id-ID ``` -2. Modify the translation files in the new folder. +2. Modify the translation `.json` files in the new folder. Keep keys flat (for example, `dialog.title`). -1. Add type to new language in the `language.ts` file. - -> Note: `I18nText` type is now automatically derived from `LanguagesSupported`, so you don't need to manually add types. - -4. Add the new language to the `languages.ts` file. +3. Add the new language to the `languages.ts` file. ```typescript export const languages = [ @@ -157,16 +144,18 @@ export const languages = [ ] ``` -5. Don't forget to mark the supported field as `true` if the language is supported. +4. Don't forget to mark the supported field as `true` if the language is supported. -1. Sometime you might need to do some changes in the server side. Please change this file as well. 👇 +5. Sometimes you might need to do some changes in the server side. Please change this file as well. 👇 https://github.com/langgenius/dify/blob/61e4bbabaf2758354db4073cbea09fdd21a5bec1/api/constants/languages.py#L5 +> Note: `I18nText` type is automatically derived from `LanguagesSupported`, so you don't need to manually add types. + ## Clean Up -That's it! You have successfully added a new language to the project. If you want to remove a language, you can simply delete the folder and remove the language from the `language.ts` file. +That's it! You have successfully added a new language to the project. If you want to remove a language, you can simply delete the folder and remove the language from the `languages.ts` file. -We have a list of languages that we support in the `language.ts` file. But some of them are not supported yet. So, they are marked as `false`. If you want to support a language, you can follow the steps above and mark the supported field as `true`. +We have a list of languages that we support in the `languages.ts` file. But some of them are not supported yet. So, they are marked as `false`. If you want to support a language, you can follow the steps above and mark the supported field as `true`. ## Utility scripts @@ -174,6 +163,6 @@ We have a list of languages that we support in the `language.ts` file. But some - Use space-separated values; repeat `--file` / `--lang` as needed. Defaults to all en-US files and all supported locales except en-US. - Protects placeholders (`{{var}}`, `${var}`, ``) before translation and restores them after. - Check missing/extra keys: `pnpm run check-i18n --file app billing --lang zh-Hans [--auto-remove]` - - Use space-separated values; repeat `--file` / `--lang` as needed. Returns non-zero on missing/extra keys (CI will fail); `--auto-remove` deletes extra keys automatically. + - Use space-separated values; repeat `--file` / `--lang` as needed. Returns non-zero on missing/extra keys; `--auto-remove` deletes extra keys automatically. -Workflows: `.github/workflows/translate-i18n-base-on-english.yml` auto-runs the translation generator on en-US changes to main; `.github/workflows/web-tests.yml` checks i18n keys on web changes. +Workflows: `.github/workflows/translate-i18n-base-on-english.yml` auto-runs the translation generator on `web/i18n/en-US/*.json` changes to main. `check-i18n` is a manual script (not run in CI). diff --git a/web/i18n-config/i18next-config.ts b/web/i18n-config/i18next-config.ts index d3a138570b..9bb9f72692 100644 --- a/web/i18n-config/i18next-config.ts +++ b/web/i18n-config/i18next-config.ts @@ -113,7 +113,7 @@ export const loadLangResources = async (lang: Locale) => { ) } -// Initial resources: only load common namespace for en-US +// Initial resources: load en-US namespaces for fallback/default locale const getInitialTranslations = () => { return { 'en-US': namespaces, diff --git a/web/i18n-config/server.ts b/web/i18n-config/server.ts index 61d20e2ee9..cb2519e69a 100644 --- a/web/i18n-config/server.ts +++ b/web/i18n-config/server.ts @@ -22,6 +22,7 @@ const initI18next = async (lng: Locale, ns: NamespaceKebabCase) => { ns, defaultNS: ns, fallbackLng: 'en-US', + keySeparator: false, }) return i18nInstance }