feat(i18n): register Brazilian Portuguese as a locale

Add pt-BR to the locale list and everything that hangs off it: the
client loader, Intl and date-fns mapping, the reply-language footers of
the Coach and insights prompts (English body, pt-BR footer, the same
path Korean and Dutch take), the citation-coverage normative words, the
no-key fallback copy, the AUDIT-C standard-drink line, country labels,
period search, CSV yes/no cells and the Telegram bare-word aliases.

The locale enums in the extract, managed-profile and record-settings
schemas now derive from the locale list instead of repeating it, and
the five notification and login-alert routes share one isLocale guard
instead of hand-rolled chains, so a later locale is one edit there.
A language tag is matched exactly and case-insensitively before its
primary subtag, so pt-BR and pt-PT browsers land on the Portuguese
bundle rather than English, and AUDIT-C no longer cuts a region tag to
two letters.

PHQ-9, GAD-7 and WHO-5 are not marked validated for pt-BR: the wording
has not been read against the published Brazilian forms, so readers get
the English items plus the existing note.
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 14:50:00 +02:00
1 parent 1619b2406a
commit 764794496c
72 files changed
+425 -159

No files matched your search

@@ -31,7 +31,7 @@ vi.mock("@/components/ui/responsive-sheet", () => ({
import { KeyboardShortcutsDialog } from "../keyboard-shortcuts-dialog";
import { primeLocales } from "@/__tests__/helpers/prime-locales";
primeLocales("de", "es", "fr", "it", "pl", "ko", "nl");
primeLocales("de", "es", "fr", "it", "pl", "ko", "nl", "pt-BR");
const EVERYTHING = {
navHrefs: ["/", "/medications", "/labs", "/timeline", "/insights", "/coach"],
@@ -106,7 +106,7 @@ describe("<KeyboardShortcutsDialog>", () => {
expect(kbd).not.toMatch(/#[0-9a-f]{3,6}|dracula|\[/i);
});
it.each(["de", "es", "fr", "it", "pl", "ko", "nl"])(
it.each(["de", "es", "fr", "it", "pl", "ko", "nl", "pt-BR"])(
"is translated in %s",
(locale) => {
const html = render({}, locale);
@@ -75,6 +75,7 @@ describe("<OfflineBanner>", () => {
"pl",
"ko",
"nl",
"pt-BR",
] as const) {
const m = loadMessages(locale);
expect(m.offlineBanner.message.length, locale).toBeGreaterThan(0);