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
13 KiB
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.
- „
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.gotimeAgoL265,fmtTimeL318,fmtBytesL403), 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, andstateLabel(the app-state words). There are four size helpers (fmtMB,fmtGB,fmtBytes,humanBytes), not one.timeAgobuilds Hungarian with%d(„%d perce") — the plural problem lives there. - „
fmtMB, used 10× in templates" — 8× (grep -cover templates, and the script agrees). - „~13
.Format(calls ininternal/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 8time.RFC3339(machine, locale-free). The two template layouts are as stated and do disagree (2006. 01. 02. 15:04vs2006-01-02 15:04). - Template line count „1 613 (afternoon) / ~1 838 (morning)" — 1 553, by two independent methods
(this script, and
grep -cPon the concatenated templates underLC_ALL=C.UTF-8). Neither prompt figure was reproduced. Tried: adding typographic quotes „ ” (1 556), adding dashes and ellipsis – — … (1 751), countingstyle.csstoo (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. - 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
grepof 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 andcmd/, 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). - 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
shownGo literal actually reaches a page (classification is by statement, not by data flow) and whether anerrorliteral 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).