fix(medications): localise time-window range string

`formatTimeWindowRange` used to hard-code the German "bis ... Uhr"
suffix, so an English-locale user saw "Today, 19:00 bis 23:00 Uhr"
on the medication card and the medication form. Pass the active
locale through and emit "19:00 – 23:00" for English while keeping
the existing German output for DE users (and unchanged for legacy
call sites that still don't pass a locale).

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 13:29:07 +02:00
1 parent 79ad5c47a9
commit 180f46c24e
4 files changed
+74 -8

No files matched your search

@@ -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<string | null>(null);
@@ -438,6 +438,7 @@ export function MedicationCard({ medication, onEdit }: MedicationCardProps) {
{formatTimeWindowRange(
currentWindowStatus.schedule!.windowStart,
currentWindowStatus.schedule!.windowEnd,
locale,
)}
</span>
</p>
@@ -490,7 +491,7 @@ export function MedicationCard({ medication, onEdit }: MedicationCardProps) {
{t("medications.nextIntake")}
</span>{" "}
{dayLabel && `${dayLabel}, `}
{formatTimeWindowRange(s.windowStart, s.windowEnd)}
{formatTimeWindowRange(s.windowStart, s.windowEnd, locale)}
{s.label && (
<span className="hidden sm:inline"> ({s.label})</span>
)}
@@ -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}`}
</span>{" "}
<span className="text-foreground/70 font-normal">
({formatNextWindowSummary(s, t, fmt.dateShort)})
({formatNextWindowSummary(s, t, fmt.dateShort, locale)})
</span>
</p>
<DropdownMenu>
@@ -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",
);
});
});
+22 -2
View File
@@ -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`;
}