docs/guides/i18n.md
[!WARNING] This document is machine translated from Chinese. While we strive for accuracy, there may be some imperfections in the translation.
i18n Ally is a powerful VSCode extension that provides real-time feedback during development, helping developers detect missing or incorrect translations earlier.
The plugin has already been configured in the project — simply install it to get started.
Never use flat structures like "add.button.tip": "Add". Instead, adopt a clear nested structure:
// Wrong - Flat structure
{
"add.button.tip": "Add",
"delete.button.tip": "Delete"
}
// Correct - Nested structure
{
"add": {
"button": {
"tip": "Add"
}
},
"delete": {
"button": {
"tip": "Delete"
}
}
}
t()We strongly advise against using template strings for dynamic interpolation. While convenient in general JavaScript development, they cause several issues in i18n scenarios.
Tools like i18n Ally cannot parse dynamic content within template strings, resulting in:
// Not recommended - Plugin cannot resolve
const message = t(`fruits.${fruit}`);
Template strings appear as raw code instead of the final translated text in IDEs, degrading the development experience.
Since the plugin cannot track such usages, developers must manually verify the existence of corresponding keys in language files.
To avoid missing keys, all dynamically translated texts should first maintain a FooKeyMap, then retrieve the translation text through a function.
For example:
// src/renderer/i18n/label.ts
const themeModeKeyMap = {
dark: "settings.theme.dark",
light: "settings.theme.light",
system: "settings.theme.system",
} as const;
export const getThemeModeLabel = (key: string): string => {
return themeModeKeyMap[key] ? t(themeModeKeyMap[key]) : key;
};
By avoiding template strings, you gain better developer experience, more reliable translation checks, and a more maintainable codebase.
The project includes several scripts to automate i18n-related tasks:
i18n:check - Validate i18n StructureThis script checks:
pnpm i18n:check
i18n:sync - Synchronize JSON Structure and Sort OrderThis script uses zh-cn.json as the source of truth to sync structure across all language files, including:
[to be translated]pnpm i18n:sync
i18n:unused - Find Unused KeysThis script scans the codebase for i18n key references and reports keys that are present in zh-cn.json but not found in source code.
This command only prints a report and does not modify any files:
pnpm i18n:unused
The report includes:
For machine-readable output, use JSON mode:
pnpm i18n:unused --json
Use i18n:remove-unused when you want to delete unused keys. Cleaning is opt-in and only runs through this remove command.
Run interactive cleanup:
pnpm i18n:remove-unused
The prompt lists top-level namespaces, such as common, settings, or translate. Select one or more namespaces to delete only the unused leaf keys in those groups.
Run non-interactive cleanup for specific namespaces:
pnpm i18n:remove-unused --groups common,settings
Run non-interactive cleanup for all unused keys:
pnpm i18n:remove-unused --all
Cleanup updates both directories:
src/renderer/i18n/locales/*.jsonsrc/renderer/i18n/translate/*.jsonAfter deletion, the script prunes empty objects and sorts keys to keep the files consistent with the existing i18n format.
The scanner recognizes common static i18n patterns:
t("key") and i18n.t("key")<Trans i18nKey="key" />titleKey, labelKey, descriptionKey, messageKey, and i18nKeysrc/renderer/i18n/label.tst("key"), which keeps i18n Ally-style explicit references validSHORTCUT_DEFINITIONSt(condition ? "a.key" : "b.key")The exact text match is intentionally conservative: if a complete key string appears in source code, the key is treated as used. This may keep a few truly unused keys, but it avoids deleting keys that are referenced through helper maps, indirect calls, or dynamic code paths.
pnpm i18n:unused and review the grouped report.pnpm i18n:remove-unused --groups <namespace>, or use pnpm i18n:remove-unused --all when the full report has already been reviewed.pnpm i18n:check after cleanup.i18n:translate - Automatically Translate Pending TextsThis script fills in texts marked as [to be translated] by calling an OpenAI-compatible endpoint.
Typically, after adding new texts in zh-cn.json, run i18n:sync, then i18n:translate to complete translations.
Source text always comes from the base locale by full key path, and the [to be translated] marker never enters model input, so nothing can echo it or retranslate an already-translated string.
Each locale gets one batched request carrying the full key path, the zh-cn reference, the glossary, and a handful of already-translated strings from the same namespaces as style examples. Those examples are what keep the register consistent — without them a model writes German in the informal du form, while the catalog is 511:3 formal.
Every reply is then validated deterministically. A translation that loses an interpolation variable, drops a <Trans> component tag such as <provider>, carries a bracketed note or echoes the placeholder marker is discarded and its [to be translated] placeholder kept, so the next run retries it. The script exits non-zero when anything was discarded.
Set TRANSLATION_API_KEY (and optionally TRANSLATION_BASE_URL / TRANSLATION_MODEL) in your environment or .env.
pnpm i18n:translate # every locale
pnpm i18n:translate --locale ja-jp --dry-run # one locale, print results without writing
Strings are sent in batches of up to 200 (I18N_BATCH_SIZE). A larger batch is cheaper and faster because the prompt, glossary and style examples are not repeated per request; 400 strings took 289 s in batches of 50 and 118 s in batches of 200, with identical completeness.
An agentic variant that read the codebase for each key's UI context was measured against this pipeline and removed — it cost roughly 50× more for no quality gain that survived scrutiny. See PR #18510 if that comes up again.
Terminology lives in scripts/i18n-glossary.json and is maintained by hand:
doNotTranslate — product and protocol names that must survive verbatim. Enforced by the validator.terms — preferred translation per locale, plus a note disambiguating the English (for example Agent vs Assistant). Supplied to the model as guidance; not enforced, because most languages inflect these words.Add an entry whenever a term is being translated inconsistently or the English is ambiguous out of context.
zh-cn.jsonpnpm i18n:sync to propagate the keys to other language filespnpm i18n:translate to perform machine translationpnpm i18n:check to catch i18n issues early.user.profile.avatar.upload.error