Rebaseline the i18n hardcoded-string lint guardrail for shipping dashboard surfaces. - Add scoped i18n lint ignores for non-shipping tests, stories, and deferred source-copy files with follow-up rationale. - Localize remaining plugin/view copy and replace technical glyph literals that should not be linted as prose. - Refresh synced locale catalogs and generated resource types, and document the lint baseline policy. Files changed: .changeset/fn-6749-i18n-lint-rebaseline.md | 5 + docs/i18n-contributing.md | 14 + i18next.config.ts | 96 ++ .../dashboard/app/components/AgentDetailView.tsx | 4 +- .../app/components/ConversationHistory.tsx | 4 +- .../app/components/DockerNodeOnboardingModal.tsx | 8 +- .../components/MilestoneSliceInterviewModal.tsx | 4 +- .../app/components/MissionInterviewModal.tsx | 4 +- .../app/components/ModelSelectionModal.tsx | 4 +- .../dashboard/app/components/NodeDetailModal.tsx | 15 +- .../dashboard/app/components/PlanningModeModal.tsx | 4 +- .../dashboard/app/components/SessionTerminal.tsx | 15 +- .../app/components/SubtaskBreakdownModal.tsx | 4 +- packages/dashboard/app/components/TaskForm.tsx | 4 +- .../dashboard/app/components/TerminalModal.tsx | 18 +- .../dashboard/app/plugins/pluginViewRegistry.tsx | 7 +- packages/i18n/locales/en/app.json | 890 +++++++++--- packages/i18n/locales/en/common.json | 82 +- packages/i18n/locales/es/app.json | 986 ++++++++++--- packages/i18n/locales/es/common.json | 82 +- packages/i18n/locales/fr/app.json | 986 ++++++++++--- packages/i18n/locales/fr/common.json | 82 +- packages/i18n/locales/ko/app.json | 1482 ++++++++++++++------ packages/i18n/locales/ko/cli.json | 8 +- packages/i18n/locales/ko/common.json | 82 +- packages/i18n/locales/zh-CN/app.json | 1482 ++++++++++++++------ packages/i18n/locales/zh-CN/cli.json | 8 +- packages/i18n/locales/zh-CN/common.json | 82 +- packages/i18n/locales/zh-TW/app.json | 1482 ++++++++++++++------ packages/i18n/locales/zh-TW/cli.json | 8 +- packages/i18n/locales/zh-TW/common.json | 82 +- packages/i18n/src/resources.d.ts | 459 +++--- 32 files changed, 6055 insertions(+), 2438 deletions(-) Fusion-Task-Id: FN-6749 Fusion-Task-Lineage: 789a7cbb-fdf8-4190-8cd2-6bf7d5e2d552
3.8 KiB
Localization (i18n) contributor guide
Fusion's UI is localized with react-i18next. English (en) is the
source-of-truth language; everything else is a translation of it. Both UI
surfaces — the React dashboard and the Ink terminal UI — share one set of
catalogs and config in the @fusion/i18n package.
Where things live
| Path | What it is |
|---|---|
packages/i18n/locales/{locale}/{namespace}.json |
Authored catalogs (translators edit here). en is the source. |
packages/i18n/src/config.ts |
Shared i18next config: namespaces, fallback chain, plural setup. |
packages/core (SUPPORTED_LOCALES, Locale) |
The single list of supported locale codes. |
i18next.config.ts (repo root) |
i18next-cli workflow config. |
Namespaces: common (shared), app (dashboard-only), errors, and cli
(terminal-only). The dashboard loads common/app/errors; the CLI loads
common/cli/errors.
The workflow
All commands run from the repo root:
pnpm i18n:extract # pull t()/<Trans> keys from source into the en catalogs
pnpm i18n:sync # propagate the en key structure to every other locale
pnpm i18n:types # regenerate key types from the en catalogs
pnpm i18n:status # per-locale completion report
pnpm i18n:lint # flag hardcoded user-facing strings
pnpm i18n:gen-cli # regenerate the CLI static catalog import map
Lint baseline policy
pnpm i18n:lint is the hardcoded user-facing string guardrail and must stay
green. Its file scope intentionally matches extraction: tests and stories are
excluded because they are non-shipping fixtures and extract already ignores
them. Suppress non-translatable token categories with narrow
lint.ignoredTags / lint.ignoredAttributes entries, such as keyboard-key
content in <kbd>, instead of hiding source directories.
Any remaining user-facing copy must be localized with t() / <Trans> and an
en catalog entry. A temporary deferral is only acceptable when it is scoped to
specific files or a small cluster in lint.ignore, includes an FNXC rationale,
and has a filed follow-up task that removes the ignore.
Translating an existing language
- Run
pnpm i18n:syncso every catalog has the currentenkeys (untranslated entries are empty strings). - Fill the empty strings in
packages/i18n/locales/{locale}/*.json. - Keep interpolation placeholders verbatim:
{{brand}},{{detail}},{{key}}. Never translate a[{{key}}]keybinding accelerator — only the words around it. pnpm i18n:statusto confirm the locale is complete.
zh-CN and zh-TW are independent — different script and vocabulary. Do
not machine-convert one into the other.
Adding a new language (near-zero code)
- Add the locale code to
SUPPORTED_LOCALESinpackages/core/src/types.tsand tolocalesini18next.config.ts. pnpm i18n:sync— scaffolds a full set of catalog files for the new locale with the correct plural categories.pnpm i18n:gen-cli— adds the locale to the CLI's static import map.- Translate the new catalogs, then
pnpm i18n:statusto verify.
No feature code changes are required: the dashboard discovers the locale through
the generated app/locales/ tree, and the CLI through the regenerated import
map. Add the language's endonym to ENDONYMS in
packages/dashboard/app/components/LanguageSelector.tsx so it appears in the
Settings switcher.
Using a non-English locale
-
Dashboard / mobile: Settings → Appearance → Language. The choice persists to
localStorageand to server settings. -
Terminal UI: resolved from
--lang <code>→ the saved dashboard language setting → theLC_ALL/LC_MESSAGES/LANG/LANGUAGEenvironment →en.fusion dashboard --lang zh-TW