feat(editor): translate sidecar labels through language files beside each sidecar #258

Merged
dominik.polakovics merged 1 commit from afk/227 into main 2026-10-13 11:39:17 +02:00

Closes #227

What

  • Language files: <family>/<name>.<tag>.yaml beside a sidecar translates its text (element label/description/category/items/params, page type label/fields, backend layout label/columns, module label), keyed by name. internal/sidecardir owns recognition (LangFile, via pagepath.ValidLangTag) and pairing; each family decodes its own keys. These are load errors against the language file: a file with no source, a param/field/column/items the source lacks, a non-text key, or a duplicate .yaml/.yml. Missing or empty entries fall back to the sidecar's own text, and a language file whose source failed to load is skipped silently.

  • Wire: additive i18n (tag → {label, description, category}, omitted when empty) on these objects:

    • element schema, param, items
    • page type, backend layout and slot
    • module, FieldInfo, filter
    • FormField, the form's items description (i18n/itemI18n)
    • the page payload's layout
    • link-picker element rows

    Sites without language files serve byte-identical responses (checked on testdata/site: no i18n key in any of the four list endpoints).

  • Client: web/src/api/labels.ts resolves labelCode → i18n[locale()] → label, and description/category the same way without the code step. The decoders resolve in place. The insert dialog groups by the written category, shows the translated one and sorts the side list by it; search matches both.

  • Scaffold: 29 .de.yaml files (17 elements, 3 page types, 2 backend layouts, news and categories bundle sidecars and modules), following web/src/i18n/glossary.md. Bundle versions are bumped to news 6 and categories 3, so re-adding fills the language files in. harness add/remove treat language files as ordinary bundle files and never count them as types. That covers receipt type lists, bundle validation context and the children sweep.

  • Docs and skills: AGENTS.md ("Languages", the new "Sidecar language files", the element, page-type and module sections), the theme README layout table, README (Scaffolding, Feature bundles, API shapes). Skills updated: add-element, add-page-type, add-backend-layout, create-feature, setup-site, verify-site, plus remove-element and redo-theme where a statement became false.

Tests

  • Loaders: error cases and the "only i18n changes" invariant in every family. JSON tests show i18n absent without language files.
  • Server: form, items, layout view and link targets.
  • checkcmd: language-file load errors.
  • Scaffold:
    • TestShippedSidecarsHaveCompleteGermanFiles names the German file and the missing key.
    • TestShippedGermanSetLoads runs the real loaders.
  • CLI: receipts, validation, the children sweep, gap fill, init and harness sidecar.
  • Vitest: resolution order, the decoders and the picker's grouping, sorting and search.

Verification

  • make build, go test ./..., go vet ./..., gofmt -l ., vitest (3143) and tsc --noEmit all pass. Hugo is not on PATH here, so the Hugo-backed tests skipped.
  • harness init --lang de + harness add news + harness check: 0 problems, 29 .de.yaml files, all listed in the receipts.
  • A renamed param, a missing source and an icon: key each give a load error against the language file.
  • /api/elements shows the German map for cards.
  • Not done: the manual in-browser walkthrough in German and English. The vitest picker and decoder tests cover it.

🤖 Generated with Claude Code

Closes #227 ## What - **Language files**: `<family>/<name>.<tag>.yaml` beside a sidecar translates its text (element `label`/`description`/`category`/`items`/`params`, page type `label`/`fields`, backend layout `label`/`columns`, module `label`), keyed by name. `internal/sidecardir` owns recognition (`LangFile`, via `pagepath.ValidLangTag`) and pairing; each family decodes its own keys. These are load errors against the language file: a file with no source, a param/field/column/items the source lacks, a non-text key, or a duplicate `.yaml`/`.yml`. Missing or empty entries fall back to the sidecar's own text, and a language file whose source failed to load is skipped silently. - **Wire**: additive `i18n` (`tag → {label, description, category}`, omitted when empty) on these objects: - element schema, param, items - page type, backend layout and slot - module, `FieldInfo`, filter - `FormField`, the form's items description (`i18n`/`itemI18n`) - the page payload's `layout` - link-picker element rows Sites without language files serve byte-identical responses (checked on `testdata/site`: no `i18n` key in any of the four list endpoints). - **Client**: `web/src/api/labels.ts` resolves `labelCode` → `i18n[locale()]` → `label`, and description/category the same way without the code step. The decoders resolve in place. The insert dialog groups by the written category, shows the translated one and sorts the side list by it; search matches both. - **Scaffold**: 29 `.de.yaml` files (17 elements, 3 page types, 2 backend layouts, news and categories bundle sidecars and modules), following `web/src/i18n/glossary.md`. Bundle versions are bumped to news 6 and categories 3, so re-adding fills the language files in. `harness add`/`remove` treat language files as ordinary bundle files and never count them as types. That covers receipt type lists, bundle validation context and the children sweep. - **Docs and skills**: AGENTS.md ("Languages", the new "Sidecar language files", the element, page-type and module sections), the theme README layout table, README (Scaffolding, Feature bundles, API shapes). Skills updated: add-element, add-page-type, add-backend-layout, create-feature, setup-site, verify-site, plus remove-element and redo-theme where a statement became false. ## Tests - **Loaders**: error cases and the "only `i18n` changes" invariant in every family. JSON tests show `i18n` absent without language files. - **Server**: form, items, layout view and link targets. - **`checkcmd`**: language-file load errors. - **Scaffold**: - `TestShippedSidecarsHaveCompleteGermanFiles` names the German file and the missing key. - `TestShippedGermanSetLoads` runs the real loaders. - **CLI**: receipts, validation, the children sweep, gap fill, init and `harness sidecar`. - **Vitest**: resolution order, the decoders and the picker's grouping, sorting and search. ## Verification - `make build`, `go test ./...`, `go vet ./...`, `gofmt -l .`, vitest (3143) and `tsc --noEmit` all pass. Hugo is not on PATH here, so the Hugo-backed tests skipped. - `harness init --lang de` + `harness add news` + `harness check`: 0 problems, 29 `.de.yaml` files, all listed in the receipts. - A renamed param, a missing source and an `icon:` key each give a load error against the language file. - `/api/elements` shows the German map for `cards`. - Not done: the manual in-browser walkthrough in German and English. The vitest picker and decoder tests cover it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(editor): translate sidecar labels through language files beside each sidecar
All checks were successful
ci / build-and-test (pull_request) Successful in 3m10s
ci / release (pull_request) Has been skipped
f18084790e
A sidecar <family>/<name>.yaml may now have <name>.<tag>.yaml beside it in
all four families (elements, page types, backend layouts, editor modules),
holding only translated text keyed by name: label, description, category,
items, params, fields, columns. internal/sidecardir owns the rule
(LangFile) and the pairing; each family decodes its own keys. A language
file without a source, naming an entry the source lacks, or holding a
non-text key is a load error against the language file; missing entries
fall back to the sidecar's own text.

Every label-bearing wire object gains an additive i18n map (omitted when
empty, so sites without language files serve byte-identical responses).
The client's label helper resolves labelCode -> i18n[ui locale] -> label;
the insert dialog groups by the written category, shows and sorts by the
translated one, and searches both.

The scaffold ships a German file beside every sidecar, including the news
and categories bundles (bumped to 6 and 3). harness add/remove treat
language files as ordinary bundle files and never as types. AGENTS.md, the
theme README, README and the affected skills document the files.

Closes #227

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

PASS — validated by the lander.

Signal: CI aggregate success (ci / build-and-test: npm build + vitest, go vet, go test ./... with Hugo 0.167.0, so the agentsdoc and skills drift tests ran). Also checked locally: tsc --noEmit clean (CI's vite build does not type-check), gofmt clean on the changed Go files. Branch is level with main (merge base = main head), so there is nothing to resolve.

Conventions: Conventional Commits title; Closes #227 present; the diff matches #227's contract. It covers the language files in all four families via sidecardir.LangFile/Load, the additive omitempty i18n wire map, client resolution in api/labels.ts, 29 .de.yaml files and the AGENTS.md, theme README, README and skills updates CLAUDE.md requires.

One-owner check: sidecardir.LangFile is the single language-file recogniser (loaders, receipts, children sweep, bundle validation). pagepath.ValidLangTag owns the tag. Element description/category rules are shared with the source through parseDescription/parseCategory. No sibling copies found.

Notes (non-blocking):

  • The news (5→6) and categories (2→3) version bumps are not in the issue, but they are explained (re-add fills the language files in) and documented.
  • A whitespace-only translated label is stored untrimmed (sidecardir.Text). This is an edge case, not a concern.
[autoland] verdict: pass PASS — validated by the lander. Signal: CI aggregate `success` (ci / build-and-test: npm build + vitest, go vet, go test ./... with Hugo 0.167.0, so the agentsdoc and skills drift tests ran). Also checked locally: `tsc --noEmit` clean (CI's vite build does not type-check), gofmt clean on the changed Go files. Branch is level with main (merge base = main head), so there is nothing to resolve. Conventions: Conventional Commits title; `Closes #227` present; the diff matches #227's contract. It covers the language files in all four families via sidecardir.LangFile/Load, the additive omitempty `i18n` wire map, client resolution in api/labels.ts, 29 .de.yaml files and the AGENTS.md, theme README, README and skills updates CLAUDE.md requires. One-owner check: `sidecardir.LangFile` is the single language-file recogniser (loaders, receipts, children sweep, bundle validation). `pagepath.ValidLangTag` owns the tag. Element description/category rules are shared with the source through parseDescription/parseCategory. No sibling copies found. Notes (non-blocking): - The news (5→6) and categories (2→3) version bumps are not in the issue, but they are explained (re-add fills the language files in) and documented. - A whitespace-only translated label is stored untrimmed (sidecardir.Text). This is an edge case, not a concern.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!258
No description provided.