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

@@ -108,7 +108,7 @@ describe("<ImportPanel> — SSR smoke", () => {
});
describe("<AppleHealthEstimateWarning>", () => {
it.each(["en", "de", "es", "fr", "it", "pl", "ko", "nl"] as const)(
it.each(["en", "de", "es", "fr", "it", "pl", "ko", "nl", "pt-BR"] as const)(
"renders localized estimate disclosure for %s",
(locale) => {
const html = render(<AppleHealthEstimateWarning days={3} />, locale);
@@ -10,7 +10,17 @@ import {
ManagedRecordSettingsForm,
} from "../managed-record-settings-section";
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko", "nl"] as const;
const LOCALES = [
"de",
"en",
"es",
"fr",
"it",
"pl",
"ko",
"nl",
"pt-BR",
] as const;
const render = (
family: Parameters<typeof ManagedRecordSettingsForm>[0]["family"],
@@ -215,7 +215,7 @@ describe("<AppleHealthCard>", () => {
}
});
it.each(["en", "de", "es", "fr", "it", "pl", "ko", "nl"] as const)(
it.each(["en", "de", "es", "fr", "it", "pl", "ko", "nl", "pt-BR"] as const)(
"renders localized Apple Health copy for %s without leaking keys",
(locale) => {
statusPayload = { lastSyncedAt: null, health: null };
@@ -15,6 +15,7 @@ import itMessages from "../../../../../messages/it.json";
import ko from "../../../../../messages/ko.json";
import nl from "../../../../../messages/nl.json";
import pl from "../../../../../messages/pl.json";
import ptBR from "../../../../../messages/pt-BR.json";
import {
diagnosticsText,
@@ -34,6 +35,7 @@ const CATALOGS: Record<Locale, unknown> = {
pl,
nl,
ko,
"pt-BR": ptBR,
};
function translator(locale: Locale) {
@@ -46,6 +46,7 @@ import { SettingsCardActions } from "./_card-actions";
import { SettingsCardHeader } from "./_card-header";
import { SettingsCard } from "./settings-card";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
import { locales } from "@/lib/i18n/config";
interface ManagedRecordSettingsResponse {
recordId: string;
@@ -411,7 +412,7 @@ function ProfileSettingsForm({
<option value="">
{t("settings.sharedRecord.managedSettings.profile.systemDefault")}
</option>
{["de", "en", "es", "fr", "it", "pl", "ko", "nl"].map((locale) => (
{locales.map((locale) => (
<option key={locale} value={locale}>
{locale}
</option>