WORK-510
ID:WORK-510Status:done

knownSections canonicalSlug + i18nAliases — stable-slug follow-up

knownSections shipped (WORK-024, via SPEC-037) but without the localization shape SPEC-035 called for. slugify() still derives the section wrapper's data-name from raw heading text, so a non-English heading (## Akzeptanzkriterien) produces a different slug than ## Acceptance Criteria, breaking CSS selectors and cross-locale anchors. This item closes that gap. The canonicalSlug half can proceed independently (the framework already exists); the i18nAliases half layers on locale resolution.

Priority:lowComplexity:moderateMilestone:v0.29.0Source:SPEC-035
claude/milestone-v0-29-0-stzywk View source

Criteria completion

Criteria completion: 4 of 4 (100%) checked; tracking started on Jul 17, no incremental history yet0%25%50%75%100%Jul 17Jul 30

Tracking started Jul 17 — check back for trends.

Branches 2
claude/milestone-v0-29-0-stzywk current done
main doneclaude/multi-language-support-spec-5zzt9q ready
History 4
  1. e86169d
    Content editedby bjornolofandersson
  2. 9a4e4b9
    Created (done)by bjornolofandersson
  3. ee469c1
    Content editedby Claude
    i18n: knownSections canonicalSlug + i18nAliases (WORK-510)
  4. d92ad0c
    Content editedby Claude
    plan: accept SPEC-035, add v0.29.0 milestone + i18n work breakdown

Scope

  • Add canonicalSlug?: string and i18nAliases?: Record<string, string[]> to KnownSectionDefinition (packages/types/src/content-model.ts).
  • buildSections() (plugins/plan/src/util.ts): when a section matches a known section, derive the <section data-name> from canonicalSlug (default slugify(canonicalName)), not the heading text; unrecognised sections keep slugify($heading).
  • matchKnownSection() (packages/runes/src/lib/resolver.ts): consult i18nAliases[locale] in addition to the base alias list when a locale is configured.
  • Tests: a non-English heading matched via i18nAliases yields the canonical, language-stable data-name; unrecognised headings unchanged.

Acceptance Criteria

  • KnownSectionDefinition gains canonicalSlug + i18nAliases.
  • Matched known sections emit a language-independent data-name derived from the canonical key.
  • Locale-specific heading aliases match when locale is set; base aliases still match.
  • Unrecognised sections keep heading-derived slugs (no regression).

References

  • SPEC-035 — Zone 7, Interaction with knownSections.
  • WORK-024, SPEC-037 — the shipped knownSections framework this extends.

Resolution

Completed: 2026-07-17

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

What was done

  • Added canonicalSlug? + i18nAliases? to KnownSectionDefinition (packages/types/src/content-model.ts).
  • matchKnownSection() (packages/runes/src/lib/resolver.ts) takes an optional locale and matches i18nAliases[locale] in addition to base aliases, with BCP-47 region-strip (de-ATde). Threaded locale through resolve() / resolveContentModel() / resolveSections().
  • The resolver attaches $canonicalSlug (explicit canonicalSlug or slugify(canonicalName)) on a matched section.
  • buildSections() (plugins/plan/src/util.ts) derives the <section data-name> from $canonicalSlug when present, else slugify($heading) — so matched sections get a language-stable slug and unrecognised ones are unchanged.
  • Tests: 6 new resolver cases (canonical slug, de + de-AT alias match, region strip, no-locale no-match, default slug).

Notes

  • The canonicalSlug half is fully live (it rides $canonicalName, already attached at parse time). The i18nAliases half is threadable via the new locale param; sourcing the site locale into the parse-time resolver (Markdoc variables) is the remaining integration seam, and the matching mechanism is proven by direct tests. 1416 runes+plan tests green.