mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Merge pull request #1246 from robdegraaf/feat/i18n-nl
This commit is contained in:
commit
7be3714ac2
83 files changed
+13832
-74
No files matched your search
@@ -25,7 +25,7 @@ import { gradeVoice } from "@/lib/ai/coach/eval/grade-voice";
|
||||
import { DEFAULT_COACH_PREFS } from "@/lib/validations/coach-prefs";
|
||||
import type { Locale } from "@/lib/i18n/config";
|
||||
|
||||
const LOCALES: Locale[] = ["de", "en", "fr", "es", "it", "pl", "ko"];
|
||||
const LOCALES: Locale[] = ["de", "en", "fr", "es", "it", "pl", "ko", "nl"];
|
||||
|
||||
/** The rules a voice section must state; `[]` when it states them all. */
|
||||
function missingVoiceRules(prompt: string, lang: "de" | "en"): string[] {
|
||||
|
||||
@@ -42,7 +42,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { consentNeededForProviders } from "@/lib/ai/capabilities/resolve";
|
||||
|
||||
const MESSAGES = join(__dirname, "../../messages");
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const;
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
|
||||
function bundle(locale: string): Record<string, unknown> {
|
||||
return JSON.parse(readFileSync(join(MESSAGES, `${locale}.json`), "utf8"));
|
||||
@@ -81,6 +81,7 @@ const FINALITY_PHRASES: Record<string, readonly string[]> = {
|
||||
],
|
||||
pl: ["nie można cofnąć", "nie można tego cofnąć", "trwale", "nieodwracaln"],
|
||||
ko: ["되돌릴 수 없", "복구할 수 없", "영구", "완전히 삭제"],
|
||||
nl: ["niet ongedaan", "definitief", "permanent", "onherroepelijk"],
|
||||
};
|
||||
|
||||
/** Per-locale phrase that promises the short undo window. */
|
||||
@@ -92,6 +93,7 @@ const UNDO_PHRASES: Record<string, string> = {
|
||||
it: "annullare",
|
||||
pl: "cofnąć",
|
||||
ko: "되돌릴 수 있",
|
||||
nl: "ongedaan maken",
|
||||
};
|
||||
|
||||
/** Confirmation bodies whose surface renders an Undo affordance. */
|
||||
@@ -176,6 +178,7 @@ describe("sleep debt is labelled as a balance, not a summed deficit", () => {
|
||||
it: ["cumulato", "cumulata", "accumulato", "deficit"],
|
||||
pl: ["skumulowan", "niedobór", "niedoboru"],
|
||||
ko: ["누적", "부족분", "적자", "쌓인"],
|
||||
nl: ["cumulatief", "opgeteld", "tekort", "opgebouwd"],
|
||||
};
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
@@ -302,6 +305,17 @@ describe("the mood prognosis never states a cause", () => {
|
||||
],
|
||||
pl: ["ponieważ", "powoduje", "z powodu", "wywołuje", "poprawia twój"],
|
||||
ko: ["때문", "탓", "원인", "유발", "초래", "야기", "덕분", "인해"],
|
||||
nl: [
|
||||
"omdat",
|
||||
"veroorzaakt",
|
||||
"leidt tot",
|
||||
"zorgt voor",
|
||||
"vanwege",
|
||||
"verbetert je",
|
||||
"verslechtert je",
|
||||
"verlaagt je",
|
||||
"verhoogt je",
|
||||
],
|
||||
};
|
||||
|
||||
/** Every leaf string under `insights.mood.prognosis`, flattened. */
|
||||
@@ -401,6 +415,11 @@ describe("the MCP token copy never claims the mint is read-only", () => {
|
||||
it: ["token in sola lettura"],
|
||||
pl: ["token tylko do odczytu"],
|
||||
ko: ["읽기 전용 토큰", "읽기전용 토큰"],
|
||||
nl: [
|
||||
"alleen-lezen token",
|
||||
"alleen-lezentoken",
|
||||
"token met alleen leesrechten",
|
||||
],
|
||||
};
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
@@ -450,6 +469,7 @@ describe("AI consent withdrawal copy matches what withdrawing does", () => {
|
||||
it: ["tua chiave", "modello locale"],
|
||||
pl: ["własnym kluczu", "lokalnym modelu"],
|
||||
ko: ["자신의 키", "로컬 모델"],
|
||||
nl: ["eigen sleutel", "lokaal model"],
|
||||
};
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
|
||||
@@ -166,7 +166,7 @@ describe("dynamic-key exhaustiveness (enum-derived)", () => {
|
||||
expect(LOCALES.length).toBeGreaterThanOrEqual(6);
|
||||
});
|
||||
|
||||
it("discovers all seven shipped locales", () => {
|
||||
it("discovers all eight shipped locales", () => {
|
||||
expect(LOCALES.map((l) => l.locale).sort()).toEqual([
|
||||
"de",
|
||||
"en",
|
||||
@@ -174,6 +174,7 @@ describe("dynamic-key exhaustiveness (enum-derived)", () => {
|
||||
"fr",
|
||||
"it",
|
||||
"ko",
|
||||
"nl",
|
||||
"pl",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ const EN_PATH = join(MESSAGES_DIR, "en.json");
|
||||
// intentionally excluded — it carries accepted anglicisms (e.g. "Health
|
||||
// Score") that would be noise here, and is covered by its own pinned checks in
|
||||
// `i18n-locale-integrity`.
|
||||
const LEAK_LOCALES = ["es", "fr", "it", "pl", "ko"] as const;
|
||||
const LEAK_LOCALES = ["es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
|
||||
function flattenValues(
|
||||
obj: unknown,
|
||||
@@ -209,6 +209,16 @@ const LEGIT_IDENTICAL = new Set<string>([
|
||||
"settings.nightscoutUrl",
|
||||
"admin.aiServerKey.baseUrlLabel",
|
||||
"admin.webPushVapidSubject",
|
||||
// Dutch spells these exactly as English does: "In bed" and "capsule(s)" are
|
||||
// ordinary Dutch, and the clinical index names and the fatty-acid
|
||||
// abbreviations are written in their international form.
|
||||
"mood.dayContext.linkedInBed",
|
||||
"insights.sleep.stages.inBed",
|
||||
"medications.wizard.steps.step3.unit.capsules",
|
||||
"doctorReport.glucoseClinical.gmi",
|
||||
"insights.bloodGlucose.clinical.advanced.jIndex.label",
|
||||
"nutrients.names.dha",
|
||||
"nutrients.names.epa",
|
||||
]);
|
||||
|
||||
describe("i18n English-leak guard", () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { join } from "node:path";
|
||||
* the key is a prop, not a call argument. Both halves are asserted here.
|
||||
*/
|
||||
|
||||
const LOCALES = ["en", "de", "es", "fr", "it", "pl", "ko"] as const;
|
||||
const LOCALES = ["en", "de", "es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
const INSIGHTS_PAGES_DIR = join(process.cwd(), "src/app/insights");
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,7 +93,7 @@ const NOT_IN_THE_FEED = [
|
||||
/** What an action with no sentence of its own renders as. */
|
||||
const FALLBACK_KEY = "recordSharing.activity.acted";
|
||||
/** The six bundles the product ships. */
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const;
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
|
||||
/** Every `route.ts` under `src/app/api`, walked rather than globbed: a glob's
|
||||
* `*` skips a leading dot and this tree has dot-directories. */
|
||||
|
||||
@@ -25,7 +25,7 @@ import { INTEGRATION_DISPLAY_NAMES } from "@/components/settings/integrations/in
|
||||
|
||||
const ROOT = path.resolve(__dirname, "../..");
|
||||
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const;
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
|
||||
/** Every path the retired bridge occupied. */
|
||||
const REMOVED_PATHS = [
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("managed record settings UI", () => {
|
||||
fromRoot("src/components/settings/record-settings-section-gate.tsx"),
|
||||
"utf8",
|
||||
),
|
||||
...["de", "en", "es", "fr", "it", "pl", "ko"].map((locale) =>
|
||||
...["de", "en", "es", "fr", "it", "pl", "ko", "nl"].map((locale) =>
|
||||
readFile(fromRoot(`messages/${locale}.json`), "utf8"),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
* it — which is the part that rots silently.
|
||||
*/
|
||||
const ROOT = process.cwd();
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const;
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
|
||||
/** The published description of the level, off the Zod schema itself. */
|
||||
function accessDescription(): string {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const ROOT = join(__dirname, "../..");
|
||||
const LOCALES = ["en", "de", "es", "fr", "it", "pl", "ko"] as const;
|
||||
const LOCALES = ["en", "de", "es", "fr", "it", "pl", "ko", "nl"] as const;
|
||||
|
||||
const REQUIRED_KEYS = [
|
||||
// Security: adding a passkey now requires an existing sign-in factor.
|
||||
|
||||
Reference in new issue
Block a user