Skip to content

feat(ui): wire the i18n foundation into the web UI (#187)#191

Open
angela-helios wants to merge 4 commits into
feat/htmx-ui-foundationfrom
feat/i18n-ui-wiring
Open

feat(ui): wire the i18n foundation into the web UI (#187)#191
angela-helios wants to merge 4 commits into
feat/htmx-ui-foundationfrom
feat/i18n-ui-wiring

Conversation

@angela-helios

@angela-helios angela-helios commented Jul 6, 2026

Copy link
Copy Markdown

Wires the i18n foundation from #187 into the UI crate from #186, so the two foundations meet: one negotiated locale per request, threaded from the browser to the templates.

Stacked PR — depends on #188. Base branch is feat/htmx-ui-foundation; merge #188 first, then this. (The first commit merges main to pick up the locales/ catalogs from #187, so the diff shrinks further once the base catches up with main.)

What this adds

  • crates/ui/src/i18n.rs — the runtime wiring Foundation: multi-language support (i18n) for the HFS UI (en/es/de) #187 deliberately deferred to this crate:
    • negotiate_locale middleware: one RequestLocale per request, precedence ?lang= override → hfs_lang cookie → Accept-Language (RFC 4647 §3.4 Lookup with subtag truncation, quality-ordered) → en. An explicit ?lang= is persisted in the cookie (SameSite=Lax, 1 year).
    • I18n template helper: t(key) / t_arg(key, name, value) against the Fluent catalogs, embedded at compile time via fluent-templates (static_loader!) — no runtime file/CDN dependency, same stance as the vendored assets. Fallback chain per the ratified policy: negotiated locale → en; unknown keys render the key, never a blank or a crash.
  • Templates hold keys, not prosebase.html, index.html, and partials/status.html now resolve everything through i18n.t(...); <html lang> reflects the negotiated locale; a no-JS language switcher (plain ?lang= links) marks the active locale with aria-current.
  • Catalog additions (same key set in en/es/de, en first as source of truth): home-lede, status-version, status-last-checked, action-refresh-status.
  • Tests — negotiation precedence and RFC 4647 matching, per-locale catalog lookups, CLDR plural selection, and a key-set parity test that parses the three .ftl files and fails if any locale drifts from en. Plus tests/i18n_http.rs: end-to-end through the mounted router (Accept-Language selects the language, ?lang= sets the cookie, the cookie sticks, htmx fragments are localized too).
  • Docsdocs/multi-language.md §2/§10 updated to the crate's post-review name (helios-ui) and to point at the now-existing wiring.

Verified against the running server

Built hfs --features ui and drove /ui with a real client: German via Accept-Language: de-DE, de;q=0.9, Spanish via ?lang=es (Set-Cookie observed, subsequent cookie-only request stays Spanish), English default, localized htmx fragment on HX-Request.

Heads-up for reviewers

main currently contains a parallel UI scaffold at crates/web (helios-web, commit 5f1bbd9) that overlaps with this crate (crates/ui, renamed from helios-web in #188 per review). After the merge that this branch carries, both crates coexist in the workspace. This PR wires i18n into crates/ui only; reconciling the two scaffolds is a #186 decision that should happen in #188 before or when this lands.

Closes the runtime-wiring follow-up of #187 (the catalogs, conventions, and discussion doc are already on main).

Plug the locale structure from feat/i18n-foundation into the helios-ui
crate from #186 — the runtime wiring #187 deliberately deferred:

- crates/ui/src/i18n.rs: negotiate_locale middleware producing one
  RequestLocale per request (?lang= override -> hfs_lang cookie ->
  Accept-Language via RFC 4647 Lookup -> en), an explicit ?lang= choice
  persisted in the cookie, Vary: Accept-Language, Cookie on responses,
  and the I18n Fluent lookup helper templates resolve keys through.
- Catalogs embedded at compile time via fluent-templates static_loader
  (no runtime file/CDN dependency, same stance as the vendored assets),
  falling back negotiated locale -> en, key echo for unknown keys.
- Templates now hold catalog keys, not prose; <html lang> reflects the
  negotiated locale; no-JS language switcher with aria-current.
- New keys (home-lede, status-version, status-last-checked,
  action-refresh-status) seeded across en/es/de; en stays the source
  of truth and a test parses all three .ftl files to enforce key-set
  parity.
- tests/i18n_http.rs drives the mounted router end to end: header
  negotiation, override cookie, persistence, localized htmx fragments.
- docs/multi-language.md: crate name updated to helios-ui post-#188
  review; point at the now-existing wiring.
…at/i18n-ui-wiring

# Conflicts:
#	Cargo.lock
#	crates/ui/Cargo.toml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant