Files
HealthLog/src/lib/time-window-format.ts
T
Marc-André Bombeck 3c4bbdef4f feat(i18n): MaintainershipBanner notice + foundation for 4 new locales
Add the four AI-initial locale bundles (FR / ES / IT / PL) and a
small dismissible MaintainershipBanner strip that paints at the top
of the auth shell whenever the active locale falls outside the
maintainer-curated set (currently DE + EN). The banner names the
AI-initial provenance and links to a GitHub translation-feedback
template so users can contribute fixes back. Dismissal persists
per-locale in localStorage so dismissing the FR banner does not
hide the IT banner once the user switches language.

The bundles are generated by scripts/i18n/build-locale.py — a layered
walk of messages/en.json that applies a vocabulary lookup keyed on
the lowercase EN value, preserves every {interpolation} placeholder
and <tag> marker verbatim, and falls through to the English value
for long-tail prose the script can't translate. The banner advertises
this status so users know what they are looking at. All four locale
files pass the i18n-locale-integrity test verbatim — full key
parity to en.json, no empty values, no TODO/FIXME placeholders,
no value-equals-last-segment matches.

Foundation work bundled with the banner so the build never lands in
a half-wired state:
- locales array extended to ["de","en","fr","es","it","pl"] with
  matching localeLabels + MAINTAINED_LOCALES set + isMaintainedLocale
  helper in src/lib/i18n/config.ts.
- INTL_LOCALE_MAP and parseLocaleFromAcceptLanguage in
  format-locale.ts gained the four new tags so SSR locale resolution
  and Intl number/date formatting work end-to-end.
- i18n/context.tsx and i18n/server-translator.ts import the four new
  JSON bundles alongside DE + EN.
- Coach (src/lib/ai/coach/system-prompt.ts) and the strict insights
  generator (src/lib/ai/prompts/insight-generator.ts) ship the EN
  system prompt with a one-line REPLY LANGUAGE footer for the four
  AI-initial locales. The DE branch keeps its hand-curated body
  verbatim. Routing through the EN system prompt preserves every
  safety contract (zero hallucinations, no dose-prescription,
  evidence-block sentinel format) without re-validating four new
  language bodies.
- Five locale-parameter signatures (no-key-fallbacks, glp1-plateau,
  time-window-format, target-prompts, medication-form) widened
  from "de" | "en" to the canonical Locale type. Helpers that
  ship DE+EN copy route non-DE locales to the EN body, mirroring
  the same fallback chain the JSON bundles use.
- OpenGraph alternateLocale on the root layout lists the four new
  BCP-47 tags so social-link previews advertise the broader
  language coverage.

Tests:
- 4 new SSR tests pin the locale gate (EN/DE hidden, FR/ES/IT/PL
  visible by default, dismissed flag honoured) and the re-exported
  isMaintainedLocale helper.
- parseLocaleFromAcceptLanguage gains a FR/ES/IT/PL coverage block
  alongside the existing DE / EN / unknown branches.
- The existing i18n-locale-integrity suite auto-discovers each new
  messages/<locale>.json and runs the 25-sub-test parity + value-
  quality battery against it — all six locales pass.

LocalePicker is data-driven (settings and onboarding both iterate
the locales array) so promoting the array to 6 entries grows both
dropdowns without per-call-site changes. The PDF doctor-report
renderer is t()-driven for every label, so the four new JSON
bundles give that renderer FR/ES/IT/PL output without touching
doctor-report-pdf-core.ts.
2026-05-14 15:40:34 +02:00

38 lines
1.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export function formatTimeWindowPart(value: string): string {
const m = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
if (!m) return value;
const hh = m[1].padStart(2, "0");
const mm = m[2];
return `${hh}:${mm}`;
}
import type { Locale } from "@/lib/i18n/config";
/**
* 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.
*
* v1.4.25 W9e — locales other than DE render with the language-neutral
* "08:00 – 12:00" form (the same as EN) until a locale-specific suffix
* ships. The DE branch keeps its trailing "Uhr" suffix verbatim.
*/
export function formatTimeWindowRange(
start: string,
end: string,
locale?: Locale,
): string {
const s = formatTimeWindowPart(start);
const e = formatTimeWindowPart(end);
if (locale === undefined || locale === "de") {
return `${s} bis ${e} Uhr`;
}
return `${s} – ${e}`;
}