Add a production i18n lint baseline while replacing dashboard copy with translation keys. - Localize task, board, mailbox, report, graph, settings, and shared dashboard surfaces. - Regenerate locale catalogs and resource types for the migration keys. - Add the i18n lint-baseline regression test and contributor guidance. Files changed: .changeset/fn-013-i18n-lint-baseline.md | 7 + docs/i18n-contributing.md | 11 + .../dashboard/app/components/ActiveAgentsPanel.tsx | 2 +- .../dashboard/app/components/AgentDetailView.tsx | 2 +- packages/dashboard/app/components/Board.tsx | 19 +- packages/dashboard/app/components/ChatView.tsx | 2 +- .../dashboard/app/components/ComposeChatPanel.tsx | 14 +- packages/dashboard/app/components/DockTaskList.tsx | 16 +- .../dashboard/app/components/FloatingWindow.tsx | 6 +- .../dashboard/app/components/GitHubImportModal.tsx | 4 +- .../dashboard/app/components/KnowledgeGraphPanel.tsx | 21 +- packages/dashboard/app/components/ListView.tsx | 2 +- .../app/components/MailboxArtifactAttachment.tsx | 16 +- packages/dashboard/app/components/MailboxModal.tsx | 12 +- .../app/components/MailboxStructuralItem.tsx | 13 +- .../app/components/MailboxTaskProposal.tsx | 10 +- .../app/components/MailboxTaskRecommendations.tsx | 12 +- packages/dashboard/app/components/MailboxView.tsx | 14 +- packages/dashboard/app/components/MermaidDiagram.tsx | 4 +- packages/dashboard/app/components/MeshTopology.tsx | 6 +- .../dashboard/app/components/MessageComposer.tsx | 24 +- .../app/components/NativeStructurePreview.tsx | 14 +- packages/dashboard/app/components/ProviderLoginDialog.tsx | 24 +- packages/dashboard/app/components/ReportActionMenu.tsx | 6 +- packages/dashboard/app/components/ReportModal.tsx | 26 +- packages/dashboard/app/components/TaskChatTab.tsx | 10 +- packages/dashboard/app/components/TaskDetailModal.tsx | 28 +- .../app/components/TaskVerificationStatus.tsx | 10 +- .../dashboard/app/components/WhatsAppChatPairingPanel.tsx | 38 +- .../components/command-center/CommandCenter.tsx | 4 +- .../components/command-center/IdeationPanel.tsx | 12 +- .../components/settings/sections/McpServersCard.tsx | 2 +- .../components/settings/sections/MemorySection.tsx | 4 +- .../app/task-modal-touch-resize-e2e-fixture.tsx | 28 +- packages/i18n/locales/en/app.json | 247 +++++++- packages/i18n/locales/es/app.json | 256 +++++++- packages/i18n/locales/fr/app.json | 256 +++++++- packages/i18n/locales/ko/app.json | 256 +++++++- packages/i18n/locales/pt-BR/app.json | 705 ++++++++++++++++++++ packages/i18n/locales/zh-CN/app.json | 256 +++++++- packages/i18n/locales/zh-TW/app.json | 256 +++++++- .../i18n/src/__tests__/i18n-lint-baseline.test.ts | 31 + packages/i18n/src/resources.d.ts | 229 ++++++- 43 files changed, 2627 insertions(+), 288 deletions(-) Fusion-Task-Id: FN-013 Fusion-Task-Lineage: 846ac221-c7c3-4ff0-a30d-69479b614765 Co-authored-by: Fusion <noreply@runfusion.ai>
5.9 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 # key-parity gate: structure only, empty values allowed
pnpm i18n:status:report # upstream completeness report (informational; may exit non-zero)
pnpm i18n:lint # flag hardcoded user-facing strings
pnpm i18n:gen-cli # regenerate the CLI static catalog import map
pnpm i18n:status must be green before landing catalog or extraction changes,
but it only verifies that every secondary catalog has the same structural keys as
en. Empty secondary-locale values ("") are expected placeholders and do not
fail the gate because runtime falls back to English. Use
pnpm i18n:status:report when you want the upstream completeness report that
counts empty placeholders as untranslated; that report is informational and may
exit non-zero until translations are filled.
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. The settings sections
cluster is no longer deferred as of FN-6771; keep those files covered by lint.
The production catalog currently has a clean lint baseline: pnpm i18n:lint
must finish with No issues found.. Keep the executable regression alongside the
catalog tests so future shipping copy cannot reintroduce debt:
pnpm --filter @fusion/i18n exec vitest run src/__tests__/i18n-lint-baseline.test.ts src/__tests__/i18n-gate-coverage.test.ts --silent=passed-only --reporter=dot
i18n-lint-baseline.test.ts imports the root i18next.config.ts and calls the
installed runLinter API directly. It therefore checks the same production
inputs as the CLI rather than relying on a mocked catalog or a source-text count.
The @fusion/i18n regression tests also assert the lint-ignore scope and live
catalog key parity so those guardrails cannot silently drift.
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. - Run
pnpm i18n:statusto confirm key parity still holds, then optionally runpnpm i18n:status:reportto inspect remaining untranslated placeholders.
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.- Run
pnpm i18n:statusto verify the new locale has the same key structure asen. Translate the new catalogs as time allows, usingpnpm i18n:status:reportas the informational completeness report.
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, add a prompt label to LOCALE_LABELS in
packages/dashboard/src/ai-translate.ts (compile-enforced), and add a display
name to localeDisplayName in
packages/core/src/i18n/detect-content-language.ts. For Latin-script languages,
also consider a stopword list in LATIN_STOPWORDS there so content-language
detection can recognize the language (entries must be accent-stripped).
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