editor: engine-generated form labels stay English in a translated editor #236

Closed
opened 2026-10-12 19:25:45 +02:00 by dominik.polakovics · 0 comments

This was generated by AI during triage.

Summary

Every form label and note the engine composes in Go (not a sidecar) carries a message code next to its English text, so the editor translates it through the catalog the way it already translates the Enabled switch and the Yes/No filter options. In a German editor the translation properties form, a page's Appearance tab and the module list headers stop showing English.

Current behavior

The engine builds a few form fields and labels itself, as plain English strings, and the client shows them as written. Verified sites (2026-10-13):

  • internal/pageops/translation_settings.go: translationSlugField (label "Address segment"), on every translation's properties form, and translationReadOnlyNote ("Follows the default-language page; change it there."), set as FormField.Note on each read-only field of a translation form.
  • internal/pageops/settings.go: the implicit title field (label "Title") and the two Appearance-tab selects FieldBackendLayout ("Backend layout") and FieldBackendLayoutNextLevel ("Backend layout for sub-pages").
  • internal/editormodule/load.go: builtinLabel ("Title", "Date", "Status") — the FieldInfo.Label a module's columns, cards and sort control get for a built-in field no listed page type declares.

The wire already has the pattern for this: element.FormField.LabelCode and editormodule.FilterOption.LabelCode carry an apimsg code (label.enabled, label.yes, …), and the client translates by code in decodeFormDescription (web/src/api/forms.ts) and in fetchModules (web/src/api/modules.ts). The five sites above have no code; FormField.Note has no code field at all; FieldInfo has no LabelCode.

Desired behavior

  • Each engine-composed label above carries its code (label.* in the apimsg registry, next to the existing label.home, label.enabled …) on the wire, in the field the existing pattern uses: labelCode on FormField and on FieldInfo. The English label text is unchanged, byte for byte.
  • FormField.Note gains noteCode the same way; the read-only translation note is the first user.
  • The English catalog and the German catalog carry a server.<code> entry for every new code, so the parity test (which reads the exported registry) passes; the German wording follows web/src/i18n/glossary.md (register and terms; "Seite", "Bereich", …).
  • The client translates every labelCode/noteCode it receives in one place: one helper in the api layer that resolves a label-bearing object's display text (code → catalog, else the text as sent). decodeFormDescription and fetchModules today each inline the same code→label step; replace both with the helper rather than adding a third copy. #227 extends this same helper with site-authored translations, so it must be the single point where "what label do I show" is answered.
  • A label a sidecar declares stays without a code and is shown as written (that is #227's job, not this issue's).

Decisions

  • Code plus client catalog, never a translated string from the server — the i18n decision of #224/#225: the SPA owns the one translation runtime and the one catalog, and the server's English wire strings stay stable for the CLI, agents and tests.
  • Reuse the labelCode field and the label.* namespace rather than a new engine.* scheme — the field and the client path exist; a parallel mechanism would be a second owner of the same rule.
  • The built-in module field labels are in scope although the original report did not list them — same defect, same fix, found while verifying the report.

Key interfaces

  • apimsg registry (internal/apimsg/codes_core.go holds the Label* codes) and its export test that writes web/src/i18n/servermessages.json.
  • element.FormField (internal/element/form.go): LabelCode, new NoteCode.
  • editormodule.FieldInfo (internal/editormodule/schema.go) and builtinLabel/fieldLabel in load.go.
  • pageops settings builders in settings.go and translation_settings.go.
  • web/src/api/forms.ts (decodeFormDescription), web/src/api/modules.ts (fetchModules), serverMessage in the api layer; web/src/widgets/FormFields.tsx shows field.note as the hint.
  • Catalogs web/src/i18n/en.json, de.json; parity test web/src/test/i18nParity.test.ts.
  • README documents the form description's field shape (labelCode) in the API section — add noteCode and the FieldInfo code there.

Acceptance criteria

  • With Accept-Language: de, a translation's properties form shows the address-segment label and the read-only note in German; a page's Appearance tab shows both backend-layout selects in German; the properties form's title field is "Titel"; a module whose page type does not declare title/date shows German column headers and sort labels.
  • With English, every string above is byte-identical to today (existing Go and vitest assertions pass unchanged).
  • Every new code is in the registry, exported, and present in en.json and de.json; the parity test passes.
  • The client has exactly one function that turns a coded label (or note) into display text; forms.ts and modules.ts both call it and no longer inline the lookup.
  • README's description of the form field shape names noteCode and the module field's labelCode.

How to verify

  • make test; make build.
  • harness serve --site testdata/multilingual-site, browser in German: open a translation's properties (address segment + note), a page's Appearance tab, and the news module on testdata/site after removing title from a listed page type's fields to see the built-in label.
  • curl -s http://127.0.0.1:8090/api/form for a translation page: the fields carry labelCode/noteCode next to unchanged label/note.

Assumptions to check

  • Whether the parity test already fails on a registry code with no catalog entry (it should, per #225); if it only checks used keys, extend it so a new label.* code cannot land untranslated.
  • Whether any other Go site composes an editor-facing label without a code — grep Label: string literals in internal/pageops and internal/editormodule before closing.

Out of scope

  • Sidecar-declared labels (elements, fields, page types, modules, layouts) — #227.
  • Sidecar load diagnostics, CLI output, Hugo output (stay English by decision).

Blocked by

None - can start immediately

Original report

Found during the German layout pass (#226).

A few form fields and notes are not authored by the site but generated by the engine in Go, as plain English strings, so they stay English when the editor runs in German:

  • internal/pageops/translation_settings.go: translationSlugField.Label "Address segment" (shown on every translation's properties form) and translationReadOnlyNote "Follows the default-language page; change it there."
  • internal/pageops/settings.go: the implicit "Title" field label (line ~85), and the backend-layout selects "Backend layout" / "Backend layout for sub-pages" (line ~427).

These are not site-authored labels (which #225/#226 deliberately leave untranslated), so the editor's catalog should cover them. #226 kept the server unchanged (out of scope there), so this needs a small server-side change: send a stable label key (or code, as #224 did for errors) next to the English label, and translate it in the client catalog with an engine.* / form.engine.* key in en.json and de.json.

Acceptance: with Accept-Language: de, a translation's properties form and a page's Appearance tab show German labels for these fields; the parity test covers the new keys.

> *This was generated by AI during triage.* ## Summary Every form label and note the engine composes in Go (not a sidecar) carries a message code next to its English text, so the editor translates it through the catalog the way it already translates the Enabled switch and the Yes/No filter options. In a German editor the translation properties form, a page's Appearance tab and the module list headers stop showing English. ## Current behavior The engine builds a few form fields and labels itself, as plain English strings, and the client shows them as written. Verified sites (2026-10-13): - `internal/pageops/translation_settings.go`: `translationSlugField` (label "Address segment"), on every translation's properties form, and `translationReadOnlyNote` ("Follows the default-language page; change it there."), set as `FormField.Note` on each read-only field of a translation form. - `internal/pageops/settings.go`: the implicit title field (label "Title") and the two Appearance-tab selects `FieldBackendLayout` ("Backend layout") and `FieldBackendLayoutNextLevel` ("Backend layout for sub-pages"). - `internal/editormodule/load.go`: `builtinLabel` ("Title", "Date", "Status") — the `FieldInfo.Label` a module's columns, cards and sort control get for a built-in field no listed page type declares. The wire already has the pattern for this: `element.FormField.LabelCode` and `editormodule.FilterOption.LabelCode` carry an `apimsg` code (`label.enabled`, `label.yes`, …), and the client translates by code in `decodeFormDescription` (`web/src/api/forms.ts`) and in `fetchModules` (`web/src/api/modules.ts`). The five sites above have no code; `FormField.Note` has no code field at all; `FieldInfo` has no `LabelCode`. ## Desired behavior - Each engine-composed label above carries its code (`label.*` in the `apimsg` registry, next to the existing `label.home`, `label.enabled` …) on the wire, in the field the existing pattern uses: `labelCode` on `FormField` and on `FieldInfo`. The English `label` text is unchanged, byte for byte. - `FormField.Note` gains `noteCode` the same way; the read-only translation note is the first user. - The English catalog and the German catalog carry a `server.<code>` entry for every new code, so the parity test (which reads the exported registry) passes; the German wording follows `web/src/i18n/glossary.md` (register and terms; "Seite", "Bereich", …). - The client translates every `labelCode`/`noteCode` it receives in one place: one helper in the api layer that resolves a label-bearing object's display text (code → catalog, else the text as sent). `decodeFormDescription` and `fetchModules` today each inline the same code→label step; replace both with the helper rather than adding a third copy. #227 extends this same helper with site-authored translations, so it must be the single point where "what label do I show" is answered. - A label a sidecar declares stays without a code and is shown as written (that is #227's job, not this issue's). ## Decisions - Code plus client catalog, never a translated string from the server — the i18n decision of #224/#225: the SPA owns the one translation runtime and the one catalog, and the server's English wire strings stay stable for the CLI, agents and tests. - Reuse the `labelCode` field and the `label.*` namespace rather than a new `engine.*` scheme — the field and the client path exist; a parallel mechanism would be a second owner of the same rule. - The built-in module field labels are in scope although the original report did not list them — same defect, same fix, found while verifying the report. ## Key interfaces - `apimsg` registry (`internal/apimsg/codes_core.go` holds the `Label*` codes) and its export test that writes `web/src/i18n/servermessages.json`. - `element.FormField` (`internal/element/form.go`): `LabelCode`, new `NoteCode`. - `editormodule.FieldInfo` (`internal/editormodule/schema.go`) and `builtinLabel`/`fieldLabel` in `load.go`. - `pageops` settings builders in `settings.go` and `translation_settings.go`. - `web/src/api/forms.ts` (`decodeFormDescription`), `web/src/api/modules.ts` (`fetchModules`), `serverMessage` in the api layer; `web/src/widgets/FormFields.tsx` shows `field.note` as the hint. - Catalogs `web/src/i18n/en.json`, `de.json`; parity test `web/src/test/i18nParity.test.ts`. - README documents the form description's field shape (`labelCode`) in the API section — add `noteCode` and the `FieldInfo` code there. ## Acceptance criteria - [ ] With `Accept-Language: de`, a translation's properties form shows the address-segment label and the read-only note in German; a page's Appearance tab shows both backend-layout selects in German; the properties form's title field is "Titel"; a module whose page type does not declare title/date shows German column headers and sort labels. - [ ] With English, every string above is byte-identical to today (existing Go and vitest assertions pass unchanged). - [ ] Every new code is in the registry, exported, and present in `en.json` and `de.json`; the parity test passes. - [ ] The client has exactly one function that turns a coded label (or note) into display text; `forms.ts` and `modules.ts` both call it and no longer inline the lookup. - [ ] README's description of the form field shape names `noteCode` and the module field's `labelCode`. ## How to verify - `make test`; `make build`. - `harness serve --site testdata/multilingual-site`, browser in German: open a translation's properties (address segment + note), a page's Appearance tab, and the news module on `testdata/site` after removing `title` from a listed page type's fields to see the built-in label. - `curl -s http://127.0.0.1:8090/api/form` for a translation page: the fields carry `labelCode`/`noteCode` next to unchanged `label`/`note`. ## Assumptions to check - Whether the parity test already fails on a registry code with no catalog entry (it should, per #225); if it only checks used keys, extend it so a new `label.*` code cannot land untranslated. - Whether any other Go site composes an editor-facing label without a code — grep `Label:` string literals in `internal/pageops` and `internal/editormodule` before closing. ## Out of scope - Sidecar-declared labels (elements, fields, page types, modules, layouts) — #227. - Sidecar load diagnostics, CLI output, Hugo output (stay English by decision). ## Blocked by None - can start immediately ## Original report Found during the German layout pass (#226). A few form fields and notes are not authored by the site but generated by the engine in Go, as plain English strings, so they stay English when the editor runs in German: - `internal/pageops/translation_settings.go`: `translationSlugField.Label` "Address segment" (shown on every translation's properties form) and `translationReadOnlyNote` "Follows the default-language page; change it there." - `internal/pageops/settings.go`: the implicit "Title" field label (line ~85), and the backend-layout selects "Backend layout" / "Backend layout for sub-pages" (line ~427). These are not site-authored labels (which #225/#226 deliberately leave untranslated), so the editor's catalog should cover them. #226 kept the server unchanged (out of scope there), so this needs a small server-side change: send a stable label key (or code, as #224 did for errors) next to the English label, and translate it in the client catalog with an `engine.*` / `form.engine.*` key in `en.json` and `de.json`. Acceptance: with `Accept-Language: de`, a translation's properties form and a page's Appearance tab show German labels for these fields; the parity test covers the new keys.
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#236
No description provided.