v0.247.0: i18n spike — the dashboard can speak English, Hungarian byte-identical
gates / gates (push) Successful in 19s

Message bundles (internal/i18n) expanded into templates before parsing, one
template set per language. Launcher, /backups, /apps/<slug> and the layout
converted; household language setting, POST /settings/language, ?lang= override,
report field. Parity test against fixtures captured from unconverted templates;
copy gates read templates expanded; new i18n_missing_gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 14:50:57 +02:00
parent 89dd3e94b1
commit 612c417024
49 changed files with 11734 additions and 248 deletions
+42
View File
@@ -1,3 +1,45 @@
## v0.247.0 — the dashboard can speak English: mechanism proven on three pages, Hungarian byte-identical (2026-09-17, i18n spike)
**MinAgent: 0.131.0** (unchanged — nothing here talks to the agent). **No hub release needed:** the
report gains an additive `language` field the hub stores raw and does not read yet.
**Nothing changes for a household.** Hungarian stays the default and the only language shown; the
switch appears only on an English page or a request carrying `?lang=`.
- **Mechanism — a flat message bundle, expanded BEFORE the template is parsed.**
`internal/i18n/locales/{hu,en}.json` (embedded; hu authoritative). Converted templates carry
`{{T "key"}}` markers; `loadTemplates` parses one template set per language after `i18n.Expand` has
replaced them, so the Hungarian set is parsed from the same bytes, in the same escaping contexts, as
before. Chosen over `golang.org/x/text/message` because the inventory found no gender and only
count-plurals (English `key.one`/`key.other`; Hungarian has one form), and over a runtime `T` func
because the contextual escaper would change Hungarian bytes (`+`, `'`, `"`) and need per-context
handling inside `<script>`. A key missing in English shows Hungarian and is counted; an undefined key
fails the template load.
- **Three pages + the layout converted:** the launcher, `/backups`, `/apps/<slug>`, and `layout.html`
(menu, banners, the layout's delete/remove modals) — 252 markers, 235 Hungarian keys, all translated.
Go-side copy on those pages: page titles (`TitleKey`), and English overrides for `stateLabel`,
`timeAgo`, `timeAgoStr`, `nextRunLabel`, `statusText`. `<html lang>` follows the language.
- **The setting:** `settings.json` `language` (`hu`|`en`, empty = hu); `POST /settings/language`;
`?lang=` per-request override (never persisted); reported to the hub as `"language"` at all four
report build sites.
- **Gates.** New `i18n_missing_gate.py` (keys exist, no orphans, English gap and formal „ön" count are
ratchets, no "please"/"kindly"). **Found while converting:** the retrieval-promise gate went STALE the
moment copy moved to the bundle, and the emoji / native-confirm / secret-in-markup gates went BLIND
to it — they now read templates expanded (`scripts/i18n_bundle.py`); mojibake scans the bundles.
Decoys for all of it in `test_gate_decoys.py`; the HEAD versions of the emoji and retrieval gates
were run against the bundle decoys and passed them.
- **Tools:** `scripts/i18n_extract.py` converts a template. Its first pass missed eight ASCII-only
Hungarian strings; they were found by eye and the word list extended.
**Tests, each red-proofed (seen failing, then passing):** `TestI18nParity` — 14 fixture states
captured from a clean worktree of `origin/main` 89dd3e94b1de; one changed byte in hu.json fails it
naming the page and line. `TestI18nEnglishPages` — no Hungarian template copy, raw key, marker or new
empty element on the English render; removing one English key fails it. `TestLanguageSwitch_EndToEnd`
(real router: hidden for Hungarian, `?lang=` door, save, 400 on `de`, back to hidden) — fails with
`addLanguageData` removed. `TestI18nJSContextValuesAreSafe` — fails on a quote in a JS-context English
value. `TestBundleParametersMatchAcrossLanguages` — fails when English drops `{{.RecoveryAbandonDate}}`.
`TestLocaleFuncsHungarianBundleMatchesFuncMap` — fails when hu.json's state word differs from the funcmap.
## v0.246.0 — an interrupted restore is told, and the recovery-code reminder waits until the box can take it (2026-09-17, R-550 / R-546)
**MinAgent: 0.131.0** (unchanged — the readiness check reads the escrow preflight, served since agent