Submission generator: language selector (DE / EN) to drive output document language #108

Open
opened 2026-05-25 14:24:15 +00:00 by mAi · 2 comments
Collaborator

m's report (2026-05-25 16:22)

we may need further improvements to the generator forms: Which language?

Scope

The submission generator currently produces output in the language baked into the template body (mostly DE for the demo templates). m wants the user to explicitly pick the output language (DE or EN) on the draft editor, and the generator should:

  1. Pick the per-firm template variant matching the chosen language (e.g. de.inf.lg.erwidg.docx vs de.inf.lg.erwidg.en.docx).
  2. Resolve language-aware variables from SubmissionVarsService (e.g. {{procedural_event.name_de}} vs {{procedural_event.name_en}}).
  3. Set the document's locale + default language metadata so Word renders dates and numbers correctly.

What to do

Backend

  • Add language column to paliad.submission_drafts (text NOT NULL DEFAULT 'de', CHECK in ('de','en')). Migration 129+ (verify slot).
  • SubmissionVarsService.Build already returns both _de and _en variants; introduce a language parameter that selects the right one for unsuffixed placeholders (e.g. {{procedural_event.name}} becomes the language-matched value).
  • Template fallback chain: prefer language-suffixed file ({code}.{lang}.docx) → fallback to unsuffixed → fallback to _skeleton.{lang}.docx → universal HL skeleton.
  • Export endpoint accepts a ?language=de|en query OR reads from the draft row.

Frontend

  • Draft editor sidebar: small toggle / radio above the variables list: Sprache: ● DE ○ EN.
  • Switching language re-renders the preview (autosave the language choice).
  • Indicate to the user when the chosen language has no per-firm template ("Fallback: universelles Skelett").

i18n

  • New keys: submission.draft.language, submission.draft.language.de, submission.draft.language.en, submission.draft.language.fallback_notice.

Files most likely touched

  • internal/models/models.go — add Language to SubmissionDraft
  • internal/db/migrations/12X_submission_drafts_language.up.sql
  • internal/services/submission_vars.go — language-aware resolution
  • internal/services/submission_templates.go — language-suffixed template lookup
  • internal/handlers/submissions.go + submission_drafts.go
  • frontend/src/submission-draft.tsx / client/submission-draft.ts
  • frontend/src/client/i18n.ts + frontend/src/i18n-keys.ts

Hard rules

  • Default language for new drafts = de (per project CLAUDE.md primary-language convention).
  • Existing drafts inherit de via the column default.
  • go build ./... && go test ./internal/... && cd frontend && bun run build clean.
  • Branch: mai/<worker>/submission-draft-language-selector.

Out of scope

  • Multi-language single-document output (e.g. a bilingual submission). Pick one at a time.
  • Other languages beyond DE/EN.
  • Auto-detecting the user's preferred language from browser settings.

Reporting

mai report completed with branch + SHAs + migration slot + UX path: open a draft → switch language toggle → confirm preview re-renders with EN labels + EN-suffixed values → export → docx is EN.

## m's report (2026-05-25 16:22) > we may need further improvements to the generator forms: Which language? ## Scope The submission generator currently produces output in the language baked into the template body (mostly DE for the demo templates). m wants the user to **explicitly pick** the output language (DE or EN) on the draft editor, and the generator should: 1. Pick the per-firm template variant matching the chosen language (e.g. `de.inf.lg.erwidg.docx` vs `de.inf.lg.erwidg.en.docx`). 2. Resolve language-aware variables from SubmissionVarsService (e.g. `{{procedural_event.name_de}}` vs `{{procedural_event.name_en}}`). 3. Set the document's locale + default language metadata so Word renders dates and numbers correctly. ## What to do ### Backend - Add `language` column to `paliad.submission_drafts` (text NOT NULL DEFAULT 'de', CHECK in ('de','en')). Migration 129+ (verify slot). - `SubmissionVarsService.Build` already returns both `_de` and `_en` variants; introduce a `language` parameter that selects the right one for unsuffixed placeholders (e.g. `{{procedural_event.name}}` becomes the language-matched value). - Template fallback chain: prefer language-suffixed file (`{code}.{lang}.docx`) → fallback to unsuffixed → fallback to `_skeleton.{lang}.docx` → universal HL skeleton. - Export endpoint accepts a `?language=de|en` query OR reads from the draft row. ### Frontend - Draft editor sidebar: small toggle / radio above the variables list: `Sprache: ● DE ○ EN`. - Switching language re-renders the preview (autosave the language choice). - Indicate to the user when the chosen language has no per-firm template ("Fallback: universelles Skelett"). ### i18n - New keys: `submission.draft.language`, `submission.draft.language.de`, `submission.draft.language.en`, `submission.draft.language.fallback_notice`. ## Files most likely touched - `internal/models/models.go` — add `Language` to `SubmissionDraft` - `internal/db/migrations/12X_submission_drafts_language.up.sql` - `internal/services/submission_vars.go` — language-aware resolution - `internal/services/submission_templates.go` — language-suffixed template lookup - `internal/handlers/submissions.go` + `submission_drafts.go` - `frontend/src/submission-draft.tsx` / `client/submission-draft.ts` - `frontend/src/client/i18n.ts` + `frontend/src/i18n-keys.ts` ## Hard rules - Default language for new drafts = `de` (per project CLAUDE.md primary-language convention). - Existing drafts inherit `de` via the column default. - `go build ./... && go test ./internal/... && cd frontend && bun run build` clean. - Branch: `mai/<worker>/submission-draft-language-selector`. ## Out of scope - Multi-language single-document output (e.g. a bilingual submission). Pick one at a time. - Other languages beyond DE/EN. - Auto-detecting the user's preferred language from browser settings. ## Reporting `mai report completed` with branch + SHAs + migration slot + UX path: open a draft → switch language toggle → confirm preview re-renders with EN labels + EN-suffixed values → export → docx is EN.
mAi self-assigned this 2026-05-25 14:24:15 +00:00
Author
Collaborator

Shipped (gitster: demeter)

Branch: mai/demeter/gitster-submission
Commit: 669764e
Migration slot: 130 (bumped from 129 to deconflict with atlas's #96, per paliadin instruction)

Backend

  • paliad.submission_drafts.language text NOT NULL DEFAULT 'de' (CHECK in ('de','en')). Existing rows inherit de via the default — every legacy draft renders byte-for-byte identically.
  • SubmissionVarsContext.Lang overrides the user's UI lang. Build() uses it when set; falls back to user.Lang otherwise, so Slice 1's format-only /generate keeps working unchanged.
  • SubmissionDraftService.BuildRenderBag threads draft.Language through; Create / EnsureLatest seed from the UI lang (DE default).
  • DraftPatch.Language added; Update validates and rejects values outside {de,en}. Both project-scoped and global PATCH endpoints surface the field.
  • resolveSubmissionTemplate(ctx, code, lang) replaces the lang-less predecessor. Returns the matched tier (per_code_lang / per_code / skeleton_lang / skeleton / letterhead) so the editor knows whether the resolved template language-matches the request.
  • fileRegistry registers the EN skeleton sibling submission/_skeleton.en.docx alongside the DE one. Per-code EN variants land in submissionTemplateENRegistry (empty for now — HLC authors per {code}.en.docx). Gitea 404s on missing variants fall through silently.
  • POST /api/projects/{id}/submissions/{code}/generate accepts ?language=de|en override; defaults to the user's UI lang.

Frontend

  • DE/EN radio above the variables list in frontend/src/submission-draft.tsx. Switching the radio PATCHes language and the server returns the freshly-resolved bag + preview HTML so the lawyer sees EN values immediately without a reload.
  • Fallback notice ("Fallback: universelles Skelett (keine sprachspezifische Vorlage)." / "Fallback: universal skeleton (no language-matched template).") rendered when the resolved tier doesn't language-match the request.
  • 4 new i18n keys (DE + EN): submissions.draft.language[.de|.en|.fallback_notice].
  • CSS for the toggle (.submission-draft-language-row, .submission-draft-language-fallback).

Tests

  • services.TestNormalizeDraftLanguage covers DE / EN / case / whitespace / unknown.
  • services.TestAddRuleVars_LanguageSelectsMatchedName pins procedural_event.name (and the legacy rule.name alias) to the language-matched value across DE + EN; explicit _de / _en keys stay unchanged regardless of lang.
  • handlers.TestLanguageFallback truth table for all 10 (lang × tier) combinations — pins which tier counts as a fallback for the UI signal.
  • All pre-existing tests pass (go test ./internal/...).

UX path

  1. Open a draft (/projects/{id}/submissions/{code}/draft/{draft_id}).
  2. Sprache: DE | EN appears above the variables list; the radio reflects the persisted column value.
  3. Click EN → PATCH /api/submission-drafts/{id} with {language:"en"} → server returns updated view with lang="en", language_fallback=true, EN-resolved values for procedural_event.name, our_side_en, etc.
  4. Preview re-renders with the EN values; fallback notice shows because no per-code EN template is registered yet.
  5. Click Als .docx exportieren → downloads Statement of Defence-{case}-{YYYY-MM-DD}.docx (file name picks EN via rule.NameEN).
  6. Switch back to DE → values flip back, fallback notice disappears (tplTierPerCode for DE drafts counts as first-class).

Out of scope (deferred per task brief)

  • Bilingual single-document output (each draft picks one language).
  • Languages beyond DE/EN.
  • Auto-detect from browser settings.
  • Authoring the actual EN .docx templates — files land in HL/mWorkRepo under submission/{code}.en.docx and submission/_skeleton.en.docx. Registry slot already exists; today it falls through to DE.

Build hygiene

  • go build ./... clean.
  • go vet ./... clean.
  • go test ./internal/... clean.
  • cd frontend && bun run build clean (2877 i18n keys, data-i18n scan clean).

Note on coordination

Paliadin flagged mid-flight that atlas's #96 also claimed slot 129. Migration was renamed to 130 before commit — no Go code references the slot number, so the rename was a single mv pair.

## Shipped (gitster: demeter) Branch: `mai/demeter/gitster-submission` Commit: [669764e](https://mgit.msbls.de/m/paliad/commit/669764e86f9720962f33b2642969354cd40a918e) Migration slot: **130** (bumped from 129 to deconflict with atlas's #96, per paliadin instruction) ### Backend - `paliad.submission_drafts.language text NOT NULL DEFAULT 'de'` (CHECK in `('de','en')`). Existing rows inherit `de` via the default — every legacy draft renders byte-for-byte identically. - `SubmissionVarsContext.Lang` overrides the user's UI lang. `Build()` uses it when set; falls back to `user.Lang` otherwise, so Slice 1's format-only `/generate` keeps working unchanged. - `SubmissionDraftService.BuildRenderBag` threads `draft.Language` through; `Create` / `EnsureLatest` seed from the UI lang (DE default). - `DraftPatch.Language` added; `Update` validates and rejects values outside `{de,en}`. Both project-scoped and global PATCH endpoints surface the field. - `resolveSubmissionTemplate(ctx, code, lang)` replaces the lang-less predecessor. Returns the matched tier (`per_code_lang` / `per_code` / `skeleton_lang` / `skeleton` / `letterhead`) so the editor knows whether the resolved template language-matches the request. - `fileRegistry` registers the EN skeleton sibling `submission/_skeleton.en.docx` alongside the DE one. Per-code EN variants land in `submissionTemplateENRegistry` (empty for now — HLC authors per `{code}.en.docx`). Gitea 404s on missing variants fall through silently. - `POST /api/projects/{id}/submissions/{code}/generate` accepts `?language=de|en` override; defaults to the user's UI lang. ### Frontend - **DE/EN radio** above the variables list in `frontend/src/submission-draft.tsx`. Switching the radio PATCHes `language` and the server returns the freshly-resolved bag + preview HTML so the lawyer sees EN values immediately without a reload. - **Fallback notice** ("Fallback: universelles Skelett (keine sprachspezifische Vorlage)." / "Fallback: universal skeleton (no language-matched template).") rendered when the resolved tier doesn't language-match the request. - 4 new i18n keys (DE + EN): `submissions.draft.language[.de|.en|.fallback_notice]`. - CSS for the toggle (`.submission-draft-language-row`, `.submission-draft-language-fallback`). ### Tests - `services.TestNormalizeDraftLanguage` covers DE / EN / case / whitespace / unknown. - `services.TestAddRuleVars_LanguageSelectsMatchedName` pins `procedural_event.name` (and the legacy `rule.name` alias) to the language-matched value across DE + EN; explicit `_de` / `_en` keys stay unchanged regardless of lang. - `handlers.TestLanguageFallback` truth table for all 10 (lang × tier) combinations — pins which tier counts as a fallback for the UI signal. - All pre-existing tests pass (`go test ./internal/...`). ### UX path 1. Open a draft (`/projects/{id}/submissions/{code}/draft/{draft_id}`). 2. **Sprache: DE | EN** appears above the variables list; the radio reflects the persisted column value. 3. Click EN → `PATCH /api/submission-drafts/{id}` with `{language:"en"}` → server returns updated view with `lang="en"`, `language_fallback=true`, EN-resolved values for `procedural_event.name`, `our_side_en`, etc. 4. Preview re-renders with the EN values; fallback notice shows because no per-code EN template is registered yet. 5. Click **Als .docx exportieren** → downloads `Statement of Defence-{case}-{YYYY-MM-DD}.docx` (file name picks EN via `rule.NameEN`). 6. Switch back to DE → values flip back, fallback notice disappears (`tplTierPerCode` for DE drafts counts as first-class). ### Out of scope (deferred per task brief) - Bilingual single-document output (each draft picks one language). - Languages beyond DE/EN. - Auto-detect from browser settings. - Authoring the actual EN .docx templates — files land in HL/mWorkRepo under `submission/{code}.en.docx` and `submission/_skeleton.en.docx`. Registry slot already exists; today it falls through to DE. ### Build hygiene - `go build ./...` clean. - `go vet ./...` clean. - `go test ./internal/...` clean. - `cd frontend && bun run build` clean (2877 i18n keys, data-i18n scan clean). ### Note on coordination Paliadin flagged mid-flight that atlas's #96 also claimed slot 129. Migration was renamed to 130 before commit — no Go code references the slot number, so the rename was a single `mv` pair.
Author
Collaborator

Rebased onto main (post-knuth #107 merge)

Branch: mai/demeter/gitster-submission @ af7a1e9 (force-pushed; previous SHA 669764e).

Conflicts resolved per paliadin's coordination note — kept BOTH contributions:

internal/handlers/files.go

  • t-paliad-275's firmSkeletonSubmissionSlug registry entry + const (HL-formatted skeleton).
  • t-paliad-276's skeletonSubmissionENSlug registry entry + const (EN skeleton sibling).

internal/handlers/submission_drafts.go (resolveSubmissionTemplate)

Merged the firm-skeleton tier (t-paliad-275) with the language-aware lookup (t-paliad-276). Final fallback order:

  1. per-firm per-(code, lang)de.inf.lg.erwidg.en.docx etc. (most specific)
  2. per-firm per-code (unsuffixed, DE-baked baseline)
  3. language-matched skeleton_skeleton.en.docx; only fires for EN drafts since DE drafts prefer the firm-skeleton (step 4).
  4. firm-formatted skeleton_firm-skeleton.docx (HL styles, DE prose). First-class for DE drafts; counts as language_fallback=true for EN drafts (handled by languageFallback()).
  5. universal _skeleton.docx (plain DE, backstop)
  6. HL Patents Style letterhead (last-ditch, no placeholders)

The tplTierSkeletonLang tier still trumps any DE skeleton for EN drafts, so an EN draft with a registered _skeleton.en.docx gets a language-matched preview even when no per-code template exists.

Build hygiene

  • go build ./... clean.
  • go vet ./... clean.
  • go test ./internal/... clean (all 3 new tests + every pre-existing test pass).
  • cd frontend && bun run build clean.

Ready for merge.

## Rebased onto main (post-knuth #107 merge) Branch: `mai/demeter/gitster-submission` @ [af7a1e9](https://mgit.msbls.de/m/paliad/commit/af7a1e9c563b658a0f616f7aff00f35b3326ae5d) (force-pushed; previous SHA 669764e). Conflicts resolved per paliadin's coordination note — kept BOTH contributions: ### `internal/handlers/files.go` - t-paliad-275's `firmSkeletonSubmissionSlug` registry entry + const (HL-formatted skeleton). - t-paliad-276's `skeletonSubmissionENSlug` registry entry + const (EN skeleton sibling). ### `internal/handlers/submission_drafts.go` (resolveSubmissionTemplate) Merged the firm-skeleton tier (t-paliad-275) with the language-aware lookup (t-paliad-276). Final fallback order: 1. **per-firm per-(code, lang)** — `de.inf.lg.erwidg.en.docx` etc. (most specific) 2. **per-firm per-code** (unsuffixed, DE-baked baseline) 3. **language-matched skeleton** — `_skeleton.en.docx`; only fires for EN drafts since DE drafts prefer the firm-skeleton (step 4). 4. **firm-formatted skeleton** — `_firm-skeleton.docx` (HL styles, DE prose). First-class for DE drafts; counts as `language_fallback=true` for EN drafts (handled by `languageFallback()`). 5. **universal `_skeleton.docx`** (plain DE, backstop) 6. **HL Patents Style letterhead** (last-ditch, no placeholders) The `tplTierSkeletonLang` tier still trumps any DE skeleton for EN drafts, so an EN draft with a registered `_skeleton.en.docx` gets a language-matched preview even when no per-code template exists. ### Build hygiene - `go build ./...` clean. - `go vet ./...` clean. - `go test ./internal/...` clean (all 3 new tests + every pre-existing test pass). - `cd frontend && bun run build` clean. Ready for merge.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#108
No description provided.