Files
fusion/packages/dashboard/app/components/__tests__/LanguageSelector.test.tsx
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

69 lines
2.4 KiB
TypeScript

import { SUPPORTED_LOCALES } from "@fusion/core";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useLanguage } from "../../hooks/useLanguage";
vi.mock("../../hooks/useLanguage", () => ({ useLanguage: vi.fn() }));
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (key: string, def?: string) => def ?? key }),
}));
const { LanguageSelector } = await import("../LanguageSelector");
const mockUseLanguage = vi.mocked(useLanguage);
const setLanguage = vi.fn();
const clearLanguage = vi.fn();
describe("LanguageSelector", () => {
beforeEach(() => {
setLanguage.mockClear();
clearLanguage.mockClear();
mockUseLanguage.mockReturnValue({
language: "en",
supportedLocales: SUPPORTED_LOCALES,
setLanguage,
clearLanguage,
hasExplicitChoice: true,
});
});
it("renders all seven language endonyms", () => {
render(<LanguageSelector />);
for (const name of ["English", "简体中文", "繁體中文", "Français", "Español", "한국어", "Português (Brasil)"]) {
expect(screen.getByText(name)).toBeTruthy();
}
});
it("marks the active locale as pressed", () => {
render(<LanguageSelector />);
expect(screen.getByText("English").getAttribute("aria-pressed")).toBe("true");
expect(screen.getByText("Français").getAttribute("aria-pressed")).toBe("false");
});
it("calls setLanguage with the selected locale code", () => {
render(<LanguageSelector />);
fireEvent.click(screen.getByText("简体中文"));
expect(setLanguage).toHaveBeenCalledWith("zh-CN");
});
it("offers an Auto option that clears the explicit choice", () => {
render(<LanguageSelector />);
const auto = screen.getByText("Auto");
expect(auto.getAttribute("aria-pressed")).toBe("false");
fireEvent.click(auto);
expect(clearLanguage).toHaveBeenCalledTimes(1);
});
it("marks Auto as pressed (and no locale) when no explicit choice exists", () => {
mockUseLanguage.mockReturnValue({
language: "fr", // detected, not chosen
supportedLocales: SUPPORTED_LOCALES,
setLanguage,
clearLanguage,
hasExplicitChoice: false,
});
render(<LanguageSelector />);
expect(screen.getByText("Auto").getAttribute("aria-pressed")).toBe("true");
expect(screen.getByText("Français").getAttribute("aria-pressed")).toBe("false");
});
});