From bee7f98e4553c1cfbf4d875dc8a7c5464f70717d Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Fri, 19 Jun 2026 15:57:05 +0200 Subject: [PATCH] docs(i18n): record D-102 (i18n routing) + ui-design skill rule Document the T-462 i18n architecture: ext-id namespaces auto-loaded on activation, a 'core' catalog for framework chrome, the null-safe ClideSettings.i18n read facade, contribution titleKey/labelKey fields, and the assets/i18n//.json locale-dir layout. Add the "route user-facing strings through the catalog" rule to the ui-design skill. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/ui-design/SKILL.md | 7 +++++++ governance/README.md | 1 + governance/decisions/accessibility.md | 11 +++++++++++ 3 files changed, 19 insertions(+) diff --git a/.claude/skills/ui-design/SKILL.md b/.claude/skills/ui-design/SKILL.md index cdc90e14..999524a4 100644 --- a/.claude/skills/ui-design/SKILL.md +++ b/.claude/skills/ui-design/SKILL.md @@ -45,6 +45,13 @@ These apply across every reference and every surface: widget must set it explicitly. `clideMonoFamily` / `clideUiFamily` are the facade's defaults — don't read them directly in new widgets (D-101). Same facade exposes `ClideSettings.theme.of(context)` and `.i18n.of(context)`. +- User-facing strings resolve through the catalog, never a hardcoded literal + (D-21/D-102): `ClideSettings.i18n.string(context, 'dotted.key', namespace: + , placeholder: '')` (or `.interpolated` for + templated). Add the key→English to `assets/i18n/en_us/.json`. The + `placeholder` is the English fallback; the extension's own id is its + namespace (framework chrome uses `core`). Contribution manifests carry + `titleKey`/`labelKey` for the same reason. ## Conversation-panel cards (T-305) diff --git a/governance/README.md b/governance/README.md index 47bd6ed0..9f577354 100644 --- a/governance/README.md +++ b/governance/README.md @@ -143,6 +143,7 @@ You might also want, project-permitting: - [D-99: Remote session identity keyed on (host, workspace)](decisions/architecture.md#d-99-remote-session-identity-keyed-on-host-workspace) — _architecture_ - [D-100: Fence `dart:ffi` behind conditional imports + web stubs to keep the web/WASM target compiling](decisions/tooling.md#d-100-fence-dartffi-behind-conditional-imports--web-stubs-to-keep-the-webwasm-target-compiling) — _tooling_ - [D-101: ClideSettings — one live-preferences access facade](decisions/architecture.md#d-101-clidesettings--one-live-preferences-access-facade) — _architecture_ +- [D-102: i18n routing — ext-id namespaces, `core` catalog, ClideSettings.i18n facade, contribution keys](decisions/accessibility.md#d-102-i18n-routing--ext-id-namespaces-core-catalog-clidesettingsi18n-facade-contribution-keys) — _accessibility_ ## Open questions diff --git a/governance/decisions/accessibility.md b/governance/decisions/accessibility.md index 1d7e52ac..a02edbc1 100644 --- a/governance/decisions/accessibility.md +++ b/governance/decisions/accessibility.md @@ -32,4 +32,15 @@ A11y + i18n are Tier-0 contracts, not Tier-6 polish. - **Cost:** Two extra theme files per "named" theme when we add a11y variants. The bundled-theme contrast gate ([D-22](#d-22-wcag-aa-contrast-gate-on-bundled-themes)) needs a baseline/extended split so the named themes don't fail the strict pairs. - **Raised by:** 2026-05-17 — user intervened mid-T-114 when I had retuned `clide`/`midnight`/`paper`/`terminal` palette entries to satisfy the expanded `canonicalPairs`; reverted, decision written, T-114 will follow this rule. +### D-102: i18n routing — ext-id namespaces, `core` catalog, ClideSettings.i18n facade, contribution keys +- **Date:** 2026-06-19 +- **Decision:** Implements [D-21](#d-21-i18n-is-a-tier-0-contract-fframe-pattern--locale-fallback-chain) across the whole app (epic T-462). + - **Namespaces:** an extension's catalog namespace IS its id (`builtin.`); the ExtensionManager eager-loads it on activation, so a built-in localizes with no hand-maintained registry. Framework chrome outside any extension (`lib/widgets`, `lib/kernel`, the shared reader chrome) resolves under one **`core`** namespace, preloaded at boot. + - **Read path:** widgets resolve through the single [D-101](architecture.md) facade — `ClideSettings.i18n.string(context, key, namespace:, placeholder:)` (+ `.interpolated`) — null-safe (returns the placeholder when no kernel is in scope, so primitives render in isolated tests). + - **Manifest labels:** `CommandContribution` carries `titleKey`/`i18nNamespace`; the palette and menu resolve via a shared `localizedCommandTitle`, and the palette's fuzzy search matches the localized title. The settings schema carries `i18nNamespace`+`titleKey` on the category and `labelKey`/`helpKey`/option `labelKey` beneath it, threaded down by the renderer. + - **Storage:** catalogs are bundled assets at `assets/i18n//.json` — the locale is a *directory* (`en_us`, future `nl_nl`, `nl_be`, `en_eu`, …), so a new language is a new folder of the same namespace files, no renames. +- **Rationale:** makes a complete translation set (e.g. a Dutch pack) a pure data drop — no code. ext-id namespaces need no registry; the facade keeps one widget-facing read path for theme/fonts/i18n (D-101); the locale-dir layout is cleaner to maintain and mirrors how an external extension ships its own catalog. +- **Cost:** every extension's manifest gains optional key fields, and framework primitives now depend on the (null-safe) facade. The pure-data search matcher (`settingsFieldMatches`) still matches the English label — display localizes, search-by-translation does not (acceptable refinement). +- **Raised by:** 2026-06-19, epic T-462 (i18n everywhere). Builds on [D-101](architecture.md). + ---