cc2ad41516
gates / gates (push) Successful in 21s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
110 lines
8.6 KiB
Markdown
110 lines
8.6 KiB
Markdown
# REPORT — controller v0.247.0: the i18n spike — English on three pages, Hungarian byte-identical
|
||
|
||
**Date:** 2026-09-17 · **Task:** localisation starter (spike + design), Phase 1 · **Baseline:** `main`
|
||
`89dd3e94b1de` (v0.246.0, MinAgent 0.131.0) · **Released:** `612c417` → image
|
||
`gitea.dooplex.hu/admin/felhom-controller:0.247.0` · **MinAgent:** 0.131.0 (unchanged).
|
||
**Architecture read before any claim:** `felhom.eu/documentation/architecture/02-controller-module-map.md`
|
||
(templates, setup wizard obsolete) and `05-hub-architecture.md` (report ingestion, config push).
|
||
**Design written after the spike ran:** `felhom.eu/documentation/architecture/10-localisation.md`.
|
||
**Inventory:** `felhom.eu/documentation/audits/I18N-INVENTORY-2026-09-17.md`.
|
||
|
||
## Claims in the brief that turned out wrong — named first
|
||
|
||
The brief named four already. Further ones (details and methods in the inventory §0):
|
||
|
||
1. **„`fmtTime`, `fmtBytes`, `timeAgo` do not exist; `fmtMB` is the one size helper"** — all three
|
||
exist (`internal/web/funcmap.go` L265/L318/L403), plus nine more copy- or locale-producing helpers
|
||
and four size helpers. `timeAgo` builds „%d perce" — the plural problem lives there.
|
||
2. **„`fmtMB` used 10× in templates"** — 8×.
|
||
3. **„~13 `.Format(` layout calls in `internal/web`"** — 10 at the baseline (plus 8 `time.RFC3339`).
|
||
4. **Template lines 1 613 / 1 838** — 1 553, by two methods; neither figure reproduced by four variants.
|
||
5. **Go literals 1 234 in 99 files** — 1 141 in 84 (a lexer, comments skipped); comments explain most of
|
||
the difference.
|
||
6. **Banner 33 lines** — 34 once ASCII-only Hungarian words count (method difference).
|
||
|
||
## What shipped
|
||
|
||
- **Mechanism (b), expanded at template LOAD:** `internal/i18n` (`Bundle`, `Expand`, `Msg`, `Plural`,
|
||
embedded `locales/hu.json` + `en.json`). `loadTemplates` parses one template set per language via
|
||
`parseTemplateSet` (= `ParseFS` + `Expand`); `s.tmpl` stays Hungarian. Why not a runtime `T` func: the
|
||
escaper changes `+ ' "` bytes and 618 of 1 867 template strings are inside `<script>`. Why not
|
||
`x/text/message`: no gender, only count plurals — a dependency for nothing.
|
||
- **Converted:** `launcher.html`, `backups.html`, `app_info.html`, `layout.html`, `backups_shared.html`
|
||
(`backups_empty`) — 252 markers, 235 hu keys, all translated (238 en keys incl. plural forms).
|
||
Page titles via `TitleKey`; English overrides for `stateLabel`, `timeAgo`, `timeAgoStr`,
|
||
`nextRunLabel`, `statusText`; `<html lang>` follows.
|
||
- **Setting / switch / report:** `settings.json` `language`; `POST /settings/language`; `?lang=`;
|
||
switch in the sidebar footer only when not Hungarian or `?lang=` is present; `"language"` in the hub
|
||
report at all four build sites in `cmd/controller/main.go`.
|
||
- **Gates:** new `i18n_missing_gate.py` (registered in `controller_gates.py`). `emoji`,
|
||
`native_confirm`, `secret_in_markup`, `retrieval_promise` now read templates expanded
|
||
(`scripts/i18n_bundle.py`); `mojibake` scans `.json`. **Found while converting:** retrieval-promise
|
||
went STALE (red) the moment copy moved; the other three went silently BLIND — the HEAD emoji gate
|
||
passed a planted emoji in `en.json`, and the HEAD retrieval gate never saw a planted promise in
|
||
`hu.json`.
|
||
- **Tool:** `scripts/i18n_extract.py`. First pass pulled layout `<span>`s and a button-link pair into
|
||
messages and mis-split a JS string that starts inside a tag — fixed and re-run from clean templates.
|
||
It also **missed eight ASCII-only Hungarian strings** (Megszakadt, Elavult, Csak x86, Pi kompatibilis,
|
||
Rendszermonitor, Most nem, sikertelen, ismeretlen) and one JS fragment („adatait ide:"), found by a
|
||
by-eye review of what it left; the word list was extended and the fragment converted by hand.
|
||
- Two existing tests (`TestTemplatesParse`, `TestTemplatesParseWithFuncmap`) parsed raw templates with
|
||
`ParseFS`; they now parse through `parseTemplateSet` for every language.
|
||
|
||
## Red-proofs — each seen failing, then passing (`felhom.eu/documentation/audits/i18n-2026-09-17/redproofs/`)
|
||
|
||
| test | mutation | failure seen |
|
||
|---|---|---|
|
||
| `TestI18nParity` | fixture file absent | `no parity fixture` (red before the fixture existed) |
|
||
| `TestI18nParity` | „Megosztási link" → „…linK" in hu.json | `launcher_full: Hungarian page CHANGED at line 261` (+ launcher_nopassword) |
|
||
| `TestI18nEnglishPages` | `layout.inditopult` deleted from en.json | `Hungarian template copy on the English page, line 111 … Indítópult` (5 cases) |
|
||
| `TestLanguageSwitch_EndToEnd` | `s.addLanguageData` removed from `executeTemplate` | `?lang=en must render English and show the switch`; title and switch missing |
|
||
| `TestI18nJSContextValuesAreSafe` | en `launcher.masolva` = `Copied'` | `contains '\'', which breaks its context` |
|
||
| `TestBundleParametersMatchAcrossLanguages` | en banner drops `{{.RecoveryAbandonDate}}` | `template fields ".RecoveryAbandonDays", Hungarian has ".RecoveryAbandonDate,.RecoveryAbandonDays"` |
|
||
| `TestLocaleFuncsHungarianBundleMatchesFuncMap` | hu `func.state.running` = „Futás" | `hu.json "Futás", funcmap "Fut"` |
|
||
| gate decoys (`test_gate_decoys.py`) | undefined key, „Please", gap grew, new formal form; emoji / confirm( / secret / retrieval promise planted in a bundle | all 18 decoys rejected; HEAD emoji gate passed the bundle emoji, HEAD retrieval gate did not see the bundle promise |
|
||
|
||
Fixtures: captured by running the parity test with `-update-i18n-golden` in a clean `git worktree` of
|
||
`origin/main` 89dd3e94b1de (the test file adapted to `s.tmpl`), then copied in; the current tree passed
|
||
them before AND after conversion. All files restored after each mutation (`cmp`).
|
||
|
||
## Gates
|
||
|
||
- Green gate: `go build ./... && go vet ./... && go test ./...` → rc 0, 29 packages ok (first run found
|
||
the two raw-`ParseFS` tests; fixed; re-run green).
|
||
- `controller_gates.py --fast` → all OK incl. `i18n` (English missing 0 / ceiling 0; formal forms 6 /
|
||
ceiling 6). Pre-push hook ran them on the push.
|
||
|
||
## Live validation (endpoint-level; no browser on DooPlex) — demo-hp guest 9201
|
||
|
||
Deployed by hand (`/etc/felhom-controller-image`, bootstrap restart); **fleet floor NOT raised; no
|
||
golden.** Startup: `loadTemplates: lang=hu … 252 markers expanded` and `lang=en … 252`.
|
||
|
||
- **Hungarian unchanged:** launcher, `/backups`, `/apps/privatebin` fetched on 0.246.0 and on 0.247.0 —
|
||
identical after replacing version string and CSRF token; raw sizes equal (44 462 / 46 250 / 42 081).
|
||
A fetch 7 s after restart differed only in app state tags („Nem egészséges" while health warmed up).
|
||
- **English:** `?lang=en` and, after `POST /settings/language lang=en` (302 → `/launcher`, settings
|
||
`"language": "en"`, INFO `language switch: household language set to en`), identical renders. No
|
||
accented Hungarian from template copy. Remaining Hungarian, all Go view models or catalog: backup-target
|
||
banner/offer and whole-guest tier labels (`/backups`); the „Naprakész" update badge and the catalog
|
||
tagline / use cases / first steps (`/apps/privatebin`). Launcher: none.
|
||
- **Hub report:** stored reports read no field (0.246.0), `hu`, `en` (12:54:43Z), `hu` (12:55:22Z).
|
||
Negative control: demo-felhom (0.246.0) no field.
|
||
- **Refusals:** no `_csrf` → 403; no session → 302 to login; setting unchanged.
|
||
- Evidence (CSRF redacted): `felhom.eu/documentation/audits/i18n-2026-09-17/live/`.
|
||
|
||
## Teardown
|
||
|
||
Machine: guest temp scripts, password file, session file shredded/removed; box left on `hu`, 0.247.0.
|
||
Host: the tarball removed from demo-hp. Hub: nothing written; the DB copy read for reports shredded on
|
||
DooPlex. Nothing provisioned. The scratch worktree of `origin/main` is removed.
|
||
|
||
## Observations
|
||
|
||
1. The wire-contract gate passes an unread field whose name appears in a hub Go comment (`language`). **FILED: R-555**
|
||
2. Four places decide behaviour by matching Hungarian wording (HTTP status, quota class, alert placement, a persisted warning marker). **FILED: R-553**
|
||
3. The extractor and the inventory detect ASCII-only Hungarian only through a word list; eight strings slipped past on three pages. **FILED: R-556**
|
||
4. `TestI18nEnglishPages` subtracts fixture data strings, so a template word equal to a data string (the launcher title „Indítópult") is masked on the launcher cases. **FILED: R-556**
|
||
5. Six formal „ön" forms in the converted Hungarian copy, now counted by a ratchet. **FILED: R-516**
|
||
6. Dates and sizes are not locale-formatted; the two template date layouts disagree; sizes use a decimal point. **FILED: R-562**
|
||
7. `storage_handlers_test.go` imported `text/template` for its template-parse check. **NOT-A-FINDING: a test-only parse check with no escaping assertion; it now parses through the production `parseTemplateSet`, which uses `html/template`.**
|