diff --git a/src/components/medications/medication-card.tsx b/src/components/medications/medication-card.tsx index f7abbabc0..1ddcd4c1a 100644 --- a/src/components/medications/medication-card.tsx +++ b/src/components/medications/medication-card.tsx @@ -193,7 +193,7 @@ function isLastIntakeInCurrentWindow( export function MedicationCard({ medication, onEdit }: MedicationCardProps) { const queryClient = useQueryClient(); - const { t } = useTranslations(); + const { t, locale } = useTranslations(); const fmt = useFormatters(); const [intakeLoading, setIntakeLoading] = useState(null); @@ -438,6 +438,7 @@ export function MedicationCard({ medication, onEdit }: MedicationCardProps) { {formatTimeWindowRange( currentWindowStatus.schedule!.windowStart, currentWindowStatus.schedule!.windowEnd, + locale, )}

@@ -490,7 +491,7 @@ export function MedicationCard({ medication, onEdit }: MedicationCardProps) { {t("medications.nextIntake")} {" "} {dayLabel && `${dayLabel}, `} - {formatTimeWindowRange(s.windowStart, s.windowEnd)} + {formatTimeWindowRange(s.windowStart, s.windowEnd, locale)} {s.label && ( ({s.label}) )} diff --git a/src/components/medications/medication-form.tsx b/src/components/medications/medication-form.tsx index 91417ed8b..32b31300c 100644 --- a/src/components/medications/medication-form.tsx +++ b/src/components/medications/medication-form.tsx @@ -131,16 +131,17 @@ function formatNextWindowSummary( schedule: Schedule, t: TranslateFn, formatShortDate: (date: Date) => string, + locale: "de" | "en", ): string { const isSimpleDaily = schedule.daysOfWeek.length === 0 && schedule.intervalWeeks === 1; if (isSimpleDaily) { - return `${t("medications.nextSchedulePrefix")} ${t("medications.nextScheduleDaily")}, ${formatTimeWindowRange(schedule.windowStart, schedule.windowEnd)}`; + return `${t("medications.nextSchedulePrefix")} ${t("medications.nextScheduleDaily")}, ${formatTimeWindowRange(schedule.windowStart, schedule.windowEnd, locale)}`; } const nextDate = getNextRecurrenceDate(schedule); const nextText = nextDate - ? `${formatShortDate(nextDate)}, ${formatTimeWindowRange(schedule.windowStart, schedule.windowEnd)}` + ? `${formatShortDate(nextDate)}, ${formatTimeWindowRange(schedule.windowStart, schedule.windowEnd, locale)}` : "—"; return `${t("medications.nextSchedulePrefix")} ${nextText}`; } @@ -234,7 +235,7 @@ export function MedicationForm({ initial, }: MedicationFormProps) { const queryClient = useQueryClient(); - const { t } = useTranslations(); + const { t, locale } = useTranslations(); const fmt = useFormatters(); const doseUnits = [ @@ -565,7 +566,7 @@ export function MedicationForm({ `${t("medications.formSchedule")} ${i + 1}`} {" "} - ({formatNextWindowSummary(s, t, fmt.dateShort)}) + ({formatNextWindowSummary(s, t, fmt.dateShort, locale)})

diff --git a/src/lib/__tests__/time-window-format.test.ts b/src/lib/__tests__/time-window-format.test.ts new file mode 100644 index 000000000..106f0b28a --- /dev/null +++ b/src/lib/__tests__/time-window-format.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from "vitest"; + +import { + formatTimeWindowPart, + formatTimeWindowRange, +} from "@/lib/time-window-format"; + +describe("formatTimeWindowPart", () => { + test("zero-pads single-digit hours", () => { + expect(formatTimeWindowPart("8:00")).toBe("08:00"); + }); + + test("preserves two-digit hours", () => { + expect(formatTimeWindowPart("19:30")).toBe("19:30"); + }); + + test("returns input unchanged when not a HH:MM string", () => { + expect(formatTimeWindowPart("invalid")).toBe("invalid"); + }); +}); + +describe("formatTimeWindowRange", () => { + test("German locale renders 'bis ... Uhr' (default behaviour preserved)", () => { + expect(formatTimeWindowRange("19:00", "23:00", "de")).toBe( + "19:00 bis 23:00 Uhr", + ); + }); + + test("English locale renders 'from ... to ...' instead of mixed-language 'bis Uhr'", () => { + // Regression for v1.4.19 F-01: app rendered "Today, 19:00 bis 23:00 Uhr" + // for English users because the formatter hard-coded German. + expect(formatTimeWindowRange("19:00", "23:00", "en")).toBe( + "19:00 – 23:00", + ); + }); + + test("falls back to the German format when no locale is supplied", () => { + // Backwards-compat: existing call sites that have not been migrated + // continue to render the historic German string. + expect(formatTimeWindowRange("08:00", "12:00")).toBe( + "08:00 bis 12:00 Uhr", + ); + }); +}); diff --git a/src/lib/time-window-format.ts b/src/lib/time-window-format.ts index 037672021..858e47f82 100644 --- a/src/lib/time-window-format.ts +++ b/src/lib/time-window-format.ts @@ -6,6 +6,26 @@ export function formatTimeWindowPart(value: string): string { return `${hh}:${mm}`; } -export function formatTimeWindowRange(start: string, end: string): string { - return `${formatTimeWindowPart(start)} bis ${formatTimeWindowPart(end)} Uhr`; +/** + * Render a daily intake time window like "08:00 bis 12:00 Uhr" / "08:00 – 12:00". + * + * The historic call sites (see `medication-card.tsx`, `medication-form.tsx`) + * never passed a locale, so the renderer hard-coded the German "bis ... Uhr" + * suffix and the English locale ended up with mixed-language strings such as + * `"Today, 19:00 bis 23:00 Uhr"`. Pass the active UI locale so that the right + * separator + suffix are picked. When no locale is provided we keep the + * legacy German output for backwards compatibility with any consumer we may + * have missed. + */ +export function formatTimeWindowRange( + start: string, + end: string, + locale?: "de" | "en", +): string { + const s = formatTimeWindowPart(start); + const e = formatTimeWindowPart(end); + if (locale === "en") { + return `${s} – ${e}`; + } + return `${s} bis ${e} Uhr`; }