You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lay the foundation for multi-language support across HFS, so the user
interface can be presented in multiple languages — starting with English,
Spanish, and German — and so every layer from the browser to the terminology
backend agrees on the requested language.
Today HFS has no UI i18n layer: UI chrome and error prose are English-only, and
there is no locale-negotiation or message-catalog machinery. This issue
establishes the structure, conventions, and rules of the road — plus a
working skeleton and a discussion document — so the first localization PRs set
the right precedent rather than an accidental one. As with the UI foundation
(#186), the goal is not a fully translated product; it is to answer, with a
running structure and a written rationale: "When we add a language to HFS,
where does everything go, and why?"
The one thing HFS already does well here is terminology: HTS imports
multi-language SNOMED CT (and LOINC linguistic variants) and serves the right
term per displayLanguage / Accept-Language. This foundation builds on that
rather than reinventing it.
Motivation
Our UI must be usable by Spanish- and German-speaking users, not just English.
Localization is not one thing — UI chrome, locale negotiation, API/error
messages, FHIR content pass-through, terminology display, and formatting are
separate layers. Without an agreed model they get conflated, producing bugs
like a Spanish nav bar over English SNOMED terms.
Approach (proposed — finalized in the discussion doc)
One negotiated locale per request, computed once, threaded to every layer —
the UI chrome, the error text, and the terminology displayLanguage all read
the same value.
Locale negotiation middleware producing a RequestLocale, with precedence:
explicit override (?lang= / cookie from the switcher) → saved user preference
(feat/user-ui-settings) → Accept-Language (RFC 4647 Lookup) → en default.
Fallback chain, never a crash or blank: negotiated → base language → en.
A half-translated locale is always usable.
Errors (OperationOutcome.text / issue.details.text) come from the same
catalogs (error-* keys); machine-facing issue.code / codings stay stable.
Terminology reuses HTS as-is — the request locale is passed straight
through as displayLanguage; we do not build a second UI-local translation
table for clinical terms.
Full detail — including the front-to-back request-flow diagram, the six
localization layers, and the terminology integration — is in the discussion
document delivered on the branch: docs/multi-language.md.
File placement — rules of the road (ratified in the doc)
Where things go
locales/<locale>/main.ftl — all translatable UI text; en is the source
of truth and defines the canonical key set.
Locale-negotiation middleware — one RequestLocale per request, in request
extensions, forwarded to HTS as displayLanguage.
A thin template helper resolving keys against the request locale.
Where things must NOT go
No hardcoded human-readable strings in Rust or templates — if a user can
read it, it comes from a catalog (logs/dev errors stay English).
No sentence-building by string concatenation — one keyed message with
placeables and a plural selector.
No if lang == "de" branching in handler logic — differences live in the
catalog, not Rust control flow.
No duplicate translation table for clinical terms — SNOMED/LOINC
designations are authoritative; pass the locale through to HTS.
Do not localize identifiers, codes, URLs, FHIR element names, UCUM units,
or FHIR wire date/time formats.
Existing terminology (SNOMED) localization — build on this
Multi-language import: the SNOMED RF2 importer
(crates/hts/src/import/snomed_rf2.rs) imports all active descriptions in
every language in the archive (incl. national-edition per-language files) as concept.designation entries tagged with the RF2 languageCode, and emits preferredForLanguage designations from the language reference sets.
Scoped import via LanguageFilter (HTS_IMPORT_LANGUAGES / --languages).
Runtime selection:$expand / $lookup / $validate-code honor displayLanguage and Accept-Language; matching is RFC 4647 §3.4 Lookup in crates/hts/src/language.rs, reconciling heterogeneous tags (SNOMED bare de
vs LOINC de-DE vs browser de-DE).
Deliverables
A branch — feat/i18n-foundation (off main, pushed) containing:
Working structure: seeded Fluent UI message catalogs locales/{en,es,de}/main.ftl (English source + Spanish + German), with the
same key set across all three and CLDR plural selectors.
locales/README.md — catalog conventions and how to add a language.
How UI i18n relates to the existing HTS SNOMED multi-language support.
The language roadmap and references.
Runtime wiring (negotiation middleware, the Askama/Fluent helper, Cargo.toml)
intentionally rides on the helios-web crate from #186 so the two don't
conflict — this issue establishes the shape and the rules; #186 provides
the crate they plug into.
Acceptance criteria
Branch feat/i18n-foundation exists with a working locale structure.
Seeded catalogs for English, Spanish, and German with a shared key set.
Discussion document on the branch covering approach, guidelines, and rules
of the road, and referencing the existing HTS SNOMED multi-language support.
Approach reviewed/ratified so subsequent UI PRs follow it.
Out of scope
A fully translated UI, or translating patient/clinical (FHIR) content —
we select among existing translations, we do not generate them.
Summary
Lay the foundation for multi-language support across HFS, so the user
interface can be presented in multiple languages — starting with English,
Spanish, and German — and so every layer from the browser to the terminology
backend agrees on the requested language.
Today HFS has no UI i18n layer: UI chrome and error prose are English-only, and
there is no locale-negotiation or message-catalog machinery. This issue
establishes the structure, conventions, and rules of the road — plus a
working skeleton and a discussion document — so the first localization PRs set
the right precedent rather than an accidental one. As with the UI foundation
(#186), the goal is not a fully translated product; it is to answer, with a
running structure and a written rationale: "When we add a language to HFS,
where does everything go, and why?"
The one thing HFS already does well here is terminology: HTS imports
multi-language SNOMED CT (and LOINC linguistic variants) and serves the right
term per
displayLanguage/Accept-Language. This foundation builds on thatrather than reinventing it.
Motivation
messages, FHIR content pass-through, terminology display, and formatting are
separate layers. Without an agreed model they get conflated, producing bugs
like a Spanish nav bar over English SNOMED terms.
(catalog format, key naming, fallback behavior, where negotiation lives). We
should decide that deliberately, up front — the same rationale as Foundation: HTMX-first web UI (POC, structure, and rules of the road) #186.
Approach (proposed — finalized in the discussion doc)
One negotiated locale per request, computed once, threaded to every layer —
the UI chrome, the error text, and the terminology
displayLanguageall readthe same value.
locales/<locale>/main.ftl. Chosen over flat JSON/gettextforgrammar-correct CLDR plurals/selectors, asymmetric translation, and
first-class Rust support via
fluent-templates,which integrates with Askama (the templating engine proposed in Foundation: HTMX-first web UI (POC, structure, and rules of the road) #186).
Catalogs are embedded at build time (single binary, no runtime CDN —
air-gap-friendly), consistent with Foundation: HTMX-first web UI (POC, structure, and rules of the road) #186's asset stance.
RequestLocale, with precedence:explicit override (
?lang=/ cookie from the switcher) → saved user preference(
feat/user-ui-settings) →Accept-Language(RFC 4647 Lookup) →endefault.negotiated → base language → en.A half-translated locale is always usable.
OperationOutcome.text/issue.details.text) come from the samecatalogs (
error-*keys); machine-facingissue.code/ codings stay stable.through as
displayLanguage; we do not build a second UI-local translationtable for clinical terms.
Full detail — including the front-to-back request-flow diagram, the six
localization layers, and the terminology integration — is in the discussion
document delivered on the branch:
docs/multi-language.md.File placement — rules of the road (ratified in the doc)
Where things go
locales/<locale>/main.ftl— all translatable UI text;enis the sourceof truth and defines the canonical key set.
RequestLocaleper request, in requestextensions, forwarded to HTS as
displayLanguage.Where things must NOT go
read it, it comes from a catalog (logs/dev errors stay English).
placeables and a plural selector.
if lang == "de"branching in handler logic — differences live in thecatalog, not Rust control flow.
rendered server-side (consistent with Foundation: HTMX-first web UI (POC, structure, and rules of the road) #186).
designations are authoritative; pass the locale through to HTS.
or FHIR wire date/time formats.
Existing terminology (SNOMED) localization — build on this
(
crates/hts/src/import/snomed_rf2.rs) imports all active descriptions inevery language in the archive (incl. national-edition per-language files) as
concept.designationentries tagged with the RF2languageCode, and emitspreferredForLanguagedesignations from the language reference sets.LanguageFilter(HTS_IMPORT_LANGUAGES/--languages).$expand/$lookup/$validate-codehonordisplayLanguageandAccept-Language; matching is RFC 4647 §3.4 Lookup incrates/hts/src/language.rs, reconciling heterogeneous tags (SNOMED baredevs LOINC
de-DEvs browserde-DE).Deliverables
feat/i18n-foundation(offmain, pushed) containing:locales/{en,es,de}/main.ftl(English source + Spanish + German), with thesame key set across all three and CLDR plural selectors.
locales/README.md— catalog conventions and how to add a language.docs/multi-language.md— covering:negotiated locale).
pass-through, formatting, and security/escaping notes.
Acceptance criteria
feat/i18n-foundationexists with a working locale structure.of the road, and referencing the existing HTS SNOMED multi-language support.
Out of scope
we select among existing translations, we do not generate them.
the i18n structure and conventions that plug into it.
translation once the structure holds).
References
Accept-Language— https://developer.mozilla.org/docs/Web/HTTP/Headers/Accept-Language$expanddisplayLanguage— https://hl7.org/fhir/valueset-operation-expand.html