i18n: externalize UI chrome + Spanish catalog (LC-188) #235
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lc-188-i18n-externalize"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-on to the LC-100 i18n framework (#233): externalize the hardcoded English UI-chrome strings across the server-rendered templates and ship a full Spanish catalog. The
i18n_catalogCI gate is green (every template-referenced key exists in English; en and es key sets match exactly).What lands
server/locales/{en,es}/<area>.ftl); Fluent merges them at load, and the gate's coverage check unions all.ftlper locale. 14 area files per locale, en/es at parity.#[derive(Template)]now importscrate::i18n::filters, so any template or partial can use{{ "key"|t }}without per-area coordination.Conventions (docs/i18n.md)
"key"|t(the spaced form parses as bitwise-or and won't compile).tfilter takes no args).{% if n == 1 %}two-key pattern.Out of scope (per the LC-100 spec)
ws/live-update fragments render outside the request task (no locale task-local) -> fall back to English.<script>blocks are not reachable by the template filter.Notes
routes_bookmarks: the empty-state assertion now matches an escaping-stable substring. Askama HTML-escapes thetfilter output, so the apostrophe in "haven't" renders as an entity (the string was previously static literal HTML). This is correct/secure behavior; only the test's literal-match needed updating.Verification
just test+just test-saasgreen;cargo fmt --allclean;i18n_cataloggate passes; no spaced-pipe filter forms.Try it
Settings -> Language -> Español localizes the app; clearing it falls back to
Accept-Languagethen English.Follow-on to the LC-100 framework: externalize the hardcoded English UI-chrome strings across the server-rendered templates and provide full Spanish coverage. The catalog is split per area (one .ftl per area per locale; Fluent merges them at load), and the i18n_catalog CI gate stays green (every referenced key exists in English; en and es key sets match exactly). Areas externalized: auth, two_factor, enclave, room, partials (sidebar/nav/switcher), admin, settings, status, saved, invitations, inbox, reminders, home, search, users, dm, activity, voice, plus the root templates (layout, not_found, poll/scheduled modals, admin_layout). Every view module that owns a Template now imports crate::i18n::filters so any template/partial can use the `| t` filter (no per-area coordination). Conventions (docs/i18n.md): keys are kebab-case, area-prefixed, no dots; the no-space `"key"|t` form (the spaced form parses as bitwise-or); strings that embed a dynamic value mid-sentence are split into adjacent keys with the value emitted inline (the `t` filter takes no args); plural cases use the existing `{% if n == 1 %}` two-key pattern. Out of scope (per the LC-100 spec): ws/ live-update fragments render outside the request task so they fall back to English; outbound email templates; user-authored content (room descriptions, branding body) stays a single string per row. JS string literals inside <script> blocks are not reachable by the template filter and are left as-is. routes_bookmarks: the empty-state assertion now matches an escaping-stable substring - Askama HTML-escapes the t filter output, so the apostrophe in "haven't" renders as an entity (a string that was previously static literal HTML). just test + just test-saas green; cargo fmt --all clean; i18n_catalog gate passes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>