Files
fusion/docs/i18n-contributing.md
Victor Canô a09e0cb87f feat(i18n): add Português (Brasil) (pt-BR) locale (#3347)
## Summary

Adds **Português (Brasil)** (`pt-BR`) as a supported locale:

- Selectable as **Translation target language** (project settings) and
as the dashboard / terminal UI language.
- Full machine-drafted catalogs (`app`, `cli`, `common`), disclosed in
`packages/i18n/locales/TRANSLATION_STATUS.md` following the pattern
#1352 established — reviewed for glossary/register consistency (0.18%
untranslated, matching only keys that are empty in `en`), but
native-speaker corrections are welcome.
- Brazilian Portuguese content-language detection (accent-stripped
stopword list — the scorer strips diacritics before matching, so
accented entries never match; `com`/`mais` deliberately omitted to avoid
bare-domain `.com` and French collisions, with regression tests for both
directions).
- `pt`/`pt-PT` browser and environment locales resolve to `pt-BR` on all
three detection paths (`FALLBACK_LNG` routing plus a `pt` branch in
`normalizeToSupportedLocale`, mirroring the existing `zh` handling).
- `README.pt-BR.md` + switcher links in all READMEs, docs updates
(`settings-reference`, `cli-reference`, `i18n-contributing`, `--lang`
help text), changeset (`minor`).

Drive-by fixes bundled: `TRANSLATION_STATUS.md` was missing the `ko`
row; the LanguageSelector endonym test was missing `한국어`;
`docs/i18n-contributing.md` now names the two compile-enforced display
maps (`LOCALE_LABELS`, `localeDisplayName`) a new locale must update;
the `--lang` CLI help text no longer drifts from its validator.

## Test plan

- `pnpm i18n:status` (key parity gate) green; catalogs are
`i18n:sync`-idempotent.
- Updated/extended suites: core `locale-settings`, i18n
`config`/`parity`/`db-banner-catalog`/`i18n-gate-coverage`, dashboard
`useLanguage`/`LanguageSelector`/`GeneralSection.importTranslate`/`detectContentLanguage`
(incl. new pt-BR detection + bare-domain regression tests), CLI
`settings`.
- `pnpm verify:fast` (typecheck, build, boot smoke), `pnpm lint`, `pnpm
check:changesets`, and the bounded `pnpm test` lane all green locally
(the three `test:pg-gate` files fail locally only for lack of a Postgres
instance; they fail identically on clean `main`).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Brazilian Portuguese (Português (Brasil)) across the dashboard,
terminal interface, settings, and translation tools.
  * Added Portuguese translations for common interface and CLI content.
* Added automatic Portuguese language detection, locale normalization,
and fallback support.
* Added a Portuguese (Brazil) README with product, setup, and usage
documentation.

* **Documentation**
* Updated language selectors, CLI references, settings documentation,
and translation guidance.
  * Added Portuguese README links to translated documentation.
  * Added French to the documented dashboard language options.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-08-09 13:33:16 -10:00

108 lines
5.2 KiB
Markdown

# 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:
```bash
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 `@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
1. Run `pnpm i18n:sync` so every catalog has the current `en` keys (untranslated
entries are empty strings).
2. Fill the empty strings in `packages/i18n/locales/{locale}/*.json`.
3. Keep interpolation placeholders verbatim: `{{brand}}`, `{{detail}}`,
`{{key}}`. Never translate a `[{{key}}]` keybinding accelerator — only the
words around it.
4. Run `pnpm i18n:status` to confirm key parity still holds, then optionally run
`pnpm i18n:status:report` to 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)
1. Add the locale code to `SUPPORTED_LOCALES` in `packages/core/src/types.ts`
and to `locales` in `i18next.config.ts`.
2. `pnpm i18n:sync` — scaffolds a full set of catalog files for the new locale
with the correct plural categories.
3. `pnpm i18n:gen-cli` — adds the locale to the CLI's static import map.
4. Run `pnpm i18n:status` to verify the new locale has the same key structure as
`en`. Translate the new catalogs as time allows, using
`pnpm i18n:status:report` as 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 `localStorage` and to server settings.
- **Terminal UI:** resolved from `--lang <code>` → the saved dashboard language
setting → the `LC_ALL`/`LC_MESSAGES`/`LANG`/`LANGUAGE` environment → `en`.
```bash
fusion dashboard --lang zh-TW
```
[react-i18next]: https://react.i18next.com/