mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(i18n): one German word per thing across Settings
Anbieter instead of Provider, Standard instead of Default, Gesundheitsscore for every spelling of the score, and Insights where the area was called Auswertungen. The Coach's rate-limit notice no longer shows English in the German interface. A guard reads the Settings namespaces of the German catalog and fails on the retired spellings.
This commit is contained in:
1 parent
ec240bc07a
commit
131b48cc49
4 files changed
+79
-24
No files matched your search
+22
-22
@@ -677,7 +677,7 @@
|
||||
"same_time_baseline": "Aktivität zur gleichen Tageszeit",
|
||||
"upcoming_visit": "Termine"
|
||||
},
|
||||
"layoutReset": "Auf Defaults zurücksetzen",
|
||||
"layoutReset": "Auf Standard zurücksetzen",
|
||||
"layoutResetTitle": "Dashboard-Layout zurücksetzen?",
|
||||
"layoutResetBody": "Dein Kachel-Layout und deine Auswahl für die Highlights für heute werden auf die Standardwerte zurückgesetzt.",
|
||||
"layoutResetConfirm": "Layout zurücksetzen",
|
||||
@@ -1827,7 +1827,7 @@
|
||||
"phaseRed": "Rot",
|
||||
"phaseMinutes": "Min",
|
||||
"phasePercent": "%",
|
||||
"phaseResetDefaults": "Auf Defaults zurücksetzen",
|
||||
"phaseResetDefaults": "Auf Standard zurücksetzen",
|
||||
"phaseSaved": "Erinnerungsphasen gespeichert",
|
||||
"sourceWeb": "Web",
|
||||
"sourceApi": "iOS-App",
|
||||
@@ -2745,7 +2745,7 @@
|
||||
"tooltip": "Persönlicher Rekord: {value} {unit} am {date}",
|
||||
"benchmark": "Typisch in dem Alter: {low} bis {high} {unit} ({source})"
|
||||
},
|
||||
"loadError": "Auswertungen konnten nicht geladen werden",
|
||||
"loadError": "Insights konnten nicht geladen werden",
|
||||
"heroPersonalBaseline": "Basierend auf deinen letzten 90 Tagen",
|
||||
"heroRegenerating": "Wird generiert",
|
||||
"regenerateAnalysis": "Einschätzung neu erstellen",
|
||||
@@ -3088,8 +3088,8 @@
|
||||
"settingsCancel": "Abbrechen",
|
||||
"dailyLimitTitle": "Heute-Limit erreicht",
|
||||
"dailyLimitBody": "Tageslimit erreicht. Das Budget setzt sich um Mitternacht in deiner Zeitzone zurück.",
|
||||
"providerRateLimitTitle": "Provider rate-limited",
|
||||
"providerRateLimitBody": "Provider ist temporär überlastet; Reset in ~5 min.",
|
||||
"providerRateLimitTitle": "Anbieter ausgelastet",
|
||||
"providerRateLimitBody": "Der Anbieter ist gerade überlastet. In etwa 5 Minuten geht es wieder.",
|
||||
"cluster": {
|
||||
"cardio": {
|
||||
"label": "Herz-Kreislauf",
|
||||
@@ -3211,7 +3211,7 @@
|
||||
"workouts": "Geh mit mir meine letzten Workouts durch: Belastung, Häufigkeit und wie ich mich dazwischen erhole.",
|
||||
"recoveryPage": "Warum steht meine Erholung gerade dort, wo sie steht, und was treibt das?",
|
||||
"briefing": "Geh mit mir das heutige Briefing durch. Worauf sollte ich mich konzentrieren, und gibt es etwas, das einen Schritt wert ist?",
|
||||
"healthScore": "Warum liegt mein Gesundheits-Score bei {score} von 100, und was würde ihn am stärksten bewegen?",
|
||||
"healthScore": "Warum liegt mein Gesundheitsscore bei {score} von 100, und was würde ihn am stärksten bewegen?",
|
||||
"periodWeek": "Geh mit mir durch, wie meine letzte Woche über meine Gesundheitswerte hinweg lief. Was sticht heraus?",
|
||||
"periodMonth": "Geh mit mir durch, wie mein letzter Monat über meine Gesundheitswerte hinweg lief. Was sticht heraus?",
|
||||
"correlationBpCompliance": "Wie hängt meine Therapietreue mit meinem Blutdruck zusammen? Geh mit mir durch, was dieser Zusammenhang für mich bedeutet.",
|
||||
@@ -4857,10 +4857,10 @@
|
||||
}
|
||||
},
|
||||
"thresholds": {
|
||||
"defaultLabel": "Default",
|
||||
"defaultLabel": "Standard",
|
||||
"overrideToggleLabel": "Eigene Werte",
|
||||
"resetAction": "Auf Default zurücksetzen",
|
||||
"resetAllAction": "Alle auf Default zurücksetzen",
|
||||
"resetAction": "Auf Standard zurücksetzen",
|
||||
"resetAllAction": "Alle auf Standard zurücksetzen",
|
||||
"resetAllTitle": "Alle eigenen Bereiche zurücksetzen?",
|
||||
"resetAllBody": "Jede Messgröße kehrt zu ihrem Standardbereich zurück. Die von dir gesetzten Werte werden nirgends sonst gespeichert und lassen sich nicht wiederherstellen.",
|
||||
"resetAllConfirm": "Alle zurücksetzen",
|
||||
@@ -4874,7 +4874,7 @@
|
||||
"overrideWarning": "Werte außerhalb der Leitlinien können zu ungesunden Zielen führen. Nur nach ärztlicher Rücksprache überschreiben.",
|
||||
"saveSuccess": "Zielwerte aktualisiert",
|
||||
"saveError": "Zielwerte konnten nicht gespeichert werden",
|
||||
"resetSuccess": "Auf Default zurückgesetzt",
|
||||
"resetSuccess": "Auf Standard zurückgesetzt",
|
||||
"sourceOverride": "Eigener Wert",
|
||||
"metricWeight": "Gewicht",
|
||||
"metricBpSys": "Blutdruck (systolisch)",
|
||||
@@ -4890,7 +4890,7 @@
|
||||
"metricBodyWater": "Körperwasser",
|
||||
"metricBoneMass": "Knochenmasse",
|
||||
"metricOxygenSaturation": "Sauerstoffsättigung (SpO₂)",
|
||||
"unsetExplanation": "Kein eigener Wert, berechneter Default aktiv.",
|
||||
"unsetExplanation": "Kein eigener Wert, berechneter Standard aktiv.",
|
||||
"cardTitle": "Bereiche je Messgröße"
|
||||
},
|
||||
"onboarding": {
|
||||
@@ -4908,7 +4908,7 @@
|
||||
},
|
||||
"quickAdd": {
|
||||
"title": "In Sekunden erfasst",
|
||||
"body": "Über „+ Hinzufügen“ trägst du jeden Messwert in Sekunden ein: Gewicht, Blutdruck, Puls, Glukose. Versteckte Stärke: Werte lassen sich nachträglich datieren. Alles, was du hier einträgst, fließt in Trends, Health-Score und die KI-Insights."
|
||||
"body": "Über „+ Hinzufügen“ trägst du jeden Messwert in Sekunden ein: Gewicht, Blutdruck, Puls, Glukose. Versteckte Stärke: Werte lassen sich nachträglich datieren. Alles, was du hier einträgst, fließt in Trends, Gesundheitsscore und die KI-Insights."
|
||||
},
|
||||
"measurements": {
|
||||
"title": "Messwerte & Zielbereiche",
|
||||
@@ -5860,7 +5860,7 @@
|
||||
"error": "Konnte nicht gespeichert werden. Bitte erneut versuchen.",
|
||||
"toggleable": {
|
||||
"title": "Was du trackst",
|
||||
"description": "Deaktivierte Module verschwinden aus Navigation, Kacheln und Auswertungen, deine Daten bleiben erhalten."
|
||||
"description": "Deaktivierte Module verschwinden aus Navigation, Kacheln und Insights, deine Daten bleiben erhalten."
|
||||
},
|
||||
"manageIn": "In {section} verwalten",
|
||||
"operatorDisabled": "Serverweit deaktiviert",
|
||||
@@ -6004,12 +6004,12 @@
|
||||
"omitted": "{count} Bereiche stehen nicht in der Liste, weil ihre Erfassung abgeschaltet ist. Schalte ein Modul wieder ein, dann kommt sein Bereich zurück.",
|
||||
"omittedLink": "Module öffnen",
|
||||
"refusal": {
|
||||
"empty": "Ein Health Score braucht mindestens ein Feld der Gesundheit, das er bewerten kann. Behalte mindestens eines ausgewählt. Der Score nennt selbst, worauf er beruht."
|
||||
"empty": "Ein Gesundheitsscore braucht mindestens ein Feld der Gesundheit, das er bewerten kann. Behalte mindestens eines ausgewählt. Der Score nennt selbst, worauf er beruht."
|
||||
},
|
||||
"notice": {
|
||||
"upgrade": {
|
||||
"title": "Was zählt, entscheidest jetzt du",
|
||||
"body": "Welche Bereiche in deinen Health Score einflossen, richtete sich früher danach, welche Module du eingeschaltet hattest. Das ist jetzt eine eigene Entscheidung, und hier triffst du sie. Deine Zahl hat sich dadurch nicht bewegt: Sie zählt weiterhin genau das, was sie vorher gezählt hat."
|
||||
"body": "Welche Bereiche in deinen Gesundheitsscore einflossen, richtete sich früher danach, welche Module du eingeschaltet hattest. Das ist jetzt eine eigene Entscheidung, und hier triffst du sie. Deine Zahl hat sich dadurch nicht bewegt: Sie zählt weiterhin genau das, was sie vorher gezählt hat."
|
||||
},
|
||||
"changed": {
|
||||
"title": "Du hast geändert, was zählt",
|
||||
@@ -6195,7 +6195,7 @@
|
||||
"lastGeneratedAt": "Zuletzt generiert",
|
||||
"ai": {
|
||||
"chatgptConnectedBadge": "ChatGPT verbunden",
|
||||
"adminAiActiveBadge": "Admin-Provider aktiv",
|
||||
"adminAiActiveBadge": "Betreiber-Anbieter aktiv",
|
||||
"connectionExpiredBadge": "Verbindung abgelaufen",
|
||||
"connectedSince": "Verbunden seit {when}.",
|
||||
"deviceCodeHeading": "Verbindung auf chatgpt.com abschließen",
|
||||
@@ -6233,8 +6233,8 @@
|
||||
"moveUp": "Nach oben",
|
||||
"moveDown": "Nach unten",
|
||||
"removeFromChain": "Aus Kette entfernen",
|
||||
"addProvider": "Provider hinzufügen",
|
||||
"addNoneAvailable": "Alle Provider sind bereits in der Kette",
|
||||
"addProvider": "Anbieter hinzufügen",
|
||||
"addNoneAvailable": "Alle Anbieter sind bereits in der Kette",
|
||||
"saveOrder": "Reihenfolge speichern",
|
||||
"saved": "Kette gespeichert",
|
||||
"saveFailed": "Speichern der Kette fehlgeschlagen",
|
||||
@@ -6242,7 +6242,7 @@
|
||||
"resetConfirmTitle": "Kette auf Standard zurücksetzen?",
|
||||
"resetConfirmBody": "Die Kette wird auf Codex → OpenAI → Anthropic → Lokal → Admin-OpenAI zurückgesetzt. Gespeicherte Zugangsdaten bleiben erhalten."
|
||||
},
|
||||
"activeProviderHeading": "Aktiver Provider",
|
||||
"activeProviderHeading": "Aktiver Anbieter",
|
||||
"activeProviderLabel": "Anbieter einrichten",
|
||||
"providerSelect": {
|
||||
"codex": "ChatGPT-Account (Codex)",
|
||||
@@ -6285,10 +6285,10 @@
|
||||
},
|
||||
"adminOpenai": {
|
||||
"title": "Admin-OpenAI",
|
||||
"body": "Vom Betreiber bereitgestellter OpenAI-Key, genutzt als letzter Fallback ohne persönlichen Provider. Hier ist nichts einzustellen.",
|
||||
"body": "Vom Betreiber bereitgestellter OpenAI-Key, genutzt als letzter Fallback ohne persönlichen Anbieter. Hier ist nichts einzustellen.",
|
||||
"notConfigured": "Der Betreiber hat keinen geteilten OpenAI-Key auf dieser Instanz konfiguriert."
|
||||
},
|
||||
"testProvider": "Aktiven Provider testen",
|
||||
"testProvider": "Aktiven Anbieter testen",
|
||||
"testSuccess": "OK: {provider} ({model})",
|
||||
"testFailedShort": "Test fehlgeschlagen: {message}",
|
||||
"testUnexpectedResponse": "Verbindung zum KI-Anbieter fehlgeschlagen: unerwartete Antwort vom Server.",
|
||||
@@ -12334,7 +12334,7 @@
|
||||
"scopeSomeCapability": "Alles, was du nicht auswählst, bleibt für sie verschlossen.",
|
||||
"scopeManageNote": "Verwaltender Zugriff umfasst immer die gesamte Akte.",
|
||||
"scopeLeakNote": "Ein Bereich kann in seinen Notizen und Bezeichnungen auch den Rest deiner Gesundheit erwähnen. Wer einen Bereich bekommt, bekommt alles, was darin steht.",
|
||||
"scopeAggregateNote": "Die Übersicht auf dem Dashboard, der Health Score und die Tageszusammenfassung erscheinen nur, wenn die gesamte Akte geteilt ist.",
|
||||
"scopeAggregateNote": "Die Übersicht auf dem Dashboard, der Gesundheitsscore und die Tageszusammenfassung erscheinen nur, wenn die gesamte Akte geteilt ist.",
|
||||
"scopeDocumentsNote": "Ein Dokument kann alles enthalten. Teile diesen Bereich nur, wenn die Person alles davon lesen darf.",
|
||||
"scopeEmptyHint": "Wähle mindestens einen Bereich aus oder teile wieder die gesamte Akte.",
|
||||
"scopeChooseHint": "Wähle, ob die gesamte Akte oder nur einzelne Bereiche geteilt werden.",
|
||||
|
||||
+1
-1
@@ -5508,7 +5508,7 @@
|
||||
"deviceTypeToggle": "Device-type priority ({count})",
|
||||
"deviceTypeHelp": "Within a source, which device wins. When an Apple Watch and an iPhone both record steps, the higher-ranked device-type counts as the canonical reading.",
|
||||
"deviceTypeDefault": "Default device-type ladder",
|
||||
"resetDeviceTypes": "Reset device-types",
|
||||
"resetDeviceTypes": "Reset device types",
|
||||
"metrics": {
|
||||
"steps": "Steps",
|
||||
"activeEnergy": "Active energy",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* The German Settings glossary: one word per thing.
|
||||
*
|
||||
* Settings used "Provider" beside "Anbieter", "Default" beside "Standard",
|
||||
* and the score as "Health Score", "Health-Score", "Gesundheits-Score" and
|
||||
* "Gesundheitsscore", sometimes on one screen. The settled words are
|
||||
* Anbieter, Standard and Gesundheitsscore; the Insights area is "Insights"
|
||||
* (the name already dominant in the catalog). This reads the German catalog
|
||||
* under the Settings namespaces and fails on the retired spellings.
|
||||
*
|
||||
* Product names are not glossary words: "ChatGPT", "OpenAI" and the like are
|
||||
* left alone, and so is "Provider" inside a code-shaped token.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import de from "../../messages/de.json";
|
||||
|
||||
const NAMESPACES = ["settings", "thresholds"] as const;
|
||||
|
||||
const RETIRED: ReadonlyArray<{ pattern: RegExp; use: string }> = [
|
||||
{ pattern: /\bProvider\b/, use: "Anbieter" },
|
||||
{ pattern: /\bDefaults?\b/, use: "Standard" },
|
||||
{ pattern: /\bHealth[- ]?Score\b/i, use: "Gesundheitsscore" },
|
||||
{ pattern: /\bGesundheits-Score\b/, use: "Gesundheitsscore" },
|
||||
];
|
||||
|
||||
function walk(node: unknown, path: string, out: Array<[string, string]>) {
|
||||
if (typeof node === "string") out.push([path, node]);
|
||||
else if (node && typeof node === "object") {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
walk(value, path ? `${path}.${key}` : key, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("German Settings glossary", () => {
|
||||
const strings: Array<[string, string]> = [];
|
||||
for (const ns of NAMESPACES) {
|
||||
walk((de as Record<string, unknown>)[ns], ns, strings);
|
||||
}
|
||||
|
||||
it("reads the catalog (an empty read is a broken walker)", () => {
|
||||
expect(strings.length).toBeGreaterThan(500);
|
||||
});
|
||||
|
||||
it("uses the settled words", () => {
|
||||
const offenders: string[] = [];
|
||||
for (const [key, value] of strings) {
|
||||
for (const { pattern, use } of RETIRED) {
|
||||
if (pattern.test(value)) offenders.push(`${key}: "${value}" → ${use}`);
|
||||
}
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -218,7 +218,7 @@ describe("<AiSection> — dropdown-driven UX (B2)", () => {
|
||||
// both ship without the "KI"/"AI" prefix per the project-voice rule
|
||||
// ("Aktiver KI-Provider" → "Aktiver Provider"). The picker below it is
|
||||
// labelled for what it does: it chooses the form, not the order.
|
||||
expect(html).toContain("Aktiver Provider");
|
||||
expect(html).toContain("Aktiver Anbieter");
|
||||
expect(html).toContain("Anbieter einrichten");
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user