mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
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.
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
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}`;
|
||
}
|