From b8ca1c74f27f5b0d27b038f501ba0266dc118fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 21:32:15 +0200 Subject: [PATCH] i18n(relative-time): branch on count === 1 to render singular forms formatRelativeTime always read the *Ago key with count=1, so a German user reading the Insights hero saw 'vor 1 Minuten' / 'vor 1 Stunden'. Splits each bucket into One / Other variants across all locales and branches on count === 1 in the helper, matching the dashboard.staleHintWeeksOne / Other pattern already in the bundle. --- messages/de.json | 9 ++++++--- messages/en.json | 9 ++++++--- messages/es.json | 9 ++++++--- messages/fr.json | 9 ++++++--- messages/it.json | 9 ++++++--- messages/pl.json | 9 ++++++--- src/lib/i18n/relative-time.ts | 30 +++++++++++++++++++++++++++--- 7 files changed, 63 insertions(+), 21 deletions(-) diff --git a/messages/de.json b/messages/de.json index 2ba830c05..04af2a658 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1147,9 +1147,12 @@ "providerRateLimitBody": "Anbieter ist temporär überlastet; wieder verfügbar in ~5 min." }, "relativeJustNow": "gerade eben", - "relativeMinutesAgo": "vor {count} Minuten", - "relativeHoursAgo": "vor {count} Stunden", - "relativeDaysAgo": "vor {count} Tagen", + "relativeMinutesAgoOne": "vor {count} Minute", + "relativeMinutesAgoOther": "vor {count} Minuten", + "relativeHoursAgoOne": "vor {count} Stunde", + "relativeHoursAgoOther": "vor {count} Stunden", + "relativeDaysAgoOne": "vor {count} Tag", + "relativeDaysAgoOther": "vor {count} Tagen", "navOverview": "Übersicht", "navBloodPressure": "Blutdruck", "navWeight": "Gewicht", diff --git a/messages/en.json b/messages/en.json index 1032b65d4..6c7ca9eb0 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1147,9 +1147,12 @@ "providerRateLimitBody": "Provider temporarily rate-limited; retry in ~5 min." }, "relativeJustNow": "just now", - "relativeMinutesAgo": "{count} minutes ago", - "relativeHoursAgo": "{count} hours ago", - "relativeDaysAgo": "{count} days ago", + "relativeMinutesAgoOne": "{count} minute ago", + "relativeMinutesAgoOther": "{count} minutes ago", + "relativeHoursAgoOne": "{count} hour ago", + "relativeHoursAgoOther": "{count} hours ago", + "relativeDaysAgoOne": "{count} day ago", + "relativeDaysAgoOther": "{count} days ago", "navOverview": "Overview", "navBloodPressure": "Blood Pressure", "navWeight": "Weight", diff --git a/messages/es.json b/messages/es.json index 56a1ad844..ef1e71759 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1147,9 +1147,12 @@ "settingsContextHint": "Bloques opcionales en los que el coach puede apoyarse. Si desactivas alguno, se omitirá en cada respuesta." }, "relativeJustNow": "ahora mismo", - "relativeMinutesAgo": "hace {count} minutos", - "relativeHoursAgo": "hace {count} horas", - "relativeDaysAgo": "hace {count} días", + "relativeMinutesAgoOne": "hace {count} minuto", + "relativeMinutesAgoOther": "hace {count} minutos", + "relativeHoursAgoOne": "hace {count} hora", + "relativeHoursAgoOther": "hace {count} horas", + "relativeDaysAgoOne": "hace {count} día", + "relativeDaysAgoOther": "hace {count} días", "navOverview": "Resumen", "navBloodPressure": "Presión arterial", "navWeight": "Peso", diff --git a/messages/fr.json b/messages/fr.json index bde36a015..855306be8 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1147,9 +1147,12 @@ "settingsContextHint": "Blocs optionnels sur lesquels le coach peut s’appuyer. Si tu en désactives un, il sera omis dans chaque réponse." }, "relativeJustNow": "à l’instant", - "relativeMinutesAgo": "il y a {count} minutes", - "relativeHoursAgo": "il y a {count} heures", - "relativeDaysAgo": "il y a {count} jours", + "relativeMinutesAgoOne": "il y a {count} minute", + "relativeMinutesAgoOther": "il y a {count} minutes", + "relativeHoursAgoOne": "il y a {count} heure", + "relativeHoursAgoOther": "il y a {count} heures", + "relativeDaysAgoOne": "il y a {count} jour", + "relativeDaysAgoOther": "il y a {count} jours", "navOverview": "Vue d'ensemble", "navBloodPressure": "Tension artérielle", "navWeight": "Poids", diff --git a/messages/it.json b/messages/it.json index 3f2201b77..a3ae5e91f 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1147,9 +1147,12 @@ "settingsContextHint": "Blocchi opzionali su cui il coach può basarsi. Disattivandone uno, viene omesso da ogni risposta." }, "relativeJustNow": "proprio ora", - "relativeMinutesAgo": "{count} minuti fa", - "relativeHoursAgo": "{count} ore fa", - "relativeDaysAgo": "{count} giorni fa", + "relativeMinutesAgoOne": "{count} minuto fa", + "relativeMinutesAgoOther": "{count} minuti fa", + "relativeHoursAgoOne": "{count} ora fa", + "relativeHoursAgoOther": "{count} ore fa", + "relativeDaysAgoOne": "{count} giorno fa", + "relativeDaysAgoOther": "{count} giorni fa", "navOverview": "Panoramica", "navBloodPressure": "Pressione arteriosa", "navWeight": "Peso", diff --git a/messages/pl.json b/messages/pl.json index 9a4c2827e..70819f8fd 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -1147,9 +1147,12 @@ "settingsContextHint": "Opcjonalne bloki, na których coach może się opierać. Wyłączenie pominie blok w każdej odpowiedzi." }, "relativeJustNow": "przed chwilą", - "relativeMinutesAgo": "{count} minut temu", - "relativeHoursAgo": "{count} godzin temu", - "relativeDaysAgo": "{count} dni temu", + "relativeMinutesAgoOne": "{count} minutę temu", + "relativeMinutesAgoOther": "{count} minut temu", + "relativeHoursAgoOne": "{count} godzinę temu", + "relativeHoursAgoOther": "{count} godzin temu", + "relativeDaysAgoOne": "{count} dzień temu", + "relativeDaysAgoOther": "{count} dni temu", "navOverview": "Przegląd", "navBloodPressure": "Ciśnienie krwi", "navWeight": "Waga", diff --git a/src/lib/i18n/relative-time.ts b/src/lib/i18n/relative-time.ts index 64b23dad8..d6a901396 100644 --- a/src/lib/i18n/relative-time.ts +++ b/src/lib/i18n/relative-time.ts @@ -7,6 +7,11 @@ * lift the body here so a future i18n key change lands once. * * Buckets: <1m → just-now, <1h → minutes, <24h → hours, else days. + * + * v1.4.43 H6 — pluralisation. Each bucket now branches on `count === 1` + * to read the *One key (singular) or *Other key (plural), matching the + * dashboard.staleHintWeeksOne / Other pattern already in the bundle. + * Eliminates the "vor 1 Minuten" grammar bug a German user spotted. */ export function formatRelativeTime( iso: string, @@ -17,9 +22,28 @@ export function formatRelativeTime( const diffMs = Date.now() - target; if (diffMs < 60_000) return t("insights.relativeJustNow"); const minutes = Math.floor(diffMs / 60_000); - if (minutes < 60) return t("insights.relativeMinutesAgo", { count: minutes }); + if (minutes < 60) { + return t( + minutes === 1 + ? "insights.relativeMinutesAgoOne" + : "insights.relativeMinutesAgoOther", + { count: minutes }, + ); + } const hours = Math.floor(minutes / 60); - if (hours < 24) return t("insights.relativeHoursAgo", { count: hours }); + if (hours < 24) { + return t( + hours === 1 + ? "insights.relativeHoursAgoOne" + : "insights.relativeHoursAgoOther", + { count: hours }, + ); + } const days = Math.floor(hours / 24); - return t("insights.relativeDaysAgo", { count: days }); + return t( + days === 1 + ? "insights.relativeDaysAgoOne" + : "insights.relativeDaysAgoOther", + { count: days }, + ); }