i18n starter: inventory (script + audit), rows R-553..R-562, language allowlisted in wire-contract gate
gates / gates (push) Successful in 23s

Phase 0 of the localisation starter: i18n_inventory.py counts every customer-visible
Hungarian string; the audit names six further claims in the prompt that live source
disproved. Rows for the compare-not-show sites, wizard deletion, the wire-contract comment
blind spot, and localisation slices 1-6.

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:29 +02:00
parent 851198af7f
commit 5b7b2b22f1
7 changed files with 28760 additions and 1 deletions
@@ -0,0 +1,220 @@
# I18N INVENTORY — every Hungarian string a household meets, counted (2026-09-17)
**Run class:** spike, Phase 0 of the localisation starter. **Operator:** Claude Code on DooPlex.
**Baselines:** felhom-controller `89dd3e94b1de` (v0.246.0) · felhom-agent `d9864a94bf62` (v0.132.0) ·
felhom.eu `851198af7ff0` (hub v0.117.0) · app-catalog-felhom.eu `94bc5febaca2`.
**Instrument:** `scripts/i18n_inventory.py`, sha256
`be69b48d4d98ac47c0cec531cae63bee8ef009cacbbe2b5c45725cfb2fd40f0c`. Raw output and per-string JSON:
`audits/i18n-2026-09-17/inventory.md`, `inventory.json`.
**What was counted:** the controller at its baseline commit (a clean worktree of `origin/main`, so the
spike's own conversion is NOT in these numbers), the other three repos at their baselines.
**Design this feeds:** `architecture/10-localisation.md`.
---
## 0. Claims in the starter prompt that live source disproved
Named first, as the prompt asked. Four were already named in its §1; these are further ones.
1. **„`fmtTime`, `fmtBytes`, `timeAgo` — none of these exist. The FuncMap has one size helper,
`fmtMB`."** — **Wrong, and it is the claim that matters most for localisation.** All three exist
(`internal/web/funcmap.go` `timeAgo` L265, `fmtTime` L318, `fmtBytes` L403), and so do **nine more
helpers whose output is copy or locale-shaped**: `fmtGB`, `humanBytes`, `timeAgoStr`, `fmtTimeStr`,
`fmtTimeShort`, `fmtDuration`, `nextRunLabel` („ma"/„holnap"), `pruneLabel` („vasárnap"/„naponta"),
`nextPruneLabel`, `statusText`, and `stateLabel` (the app-state words). There are **four** size
helpers (`fmtMB`, `fmtGB`, `fmtBytes`, `humanBytes`), not one. `timeAgo` builds Hungarian with `%d`
(„%d perce") — the plural problem lives there.
2. **„`fmtMB`, used 10× in templates"** — **8×** (`grep -c` over templates, and the script agrees).
3. **„~13 `.Format(` calls in `internal/web/*.go` (`2006-01-02` ×6, `2006-01-02 15:04` ×4, `15:04` ×2,
`01-02 15:04` ×1)"** — at the baseline: `2006-01-02` **×4**, `2006-01-02 15:04` **×3**, `15:04` **×2**,
`01-02 15:04` ×1 = **10** layout literals, plus **8** `time.RFC3339` (machine, locale-free). The two
template layouts are as stated and do disagree (`2006. 01. 02. 15:04` vs `2006-01-02 15:04`).
4. **Template line count „1 613 (afternoon) / ~1 838 (morning)"** — **1 553**, by two independent methods
(this script, and `grep -cP` on the concatenated templates under `LC_ALL=C.UTF-8`). Neither prompt
figure was reproduced. Tried: adding typographic quotes „ ” (1 556), adding dashes and ellipsis
– — … (1 751), counting `style.css` too (1 559). The afternoon figure is **+60 (3.9 %)** and its method
is unknown; the morning figure is closest to the dash-inclusive variant. **Lines are the wrong unit
anyway** — see §1: the string count is 1 867.
5. **Go literal count „1 234 in 99 files (afternoon) / ~1 971 in 125 (morning)"** — **1 141 literals in
84 files** by a Go lexer (comments skipped), first-boot wizard excluded. A `grep` of quoted Hungarian
on non-comment lines gives 1 106 lines in 85 files, so the two methods agree. Counting lines that
hold an accented letter **including comments** gives 1 360 in 107 files; including the wizard and
`cmd/`, 1 375 in 108. The prompt's figures sit between the comment-free and comment-inclusive
counts; **comments explain most of the gap** (Hungarian quoted in Go comments is common here).
6. **Console banner „33 Hungarian lines"** — 33 by accented letter, **34** once ASCII-only Hungarian
words are counted. Not an error; a method difference, stated.
Confirmed as the prompt states: 36 page templates + `style.css`; 33 carry Hungarian; the second
template set and its one helper (`internal/setup/handlers.go` L79 `timeNow`); 8 wizard pages, **86**
Hungarian lines; `customerMessages` in the hub; 53 catalog files with `first_steps` under `app_info`;
no i18n library, no `Accept-Language`, no bundle anywhere.
---
## 1. Controls — the counts below mean something
Hungarian is matched by accented letter (the letters written as `\u` escapes, so the script itself is
ASCII) **or** by one of 46 ASCII-only Hungarian words („Fut", „Nincs", „Hiba", „Csak", „Megszakadt"…),
case-insensitive. Every surface prints a positive control (an ASCII fragment of a Hungarian word known
to be there, whose lines the matcher must flag) and a negative control (a fragment known absent; an
English sentence that must not be flagged).
| surface | positive fragment | lines holding it | flagged | negative hits | English line flagged | verdict |
|---|---|---|---|---|---|---|
| controller templates | `pult` | 20 | 20 | 0 | no | ok |
| controller Go | `ment` | 832 | 278 | 0 | no | ok |
| hub customer Go | `ment` | 35 | 18 | 0 | no | ok |
| installer banner | `Felhom` | 10 | 5 | 0 | no | ok |
| catalog | `Alkalmaz` | 5 | 5 | 0 | no | ok |
| guide | `Felhom` | 14 | 13 | 0 | no | ok |
**Measured limit of the ASCII word list.** The spike's conversion tool first used a 40-word list and
**missed eight Hungarian strings on the three pages** („Megszakadt:", „Elavult", „Csak x86",
„Pi kompatibilis", „Rendszermonitor", „Most nem", „sikertelen", „ismeretlen"); a by-eye review of what
was left found them, and they were added. **An ASCII-only Hungarian word that is not on the list is
still invisible to every count here.** The counts below are therefore floors, not totals.
---
## 2. The tables
### 2.1 Controller page templates (`internal/web/templates/`, 36 files)
**1 867 strings: 1 161 text runs + 88 attribute values + 618 inline-JS string literals.** 1 553 lines
carry an accented letter. A *run* is text up to the next block tag or control action — the unit a
translator needs.
| template | HU lines | text | attr | JS |
|---|---|---|---|---|
| deploy.html | 156 | 87 | 8 | 77 |
| storage.html | 110 | 43 | 9 | 98 |
| debug.html | 92 | 41 | 16 | 79 |
| layout.html (on every page) | 88 | 35 | 1 | 70 |
| backups_remote.html | 77 | 87 | 0 | 10 |
| settings_system.html | 84 | 58 | 0 | 36 |
| backups_apps.html | 80 | 79 | 7 | 6 |
| … 26 more | | | | |
Full per-file table: `audits/i18n-2026-09-17/inventory.md`. **One third of all template copy (618) is
inside `<script>`** — modal bodies and confirm questions built as JS strings, often with HTML inside
and with the sentence split around a `+ name +` concatenation. Most repeated: „Mentés" 10×, „Jelszó"
7×, „← Vissza" 7×, „Törlés", „Hiba", „Beállítás", „Mégsem", „Újraindítás", „Betöltés...", „Frissítés"
6× each.
### 2.2 Controller Go (non-test, wizard excluded)
**1 141 literals in 84 files.** Classified by the statement they sit in:
| kind | count | meaning |
|---|---|---|
| shown | 947 | assigned to page data, flash, alert, event text, struct fields |
| error | 184 | `fmt.Errorf` / `errors.New` — **reaches a page** wherever a handler prints `err.Error()` |
| compared | 7 | matched, not shown — §3 |
| log only | 3 | logs are English by convention; three are not |
Word-order hazards (non-log): **237** format strings with parameters, **111** concatenations, **42**
with a numeric `%d` (English needs a plural form; Hungarian does not inflect after a numeral).
Heaviest files: `web/handlers.go` 115, `cloudflare/countries.go` 113 (country names for the geo
filter), `web/offbox_handlers.go` 70, `web/storage_handlers.go` 45, `web/handler_debug.go` 39,
`backup/offbox_reconstitute.go` 38, `notify/notifier.go` 31. Most repeated: „érvénytelen kérés" 13×,
„A távoli mentési cél nincs beállítva." 9×, „Rendszermonitor" 7×.
**A classification caveat, measured:** a flash message travels to the page **inside the redirect URL**
(`?flash=<Hungarian text>`, e.g. the launcher's `ShareFlash`), so it is rendered in whatever language
the handler that redirected spoke. Slice 2 has to move these to keys, not only translate them.
### 2.3 Hub → customer
**65 literals in 4 files**: `notify/templates.go` 57 (the 39-entry `customerMessages` map, the 4
`severityLabels`, the body wrapper, and the claim / re-enroll / reset / claimed / self-bind mails),
`notify/dispatcher.go` 4, `web/customer_reset.go` 2, `web/selfbind.go` 2 (plus the public bind page's
19 Hungarian lines). 8 are multi-line raw blocks. One message (`app_start_failed`) is in the formal
„ön" register. For scale and **out of scope** (operator pages): all hub Go 83 literals; operator
templates 64 strings.
### 2.4 Installer
Console banner `scripts/iso/felhom-bootstrap.sh`: **34** lines, printed with `printf` blocks to the
console device (pairing, bound, `/etc/issue`, no-network screens). The `/etc/issue` text must stay
byte-identical to `iso/pkg/debian/postinst` and avoid ő/ű (the console font) — a constraint English
does not share but a second language on the same screen does. Download page `website/letoltes.html`:
**32** strings.
### 2.5 Catalog
**835 strings, ~4 984 words, in all 53 `.felhom.yml`.** No multi-language structure exists.
| field | strings |
|---|---|
| `app_info.use_cases[]` | 262 |
| `app_info.first_steps[]` | 233 |
| `deploy_fields[].description` | 79 |
| `deploy_fields[].label` | 68 |
| `app_info.prerequisites[]` | 60 |
| `description` | 56 |
| `app_info.tagline` | 53 |
| other (`label`, `help_text`, `default_creds`, `note`, `group`) | 24 |
The controller parses these with a plain `yaml.Unmarshal` that ignores unknown keys
(`internal/stacks/metadata.go` L317), so a sibling key per language is additive.
### 2.6 Guide
`runbooks/VOLUNTEER-first-hour.md`: 182 lines, **44 paragraphs (42 Hungarian), ~1 579 words.**
### 2.7 First-boot wizard — reported separately, never merged
`internal/setup/templates/` (8 pages): 86 Hungarian lines; **95 strings** (81 text, 12 attr, 2 JS).
Go: 16 literals. **When a household can still see it** (read from source): the controller runs
`bootstrap.MaybeIngest` (`cmd/controller/main.go` L314) and then `setup.NeedsSetup(cfg)` (L322), which
is true when `customer.id` is still empty **or** a `.needs-setup` marker exists in the data dir
(`internal/setup/setup.go` L17-25). Ingestion leaves `customer.id` empty — and the wizard therefore
starts — when `bootstrap.json` is missing, unreadable, not JSON, not schema `felhom.bootstrap/v2`, or
lacks `customer.id` / `hub.url` / `hub.retrieval_password` / the `local_api` block; when the hub pull
fails (auth and not-found at once; unreachable after retries at 2/4/8 s); or when the merge, the
atomic write or the reload fails (`internal/bootstrap/bootstrap.go` L109-163). **Operator decision
2026-09-17: out of scope, obsolete — a separate row to delete it.**
### 2.8 Dates, numbers, sizes
| where | layout | sites |
|---|---|---|
| internal/web Go | `2006-01-02` | 4 |
| internal/web Go | `2006-01-02 15:04` | 3 |
| internal/web Go | `15:04` | 2 |
| internal/web Go | `01-02 15:04` | 1 |
| template | `2006. 01. 02. 15:04` (Hungarian) | 1 |
| template | `2006-01-02 15:04` (ISO) | 1 |
Plus 25 `.Format("` calls elsewhere in the controller, and the helpers in §0.1. Sizes print `GB`/`MB`
with a **decimal point** (`%.1f`) — Hungarian convention is a decimal comma, so the Hungarian pages are
already un-Hungarian here; that is not a localisation regression and the spike does not change it.
---
## 3. Compared, not shown — each is a latent bug
A string matched by its Hungarian wording breaks the moment the wording is translated **or reworded**.
| where | what is matched | consequence if the text changes |
|---|---|---|
| `internal/api/router.go:478` | a deploy error contains „kötelező" or „memória" | a validation failure becomes **500 instead of 400** |
| `internal/backup/offbox.go:186` | a restic error contains „tárhelykeretet" | the off-site **quota** failure is no longer classified as quota |
| `internal/web/alerts.go:208` | a health warning contains „meghajtón" / „adattároló" / „meghajtó" | a disk warning moves from its inline place to the **top banner of every page** |
| `internal/web/handlers.go:927` via `offboxStaleWarningMarker` („nincs mentésre jelölt alkalmazás") | a **persisted** warning string | the „selection changed" line is no longer substituted — and a warning written in one language is compared in another |
| `internal/web/offbox_handlers.go:132` | — | **false positive** of the detector (`!= ""` belongs to `stageErr`, not the literal) |
Templates: **0** compares against Hungarian (a detector decoy with `eq .State "Leállítva"` was flagged,
so the zero is measured). JS: no `===` / `indexOf` / `includes` against a Hungarian literal.
---
## 4. What this inventory does not see
- ASCII-only Hungarian words outside the 46-word list (§1).
- Hungarian arriving at run time from outside the repos: app containers' own UIs, agent error strings
relayed verbatim, hub-pushed text.
- Whether a `shown` Go literal actually reaches a page (classification is by statement, not by data
flow) and whether an `error` literal is printed or swallowed.
- The **formal „ön" register** inside Hungarian copy (R-516) — counted only on the converted pages, by
the new `i18n_missing_gate.py` (6 occurrences).
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,176 @@
script sha256: be69b48d4d98ac47c0cec531cae63bee8ef009cacbbe2b5c45725cfb2fd40f0c
## Controls (ASCII fragment, positive + negative)
| surface | positive fragment | lines holding it | of those flagged | negative fragment hits | ASCII English line flagged | verdict |
|---|---|---|---|---|---|---|
| controller templates | `pult` | 20 | 20 | 0 | False | ok |
| controller Go | `ment` | 832 | 278 | 0 | False | ok |
| hub customer Go | `ment` | 35 | 18 | 0 | False | ok |
| installer banner | `Felhom` | 10 | 5 | 0 | False | ok |
| catalog | `Alkalmaz` | 5 | 5 | 0 | False | ok |
| guide | `Felhom` | 14 | 13 | 0 | False | ok |
## Controller page templates
36 files; 33 carry Hungarian. Lines with an accented letter: **1553**. Strings: **1161 text runs + 88 attribute values + 618 inline-JS literals = 1867**. Template compares against Hungarian: 0. Text runs with a numeric parameter: 7.
| template | HU lines | text | attr | JS | compare |
|---|---|---|---|---|---|
| deploy.html | 156 | 91 | 8 | 77 | 0 |
| storage.html | 110 | 43 | 9 | 105 | 0 |
| debug.html | 92 | 43 | 18 | 84 | 0 |
| layout.html | 88 | 38 | 1 | 70 | 0 |
| backups_remote.html | 77 | 87 | 0 | 10 | 0 |
| settings_system.html | 84 | 58 | 0 | 36 | 0 |
| backups_apps.html | 80 | 80 | 7 | 6 | 0 |
| backups_restore.html | 76 | 50 | 2 | 30 | 0 |
| sharing.html | 68 | 70 | 0 | 8 | 0 |
| recovery.html | 52 | 61 | 1 | 5 | 0 |
| storage_network.html | 66 | 38 | 6 | 20 | 0 |
| backups.html | 47 | 46 | 1 | 13 | 0 |
| backups_escrow.html | 56 | 31 | 0 | 29 | 0 |
| app_info.html | 45 | 37 | 0 | 20 | 0 |
| settings_security.html | 53 | 28 | 4 | 25 | 0 |
| monitoring.html | 48 | 43 | 0 | 13 | 0 |
| dashboard.html | 35 | 40 | 9 | 0 | 0 |
| backups_restore_wizard.html | 40 | 41 | 0 | 6 | 0 |
| storage_init.html | 33 | 18 | 1 | 25 | 0 |
| app_import.html | 36 | 31 | 1 | 9 | 0 |
| stacks.html | 29 | 29 | 7 | 0 | 0 |
| settings_notifications.html | 35 | 34 | 1 | 0 | 0 |
| storage_attach.html | 21 | 17 | 1 | 12 | 0 |
| app_export.html | 24 | 19 | 2 | 8 | 0 |
| launcher.html | 25 | 21 | 4 | 3 | 0 |
| tier2_config.html | 26 | 26 | 0 | 0 | 0 |
| claim.html | 19 | 16 | 3 | 0 | 0 |
| logs.html | 8 | 5 | 0 | 4 | 0 |
| login.html | 7 | 6 | 1 | 0 | 0 |
| launcher_share_password.html | 5 | 4 | 1 | 0 | 0 |
| backups_shared.html | 6 | 4 | 0 | 0 | 0 |
| catchall.html | 3 | 3 | 0 | 0 | 0 |
| launcher_shared.html | 3 | 3 | 0 | 0 | 0 |
| app_row.html | 0 | 0 | 0 | 0 | 0 |
| icons.html | 0 | 0 | 0 | 0 | 0 |
| meta_badge.html | 0 | 0 | 0 | 0 | 0 |
Most repeated (text + attr):
- 10x `Mentés`
- 7x `Jelszó`
- 7x `← Vissza`
- 6x `Törlés`
- 6x `Hiba`
- 6x `Beállítás`
- 6x `Mégsem`
- 6x `Újraindítás`
- 6x `Betöltés...`
- 6x `Frissítés`
## Controller Go (non-test, first-boot wizard excluded)
**1141 literals in 84 files.** shown: 947, error (may reach a page): 184, log only: 3, compared: 7. Format strings with parameters (non-log): 237; numeric %d (non-log, plural risk): 42; concatenated (non-log): 111; raw multi-line blocks: 1.
| file (top 15, non-log) | literals |
|---|---|
| felhom-controller/controller/internal/web/handlers.go | 115 |
| felhom-controller/controller/internal/cloudflare/countries.go | 113 |
| felhom-controller/controller/internal/web/offbox_handlers.go | 70 |
| felhom-controller/controller/internal/web/storage_handlers.go | 45 |
| felhom-controller/controller/internal/web/handler_debug.go | 39 |
| felhom-controller/controller/internal/backup/offbox_reconstitute.go | 38 |
| felhom-controller/controller/internal/notify/notifier.go | 31 |
| felhom-controller/controller/internal/backup/update_guard.go | 28 |
| felhom-controller/controller/internal/stacks/migrate.go | 28 |
| felhom-controller/controller/internal/stacks/update.go | 25 |
| felhom-controller/controller/internal/appexport/restore.go | 23 |
| felhom-controller/controller/internal/web/alerts.go | 22 |
| felhom-controller/controller/internal/web/share_handlers.go | 22 |
| felhom-controller/controller/internal/appexport/export.go | 21 |
| felhom-controller/controller/internal/api/router.go | 20 |
Most repeated (non-log):
- 13x `érvénytelen kérés`
- 9x `A távoli mentési cél nincs beállítva.`
- 7x `Rendszermonitor`
- 6x `Nem bekötött`
- 6x `érvénytelen csatlakoztatási pont`
- 5x `Beállítások`
- 5x `Hiányzó alkalmazás.`
- 4x `Belső hiba a jelszó mentésekor`
- 4x `Az ügynök nem elérhető.`
- 3x `egy másik mentési/visszaállítási művelet már fut`
### Compared, not shown (controller Go)
- felhom-controller/controller/internal/api/router.go:478 `kötelező`
- felhom-controller/controller/internal/api/router.go:478 `memória`
- felhom-controller/controller/internal/backup/offbox.go:186 `tárhelykeretet`
- felhom-controller/controller/internal/web/alerts.go:208 `meghajtón`
- felhom-controller/controller/internal/web/alerts.go:208 `adattároló`
- felhom-controller/controller/internal/web/alerts.go:208 `meghajtó`
- felhom-controller/controller/internal/web/offbox_handlers.go:132 `A távoli mentési cél elmentve.`
- felhom-controller/controller/internal/web/handlers.go:927 via `offboxStaleWarningMarker` (defined felhom-controller/controller/internal/web/handlers.go) `nincs mentésre jelölt alkalmazás`
### Compared, not shown (templates)
## Hub customer surfaces (notify, self-bind, reset, claim)
**65 literals in 4 files.** shown: 65, error (may reach a page): 0, log only: 0, compared: 0. Format strings with parameters (non-log): 10; numeric %d (non-log, plural risk): 0; concatenated (non-log): 0; raw multi-line blocks: 8.
| file (top 15, non-log) | literals |
|---|---|
| felhom.eu/hub/internal/notify/templates.go | 57 |
| felhom.eu/hub/internal/notify/dispatcher.go | 4 |
| felhom.eu/hub/internal/web/customer_reset.go | 2 |
| felhom.eu/hub/internal/web/selfbind.go | 2 |
Most repeated (non-log):
- 2x `Teszt értesítés`
For scale, NOT in scope: all hub Go non-test = 83 literals (shown 83, error 0, log 0, compare 0); hub operator templates = 64 strings, 52 HU lines.
## Installer
Console banner `felhom.eu/scripts/iso/felhom-bootstrap.sh`: **34** Hungarian lines.
Download page `felhom.eu/website/letoltes.html`: 24 text + 6 attr + 2 JS = **32** strings, 27 HU lines.
## Catalog
53 `.felhom.yml`; 53 carry Hungarian; **835 strings, ~4984 words**.
| field | strings |
|---|---|
| app_info.use_cases[] | 262 |
| app_info.first_steps[] | 233 |
| deploy_fields[].description | 79 |
| deploy_fields[].label | 68 |
| app_info.prerequisites[] | 60 |
| description | 56 |
| app_info.tagline | 53 |
| label | 11 |
| help_text | 6 |
| app_info.default_creds | 5 |
| note | 1 |
| group | 1 |
## Guide
`felhom.eu/documentation/runbooks/VOLUNTEER-first-hour.md`: 44 paragraphs (42 Hungarian), ~1579 words.
## First-boot wizard (reported separately -- never merged into the dashboard count)
8 templates: 86 HU lines; 81 text + 12 attr + 2 JS = **95** strings. Go: 16 literals (shown 16, error 0, log 0, compare 0).
## Dates and sizes
| where | layout | sites |
|---|---|---|
| internal/web Go | `2006-01-02` | 4 |
| internal/web Go | `2006-01-02 15:04` | 3 |
| internal/web Go | `15:04` | 2 |
| internal/web Go | `01-02 15:04` | 1 |
| template | `2006. 01. 02. 15:04` | 1 |
| template | `2006-01-02 15:04` | 1 |
`fmtMB` sites: 8 in templates, 1 in Go. `.Format("` calls in the controller outside internal/web: 25.