Files
felhom.eu/documentation/audits/I18N-INVENTORY-2026-09-17.md
T
admin 5b7b2b22f1
gates / gates (push) Successful in 23s
i18n starter: inventory (script + audit), rows R-553..R-562, language allowlisted in wire-contract gate
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
2026-09-17 14:50:29 +02:00

13 KiB
Raw Blame History

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).