Files
felhom-controller/REPORT.md
T
2026-09-17 14:58:32 +02:00

110 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.**