From d77fe9aa7de2b0086943b34d80bf3217defb4cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 22:29:24 +0100 Subject: [PATCH] fix(i18n): translate the illness labels left in English The illness screens showed "Weight", "Pulse", "Recovery" and "Day" in English in German, French, Spanish, Italian and Polish: the four keys had been copied from English into those catalogs, while their neighbours were translated. The same copy left the medication wizard's "Name" step in French, Spanish, Italian and Polish and the dashboard layout's "Chart" column in German in English. A test now holds the illness keys translated in every locale. --- messages/de.json | 10 ++-- messages/es.json | 10 ++-- messages/fr.json | 10 ++-- messages/it.json | 10 ++-- messages/pl.json | 10 ++-- .../__tests__/illness-labels-i18n.test.ts | 55 +++++++++++++++++++ 6 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 src/components/illness/__tests__/illness-labels-i18n.test.ts diff --git a/messages/de.json b/messages/de.json index b44fe4387..32eed1133 100644 --- a/messages/de.json +++ b/messages/de.json @@ -737,7 +737,7 @@ "moveUp": "Nach oben", "moveDown": "Nach unten", "layoutTileColumn": "Kachel", - "layoutChartColumn": "Chart", + "layoutChartColumn": "Diagramm", "layoutNativeOnlyTitle": "In der mobilen App sichtbar", "layoutNativeOnlyHint": "Diese Widgets zeichnet die mobile App, nicht diese Seite. Sie liegen im selben gespeicherten Layout, hier wird ihre Sichtbarkeit gesetzt.", "dragHandle": "Zum Verschieben ziehen", @@ -10999,7 +10999,7 @@ "notePlaceholder": "Wie sich der Tag anfühlte (verschlüsselt).", "severityFor": "Stärke für {symptom}", "saveError": "Der Tag konnte nicht gespeichert werden. Bitte erneut versuchen.", - "day": "Day" + "day": "Tag" }, "impact": { "0": "Voll belastbar", @@ -11088,14 +11088,14 @@ "RESTING_HEART_RATE": "Ruheherzfrequenz", "HEART_RATE_VARIABILITY": "Herzfrequenzvariabilität", "HRV_RMSSD": "HRV (RMSSD)", - "RECOVERY_SCORE": "Recovery", + "RECOVERY_SCORE": "Erholung", "OXYGEN_SATURATION": "Sauerstoffsättigung", "BODY_TEMPERATURE": "Körpertemperatur", "RESPIRATORY_RATE": "Atemfrequenz", "BLOOD_PRESSURE_SYS": "Systolischer Blutdruck", "BLOOD_PRESSURE_DIA": "Diastolischer Blutdruck", - "PULSE": "Pulse", - "WEIGHT": "Weight", + "PULSE": "Puls", + "WEIGHT": "Gewicht", "FUNCTIONAL_IMPACT": "Funktionelle Beeinträchtigung" }, "listLoadError": "Episoden konnten nicht geladen werden." diff --git a/messages/es.json b/messages/es.json index 34261c62a..c320377af 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1673,7 +1673,7 @@ "step1": { "title": "¿Cómo se llama el medicamento?", "subline": "Exactamente como aparece en la caja.", - "label": "Name", + "label": "Nombre", "placeholder": "p. ej. Ramipril", "short": "Nombre" }, @@ -10999,7 +10999,7 @@ "notePlaceholder": "Cómo te sentiste hoy (cifrado).", "severityFor": "Gravedad de {symptom}", "saveError": "No se pudo guardar el día. Inténtalo de nuevo.", - "day": "Day" + "day": "Día" }, "impact": { "0": "Plenamente funcional", @@ -11088,14 +11088,14 @@ "RESTING_HEART_RATE": "Frecuencia cardíaca en reposo", "HEART_RATE_VARIABILITY": "Variabilidad de la frecuencia cardíaca", "HRV_RMSSD": "VFC (RMSSD)", - "RECOVERY_SCORE": "Recovery", + "RECOVERY_SCORE": "Recuperación", "OXYGEN_SATURATION": "Saturación de oxígeno", "BODY_TEMPERATURE": "Temperatura corporal", "RESPIRATORY_RATE": "Frecuencia respiratoria", "BLOOD_PRESSURE_SYS": "Presión arterial sistólica", "BLOOD_PRESSURE_DIA": "Presión arterial diastólica", - "PULSE": "Pulse", - "WEIGHT": "Weight", + "PULSE": "Pulso", + "WEIGHT": "Peso", "FUNCTIONAL_IMPACT": "Impacto funcional" }, "listLoadError": "No se pudieron cargar tus episodios." diff --git a/messages/fr.json b/messages/fr.json index 63b352e78..7a9a82e03 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1673,7 +1673,7 @@ "step1": { "title": "Comment s’appelle le médicament ?", "subline": "Exactement comme indiqué sur la boîte.", - "label": "Name", + "label": "Nom", "placeholder": "par exemple Ramipril", "short": "Nom" }, @@ -10999,7 +10999,7 @@ "notePlaceholder": "Comment s'est passée la journée (chiffré).", "severityFor": "Intensité de {symptom}", "saveError": "Impossible d'enregistrer la journée. Veuillez réessayer.", - "day": "Day" + "day": "Jour" }, "impact": { "0": "Pleinement fonctionnel", @@ -11088,14 +11088,14 @@ "RESTING_HEART_RATE": "Fréquence cardiaque au repos", "HEART_RATE_VARIABILITY": "Variabilité de la fréquence cardiaque", "HRV_RMSSD": "VFC (RMSSD)", - "RECOVERY_SCORE": "Recovery", + "RECOVERY_SCORE": "Récupération", "OXYGEN_SATURATION": "Saturation en oxygène", "BODY_TEMPERATURE": "Température corporelle", "RESPIRATORY_RATE": "Fréquence respiratoire", "BLOOD_PRESSURE_SYS": "Pression artérielle systolique", "BLOOD_PRESSURE_DIA": "Pression artérielle diastolique", - "PULSE": "Pulse", - "WEIGHT": "Weight", + "PULSE": "Pouls", + "WEIGHT": "Poids", "FUNCTIONAL_IMPACT": "Retentissement fonctionnel" }, "listLoadError": "Impossible de charger vos épisodes." diff --git a/messages/it.json b/messages/it.json index 940aeb2e9..b9f7e6073 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1673,7 +1673,7 @@ "step1": { "title": "Come si chiama il farmaco?", "subline": "Esattamente come riportato sulla confezione.", - "label": "Name", + "label": "Nome", "placeholder": "ad es. Ramipril", "short": "Nome" }, @@ -10999,7 +10999,7 @@ "notePlaceholder": "Com'è andata la giornata (cifrato).", "severityFor": "Intensità di {symptom}", "saveError": "Impossibile salvare la giornata. Riprova.", - "day": "Day" + "day": "Giorno" }, "impact": { "0": "Pienamente funzionale", @@ -11088,14 +11088,14 @@ "RESTING_HEART_RATE": "Frequenza cardiaca a riposo", "HEART_RATE_VARIABILITY": "Variabilità della frequenza cardiaca", "HRV_RMSSD": "HRV (RMSSD)", - "RECOVERY_SCORE": "Recovery", + "RECOVERY_SCORE": "Recupero", "OXYGEN_SATURATION": "Saturazione di ossigeno", "BODY_TEMPERATURE": "Temperatura corporea", "RESPIRATORY_RATE": "Frequenza respiratoria", "BLOOD_PRESSURE_SYS": "Pressione arteriosa sistolica", "BLOOD_PRESSURE_DIA": "Pressione arteriosa diastolica", - "PULSE": "Pulse", - "WEIGHT": "Weight", + "PULSE": "Polso", + "WEIGHT": "Peso", "FUNCTIONAL_IMPACT": "Impatto funzionale" }, "listLoadError": "Impossibile caricare gli episodi." diff --git a/messages/pl.json b/messages/pl.json index a41d3296f..90d724037 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -1673,7 +1673,7 @@ "step1": { "title": "Jak nazywa się lek?", "subline": "Dokładnie tak, jak na opakowaniu.", - "label": "Name", + "label": "Nazwa", "placeholder": "np. Ramipryl", "short": "Nazwa" }, @@ -10999,7 +10999,7 @@ "notePlaceholder": "Jak minął dzień (zaszyfrowane).", "severityFor": "Nasilenie: {symptom}", "saveError": "Nie udało się zapisać dnia. Spróbuj ponownie.", - "day": "Day" + "day": "Dzień" }, "impact": { "0": "Pełna sprawność", @@ -11088,14 +11088,14 @@ "RESTING_HEART_RATE": "Spoczynkowe tętno", "HEART_RATE_VARIABILITY": "Zmienność rytmu serca", "HRV_RMSSD": "HRV (RMSSD)", - "RECOVERY_SCORE": "Recovery", + "RECOVERY_SCORE": "Regeneracja", "OXYGEN_SATURATION": "Saturacja tlenem", "BODY_TEMPERATURE": "Temperatura ciała", "RESPIRATORY_RATE": "Częstość oddechów", "BLOOD_PRESSURE_SYS": "Ciśnienie skurczowe", "BLOOD_PRESSURE_DIA": "Ciśnienie rozkurczowe", - "PULSE": "Pulse", - "WEIGHT": "Weight", + "PULSE": "Tętno", + "WEIGHT": "Waga", "FUNCTIONAL_IMPACT": "Wpływ na funkcjonowanie" }, "listLoadError": "Nie udało się załadować epizodów." diff --git a/src/components/illness/__tests__/illness-labels-i18n.test.ts b/src/components/illness/__tests__/illness-labels-i18n.test.ts new file mode 100644 index 000000000..604871d47 --- /dev/null +++ b/src/components/illness/__tests__/illness-labels-i18n.test.ts @@ -0,0 +1,55 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { resolveKey } from "@/lib/i18n/resolve-key"; + +/** + * The illness screens name the vital that drives a recovery gap and label the + * day picker of the log sheet through these keys. They were once copied from + * English into five catalogs, so a German reader saw "Weight", "Pulse", + * "Recovery" and "Day" among translated neighbours. The English leak guard + * skips single words, so it cannot see this; the pin below can. + */ +const MESSAGES_DIR = join(__dirname, "../../../../messages"); + +function catalog(locale: string): Record { + return JSON.parse( + readFileSync(join(MESSAGES_DIR, `${locale}.json`), "utf8"), + ) as Record; +} + +const KEYS = [ + "illness.vital.WEIGHT", + "illness.vital.PULSE", + "illness.vital.RECOVERY_SCORE", + "illness.sheet.day", +] as const; + +const english = catalog("en"); + +describe("illness labels", () => { + for (const locale of [ + "de", + "fr", + "es", + "it", + "pl", + "nl", + "pt-BR", + "ko", + "ru", + ]) { + it(`are translated in ${locale}`, () => { + const messages = catalog(locale); + for (const key of KEYS) { + const value = resolveKey(messages, key); + expect(value, `${locale} ${key}`).toBeTruthy(); + expect(value, `${locale} ${key} is still English`).not.toBe( + resolveKey(english, key), + ); + } + }); + } +});