scaffold: German labels for the shipped elements and page types #227
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle#227
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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.yamlbesidecards.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,descriptionandcategoryvalues (75 unique strings).harness initandharness addcopy them verbatim; deckle-hosted runsharness 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.yamlin 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>.yamlmay have, beside it,<name>.<code>.yamlfor 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:label,description,category,items: {label},params: {<param name>: {label}}label,fields: {<field name>: {label}}label,columns: {<column name>: {label}}labelEvery 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, inSchemaErrorBanner). 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
labelon the wire — element schema, param, items, page-type schema and field, backend-layout schema and column, module, moduleFieldInfo, and the form description'sFormField— gains an additivei18nmap: 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,descriptionandcategorykeep 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; elsei18n[<ui locale>].label→ that; elselabel. The ui locale is the catalog code the i18n module already resolved fromAccept-Language(enordetoday); no second language resolution. The same applies todescriptionandcategoryfor display and search. The insert dialog keeps grouping by the as-writtencategory(its identity) and displays the translated one; search matches both the written and the translated text.Scaffold. A
.de.yamlbeside every sidecar underscaffold/editor/and under both bundles'editor/trees, translating every text key each source has, in the register and with the terms ofweb/src/i18n/glossary.md(impersonal; Seite, Bereich, Element, Bild, Mediathek, Veröffentlichen …).harness initandharness addcopy them as ordinary files (receipts list them;harness removeremoves them with the rest). No translation logic in either command, and no new flag:--langkeeps 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.goandinternal/element/agentsdoc_test.gokeep 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 whereeditor/backendlayouts/<name>.yamlis (the theme README "Backend layouts and slots" table, and the manual's reference to it).i18n.add-element,add-page-type,add-backend-layout(write the label in the developer's language, add<name>.<code>.yamlfor each language the customer's editors work in);create-feature(a bundle's file shapes include its language files);setup-sitequestion 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
_index.de.mdbeside_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.harness addneeds no translation step; an in-place translation at init (the earlier idea for this issue) would have tied labels to--langand left a site with one label language for every user.cards.fr.yamlshows French labels once the editor has a French catalog, English until then.harness check; an incomplete one is normal while translating.sidecardirowns recognising a language file (it already owns the name rule and the directory walk), usingpagepath.ValidLangTagfor the tag; no second spelling of "is this a language suffix".content/_index.mdstays English (maintainer, 2026-10-13); it is content, not a label.Key interfaces
sidecardir.LoadandCheckName(internal/sidecardir): the walk that must pair<name>.<code>.yamlwith<name>.yamland hand the pair to each family's decoder.element.LoadDir/Schema/Param/Items(internal/element/load.go,schema.go),pagetype.LoadDir/Schema/Field,backendlayout.LoadDir/Schema/Column,editormodule.LoadDir/Module/FieldInfo(andfieldLabel).element.FormFieldand 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.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 inweb/src/i18n/.scaffold/scaffold_test.go(shipped element library guard) for the completeness test;scaffold/embed.goneeds 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 initon a fresh directory writes a.de.yamlbeside 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 andharness removeremoves them.harness checkon a site whosecards.de.yamlnames a paramcards.yamllacks reports a load error againstcards.de.yaml; the same for a language file without a source and for a non-text key. A language file missing entries loads cleanly.i18nmap (a loader test pins it)./api/elements,/api/pagetypes,/api/backendlayouts,/api/modulesand/api/formresponse is unchanged for a site with no language files.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, thenharness serve --site /tmp/swith 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./tmp/s/editor/elements/cards.yamlwithout touchingcards.de.yaml:harness checknames the language file.curl -s http://127.0.0.1:8090/api/elements | jq '.elements[] | select(.name=="cards") | .i18n'shows the German map; ontestdata/site(no language files) noi18nkey appears anywhere.Assumptions to check
sidecardir.Loadis the only directory walk for sidecars (theharness sidecargenerator andharness add's validation may walk on their own — they must not treat a language file as a sidecar).element.FormField) in exactly one place, so thei18nmap rides along once.FieldInfolabels for declared fields come through the page type's field (so the translation reaches module columns without a separate path).Out of scope
selectoption values (left/right,primary/secondary…): they are stored values the shortcodes match on, with no label; a lateroptions: {<value>: {label}}entry in the language file would be the place.feature.yamlmanifests (label, description, hints): a CLI and deckle-hosted contract, not an editor label.--lang-driven content.Accept-Languageoverride.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 initscaffolds 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 inittakes 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).