docs: v0.257.0 — README, REPORT, CONTEXT for the catalog read path (R-560)
gates / gates (push) Successful in 25s

README gains "Catalog copy in a second language" under §18: the format, the
field-by-field fallback, whole-list replacement vs key matching, the no-write-through
rule, the four templates that carry copy, and why an older controller is unaffected.

REPORT records the nine red-proofs — including the two that convicted a hollow TEST
rather than the code — and the live proof on both demo boxes, naming the method
(endpoint level, no browser on DooPlex) rather than implying a click-through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-20 14:43:52 +02:00
parent 60f0a86bd4
commit eb806f5780
3 changed files with 167 additions and 44 deletions
+50 -1
View File
@@ -3614,7 +3614,7 @@ without reaching `Images()`.
---
### 18. Dashboard language (i18n) (v0.247.0–v0.256.0)
### 18. Dashboard language (i18n) (v0.247.0–v0.257.0)
Design: `felhom.eu/documentation/architecture/10-localisation.md`. Inventory:
`felhom.eu/documentation/audits/I18N-INVENTORY-2026-09-17.md`.
@@ -3715,6 +3715,55 @@ reads** (see below).
captured from the unconverted templates (`internal/web/testdata/i18n_parity/`). Fixtures are never
regenerated to make a conversion pass.
#### Catalog copy in a second language (v0.257.0, R-560 slice 5)
The message bundle above covers text the CONTROLLER owns. An app's own words — its one-line
description, tagline, `use_cases`, `first_steps`, `prerequisites`, `default_creds`, the deploy-field
labels/descriptions/placeholders and select-option labels, `optional_config` group and field text,
integration labels, `data_paths` labels and the initial-credentials note — come from the catalog's
`.felhom.yml` and are Hungarian. They are NOT bundle keys: they belong to the catalog, change on a
catalog push, and a box may run a catalog the controller has never seen.
- **Format:** a sibling block in the SAME file, so a reviewer sees one app whole:
```yaml
description: "Titkosított jegyzet és szöveg megosztás"
i18n:
en:
description: "Encrypted note and text sharing"
```
`Metadata.I18n map[string]MetadataOverlay` (`internal/stacks/metadata_i18n.go`), `json:"-"` — the
untranslated half never travels to a page or an API answer; the merged view does.
- **Read path:** `Metadata.For(lang)`, reached only through `stacks.LocalizeStacks` (a list),
`LocalizeStackPtr` (one stack) or `Stack.MetaFor`. **`For("hu")` is the parsed struct with `I18n`
cleared and nothing else** — `TestMetaForHuIsIdentity` deep-compares it over all 53 real catalog
files in `internal/stacks/testdata/catalog/`. `LocalizeStacks` returns the INPUT SLICE for
Hungarian rather than a copy.
- **Field-by-field fallback:** an English field that is absent — or blank — shows the Hungarian one.
A half-translated app is a legal state, which is what lets the catalog be translated in batches.
- **The three prose lists replace WHOLE** (`use_cases`, `first_steps`, `prerequisites`): merging by
index produces a list nobody wrote. **Every other list is matched by its own key** —
`deploy_fields` by `env_var`, options by `value`, `optional_config` groups by `match_group` (the
Hungarian `group` value they translate; a group has no other identity), its fields by `env_var`,
integrations by `target`, `data_paths` by `path`. Position matching mistranslates silently.
- **Never writes through.** The metadata is the stack manager's and is shared by concurrent
requests; an in-place merge would put one household's language on another's page.
- **Pages:** the dashboard rows, the Apps list, the logs page, the deploy page and the app page.
`TestNoDirectMetaCopyReadOnPages` AST-parses `internal/web` and fails on any `x.Meta.<copy>` read
outside a named, reasoned allow-list — so a NEW page that reads catalog copy off the manager fails
the suite instead of quietly rendering Hungarian to an English household.
- **Two producers have no request and therefore no language** — the integration rows
(`internal/integrations`) and the initial-credentials note (`internal/stacks/initialcreds.go`).
Both are re-taken from the localised metadata in the handler.
- **Older controllers are unaffected:** `LoadMetadata` uses non-strict `yaml.Unmarshal` (this repo
constructs no `yaml.Decoder`), so a pre-0.257.0 box drops the whole block. Proven live on a 0.255.0
box: pages byte-identical before and after the block arrived, no parse warning in a live log.
- **The catalog side** carries the freeze and the gate: `app-catalog-felhom.eu/scripts/copy_freeze/hu.json`
(every Hungarian copy string, captured before any translation) and `scripts/check-copy-i18n.py`
(freeze + structure + language + credential tokens + a coverage ratchet), fifth row of that repo's
`catalog_gates.py`.
## Repository Layout
```