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`;
}