Per-draft `language` column drives the .docx output language for the
submission generator. The lawyer picks DE or EN on the draft editor's
sidebar; the generator selects the language-matched template variant
(falling back through {code}.{lang} → {code} → _skeleton.{lang} →
_skeleton → letterhead) and resolves language-aware variables
({{procedural_event.name}} → name_de vs name_en).
Schema (mig 130 — bumped from 129 to deconflict with atlas's #96):
- paliad.submission_drafts.language text NOT NULL DEFAULT 'de'
CHECK IN ('de','en'). Existing rows inherit 'de' via the default,
preserving every legacy draft's behaviour byte-for-byte.
Backend (Go):
- SubmissionVarsContext.Lang overrides the user's UI lang. Build()
uses it when set; falls back to user.Lang otherwise — Slice 1's
format-only /generate path keeps working unchanged.
- SubmissionDraftService.BuildRenderBag now threads draft.Language
through. Create/EnsureLatest seed from the UI lang (DE default).
- DraftPatch.Language landed; Update validates and rejects values
outside {de,en}. Project-scoped + global PATCH endpoints both
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
to surface the "Fallback: universelles Skelett" notice.
- fileRegistry registers the EN skeleton sibling (`_skeleton.en.docx`)
alongside the DE one; per-code EN variants land in a parallel
submissionTemplateENRegistry (empty for now — EN templates land per
HLC authoring). 404s from Gitea fall through silently.
- /api/projects/{id}/submissions/{code}/generate accepts
`?language=de|en` query override (one-shot path, no draft row to
pull the column from); defaults to the user's UI lang.
Frontend (TS/JSX):
- DE/EN radio above the variables list in the draft editor sidebar.
Switching the radio PATCHes `language` and the server returns the
freshly-resolved bag + preview HTML so the lawyer sees EN values
immediately.
- Fallback notice ("Fallback: universelles Skelett (keine
sprachspezifische Vorlage)") shows when the resolved tier doesn't
match the requested language.
- 4 new i18n keys (DE + EN) + CSS for the toggle.
Tests:
- normalizeDraftLanguage covers DE/EN/case/whitespace/unknown.
- addRuleVars language-pick test pins procedural_event.name and the
rule.name alias to the language-matched value.
- languageFallback truth table covers all 10 (lang × tier) combos.
Build hygiene: go build/vet/test clean; bun run build clean.
80 lines
2.6 KiB
Go
80 lines
2.6 KiB
Go
package services
|
|
|
|
// Regression tests for the per-draft language column (t-paliad-276).
|
|
// The draft's `language` value drives both the placeholder-bag
|
|
// language pick (`procedural_event.name` → name_de vs name_en) and the
|
|
// template-variant lookup (`{code}.{lang}.docx` fallback chain). These
|
|
// tests pin the pure-function pieces — Build wiring needs DB fixtures
|
|
// and lives in the handler-layer smoke path.
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"mgit.msbls.de/m/paliad/internal/models"
|
|
)
|
|
|
|
func TestNormalizeDraftLanguage(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{"de", "de"},
|
|
{"DE", "de"},
|
|
{" de ", "de"},
|
|
{"en", "en"},
|
|
{"EN", "en"},
|
|
{" en ", "en"},
|
|
{"fr", "de"}, // unknown collapses to de (the CHECK-allowed default)
|
|
{"", "de"},
|
|
{"english", "de"}, // strict — only the canonical two-letter code is accepted
|
|
}
|
|
for _, c := range cases {
|
|
if got := normalizeDraftLanguage(c.in); got != c.want {
|
|
t.Errorf("normalizeDraftLanguage(%q) = %q, want %q", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The placeholder bag picks the language-matched value for the
|
|
// canonical (procedural_event.name) and legacy (rule.name) keys based
|
|
// on the lang argument. This pins the wiring used by Build when a
|
|
// draft's language overrides the user's UI lang (t-paliad-276).
|
|
func TestAddRuleVars_LanguageSelectsMatchedName(t *testing.T) {
|
|
t.Parallel()
|
|
code := "de.inf.lg.erwidg"
|
|
rule := &models.DeadlineRule{
|
|
ID: uuid.New(),
|
|
SubmissionCode: &code,
|
|
Name: "Klageerwiderung",
|
|
NameEN: "Statement of Defence",
|
|
}
|
|
for _, lang := range []string{"de", "en"} {
|
|
bag := PlaceholderMap{}
|
|
addRuleVars(bag, rule, lang)
|
|
want := rule.Name
|
|
if strings.EqualFold(lang, "en") {
|
|
want = rule.NameEN
|
|
}
|
|
if got := bag["procedural_event.name"]; got != want {
|
|
t.Errorf("lang=%s: procedural_event.name = %q, want %q", lang, got, want)
|
|
}
|
|
if got := bag["rule.name"]; got != want {
|
|
t.Errorf("lang=%s: rule.name = %q, want %q (legacy alias must mirror canonical)", lang, got, want)
|
|
}
|
|
// The explicit *_de / *_en keys never change — both are always
|
|
// emitted so a template can pin one regardless of the draft's
|
|
// language. Regression guard against accidentally
|
|
// language-gating the explicit variants.
|
|
if bag["procedural_event.name_de"] != rule.Name {
|
|
t.Errorf("lang=%s: procedural_event.name_de = %q, want %q", lang, bag["procedural_event.name_de"], rule.Name)
|
|
}
|
|
if bag["procedural_event.name_en"] != rule.NameEN {
|
|
t.Errorf("lang=%s: procedural_event.name_en = %q, want %q", lang, bag["procedural_event.name_en"], rule.NameEN)
|
|
}
|
|
}
|
|
}
|