Skip to content

i18n leak detection

i18n specs/i18n/leak-detection.kmd

Detection contract for "i18n leaks" — user-facing string literals in Koder Stack UI surfaces (Flutter widgets, templ templates, TS/ JSX components, Go CLI/TUI) that bypass canonical i18n routing. Defines what counts as a leak, the allowlist categories, per-module opt-in, the audit gate, and the Stack-wide coverage registry.

When this pattern applies

Primary triggers

All triggers

Specification body

Spec — i18n leak detection

Status: v0.1.0 Draft (2026-05-28). Companion to specs/i18n/contract.kmd — that spec defines the i18n contract (locale negotiation, runtime switch, ICU keys); this spec defines how the Stack detects violations of that contract.

The audit pipeline is implemented in dev/koder-tools/cmd/koder-i18n-audit/ (Go CLI, static scan) + engines/sdk/koder_test_* (runtime audit via headless render).

R1 — Definition of "leak"

A leak is a user-facing string literal in render code that:

  1. Is not routed through any of the canonical i18n helpers:
    • Dart/Flutter: KoderL10n.t(…), Intl.message(…), widget-local helpers that internally call them
    • templ (Go HTML): loc.T(…)
    • TypeScript/JSX: t(…), i18next.t(…)
    • Go CLI/TUI: i18n.T(…), cobra.Command.Short/Long when populated from translation tables
  2. Is not in the allowlist (R2)
  3. Is not the // kds-i18n-allow operator opt-out comment
  4. Looks like prose: contains at least one whitespace OR is ≥6 chars AND mixed case OR multi-word

The classification is best-effort heuristic; false positives are expected and fixed by extending the allowlist (R2.5) — never by loosening R1.

R2 — Allowlist categories

The allowlist lives at registries/i18n-allowlist.md. Strings matching ANY of these categories are not leaks:

R2.1 — Proper names

Brand names of products, services, and visual languages NOT translated across locales. Examples: Koder, Verge, Material, GNOME, KDE Breeze, Adwaita, Cyberpunk.

R2.2 — Koder products + services

Product names that ship with English-source identity globally: Koder Hub, Koder Talk, Koder Flow, Koder Stack, Koder Kodix, Koder Dek, Koder ID, Koder Jet.

R2.3 — Code identifiers

Class/type/function names that appear in user-facing context for developer surfaces (KDS docs, code samples, error codes): KoderApp, KoderUIStyle, KoderSignInButton, KoderL10n.

R2.4 — Units, symbols, technical literals

B, KB, MB, GB, TB, ms, μs, ns, fps, Hz, MHz, HTTP, HTTPS, TLS, OAuth, OIDC, JSON, YAML, TOML.

R2.5 — Per-module extension

Each module's koder.toml can declare additional allowlist entries under [i18n_audit] extra_allowlist = ["…", "…"]. Used for module-specific brand/product names (e.g. Dek-specific terminology).

R2.6 — Operator opt-out comment

// kds-i18n-allow: <reason> on the line above a string literal marks it as an intentional non-translation (debug log, internal identifier). The reason text is mandatory and audited (no empty opt-outs).

R3 — Per-module opt-in

Each Koder Stack module opts into the audit by adding to its koder.toml:

[i18n_audit]
enabled = true                          # default false (opt-in per module)
extra_allowlist = ["MyProductName"]     # see R2.5
excludes = ["lib/legacy/**"]            # paths to skip
max_leaks = 0                           # CI gate floor; lower as fixes land

Module opts INTO the gate when ready. Rollout is incremental — no "flag day" where the audit must be green Stack-wide on day 1.

R4 — Audit gate

The koder-i18n-audit CLI runs in two modes:

R4.1 — Report mode (default)

Walks the module's source, classifies every string literal, emits JSON report at dist/i18n-audit.json (schema kds-i18n-audit-v1) with per-file + summary. Always exits 0.

R4.2 — Strict mode

koder-i18n-audit --strict exits 1 if summary.leaks > max_leaks (module's koder.toml gate floor). Used in CI to prevent regressions while phase-by-phase fixes drive max_leaks to 0.

R4.3 — Runtime mode

engines/sdk/koder_test_widgets/lib/i18n_audit.dart exposes testKoderI18nNoLeaks(widget, locales, allowlist) for Flutter widget tests. Per policies/headless-first.kmd §R8, every UI module uses this SDK rather than rolling its own — runtime audit catches dynamic strings the static scan misses (concatenation, helper-returned, computed).

Modules wire it into their test/ directory with one test per top-level screen and run on every flutter test.

R5 — Coverage matrix

meta/docs/stack/registries/i18n-coverage.md aggregates per-module audit results into a single table — Stack-wide percentage of modules passing strict mode, per-locale dictionary completeness, per-module leak count trend. Updated on every nightly CI run.

ModuleStatic leaksRuntime leaksStrict modeLocales covered
(example) products/dev/hub00en-US, pt-BR, es-ES
products/horizontal/talk12en-US (pt-BR pending)

R6 — Locales in scope

Wave-1 per specs/i18n/contract.kmd §R7: en-US (source of truth), pt-BR, es-ES. The audit asserts coverage in pt-BR + es-ES; a string that's en-US-only with no pt-BR translation IS a leak (the user sees en-US literal instead of translated text).

Wave-2 locales add columns to R5's matrix incrementally.

R7 — Trigger on commit

Module CI workflow runs koder-i18n-audit --strict on every PR that touches lib/**/*.dart (Flutter), internal/**/*.templ (Go), src/**/*.{ts,tsx} (web), or cmd/**/*.go (CLI). Mirrors the design-gen pattern of --max-broken-links (defensive gate per ratification baseline).

References