scaffold: German labels for the shipped elements and page types #227

Closed
opened 2026-10-12 14:22:49 +02:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Summary

Sidecar labels become translatable per language through a language file beside each sidecar, the way a content translation sits beside its source page: editor/elements/cards.de.yaml beside cards.yaml. The scaffold ships a German file for every element, page type, backend layout and module it ships, including the feature bundles, so a German-speaking editor and an English-speaking editor on the same site each see labels in their own language. The label in the sidecar itself stays as written and is the fallback.

Current behavior

The scaffold ships 17 elements, 3 page types, 2 backend layouts, and through the news and categories bundles 1 element, 4 page types and 2 modules, all with English label, description and category values (75 unique strings). harness init and harness add copy them verbatim; deckle-hosted runs harness init --lang <code> at site creation, so every site starts English-labelled whatever its language. Sidecar labels are single-valued on the wire and the client shows them as written (ElementPicker, ElementForm, LayoutGrid, ModuleRail, CreateDialog, FeatureModule, the form description's fields). The editor's own chrome is translated (#225, #226) and, after #236, so is every label the engine composes; a sidecar's label is the one thing that stays English in a German editor.

A file named cards.de.yaml in a sidecar directory is today a load error (sidecardir.CheckName: a dot is not a valid name), so no existing site has one.

Desired behavior

Language files. A sidecar <family>/<name>.yaml may have, beside it, <name>.<code>.yaml for any language tag (pagepath.ValidLangTag), in all four families: elements, page types, backend layouts, editor modules. The file holds only translated text, mirroring the source's structure with entries keyed by name, never by position:

  • element: label, description, category, items: {label}, params: {<param name>: {label}}
  • page type: label, fields: {<field name>: {label}}
  • backend layout: label, columns: {<column name>: {label}}
  • module: label

Every entry is optional (a missing one falls back to the source). A language file whose source sidecar does not exist, one naming a param, field or column the source lacks, or one holding any key that is not translatable text, is a load error against the language file, reported like every other sidecar load error (in harness check, in the /api/* error lists, in SchemaErrorBanner). A language file never changes anything but text: loading a sidecar with and without its language files yields the same schema apart from the translations.

Wire. Every object that carries a label on the wire — element schema, param, items, page-type schema and field, backend-layout schema and column, module, module FieldInfo, and the form description's FormField — gains an additive i18n map: language code → {label, description, category} (only the keys that object has and the file set). It is omitted when empty, so every existing response is unchanged byte for byte when a site has no language files. label, description and category keep their as-written values.

Client. The helper #236 makes the single owner of "what label do I show" gains the site-authored step. For any label-bearing object: a labelCode → the catalog; else i18n[<ui locale>].label → that; else label. The ui locale is the catalog code the i18n module already resolved from Accept-Language (en or de today); no second language resolution. The same applies to description and category for display and search. The insert dialog keeps grouping by the as-written category (its identity) and displays the translated one; search matches both the written and the translated text.

Scaffold. A .de.yaml beside every sidecar under scaffold/editor/ and under both bundles' editor/ trees, translating every text key each source has, in the register and with the terms of web/src/i18n/glossary.md (impersonal; Seite, Bereich, Element, Bild, Mediathek, Veröffentlichen …). harness init and harness add copy them as ordinary files (receipts list them; harness remove removes them with the rest). No translation logic in either command, and no new flag: --lang keeps meaning the content language.

Tests. A scaffold test holds every shipped German file complete (every text key of its source translated) and the German set loadable through the real loaders; the loader tests cover the three error cases and the "nothing but text changes" invariant; vitest covers the resolution order and the picker's grouping; scaffold/skills_test.go and internal/element/agentsdoc_test.go keep passing.

Docs and skills (CLAUDE.md requires these in the same change):

  • scaffold/site/AGENTS.md: the "Languages" paragraph that says sidecar labels are single-valued and shown as written now explains the language file, its fallback and when it shows (the editor's chrome language); the "Element sidecar schema", "Page types" and "Editor modules" sections each document the language file's keys; the backend-layout language file is documented where editor/backendlayouts/<name>.yaml is (the theme README "Backend layouts and slots" table, and the manual's reference to it).
  • README: "Scaffolding" and "Feature bundles" (both say sidecars are copied verbatim — still true, but language files are now among them); the API section's element, page-type, backend-layout, module and form-description shapes gain i18n.
  • Skills: add-element, add-page-type, add-backend-layout (write the label in the developer's language, add <name>.<code>.yaml for each language the customer's editors work in); create-feature (a bundle's file shapes include its language files); setup-site question 2 (the labels question becomes: which languages the editors work in; German and English are shipped, any other language means writing the sidecar labels in it or adding language files); verify-site's "editors' language" check (switch the browser language and check the labels follow).

Decisions

  • A language file beside the sidecar, not a per-language map inside it — the per-language map was rejected on 2026-10-12 as a schema change across every family, AGENTS.md's schema sections, the drift test and five skills; the side file leaves every sidecar grammar untouched and follows the convention content already uses (_index.de.md beside _index.md). Settled with the maintainer on 2026-10-13, superseding "sidecar labels are single-valued" from #225: the sidecar's own label is still single-valued; the translations live beside it.
  • Per feature, like TYPO3's per-extension language files — a bundle ships its own translations and harness add needs no translation step; an in-place translation at init (the earlier idea for this issue) would have tied labels to --lang and left a site with one label language for every user.
  • The server serves all languages, the client picks — the server never translates (#224/#225), and the editor's one language resolution decides what the whole screen is in; a label language shows when the chrome is in it, so a site with cards.fr.yaml shows French labels once the editor has a French catalog, English until then.
  • Entries keyed by name, not position — reordering params in the source must not silently mismatch its translations.
  • Strict on unknown keys, lenient on missing ones — a stale translation (a renamed param) must be caught by harness check; an incomplete one is normal while translating.
  • sidecardir owns recognising a language file (it already owns the name rule and the directory walk), using pagepath.ValidLangTag for the tag; no second spelling of "is this a language suffix".
  • The starter home page content/_index.md stays English (maintainer, 2026-10-13); it is content, not a label.

Key interfaces

  • sidecardir.Load and CheckName (internal/sidecardir): the walk that must pair <name>.<code>.yaml with <name>.yaml and hand the pair to each family's decoder.
  • The four loaders and schemas: element.LoadDir/Schema/Param/Items (internal/element/load.go, schema.go), pagetype.LoadDir/Schema/Field, backendlayout.LoadDir/Schema/Column, editormodule.LoadDir/Module/FieldInfo (and fieldLabel).
  • element.FormField and the form builder (internal/element/form.go), where a param's label becomes a form field's.
  • pagepath.ValidLangTag / SplitLangTag (internal/pagepath): the language-suffix owner.
  • checkcmd (internal/checkcmd/check.go, sidecars()): nothing new to write if the loaders report the errors; confirm the counts and areas still read right.
  • Client: the label helper from #236 in web/src/api/; web/src/schemas.ts; ElementPicker.tsx (categoryLabel, search), ElementForm.tsx, LayoutGrid.tsx, ModuleRail.tsx, CreateDialog.tsx, FeatureModule.tsx, ElementCard.tsx; the ui-locale signal in web/src/i18n/.
  • scaffold/scaffold_test.go (shipped element library guard) for the completeness test; scaffold/embed.go needs nothing (new files embed with the tree).
  • harness sidecar (internal/sidecar) scaffolds a draft element sidecar from a template; it must not be confused by, or emit, language files.

Acceptance criteria

  • harness init on a fresh directory writes a .de.yaml beside every element, page-type and backend-layout sidecar; harness add news (and the categories it requires) writes one beside every bundle sidecar and module; the receipts list them and harness remove removes them.
  • On that site, with the browser in German, every shipped element tile (label, description, category group), every form field label, every items-collection label, every page-type name in the create dialog, every slot label in the layout grid, and every module rail entry and column header is German; in English everything is byte-identical to today.
  • harness check on a site whose cards.de.yaml names a param cards.yaml lacks reports a load error against cards.de.yaml; the same for a language file without a source and for a non-text key. A language file missing entries loads cleanly.
  • Loading each shipped sidecar with and without its German file yields the same schema except for the i18n map (a loader test pins it).
  • Every /api/elements, /api/pagetypes, /api/backendlayouts, /api/modules and /api/form response is unchanged for a site with no language files.
  • The scaffold test fails when a shipped German file lacks a text key its source has, naming the file and key.
  • AGENTS.md, the theme README, README and the six skills named above are updated; go test ./... (drift tests included) and the vitest suite pass.

How to verify

  • make test; make build.
  • harness init --site /tmp/s --lang de && harness add news --site /tmp/s && harness check --site /tmp/s: zero errors, then harness serve --site /tmp/s with the browser in German and walk the insert dialog, an element form, the page properties, the layout grid and the News module; switch the browser to English and repeat.
  • Rename a param in /tmp/s/editor/elements/cards.yaml without touching cards.de.yaml: harness check names the language file.
  • curl -s http://127.0.0.1:8090/api/elements | jq '.elements[] | select(.name=="cards") | .i18n' shows the German map; on testdata/site (no language files) no i18n key appears anywhere.

Assumptions to check

  • That sidecardir.Load is the only directory walk for sidecars (the harness sidecar generator and harness add's validation may walk on their own — they must not treat a language file as a sidecar).
  • That the form description's fields are built from the param (element.FormField) in exactly one place, so the i18n map rides along once.
  • That FieldInfo labels for declared fields come through the page type's field (so the translation reaches module columns without a separate path).
  • Whether the insert dialog's category side list sorts by the written or the displayed name; pick the displayed one for a German editor and pin it in a test.

Out of scope

  • Translating select option values (left/right, primary/secondary …): they are stored values the shortcodes match on, with no label; a later options: {<value>: {label}} entry in the language file would be the place.
  • feature.yaml manifests (label, description, hints): a CLI and deckle-hosted contract, not an editor label.
  • The starter home page content; any --lang-driven content.
  • Translating labels on existing sites by command; the editor chrome (#225, #226); the engine's own labels (#236); deckle-hosted's per-user Accept-Language override.
  • Any language beyond German in the scaffold.

Blocked by

Original report

Follow-up from the editor i18n planning session of 2026-10-12, filed for triage: once the editor chrome is available in German (#225, #226), a German-speaking customer still sees English element, field, page-type and module labels, because harness init scaffolds them in English and sidecar labels are single-valued by decision. How a site should start with German labels is undecided.

Open questions at filing, now settled above: whether harness init takes a language option (no — labels no longer depend on it); whether sidecars carry labels in several languages (yes, through a language file beside each sidecar, not a map inside it); whether a one-off translate command is enough (no); which feature bundles need the treatment (news and categories, every sidecar and module).

> *This was generated by AI during triage.* ## Summary Sidecar labels become translatable per language through a language file beside each sidecar, the way a content translation sits beside its source page: `editor/elements/cards.de.yaml` beside `cards.yaml`. The scaffold ships a German file for every element, page type, backend layout and module it ships, including the feature bundles, so a German-speaking editor and an English-speaking editor on the same site each see labels in their own language. The label in the sidecar itself stays as written and is the fallback. ## Current behavior The scaffold ships 17 elements, 3 page types, 2 backend layouts, and through the news and categories bundles 1 element, 4 page types and 2 modules, all with English `label`, `description` and `category` values (75 unique strings). `harness init` and `harness add` copy them verbatim; deckle-hosted runs `harness init --lang <code>` at site creation, so every site starts English-labelled whatever its language. Sidecar labels are single-valued on the wire and the client shows them as written (`ElementPicker`, `ElementForm`, `LayoutGrid`, `ModuleRail`, `CreateDialog`, `FeatureModule`, the form description's fields). The editor's own chrome is translated (#225, #226) and, after #236, so is every label the engine composes; a sidecar's label is the one thing that stays English in a German editor. A file named `cards.de.yaml` in a sidecar directory is today a load error (`sidecardir.CheckName`: a dot is not a valid name), so no existing site has one. ## Desired behavior **Language files.** A sidecar `<family>/<name>.yaml` may have, beside it, `<name>.<code>.yaml` for any language tag (`pagepath.ValidLangTag`), in all four families: elements, page types, backend layouts, editor modules. The file holds only translated text, mirroring the source's structure with entries keyed by name, never by position: - element: `label`, `description`, `category`, `items: {label}`, `params: {<param name>: {label}}` - page type: `label`, `fields: {<field name>: {label}}` - backend layout: `label`, `columns: {<column name>: {label}}` - module: `label` Every entry is optional (a missing one falls back to the source). A language file whose source sidecar does not exist, one naming a param, field or column the source lacks, or one holding any key that is not translatable text, is a load error against the language file, reported like every other sidecar load error (in `harness check`, in the `/api/*` error lists, in `SchemaErrorBanner`). A language file never changes anything but text: loading a sidecar with and without its language files yields the same schema apart from the translations. **Wire.** Every object that carries a `label` on the wire — element schema, param, items, page-type schema and field, backend-layout schema and column, module, module `FieldInfo`, and the form description's `FormField` — gains an additive `i18n` map: language code → `{label, description, category}` (only the keys that object has and the file set). It is omitted when empty, so every existing response is unchanged byte for byte when a site has no language files. `label`, `description` and `category` keep their as-written values. **Client.** The helper #236 makes the single owner of "what label do I show" gains the site-authored step. For any label-bearing object: a `labelCode` → the catalog; else `i18n[<ui locale>].label` → that; else `label`. The ui locale is the catalog code the i18n module already resolved from `Accept-Language` (`en` or `de` today); no second language resolution. The same applies to `description` and `category` for display and search. The insert dialog keeps grouping by the as-written `category` (its identity) and displays the translated one; search matches both the written and the translated text. **Scaffold.** A `.de.yaml` beside every sidecar under `scaffold/editor/` and under both bundles' `editor/` trees, translating every text key each source has, in the register and with the terms of `web/src/i18n/glossary.md` (impersonal; Seite, Bereich, Element, Bild, Mediathek, Veröffentlichen …). `harness init` and `harness add` copy them as ordinary files (receipts list them; `harness remove` removes them with the rest). No translation logic in either command, and no new flag: `--lang` keeps meaning the content language. **Tests.** A scaffold test holds every shipped German file complete (every text key of its source translated) and the German set loadable through the real loaders; the loader tests cover the three error cases and the "nothing but text changes" invariant; vitest covers the resolution order and the picker's grouping; `scaffold/skills_test.go` and `internal/element/agentsdoc_test.go` keep passing. **Docs and skills** (CLAUDE.md requires these in the same change): - `scaffold/site/AGENTS.md`: the "Languages" paragraph that says sidecar labels are single-valued and shown as written now explains the language file, its fallback and when it shows (the editor's chrome language); the "Element sidecar schema", "Page types" and "Editor modules" sections each document the language file's keys; the backend-layout language file is documented where `editor/backendlayouts/<name>.yaml` is (the theme README "Backend layouts and slots" table, and the manual's reference to it). - README: "Scaffolding" and "Feature bundles" (both say sidecars are copied verbatim — still true, but language files are now among them); the API section's element, page-type, backend-layout, module and form-description shapes gain `i18n`. - Skills: `add-element`, `add-page-type`, `add-backend-layout` (write the label in the developer's language, add `<name>.<code>.yaml` for each language the customer's editors work in); `create-feature` (a bundle's file shapes include its language files); `setup-site` question 2 (the labels question becomes: which languages the editors work in; German and English are shipped, any other language means writing the sidecar labels in it or adding language files); `verify-site`'s "editors' language" check (switch the browser language and check the labels follow). ## Decisions - A language file beside the sidecar, not a per-language map inside it — the per-language map was rejected on 2026-10-12 as a schema change across every family, AGENTS.md's schema sections, the drift test and five skills; the side file leaves every sidecar grammar untouched and follows the convention content already uses (`_index.de.md` beside `_index.md`). Settled with the maintainer on 2026-10-13, superseding "sidecar labels are single-valued" from #225: the sidecar's own label is still single-valued; the translations live beside it. - Per feature, like TYPO3's per-extension language files — a bundle ships its own translations and `harness add` needs no translation step; an in-place translation at init (the earlier idea for this issue) would have tied labels to `--lang` and left a site with one label language for every user. - The server serves all languages, the client picks — the server never translates (#224/#225), and the editor's one language resolution decides what the whole screen is in; a label language shows when the chrome is in it, so a site with `cards.fr.yaml` shows French labels once the editor has a French catalog, English until then. - Entries keyed by name, not position — reordering params in the source must not silently mismatch its translations. - Strict on unknown keys, lenient on missing ones — a stale translation (a renamed param) must be caught by `harness check`; an incomplete one is normal while translating. - `sidecardir` owns recognising a language file (it already owns the name rule and the directory walk), using `pagepath.ValidLangTag` for the tag; no second spelling of "is this a language suffix". - The starter home page `content/_index.md` stays English (maintainer, 2026-10-13); it is content, not a label. ## Key interfaces - `sidecardir.Load` and `CheckName` (`internal/sidecardir`): the walk that must pair `<name>.<code>.yaml` with `<name>.yaml` and hand the pair to each family's decoder. - The four loaders and schemas: `element.LoadDir`/`Schema`/`Param`/`Items` (`internal/element/load.go`, `schema.go`), `pagetype.LoadDir`/`Schema`/`Field`, `backendlayout.LoadDir`/`Schema`/`Column`, `editormodule.LoadDir`/`Module`/`FieldInfo` (and `fieldLabel`). - `element.FormField` and the form builder (`internal/element/form.go`), where a param's label becomes a form field's. - `pagepath.ValidLangTag` / `SplitLangTag` (`internal/pagepath`): the language-suffix owner. - `checkcmd` (`internal/checkcmd/check.go`, `sidecars()`): nothing new to write if the loaders report the errors; confirm the counts and areas still read right. - Client: the label helper from #236 in `web/src/api/`; `web/src/schemas.ts`; `ElementPicker.tsx` (`categoryLabel`, search), `ElementForm.tsx`, `LayoutGrid.tsx`, `ModuleRail.tsx`, `CreateDialog.tsx`, `FeatureModule.tsx`, `ElementCard.tsx`; the ui-locale signal in `web/src/i18n/`. - `scaffold/scaffold_test.go` (shipped element library guard) for the completeness test; `scaffold/embed.go` needs nothing (new files embed with the tree). - `harness sidecar` (`internal/sidecar`) scaffolds a draft element sidecar from a template; it must not be confused by, or emit, language files. ## Acceptance criteria - [ ] `harness init` on a fresh directory writes a `.de.yaml` beside every element, page-type and backend-layout sidecar; `harness add news` (and the categories it requires) writes one beside every bundle sidecar and module; the receipts list them and `harness remove` removes them. - [ ] On that site, with the browser in German, every shipped element tile (label, description, category group), every form field label, every items-collection label, every page-type name in the create dialog, every slot label in the layout grid, and every module rail entry and column header is German; in English everything is byte-identical to today. - [ ] `harness check` on a site whose `cards.de.yaml` names a param `cards.yaml` lacks reports a load error against `cards.de.yaml`; the same for a language file without a source and for a non-text key. A language file missing entries loads cleanly. - [ ] Loading each shipped sidecar with and without its German file yields the same schema except for the `i18n` map (a loader test pins it). - [ ] Every `/api/elements`, `/api/pagetypes`, `/api/backendlayouts`, `/api/modules` and `/api/form` response is unchanged for a site with no language files. - [ ] The scaffold test fails when a shipped German file lacks a text key its source has, naming the file and key. - [ ] AGENTS.md, the theme README, README and the six skills named above are updated; `go test ./...` (drift tests included) and the vitest suite pass. ## How to verify - `make test`; `make build`. - `harness init --site /tmp/s --lang de && harness add news --site /tmp/s && harness check --site /tmp/s`: zero errors, then `harness serve --site /tmp/s` with the browser in German and walk the insert dialog, an element form, the page properties, the layout grid and the News module; switch the browser to English and repeat. - Rename a param in `/tmp/s/editor/elements/cards.yaml` without touching `cards.de.yaml`: `harness check` names the language file. - `curl -s http://127.0.0.1:8090/api/elements | jq '.elements[] | select(.name=="cards") | .i18n'` shows the German map; on `testdata/site` (no language files) no `i18n` key appears anywhere. ## Assumptions to check - That `sidecardir.Load` is the only directory walk for sidecars (the `harness sidecar` generator and `harness add`'s validation may walk on their own — they must not treat a language file as a sidecar). - That the form description's fields are built from the param (`element.FormField`) in exactly one place, so the `i18n` map rides along once. - That `FieldInfo` labels for declared fields come through the page type's field (so the translation reaches module columns without a separate path). - Whether the insert dialog's category side list sorts by the written or the displayed name; pick the displayed one for a German editor and pin it in a test. ## Out of scope - Translating `select` option values (`left`/`right`, `primary`/`secondary` …): they are stored values the shortcodes match on, with no label; a later `options: {<value>: {label}}` entry in the language file would be the place. - `feature.yaml` manifests (label, description, hints): a CLI and deckle-hosted contract, not an editor label. - The starter home page content; any `--lang`-driven content. - Translating labels on existing sites by command; the editor chrome (#225, #226); the engine's own labels (#236); deckle-hosted's per-user `Accept-Language` override. - Any language beyond German in the scaffold. ## Blocked by - #236 ## Original report Follow-up from the editor i18n planning session of 2026-10-12, filed for triage: once the editor chrome is available in German (#225, #226), a German-speaking customer still sees English element, field, page-type and module labels, because `harness init` scaffolds them in English and sidecar labels are single-valued by decision. How a site should start with German labels is undecided. Open questions at filing, now settled above: whether `harness init` takes a language option (no — labels no longer depend on it); whether sidecars carry labels in several languages (yes, through a language file beside each sidecar, not a map inside it); whether a one-off translate command is enough (no); which feature bundles need the treatment (news and categories, every sidecar and module).
Sign in to join this conversation.
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#227
No description provided.