08 — Form Specifications (i18n Module)
- 1. New Key form (
/admin/i18n/keys/new) (planned) - 2. Key Editor — per-locale field validation (the critical contract)
- 3. Add Locale form (
/admin/i18n/locales) (planned) - 4. Sync export (
/admin/i18n/sync) (planned) - 5. Runtime parity guarantee
Forms in the (planned) translation console: New Key, Key Editor (per-locale fields), Add Locale, Sync export. Validation derives from the live service contract (
i18n.service.ts:32-45) so the editor can never author a string the runtime can't render.
1. New Key form (/admin/i18n/keys/new) (planned)
| Field | Type | Required | Constraints / validation |
|---|---|---|---|
key | AppTextField (mono) | yes | /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/; unique; ≤ 120 chars; namespace hint errors. leave. announcement. |
params | ParamsChips (editable) | no | tokens match \w+; deduped; order-insensitive |
en value | AppTextField multiline | yes | placeholder self-check: every {token} in value ⊆ declared params (else block); { } escaping not supported (literal {{ unsupported — must match runtime regex \{(\w+)\}) |
| other locales | AppTextField | no | same placeholder check once en exists; empty allowed → missing |
Submit
POST /api/v1/i18n/keys(planned) → 201 → navigate to Key Editor.- Duplicate key → 409 inline "key exists" + "Open existing".
- Offline → banner; values preserved (in-memory draft).
Error messages (translated themselves)
Form errors must use i18n keys (errors.validation etc.) — the console is
dogfooding the module.
2. Key Editor — per-locale field validation (the critical contract)
| Rule | Enforcement | Source of truth |
|---|---|---|
| Placeholder parity | every locale's {token} set == en source set | i18n.service.ts:42-44 (/\{(\w+)\}/g) |
| Missing param | value contains {days} not in source → error: runtime would print literal {days} (i18n.service.ts:43) | runtime fallback behavior |
| Extra param | value uses {x} absent from source → amber warn (translator added token runtime won't fill) | same regex |
| Empty value in shipped locale | error, block save | translate() would return '', breaking never-empty fallback (i18n.service.ts:40) |
| Key immutability | key read-only in editor; rename = delete + create (planned) | — |
| Length | warn > 200 chars (push payload limits (forward-looking)) | — |
Save flow
PUT /api/v1/i18n/keys/:key (planned) with { values: { <locale>: string } }.
Optimistic; on 409 conflict (stale version) → dialog "Reload or overwrite"
(06 §2).
3. Add Locale form (/admin/i18n/locales) (planned)
| Field | Type | Required | Validation |
|---|---|---|---|
| locale tag | select from SUPPORTED_LOCALES minus shipped | yes | must be in i18n.service.ts:11-21 |
| direction | read-only "LTR" (all current locales LTR) | — | RTL (forward-looking) |
| name | AppTextField (native endonym) | yes | non-empty, ≤ 40 chars |
Submit → creates empty catalog → all keys missing → drift flow
(03_User_Journey.md §J5).
4. Sync export (/admin/i18n/sync) (planned)
| Field | Detail |
|---|---|
| Format | CSV: key, en, hi, ta, …, status |
| Filter | missing-only / all / placeholder-drift-only |
| Delivery | download; Crowdin upload is (planned) per IMPLEMENTATION_PLAN.md:845 |
5. Runtime parity guarantee
Whatever the editor allows, runtime behavior is fixed:
- unknown key → returns key literal (
i18n.service.ts:40, speci18n.service.spec.ts:44-46) - unknown locale → English catalog (
i18n.service.spec.ts:48-51) - missing param → literal
{name}retained (i18n.service.ts:43)
The console must therefore treat en as immutable source-of-truth for
params and all other locales as derived — enforced by §2 rules and the CI
drift check (planned).