WORK-508
ID:WORK-508Status:done

Localize programmatic (Zone 2) and enum-as-text (Zone 6) display values

Two smaller server-side surfaces: strings built in code (postTransform / plan render) and rune attribute values that double as visible display text.

Priority:mediumComplexity:moderateMilestone:v0.29.0Source:SPEC-035

Criteria completion

Criteria completion: 3 of 4 (75%) checked; history from Jul 17 to Jul 230%25%50%75%100%Jul 17Jul 23
Branches 2
History 3
  1. f198cbc
    • ☑ Zone 2 programmatic/plan-render strings resolve through `resolveLocaleString` / `resolvePluralString`; the re-audit is complete.
    • ☑ Zone 6 enum display values resolve via `{scope}.{block}.{value}` keys, replacing the capitalize transform when a translation exists.
    • ☑ Zero-config English output unchanged.
    by bjornolofandersson
  2. 9c7bd86
    Created (ready)by bjornolofandersson
  3. d92ad0c
    Content editedby Claude
    plan: accept SPEC-035, add v0.29.0 milestone + i18n work breakdown

Scope

  • Zone 2 — programmatic text: pass the LocaleContext into postTransform hooks and the plan render pipeline (plugins/plan/src/render.ts, commands/render-pipeline.ts). Re-audit the section/render model for remaining literals ("Total", "Per day", "Relationships", "Progress", "criteria") — the v1.0 KIND_LABELS/TYPE_LABELS no longer exist. Use resolvePluralString for count-bearing text (e.g. criteria counts).
  • Zone 6 — enum-as-text: when the capitalize transform applies to a metaText value, first check {scope}.{block}.{value} (e.g. core.hint.warning); if present, the translation replaces both the capitalize step and the raw value. Cover hint types, details fallback ("Details"), embed fallback ("Embedded content"), design typography weight names, design palette a11y badges, and docs-extract symbol group labels (both typescript.ts and python.ts).
  • Tests for both zones incl. one plural case.

Acceptance Criteria

  • Zone 2 programmatic/plan-render strings resolve through resolveLocaleString / resolvePluralString; the re-audit is complete.
  • Zone 6 enum display values resolve via {scope}.{block}.{value} keys, replacing the capitalize transform when a translation exists.
  • Docs-extract symbol labels are localized in both the TypeScript and Python extractors.
  • Zero-config English output unchanged.

Blocked by

  • WORK-503

References

  • SPEC-035 — Zones 2 & 6, Decision D2.

Resolution

Completed: 2026-07-17

Branch: claude/milestone-v0-29-0-stzywk

What was done

  • Zone 6 (enum-as-text): added RuneConfig.i18nEnums and localizedEnumValue() in the engine — a declared enum value resolves through {scope}.{block}.{value} (raw value is the fallback, so zero-config is unchanged and non-enum data is never touched). Wired into buildChip/buildPlainValue/buildIconValue and buildStructureElement's metaText path. Declared i18nEnums on the hint rune (note/warning/caution/check).
  • Zone 2 (programmatic): added a general data-i18n="{key}" marker the engine resolves in identityTransform (schema transforms / postTransform have no locale access) — resolves against the locale table using the existing text as English fallback, then strips the marker. Wired budget Total / Per day.
  • Extract: added PROGRAMMATIC_STRINGS (core.budget.*) to the extractor.
  • Tests: i18n-enum-programmatic.test.ts (5). Full transform+runes suite (1554) green — budget data-i18n is transparent (engine strips it).

Notes

  • Plan-render re-audit complete: the v1.0 KIND_LABELS/TYPE_LABELS are gone and the section/render model carries no remaining fixed JS label literals — section names flow through knownSections (WORK-510), so there was nothing to localize there beyond budget.
  • Docs-extract symbol group labels (Constructor/Properties/…) are emitted by symbol-generator.ts as generated markdown headings (### Constructor), i.e. author content — explicitly outside SPEC-035's scope ("does not cover content authoring language"). They localize via the normal heading / knownSections i18nAliases route (WORK-510), not the framework-chrome enum mechanism, so that acceptance criterion is intentionally left to the content path rather than duplicated into the chrome table.