i18n starter: 10-localisation.md, live evidence, capability row, STATUS + CONTEXT
gates / gates (push) Successful in 21s
gates / gates (push) Successful in 21s
Design written after the spike ran (controller v0.247.0 live on demo-hp): mechanism, flow, fallback, gates per language, catalog model, sliced plan with costs, operator rulings 1-4 recorded, CC decisions 5-6, open decisions 1b and 7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -181,6 +181,7 @@ likewise silent. Evidence: `audits/DRILL-r361-2026-08-22/evidence/06-part3-decis
|
||||
| **Indítópult (app launcher) — one-tap grid of the household's openable apps** | controller v0.163.0 | **IMPLEMENTED** | New FIRST sidebar page `/launcher`: colored tiles (deterministic slug color or `.felhom.yml` `brand_color`) + white glyph/monogram, one per openable app (tile ⟺ „Megnyitás" — subdomain presence is the single criterion; controller excluded). Operational → `<a target=_blank>` to the public URL; stopped → greyed + state badge, no link. `/` stays the Vezérlőpult. Endpoint-level + render-test verified; `felhom-controller/REPORT.md` (2026-07-24) | Live operator click-through of a real tile → app pending (browser automation not available on DooPlex). Follow-up: curate `brand_color` for top catalog apps (R-72). Sharing the launcher outside the household is now the capability-URL guest link — see the row below |
|
||||
| **Indítópult megosztás (vendég link)** — capability URL `/s/<token>` serves a standalone read-only guest launcher (no account, no admin session); optional per-share password; QR | controller v0.165.0 | **IMPLEMENTED** | 160-bit `crypto/rand` token, constant-time match (empty stored = disabled = byte-identical to the mux default 404); guest headers `noindex`/`no-referrer`/`no-store`; optional SEPARATE bcrypt share password + its own per-IP attempt map; signed cookie `HMAC(token`\|`passwordHash)` keyed with `session_secret` (rotate-token OR change-password invalidates all cookies); token redacted in logs (`/s/<redacted>`). Groups A–G (14 tests) + 3 red-proofs; **§13 endpoint-level live validation on 9201 all-pass** (`felhom-controller/REPORT.md` 2026-07-24). **Design ruling: member accounts SUPERSEDED by this capability-URL model; per-member tile visibility parked under the SSO arc (R-15).** | Full operator browser click-through + a validation doc pending → then PROVEN-LIVE. Accepted residuals: link-preview crawlers fetch once (noindex prevents indexing); reverse-proxy/CF access logs hold the path (ops-tier); the modal link carries the request Host (LAN-IP admin ⇒ LAN-IP link) |
|
||||
| Forgot dashboard password → instant reset code | controller v0.123, hub | **PROVEN-LIVE** | `DRILL-day0-take2-2026-07-12` F-15 (live re-run of the exact failure path: hash applied 1s after request, code accepted first try) | |
|
||||
| **The dashboard in English** — the household picks its language; Hungarian unchanged | controller v0.247.0 (spike) | **PARTIAL — PROVEN-LIVE for three pages** (launcher, backups overview, app page + the shared layout) | `audits/i18n-2026-09-17/live/` — on demo-hp the Hungarian pages were identical to 0.246.0 apart from version and CSRF token; `POST /settings/language` switched them to English and back; the hub stored the reported `language` (`en`, then `hu`). Unit: `TestI18nParity` (14 states vs fixtures from unconverted templates, red-proofed). Design `architecture/10-localisation.md` | 31 other templates (R-556), Go-side messages (R-557, after R-553), hub e-mails (R-558), console/download page (R-559, scope unconfirmed), catalog copy (R-560), guide + English stranger drill (R-561). The switch is hidden from Hungarian pages until R-556 |
|
||||
| Multiple household users / per-person accounts | — | **MISSING** | — | Single dashboard password; acceptable for alpha → R-15 |
|
||||
| WireGuard base infra always-on; OOB operator access (felhom-sshd, /32 peer) | agent v0.72, hub v0.35 | **IMPLEMENTED** | `SPIKE-oob-wg-operator-peer-2026-07-05`, `SPIKE-felhom-sshd-2026-07-05` | Mutual-repair desired-state arc not built → R-13. **CHECKED 2026-08-08 (R-260) and this row was NOT claiming something untrue** — it claims the capability is implemented, never that it is monitored, so no correction was owed. What WAS untrue is narrower and sat one layer down: **the hub's own OOB health check could not see whether the operator's key was installed.** `HostOOBRow` mirrored five of the agent's eight OOB fields, so `operator_key_configured` — emitted every heartbeat since agent v0.72.0, i.e. from this row's own vintage — was discarded by `encoding/json` on arrival, and `oobDegraded` returned `ok` for a box with felhom-sshd active, reachable, a valid config, a configured peer and **no operator key at all**. `operator_peer_configured`, which it did read, only says the peer IP is in desired-state — that OOB is MEANT to work, not that entry is possible. Fixed hub v0.99.0; the missing key now degrades and the alert NAMES it; a stanza too old to carry the field is reported distinctly and is never a silent ok. Pinned end-to-end from raw report JSON by `TestHostOOB_MissingOperatorKey_EndToEnd` and `TestHostOOB_NoKeyField_IsNotSilentlyOK_EndToEnd` |
|
||||
| The operator can see WHERE a managed host is — its LAN address and its WireGuard address, on the host page | agent **v0.119.0**, hub **v0.85.0** | **PROVEN-LIVE** (2026-07-31) | `audits/host-addresses-visible-2026-07-31.md` | Before this the LAN IP was **not reportable at all** — `HostMetrics` carried no address of any kind — and the WG IP existed only in `/offsite`'s peer table keyed by pubkey (peer→host, never host→peer). New wire field `addresses[]`, one row per (interface, address); `IsGlobalUnicast()` is the whole filter, chosen by MEASURING both demo boxes, and it needs no veth/fwbr denylist because that plumbing carries no IP. Rendered live on both 0.119.0 hosts matching their `ip addr` ground truth exactly. **Two honesty properties carry the risk and are both red-proofed:** WireGuard shows the hub ALLOCATION and whether the box CONFIRMS holding it (allocation alone cannot tell a live tunnel from a peer never applied), and an agent below 0.119.0 renders **UNKNOWN, never "no addresses"** — proven live on `drill-r50-0a4f9a` (0.113.0). **Not covered:** a two-LAN-bridge box and a real WG drift, neither of which exists to observe |
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
# 10 — The product in more than one language
|
||||
|
||||
> **LIVING DOCUMENT. Every localisation slice updates this file in the same session.**
|
||||
> Opened 2026-09-17 by the localisation starter, AFTER its spike ran (controller v0.247.0). Before it,
|
||||
> no architecture document mentioned localisation at all (a grep of `architecture/*.md` for
|
||||
> `i18n|locali|language` found one incidental line in 07).
|
||||
>
|
||||
> Marks, as in 07 and 02: **[DESIGN]** — a decision taken, not derived from code. **[FACT]** — observed,
|
||||
> with a citation. Unmarked means not yet classified.
|
||||
|
||||
**This file carries the REASONING. The register (`backlog/OPEN-ITEMS.md`, R-553..R-562) carries the
|
||||
work. The numbers are in `audits/I18N-INVENTORY-2026-09-17.md`. The source is the truth.**
|
||||
|
||||
---
|
||||
|
||||
## 1. The rule that makes this safe
|
||||
|
||||
**[DESIGN] The Hungarian product must render byte-for-byte the same before and after every slice —
|
||||
measured page by page, never by reading.** A household that never switches must not be able to tell a
|
||||
localisation release happened. English goes on top of an unchanged Hungarian product.
|
||||
|
||||
**[FACT]** It is enforceable, and enforced for the three converted pages: `TestI18nParity`
|
||||
(`felhom-controller/controller/internal/web/i18n_parity_test.go`) renders 14 fixture states of the
|
||||
launcher, `/backups` and `/apps/<slug>` in Hungarian and compares them with HTML captured from a clean
|
||||
worktree of `origin/main` 89dd3e94b1de **before** any template carried a marker. One changed byte in
|
||||
`hu.json` fails it naming the page and the line (red-proof,
|
||||
`felhom-controller/REPORT.md`). **Fixtures are captured from unconverted templates and never
|
||||
regenerated to make a conversion pass** — that is the release gate for every slice.
|
||||
|
||||
The one normalisation: relative ages („3 napja") become „# napja", because they come from the wall
|
||||
clock.
|
||||
|
||||
**[FACT] And live, on demo-hp guest 9201 (2026-09-17, endpoint-level):** the launcher, `/backups` and
|
||||
`/apps/privatebin` fetched in Hungarian on 0.246.0 and again on 0.247.0 are **identical apart from the
|
||||
version string and the CSRF token**, with equal raw byte counts (44 462 / 46 250 / 42 081), and again
|
||||
after a round trip through English (`audits/i18n-2026-09-17/live/`).
|
||||
|
||||
---
|
||||
|
||||
## 2. The mechanism, as measured
|
||||
|
||||
### 2.1 What was chosen, and why
|
||||
|
||||
**[DESIGN] A flat message bundle per language, expanded into the template BEFORE `html/template`
|
||||
parses it.** Decided by CC in the spike (the starter delegated the choice, §4 1.1 of the task).
|
||||
|
||||
| option | cost | verdict |
|
||||
|---|---|---|
|
||||
| (a) `golang.org/x/text/message` catalogs + a `T` func | a new external dependency; its value is gender/plural/ordinal selection | **not needed**: the inventory found no gender agreement and only count plurals; Hungarian does not inflect after a numeral |
|
||||
| (b1) flat bundle, **runtime** `T` template func | every string passes through the contextual escaper: `+`, `'`, `"` change bytes; inside `<script>` a string becomes a quoted JS literal — 618 of the 1 867 template strings are in `<script>` | **rejected**: breaks byte parity by construction |
|
||||
| **(b2) flat bundle, expanded at template LOAD** | one parsed template set per language (startup parses every template once per language — not measured); a translation is raw source text, so its context safety must be tested | **chosen**: the Hungarian set is parsed from the same bytes, in the same escaping contexts — parity holds by construction and is then measured |
|
||||
|
||||
### 2.2 How it works
|
||||
|
||||
- **[FACT]** Bundles: `controller/internal/i18n/locales/hu.json` (authoritative — every key) and
|
||||
`en.json`, embedded (`internal/i18n/i18n.go`). Flat `key → text`. A value may carry **template
|
||||
actions** (`{{.RecoveryAbandonDate}}`) and **inline markup** (`<strong>`, `<a>`): those are the
|
||||
message's parameters, kept inside one message so a translator can move them. English plurals are
|
||||
`key.one` / `key.other` (`Bundle.Plural`), and a template value may also branch
|
||||
(`{{if eq $n 1}}file{{else}}files{{end}}`).
|
||||
- **[FACT]** A converted template carries `{{T "key"}}`. `Server.loadTemplates` parses one set per
|
||||
supported language through `parseTemplateSet` = `ParseFS` + `i18n.Expand` per file, naming templates
|
||||
exactly as `ParseFS` does. `s.tmpl` stays the Hungarian set, so every renderer that predates i18n is
|
||||
unchanged.
|
||||
- **[FACT]** An **undefined** key is left in place, so the set fails to load („function T not
|
||||
defined") — loud at startup and in every render test (`TestExpandLeavesUndefinedMarker`).
|
||||
- **[FACT]** Only `executeTemplate` is language-aware (`server.go`). Login, claim, recovery and the
|
||||
guest share pages render through `s.tmpl` directly and stay Hungarian until their slice.
|
||||
- **[FACT]** Go-side copy on a converted page: the handler names its title key (`data["TitleKey"]`);
|
||||
the non-Hungarian sets override the copy-producing funcs `stateLabel`, `timeAgo`, `timeAgoStr`,
|
||||
`nextRunLabel`, `statusText` from the bundle (`web/i18n_web.go` `localeFuncs`). The Hungarian funcs
|
||||
are not touched; `TestLocaleFuncsHungarianBundleMatchesFuncMap` pins that `hu.json` carries the same
|
||||
words they return.
|
||||
|
||||
### 2.3 What a translation must not do (pinned by tests)
|
||||
|
||||
- **Carry different parameters.** Same `{{.Field}}` set and same printf verbs as the Hungarian
|
||||
(`TestBundleParametersMatchAcrossLanguages`).
|
||||
- **Break its context.** Expansion is textual; inside a JS string a bare `'`/`"`/`\` or newline ends
|
||||
it, inside a double-quoted attribute a `"` ends it — and `html/template` cannot see this, because it
|
||||
reads the expansion as the author's own source. A value may carry a quote only where the Hungarian
|
||||
carries the same one (`TestI18nJSContextValuesAreSafe`, red-proofed with `Copied'`). English
|
||||
therefore avoids apostrophes in JS contexts („do not", not „don't"); elsewhere it uses ’.
|
||||
- **Render blank or raw.** No raw key, no marker, no element empty in English that is not empty in
|
||||
Hungarian (`TestI18nEnglishPages`).
|
||||
|
||||
**[FACT] What stays Hungarian on the English pages, live:** only Go view-model text (the backup-target
|
||||
banner and offer, the whole-guest tier labels, the update badge „Naprakész") and catalog copy (the
|
||||
app's tagline, use cases, first steps) — slices 2 and 5. No template copy.
|
||||
|
||||
**[FACT] Measured limit of the English page test:** it subtracts every string the fixture DATA carries
|
||||
(catalog copy, handler messages) before looking for Hungarian — so a template word identical to a data
|
||||
string is masked (the launcher cases pass the nav's „Indítópult" as the page title). The backups and
|
||||
app cases cover the nav; the limitation is recorded, not fixed.
|
||||
|
||||
---
|
||||
|
||||
## 3. Who picks the language, and how it flows
|
||||
|
||||
**[DESIGN] Operator decision 2 (2026-09-17, agreed): the operator sets it per customer on the hub at
|
||||
creation (default Hungarian); the household can switch it on their dashboard; the box reports it so
|
||||
the hub's e-mails follow.**
|
||||
|
||||
- **[FACT] Built in v0.247.0:** `settings.json` `language` (`hu`|`en`; empty reads `hu`,
|
||||
`Settings.GetLanguage`); `POST /settings/language` (session CSRF like every form; `lang`, `back`;
|
||||
redirect drops the query); `?lang=hu|en` per-request override, never persisted; the hub report's
|
||||
`"language"` field, always present, set at all four report build sites in `cmd/controller/main.go`.
|
||||
- **[FACT] Live 2026-09-17 on demo-hp:** `POST /settings/language lang=en` → 302, `settings.json`
|
||||
`"language": "en"`, the three pages English; the hub's stored reports read no field (0.246.0), `hu`,
|
||||
`en` at 12:54:43Z, `hu` at 12:55:22Z after switching back; without `_csrf` → 403 and nothing changed
|
||||
(`audits/i18n-2026-09-17/live/README.md`).
|
||||
- **[DESIGN] Not built — slice 3:** the hub stores a per-customer language, renders it into
|
||||
`controller.yaml` next to `customer.id/name/domain/email` (`hub/internal/configgen/configgen.go`),
|
||||
and the box uses it **only while the household has never chosen** (`settings.json` empty). The
|
||||
household's own choice always wins; the hub's e-mails follow the language the box **reports**, which
|
||||
is therefore the household's choice. The hub decodes the report with independent `json.Unmarshal`
|
||||
calls and no `DisallowUnknownFields` (`hub/internal/api/handler.go` `handleReport`), so the field
|
||||
was additive.
|
||||
|
||||
**[DESIGN] Decided by CC in the spike — operator may reverse: the switch is SHOWN only on a page that
|
||||
is not Hungarian, or on a request carrying `?lang=`.** One answerable sentence: *should a Hungarian
|
||||
household see an English switch while only three pages are English?* Options: (1) show it to everyone
|
||||
now — cost: one click from a half-English dashboard, and every page but three keeps a Hungarian body;
|
||||
(2) hide it until slice 1 converts the remaining pages — cost: a household cannot discover English
|
||||
yet, which no household has asked for. **Chosen (2)**: it follows rule §1 (nothing visible changes)
|
||||
and is reversed by deleting one condition in `addLanguageData`. The way back from English is always on
|
||||
screen.
|
||||
|
||||
---
|
||||
|
||||
## 4. Fallback
|
||||
|
||||
**[DESIGN] Operator decision 3 (2026-09-17, agreed): a missing English line shows the Hungarian one
|
||||
and is counted by a gate; a missing line never shows a key or an empty box.**
|
||||
|
||||
- **[FACT]** `Bundle.Text` falls back to Hungarian and flags it; the loader logs
|
||||
`i18n: en template set shows Hungarian for N markers`; `Bundle.Msg` of a key absent everywhere
|
||||
returns the key (visible, never blank) and `TestBundleKeysUsedExistInHungarian` refuses to ship one.
|
||||
- **[FACT]** `controller/scripts/i18n_missing_gate.py` (in `controller_gates.py`, pre-push): keys exist,
|
||||
no orphans, **the English gap is a ratchet** — `EN_MISSING_CEILING` (0 today) convicts above AND
|
||||
below, so it can only be lowered deliberately. Raising it is a decision recorded here.
|
||||
|
||||
---
|
||||
|
||||
## 5. The gates, per language
|
||||
|
||||
**[FACT] Found in the spike: moving copy out of the templates blinded four gates and staled one.**
|
||||
The retrieval-promise gate went red on STALE allowlist entries the moment the first page converted;
|
||||
the emoji, native-confirm and secret-in-markup gates silently stopped seeing the moved copy. The HEAD
|
||||
versions of the emoji and retrieval gates PASSED a planted emoji and a planted retrieval promise in the
|
||||
bundles. They now judge the page **as rendered in each language** (`controller/scripts/i18n_bundle.py`
|
||||
`read_template(path, lang)`); mojibake scans the bundles; decoys for each are in
|
||||
`controller/scripts/test_gate_decoys.py`.
|
||||
|
||||
**[DESIGN] Voice, per language:**
|
||||
- **hu** — the product speaks in „te". The converted copy carries **6 formal („ön") forms** (R-516).
|
||||
They are not fixed by a localisation release (§1), so the gate counts them against
|
||||
`HU_FORMAL_CEILING` = 6 as a ratchet: a new one convicts, fixing one lowers the ceiling.
|
||||
- **en** — second person, plain: no „please", no „kindly".
|
||||
|
||||
**[FACT] Not yet per-language:** the retrieval-promise gate's stems are Hungarian only — an English
|
||||
retrieval promise is unscanned (in R-556). The hub copy gate and the catalog have no language concept
|
||||
(`scripts/hub_copy_gate.py` stems end in a Hungarian character class; `catalog_gates.py` checks no copy).
|
||||
|
||||
---
|
||||
|
||||
## 6. Deliberately out
|
||||
|
||||
- **[DESIGN] The hub's operator pages** — the operator reads them; they stay English (decision 1).
|
||||
- **[DESIGN] The first-boot wizard** (`controller/internal/setup/`, 8 pages, 95 strings) — out of scope
|
||||
and obsolete (decision 4, 2026-09-17; `02-controller-module-map.md` L56). A household still reaches
|
||||
it when bootstrap ingestion leaves `customer.id` empty (inventory §2.7). Its deletion is **R-554**.
|
||||
- **[FACT]** App containers' own UIs (Uptime Kuma, PrivateBin…) — not Felhom's copy (R-516 items 5-6).
|
||||
|
||||
---
|
||||
|
||||
## 7. The catalog's copy model
|
||||
|
||||
**[DESIGN, proposed — slice 5 decides after seeing it work]** A sibling block per language inside
|
||||
`.felhom.yml`, not a sibling file:
|
||||
|
||||
```yaml
|
||||
description: Titkosított jegyzetek…
|
||||
app_info:
|
||||
first_steps: [...]
|
||||
i18n:
|
||||
en:
|
||||
description: Encrypted notes…
|
||||
app_info:
|
||||
first_steps: [...]
|
||||
```
|
||||
|
||||
Why: one app's copy stays in one file a reviewer sees whole; the controller's `yaml.Unmarshal`
|
||||
ignores unknown keys (`internal/stacks/metadata.go` L317), so older controllers are unaffected; a
|
||||
missing English field falls back field by field. Cost: 835 strings, ~4 984 words; the catalog copy
|
||||
gates need an English rule. Read on privatebin, nextcloud and immich (inventory §2.5).
|
||||
|
||||
---
|
||||
|
||||
## 8. Dates, numbers, plurals — as found
|
||||
|
||||
- **[FACT]** Plurals: 42 Go format strings with `%d` and 7 template runs with a numeric parameter
|
||||
(inventory §2.2/2.1). Hungarian needs one form; English two.
|
||||
- **[FACT]** Dates: 10 layout literals in `internal/web` Go, 2 in templates that **disagree with each
|
||||
other** (`2006. 01. 02. 15:04` vs `2006-01-02 15:04`), 25 more in the controller; 9 copy-producing
|
||||
helpers (`timeAgo` „%d perce", `nextRunLabel` „ma"/„holnap", `pruneLabel` „vasárnap"…).
|
||||
- **[FACT]** Sizes print a decimal POINT (`%.1f GB`) — Hungarian convention is a comma. The Hungarian
|
||||
pages are already un-Hungarian there; §1 keeps it that way until someone decides otherwise (R-562).
|
||||
- **[FACT]** Word order: 237 Go format strings and 111 concatenations; in templates, JS sentences split
|
||||
around `+ name +` („Az alkalmazás (" + name + ") törölve lett.") — translatable but fragile.
|
||||
- **[FACT]** Flash messages travel **inside the redirect URL** (`?flash=<text>`) in the language of the
|
||||
handler that redirected — slice 2 moves them to keys.
|
||||
|
||||
---
|
||||
|
||||
## 9. Compared, not shown — latent bugs a translation would trigger
|
||||
|
||||
**[FACT]** Four places decide behaviour by matching Hungarian wording (inventory §3): an HTTP status
|
||||
(`api/router.go:478`), an off-site quota classification (`backup/offbox.go:186`), an alert's placement
|
||||
(`web/alerts.go:208`), and a **persisted** warning compared by marker (`web/handlers.go:927`). Each
|
||||
breaks the day the words change in either language. **R-553 fixes them first, before slice 2 touches a
|
||||
single Go string.**
|
||||
|
||||
---
|
||||
|
||||
## 10. The plan
|
||||
|
||||
Costs are CC-hours, estimated from the spike (mechanism + three pages + layout + gates ≈ one working
|
||||
session). Each slice ends with the parity gate green for every page it touched.
|
||||
|
||||
| slice | row | what | proves | cost |
|
||||
|---|---|---|---|---|
|
||||
| 0 (done) | — | mechanism, 3 pages + layout, setting, report field, gates | Hungarian unchanged by measurement; English reachable | spent |
|
||||
| 1 | R-556 | the other 31 dashboard templates (~1 500 strings, 600 of them JS), parity fixtures per page; switch shown to everyone; English retrieval stems; the extractor's ASCII word list reviewed per page | the whole dashboard in English with Hungarian byte-identical | 12–16 h, three releases |
|
||||
| 2 | R-557 (after R-553) | Go-side customer strings: 947 shown + 184 errors; flash-in-URL → keys; country names; alert texts | a page's server messages follow the language | 16–20 h |
|
||||
| 3 | R-558 | hub: per-customer language at creation; `configgen` renders it; `customerMessages` (39), the 5 lifecycle mails and the self-bind page in English; dispatcher reads the reported language | a household's e-mails arrive in its language | 6–8 h, one hub + one controller release |
|
||||
| 4 | R-559 | console banner (34 lines, console-font limits) and the download page | — **operator to confirm it is in scope** (decision 1b) | 4–6 h + an ISO train |
|
||||
| 5 | R-560 | catalog: §7 format, controller reads it, 835 strings / ~5 000 words, catalog copy gates per language | an app card, its settings and its first steps in English | 12–16 h |
|
||||
| 6 | R-561 | the volunteer guide in English (~1 600 words); **then a stranger's first hour in English**, the 2026-09-14 walk; closes R-516 against the inventory | a newcomer can set up and use a box in English | 6–8 h |
|
||||
|
||||
---
|
||||
|
||||
## 11. Operator decisions
|
||||
|
||||
These are rulings, not proposals. Anything specced against a different assumption is wrong.
|
||||
|
||||
### 2026-09-17 (on the starter)
|
||||
|
||||
1. **Scope: what the household sees — the controller, its e-mails, the guide, the app catalog.** Not
|
||||
the hub's operator pages.
|
||||
2. **Who picks:** the operator per customer at creation (default Hungarian); the household switches on
|
||||
the dashboard; the box reports it so the hub's e-mails follow.
|
||||
3. **Fallback:** a missing English line shows the Hungarian one and is counted; never a key or a blank.
|
||||
4. **The first-boot wizard:** out of scope, obsolete — a separate row to delete it (R-554).
|
||||
|
||||
### Decided by CC in the spike — operator may reverse
|
||||
|
||||
5. **Mechanism (b2)** — §2.1.
|
||||
6. **Switch hidden while only three pages are English** — §3.
|
||||
|
||||
### Open — for the operator, in the §3 shape of `09-update-architecture.md`
|
||||
|
||||
**1b. Are the console banner and the download page in scope?** The starter's summary listed them;
|
||||
the ruling on decision 1 names „the controller, emails, guide, app catalog" and not them.
|
||||
- *Yes* — slice 4 stays: 4–6 h plus an ISO release train; an English-speaking household sees English
|
||||
from the first boot screen.
|
||||
- *No* — slice 4 is dropped: nothing to build; an English household meets a Hungarian console banner
|
||||
and download page before the dashboard.
|
||||
- **If nothing is decided:** slice 4 waits at the end of the plan; slices 1–3, 5, 6 are not blocked.
|
||||
- **Who is blocked:** nobody now; the stranger drill (slice 6) would walk a Hungarian first screen.
|
||||
|
||||
**7. Do product and interface nouns translate?** The spike translated them („Indítópult" → Launcher,
|
||||
„Vezérlőpult" → Dashboard, „Biztonsági mentés" → Backup) and kept app names and „Felhom" as they are.
|
||||
- *Translate interface nouns* (as built) — English pages read naturally; a Hungarian support person
|
||||
and an English household name the same screen differently.
|
||||
- *Keep Hungarian nouns as names* — one vocabulary for support; English pages carry untranslated
|
||||
words a newcomer must learn.
|
||||
- **If nothing is decided:** the spike's choice stands and slice 1 follows it.
|
||||
- **Who is blocked:** slice 1's translation pass (it can proceed on the spike's choice and be revised).
|
||||
Reference in New Issue
Block a user