Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

06 — Screen Specifications (i18n Module)

Detailed specifications for the (planned) admin translation console and the live catalog-delivery flow. Largest file: every screen's anatomy, states, behaviors, edge cases, a11y, motion. Components per 00-shared/03; motion tokens per 00-shared/08; a11y baseline per 00-shared/09.


0. Live flow: catalog delivery (no screen)

The only production surface today. The app:

  1. Resolves locale via Accept-Language (middleware, i18n.middleware.ts:13-18) — or passes ?locale= explicitly.
  2. Calls GET /api/v1/i18n/messages?locale=hi → full Record<string,string> (i18n.controller.ts:13-17, getMessages fallback to en, i18n.service.ts:47-49).
  3. Caches locally; refreshes on locale change or app update (cache version = catalog hash (planned)).

Contract edge cases:

  • locale=xx (unknown) → English catalog (i18n.service.spec.ts:48-51).
  • Missing header → English.
  • Response is a flat key→string map; no nested structure — the app must flatten ARB/JSON to the same flat map for lookup parity.

1. Translation Key List (/admin/i18n/keys) (planned)

Anatomy

AppBar "Translations"            [Sync status chip] [FAB +]
SearchBar (debounced, min 2 chars)
Filter chips: All | missing:hi | missing:ta | has-params | namespace:errors
────────────────────────────────────────────────────────
DataTable / ListTile rows:
  key (mono, truncated)  · params chips · coverage badge · edited
[Pagination: page size 25, "load more"]

States

StateBehavior
loadingAppSkeleton rows (10); search disabled until first paint
successrows with per-locale coverage badge (ok = all shipped locales present, missing = ≥1 shipped locale lacks key)
emptyAppEmptyState "No keys" + CTA "Create first key"; distinguishes "no filters match" vs "zero keys"
error401 → redirect login; 403 (no i18n.* perm (planned)) → permission state with copy; 5xx → retry CTA
offlineAppOfflineBanner + cached last data (stale-while-revalidate)

Behaviors

  • Row tap → Key Editor (§2). Long-press → multi-select for bulk delete/export.
  • Locale filter chips are multi-select; missing:<locale> preselects keys the given locale lacks (drill-down from Locale Management).
  • Search matches key + source value (en) + params.
  • Keyboard: / focuses search. Esc clears filters.
  • Motion: rows stagger m-fast; filter change animates list m-base fade.

a11y

  • Search labeled "Search translation keys"; results announced count (live region).
  • Badges have text labels ("missing in hi"), not color-only.
  • Focus lands on first row after search completes.

2. Key Editor (/admin/i18n/keys/:key) (planned)

Anatomy (tablet/desktop master-detail; phone single pane)

Header: key (mono)  · params chips {days}  · [Delete] [Save]
Tabs (desktop: side rail, phone: horizontal chips): en · hi · ta · te · kn · ml · gu · mr · bn
Per-locale field card:
  [locale tag + name]        [status chip: shipped|declared|missing]
  AppTextField (multiline ≥3 rows, auto-grow)
  └─ placeholder checker: "params match source" / "missing {days}" (inline, amber)
Dirty indicator per field (dot); unsaved-changes guard on exit.

States

StateBehavior
loadingskeleton; key header + params only
loadedper-locale values from GET /api/v1/i18n/keys/:key (planned)
missing localefield empty + amber outline + chip missing; block "promote" of a locale with missing keys
savingfield-level spinner; Save disabled while pending
savedsnackbar "Saved" (module-level); per-field check animates
conflictstale version → dialog "Reload or overwrite"
errorfield error inline; network → AppOfflineBanner, no data loss (drafts kept in memory)

Behaviors

  • Placeholder parity check (critical) — runs on every edit: value.match(/\{(\w+)\}/g) compared against the en source set (i18n.service.ts:42-44 regex). Mismatch = amber inline warning, Save allowed but flagged; CI drift check (planned) (IMPLEMENTATION_PLAN.md:845) blocks promotion.
  • Empty value in shipped locale → Save blocked with red inline error (empty string would degrade the never-empty guarantee, i18n.service.ts:40).
  • Delete → AppDialog confirm (destructive, danger button); deletes key in all locales. Undo snackbar 5 s (planned).
  • Unicode note: Devanagari/Tamil/Telugu etc. fields keep TextCapitalization.none; no autocorrect; maxLines ≥ 3, soft wrap.
  • Character counter: warn > 200 chars (push/payload limits (forward-looking)).

a11y

  • Per-field Semantics labels "value in hi".
  • Status changes (missing → saved) announced via live region.
  • Tab navigation order: source (en) first, then locales, actions last.

3. New Key (/admin/i18n/keys/new) (planned)

Anatomy

Form (bottom sheet on phone, card on desktop, ≤ 720 dp):
  key        (dotted path; mono; prefix helper "errors., leave., …")
  params     (AppChips input — free text chips, e.g. {days}, {name})
  en value   (required)
  hi value   (optional; shipped → recommended)
  [Create key]

Rules & states

RuleBehavior
key format/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/; invalid → inline error
duplicate409 → inline "key exists" + "Open existing" link
placeholder self-checken value's {...} tokens must ⊆ declared params; mismatch → block Create
createPOST /api/v1/i18n/keys (planned) → navigate to Editor with snackbar
error5xx → retry; offline → banner

a11y

  • Key field hint: "lowercase, dots separate namespaces".
  • Errors linked to fields; first invalid focused.

4. Locale Management (/admin/i18n/locales) (planned)

Anatomy

Grid of AppCards (2 cols phone, 4 cols desktop):
  [flag/script glyph] en — English        [status chip shipped]
  Coverage ring: 9/9 · "Add locale" dashed card
[Bottom sheet: add locale — pick from SUPPORTED_LOCALES minus shipped]

States

StateBehavior
loadingcard skeletons
emptyno declared locales → full-width add CTA
partial coveragering amber + "x missing" link → Key List filtered missing:<locale>
promote readycoverage 100% + no param drift → "Promote to shipped" enabled (makes SUPPORTED_LOCALES include it; today code-level i18n.service.ts:9-21 (planned: data-driven)**)
demoteconfirm dialog; blocking if it is the only locale with a key

Behaviors

  • Adding a locale creates an empty catalog — every key shows missing (drift flow, 03_User_Journey.md §J5).
  • Coverage = present keys / shipped-locale keys per locale.
  • RTL note: if an RTL locale (ar/ur) is ever added (forward-looking), this screen shows a direction field; all LTR today.

5. Sync / Drift view (/admin/i18n/sync) (planned)

Anatomy

Header: last CI run · [Run now] [Export CSV]
Table: locale × missing keys (count) → expand to key list → jump to editor
Placeholder drift panel: key, source params, locale params, [Fix]

States

  • success / stale ("last run 3 d ago") / failed (CI unavailable).
  • Empty: "All locales in sync" with celebratory success state.

6. Edge cases across screens

CaseHandling
hi.json missing a keyfalls back to en at runtime (i18n.service.ts:40); editor shows missing; never blank string
Param reorder/rename between localesflagged in editor + sync; runtime keeps literal {name} if param absent (i18n.service.ts:43)
Huge catalog (>10k keys)list paginates; fetch returns flat map (memory: ~1 MB per locale — acceptable; app caches one locale)
Locale promoted mid-sessionapp refetches catalog on next launch; version header (planned) enables delta push (forward-looking)
Concurrent editorslast-write-wins today; optimistic lock (version field) (planned) — conflict dialog §2
Offline admin consoleread-only cached view; saves queue (planned); catalog delivery to mobile unaffected (server-side)

7. Motion summary

  • List/filter transitions: m-base (200 ms) fade+slide.
  • Field save: m-fast check morph. Error shake reserved for form submit.
  • Coverage ring: animates on data load m-base ease-out.