Skip to content

fix(templates): a BPM task form renders its labels through its module i18n catalog (#6692) - #6706

Merged
delchev merged 1 commit into
masterfrom
fix/task-form-i18n
Aug 13, 2026
Merged

fix(templates): a BPM task form renders its labels through its module i18n catalog (#6692)#6706
delchev merged 1 commit into
masterfrom
fix/task-form-i18n

Conversation

@delchev

@delchev delchev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #6692.

Every generated BPM task form rendered in English — the title, the status stepper, the field labels and the button captions — while the application shell pages around it were fully translated.

The catalogs were never the problem. The same generation pass that renders the page already emits i18n/en-US/<Form>.form.json with a key per label (translationId(label)), and modules already ship their translated copies next to it. The page simply never consumed them: it baked every label in as a literal and did not load the translator at all.

What changed (template-form-builder-harmonia)

  • The page loads the shared translator (application-core/shell/js/services/i18n.js) and, having no SPA shell to read it from, bootstraps App.config.projectName in a one-line inline script first — the same thing the standalone report page already does. Without it, i18n.js fetches only the platform application-core chrome catalog, so every module-authored key silently stays English.
  • Labels bind through T('<project>:<tprefix>.t.<id>', '<English literal>') — title, paragraph, link, field labels, read-only detail labels, button captions — emitted by a single #tlabel macro so every control site is identical. The English literal stays as the element's own text, so a control the catalog never keyed (a hand-authored form without a label) renders exactly as before.
  • A status step keeps its untranslated label — that is what the record's status value is matched against — and gains a translated title, which is what shows. Translating label would leave every step inactive.
  • The two submit outcome messages resolve through the catalog's dialogs section (where successMsg carries the form's authored success text).

Everything is resolved at generation time — the translation id was assigned to the model by the catalog-emitting pass, so nothing is derived in the browser — and an untranslated key degrades to the baked English literal, exactly as elsewhere in the stack. The default language still renders entirely from the literals (i18n.js skips catalog loading there).

One trap worth naming: an authored label may contain an apostrophe, which would close the JS string literal the T() call sits in and break the whole Alpine expression. Both the interpolated literal and the key derived from it are escaped — translationId strips only spaces and _ . :, so Customer's Note keys as Customer'sNote. (Found by rendering the templates offline against a Velocity harness before building.)

Verification

  • New: IntentEngineIT.task_form_renders_its_labels_through_the_module_catalog — asserts both halves, that the labels land in the emitted catalog and that the generated page resolves them through it (plus the namespace bootstrap and the untranslated-label / translated-title split). HTTP-only, in the PR smoke set.
  • Unchanged and green: BPMStarterTemplateIT — a real browser that opens a generated form, fills it by field id and clicks the now x-text-bound button. That is what proves the new Alpine bindings actually evaluate; a broken expression here has no server-side symptom.
  • GenerationParityIT is unaffected: the templates are shared by both generation pipelines, so their output stays byte-identical to each other.

Out of scope (still English, filed as known gaps)

The task-form dialog title and the shell Inbox / notification-bell task names — those are cross-project (the raw BPMN task name reaches the shell with no key), and need a key on TaskDTO rather than a template change. A status step's optional description also stays as authored: the catalog pass keys label and errorMessage only, and adding a third would clash with the translation id it writes onto the same node.

🤖 Generated with Claude Code

… i18n catalog (#6692)

Every generated BPM task form rendered in English - the title, the status stepper, the field
labels and the button captions - while the application shell pages around it were fully
translated. The catalogs were never the problem: the same generation pass that renders the page
already emits `i18n/en-US/<Form>.form.json` with a key per label, and modules already ship their
translated copies next to it. The page simply never consumed them: it baked every label in as a
literal and did not load the translator at all.

It does now, following the standalone report page's precedent:

- the page loads the shared `application-core/shell/js/services/i18n.js` and, having no SPA shell
  to read it from, bootstraps `App.config.projectName` in a one-line inline script first - without
  that, i18n.js fetches only the platform `application-core` chrome catalog and every
  module-authored key silently stays English;
- the title, paragraph, link, field labels, read-only detail labels and button captions bind
  through `T('<project>:<tprefix>.t.<id>', '<English literal>')`, emitted by one `#tlabel` macro so
  every control site is identical, and the English literal stays as the element's own text so a
  control the catalog never keyed (a hand-authored form without a label) is unchanged;
- a status step keeps its untranslated `label` - that is what the record's status value is MATCHED
  against - and gains a translated `title`, which is what shows;
- the two submit outcome messages resolve through the catalog's `dialogs` section.

Everything is resolved at generation time: the translation id was assigned to the model by the
catalog-emitting pass, so nothing is derived in the browser, and an untranslated key degrades to
the baked English literal exactly as elsewhere in the stack.

An authored label may contain an apostrophe, which would close the JS string literal the `T()`
call sits in and break the whole Alpine expression, so both the interpolated literal and the KEY
derived from it are escaped - `translationId` strips only spaces and `_ . :`, so
`Customer's Note` keys as `Customer'sNote`.

Covered by `IntentEngineIT.task_form_renders_its_labels_through_the_module_catalog`, which asserts
both halves - the labels land in the emitted catalog, and the generated page resolves them through
it. `BPMStarterTemplateIT` (a real browser filling and submitting a generated form) stays green,
which is what proves the new Alpine bindings actually evaluate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit fc99ee8 into master Aug 13, 2026
10 checks passed
@delchev
delchev deleted the fix/task-form-i18n branch August 13, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BPM task forms ignore the module i18n catalogs - title, status stepper and field labels render in English

1 participant