## 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>
69 lines
2.4 KiB
TypeScript
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");
|
|
});
|
|
});
|