i18n+ui: backlog quick-wins sweep (F-32/33/34/35/44/45/47/50/58/62 + 2 mediums)

Cluster of the v1.4.21 carry-over items that each landed in ≤5 LOC.

- F-32 — drop the duplicate help paragraph beneath the dashboard
  settings header; the page-level description already covers it
- F-33 / F-34 — replace `&` with "and"/"und" in the measurements and
  mood subtitles
- F-35 — unify the test-notification button label
  (notificationTestSend) with the user-facing notificationStatus.sendTest
- F-44 — drop the `LOKI_ENDPOINT` env-var name from the user-facing
  monitoring help text; reads as "Loki integration" instead
- F-45 — admin/users table column reads "Name" instead of repeating
  the page title "Users"
- F-47 — /notifications event-matrix first column reads "Event" instead
  of repeating the page title "Notifications" — new notifications.eventColumn key
- F-50 — soften every DE error-toast "Bitte erneut versuchen" to
  "Bitte versuche es erneut", matching the friendlier tone we already
  use in the Coach error path
- F-58 — admin/reminders section header swaps Clock for Bell so the
  card icon matches the admin-shell nav tile
- F-62 — DE dashboard `greetingSalutation` reads "Hallo, {name}"
  instead of the English "Hi, {name}"
- D-CR-M-03 — formatTokenName regex broadens to non-Z ISO offsets so a
  `+02:00` timestamp suffix renders as the locale-aware chunk like a
  `Z` suffix already does
- D-DSGN-M-04 — moodLog "Copy" button uses common.copy instead of
  stripping the `!` off common.copied

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 21:40:05 +02:00
1 parent 15796c148b
commit 1499e6f83c
8 files changed
+46 -23

No files matched your search

+13 -11
View File
@@ -216,7 +216,7 @@
},
"welcomeBack": "{greeting}, willkommen zurück.",
"welcomeBackWithName": "{greeting} {name}, willkommen zurück.",
"greetingSalutation": "Hi, {name}",
"greetingSalutation": "Hallo, {name}",
"streakLabel": "Tage in Folge",
"weight": "Gewicht",
"bloodPressure": "Blutdruck",
@@ -262,7 +262,7 @@
},
"measurements": {
"title": "Messungen",
"subtitle": "Gewicht, Blutdruck, Puls & mehr",
"subtitle": "Gewicht, Blutdruck, Puls und mehr",
"addMeasurement": "Hinzufügen",
"editMeasurement": "Messwert bearbeiten",
"type": "Typ",
@@ -327,7 +327,7 @@
},
"mood": {
"title": "Stimmung",
"subtitle": "Stimmungsverlauf & Einträge",
"subtitle": "Stimmungsverlauf und Einträge",
"addEntry": "Hinzufügen",
"editEntry": "Eintrag bearbeiten",
"moodLevel": "Stimmung",
@@ -744,7 +744,7 @@
"confidenceMittel": "Mittel",
"confidenceGering": "Gering",
"loadError": "Auswertungen konnten nicht geladen werden",
"generationFailed": "Die Analyse konnte nicht erstellt werden. Bitte erneut versuchen.",
"generationFailed": "Die Analyse konnte nicht erstellt werden. Bitte versuche es erneut.",
"title": "Insights",
"subtitle": "KI-basierte Analyse deiner Gesundheitsdaten",
"overviewSubtitle": "Trends, Risiken, Fortschritt auf einen Blick",
@@ -1071,10 +1071,10 @@
"targetSys": "Systolisch (mmHg)",
"targetDia": "Diastolisch (mmHg)",
"targetWeight": "Zielgewicht (kg)",
"errorProfile": "Dein Profil konnte nicht gespeichert werden. Bitte erneut versuchen.",
"errorMedication": "Das Medikament „{name}\" konnte nicht angelegt werden. Bitte erneut versuchen oder später unter „Medikamente\" hinzufügen.",
"errorTargets": "Deine Zielwerte konnten nicht gespeichert werden. Bitte erneut versuchen.",
"errorGeneric": "Beim Abschluss des Setups ist etwas schiefgelaufen. Bitte erneut versuchen.",
"errorProfile": "Dein Profil konnte nicht gespeichert werden. Bitte versuche es erneut.",
"errorMedication": "Das Medikament „{name}\" konnte nicht angelegt werden. Bitte versuche es erneut oder lege es später unter „Medikamente\" an.",
"errorTargets": "Deine Zielwerte konnten nicht gespeichert werden. Bitte versuche es erneut.",
"errorGeneric": "Beim Abschluss des Setups ist etwas schiefgelaufen. Bitte versuche es erneut.",
"medScheduleHint": "Wir legen ein Standard-Erinnerungsfenster an (08:00–09:00 täglich). Du kannst es jederzeit unter „Medikamente\" anpassen.",
"v2": {
"title": "Willkommen bei HealthLog",
@@ -1306,7 +1306,7 @@
"passkeyNotSupported": "Dein Browser oder Gerät unterstützt Passkeys noch nicht. Bitte einen anderen Browser oder ein anderes Gerät verwenden.",
"passkeyAlreadyRegistered": "Dieses Gerät ist bereits als Passkey für dein Konto registriert.",
"passkeySecurityBlocked": "Dein Browser hat die Passkey-Anfrage aus Sicherheitsgründen blockiert. Bitte auf einer vertrauenswürdigen HTTPS-URL erneut versuchen.",
"passkeyTimeout": "Die Passkey-Aufforderung ist abgelaufen. Bitte erneut versuchen.",
"passkeyTimeout": "Die Passkey-Aufforderung ist abgelaufen. Bitte versuche es erneut.",
"passkeyUnknownError": "Passkey-Registrierung fehlgeschlagen: {message}",
"deletePasskey": "Passkey löschen?",
"deletePasskeyDescription": "Der Passkey wird unwiderruflich gelöscht.",
@@ -1810,6 +1810,7 @@
"bugReportNotConfigured": "Bugreport ist noch nicht vollständig konfiguriert.",
"bugReportSave": "Bugreport speichern",
"userManagement": "Benutzerverwaltung",
"userName": "Name",
"userEmail": "E-Mail",
"userRole": "Rolle",
"userPasskeys": "Passkeys",
@@ -1885,7 +1886,7 @@
"reminderMissedMinutesDescription": "Minuten nach Fenster-Ende bis die Einnahme als verpasst gilt.",
"reminderThresholdsSave": "Schwellenwerte speichern",
"notificationTest": "Benachrichtigungen testen",
"notificationTestSend": "Test-Notification senden",
"notificationTestSend": "Test senden",
"notificationTestSuccess": "Test-Notification wurde gesendet",
"notificationTestFailed": "Test-Notification konnte nicht gesendet werden",
"reminderCheckRun": "Reminder-Check ausführen",
@@ -2045,7 +2046,7 @@
"app-logs": {
"title": "App-Logs",
"subtitle": "Letzte strukturierte Wide-Events dieses Prozesses anzeigen.",
"processNote": "Es werden nur App-Prozess-Logs angezeigt — Worker-Logs landen in Loki, sofern LOKI_ENDPOINT konfiguriert ist. Der Puffer ist flüchtig und auf 500 Einträge begrenzt.",
"processNote": "Es werden nur App-Prozess-Logs angezeigt — Worker-Logs landen in Loki, sofern die Integration konfiguriert ist. Der Puffer ist flüchtig und auf 500 Einträge begrenzt.",
"filterTraceId": "Trace-ID",
"filterAction": "Aktion enthält…",
"filterLevel": "Level",
@@ -2433,6 +2434,7 @@
},
"notifications": {
"title": "Benachrichtigungen",
"eventColumn": "Ereignis",
"subtitle": "Wähle aus, welche Benachrichtigungen du über welchen Kanal erhalten möchtest.",
"noChannels": "Konfiguriere zuerst einen Benachrichtigungskanal in den Einstellungen.",
"goToSettings": "Zu den Einstellungen",
+6 -4
View File
@@ -262,7 +262,7 @@
},
"measurements": {
"title": "Measurements",
"subtitle": "Weight, blood pressure, pulse & more",
"subtitle": "Weight, blood pressure, pulse and more",
"addMeasurement": "Add",
"editMeasurement": "Edit measurement",
"type": "Type",
@@ -327,7 +327,7 @@
},
"mood": {
"title": "Mood",
"subtitle": "Mood tracker & entries",
"subtitle": "Mood tracker and entries",
"addEntry": "Add",
"editEntry": "Edit entry",
"moodLevel": "Mood",
@@ -1810,6 +1810,7 @@
"bugReportNotConfigured": "Bug report is not fully configured yet.",
"bugReportSave": "Save bug report settings",
"userManagement": "User Management",
"userName": "Name",
"userEmail": "Email",
"userRole": "Role",
"userPasskeys": "Passkeys",
@@ -1885,7 +1886,7 @@
"reminderMissedMinutesDescription": "Minutes after window end until the intake is considered missed.",
"reminderThresholdsSave": "Save thresholds",
"notificationTest": "Test Notifications",
"notificationTestSend": "Send test notification",
"notificationTestSend": "Send test",
"notificationTestSuccess": "Test notification sent",
"notificationTestFailed": "Failed to send test notification",
"reminderCheckRun": "Run reminder check",
@@ -2045,7 +2046,7 @@
"app-logs": {
"title": "App Logs",
"subtitle": "Tail the most recent structured wide-events emitted by this process.",
"processNote": "Showing app-process logs only — worker-process logs go to Loki when LOKI_ENDPOINT is configured. Buffer is volatile and capped at 500 entries.",
"processNote": "Showing app-process logs only — worker-process logs go to Loki when that integration is configured. Buffer is volatile and capped at 500 entries.",
"filterTraceId": "Trace ID",
"filterAction": "Action contains…",
"filterLevel": "Level",
@@ -2433,6 +2434,7 @@
},
"notifications": {
"title": "Notifications",
"eventColumn": "Event",
"subtitle": "Choose which notifications you want to receive through which channel.",
"noChannels": "Configure a notification channel in Settings first.",
"goToSettings": "Go to Settings",
+1 -1
View File
@@ -191,7 +191,7 @@ export default function NotificationsPage() {
<thead>
<tr className="border-border border-b">
<th className="text-muted-foreground px-4 py-3 text-left text-xs font-medium tracking-wider uppercase">
{t("notifications.title")}
{t("notifications.eventColumn")}
</th>
{activeChannels.map((ch) => (
<th
@@ -63,8 +63,13 @@ function TruncatedCell({
* "iOS auto-login · 05.05.2026 19:46" while leaving any non-ISO suffix
* (manual names, device fingerprints, etc.) untouched.
*/
// v1.4.22 D / D-CR-M-03 — broaden the ISO match to include non-Z
// offsets (e.g. `+02:00`, `-05:30`). The previous regex only matched
// UTC (`Z`), so a token name carrying a local-tz timestamp slipped
// through unformatted and rendered the raw `2026-05-05T19:46:20.603+02:00`
// suffix instead of the locale-aware `05.05.2026 21:46` chunk.
const TOKEN_NAME_ISO_RE =
/^(.+?)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)(.*)$/;
/^(.+?)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2}))(.*)$/;
// Locale-fixed dd.MM.yyyy HH:mm in the Europe/Berlin display zone — the
// rest of the app shows timestamps in Berlin time (`format-locale.ts`),
+4 -1
View File
@@ -115,7 +115,10 @@ export function RemindersSection() {
return (
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center gap-2">
<Clock className="text-primary h-5 w-5" />
{/* v1.4.22 D / F-58 — icon swap so the section header matches
the admin-shell nav tile (Bell). Clock now lives on the
timing chips inline instead of competing as the page icon. */}
<Bell className="text-primary h-5 w-5" />
<div className="text-lg font-semibold">
{t("admin.medicationReminders")}
</div>
@@ -311,7 +311,7 @@ export function UserManagementSection() {
<thead>
<tr className="text-muted-foreground border-b text-xs">
<th className="px-3 py-2 text-left font-medium">
{t("admin.users")}
{t("admin.userName")}
</th>
<th className="px-3 py-2 text-left font-medium">
{t("admin.userEmail")}
@@ -181,9 +181,17 @@ export function DashboardLayoutSection({ id }: { id: string }) {
{t("dashboard.layoutReset")}
</Button>
</div>
<p className="text-muted-foreground text-sm">
{t("dashboard.customizeSubtitle")}
</p>
{/*
v1.4.22 D / F-32 — the surrounding `<DashboardSection>` page
already renders a `settings.sections.dashboard.description`
paragraph beneath the H1. The previous in-card help line
repeated the same idea ("Choose which cards appear …
Defaults work out of the box.") right next to the comparison
picker, giving the page three muted-foreground help blocks
stacked on top of each other. Removing this duplicate keeps
the comparison picker as the only thing between the header
and the widget table.
*/}
{/* v1.4.16 phase B8 — comparison baseline picker. Lives at the top
of the section because it changes how every chart + tile below
@@ -721,7 +721,10 @@ function MoodLogCard({
setMsgType("success");
}}
>
{t("common.copied").replace("!", "")}
{/* v1.4.22 D / D-DSGN-M-04 — use the proper
common.copy key instead of stripping the
exclamation off common.copied. */}
{t("common.copy")}
</Button>
</div>
<p className="text-muted-foreground mt-1 text-xs">