Skip to content

feat(validate): language tags and set metadata are engine rules (#190) - #223

Merged
astrapi69 merged 1 commit into
mainfrom
feat/190-language-rules
Sep 25, 2026
Merged

astrapi69 merged 1 commit into
mainfrom
feat/190-language-rules

Conversation

@astrapi69

Copy link
Copy Markdown
Owner

Summary

Closes #190. The language-pair and set-metadata rules move into the engine, in the version from the decision brief in the issue (owner, 2026-09-25: "deine Empfehlungen hören sich gut an").

Rule Severity Where Template before
E-LANG-TAG: not a well-formed BCP 47 tag error set and lesson target_language / source_language two-letter primary subtag or error
W-LANG-TAG-CANONICAL: well-formed, not canonical (deu, EN, iw), naming the canonical form warning same not checked
W-LANG-PAIR-SAME: source and target are one language, in a language set warning manifest error
W-SET-TITLE-NATIVE: a language set without title_native warning manifest error
W-CARD-BACK-SCRIPT: card backs with letters but none in the script of a non-Latin source language, one warning per lesson warning lesson error, for a table of six languages
  • Tags are checked with Intl.getCanonicalLocales. Three-letter subtags without a two-letter code (gsw, yue, fil) are valid.
  • The script check takes the CLDR likely script (Intl.Locale(...).maximize().script), with a small mapping for the composite scripts Jpan, Kore, Hans/Hant. That covers every language, including hi, he, ka and th, which the template's table skipped.
  • New option: validateLesson and validateLessonRules take options.sourceLanguage (the set's). A lesson's own source_language wins over it.
  • No new dependency. The module (src/language-rules.ts) has no ajv and no node:*, so it is on the /rules entry.

Measured

This build ran over the 53 sets and 631 lessons of the ten content repositories. Every lesson was matched to its set, and each set's source language was passed in.

  • 0 findings for every rule.
  • Seeded faults are found in real content:
    • a Latin back in the Greek set, as W-CARD-BACK-SCRIPT on bonjour;
    • deu in the hub manifest, as W-LANG-TAG-CANONICAL;
    • en_US, as E-LANG-TAG;
    • a deleted title_native, as W-SET-TITLE-NATIVE.
  • Severities that dropped in the move: the pair and title_native checks were errors in the template and are warnings here. A repo that needs them blocking gets that from adaptive-learner-content-template#83.

Test plan

  • src/language-rules.test.ts: 43 tests, written RED first (21 failed before). The cases:
    • reproductions: gsw is valid, en_US is an error, a Hindi source is checked;
    • canonical and non-canonical tags;
    • the legacy language alias and a lesson's own tags;
    • the pair check with de-AT/de, the en default, non-language sets, and silence behind a malformed tag;
    • title_native absent, empty or null, and non-language sets;
    • scripts for el, ru, ja (kana and kanji), ko, zh, zh-TW, hi, he, ka;
    • a loanword, a back without letters, and silence for a Latin, unknown or malformed source;
    • the lesson's own source winning over the caller's;
    • validateLessonRules taking the option.
  • The params gate has one case per id that carries params. The rule catalog and the params table are updated.
  • make release-check, make prose-check and npm run docs:api:check pass.
  • Bundle gate: core 87.2 of 90 kB gzip. The /rules entry is 27.3 kB (+2.4 kB), re-measured and documented.

Docs

  • lesson-format.md: a new "Language tags" section, catalog rows and params rows.
  • validation.md: a Layer 2 row, the lint list with the sourceLanguage option, and the /rules size.
  • architecture.md: engine side done, the dropped severities, the open item and the /rules size.
  • CHANGELOG.md.

Downstream (for the release PR)

  • Template: drops validate_set_meta and back_looks_like_source, and passes each set's source_language to validateLesson in validate_with_engine.mjs. This is mirrored into the ten repos.
  • App: the share check and the backend follow the engine's tag rule instead of their own shapes (the backend's ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})?$ rejects zh-Hant-TW).

🤖 Generated with Claude Code

The language-pair and set-metadata checks move from the content
template's validator into the engine, in the version decided on
2026-09-25:

- E-LANG-TAG: a target/source language (set or lesson) is not a
  well-formed BCP 47 tag; W-LANG-TAG-CANONICAL: well-formed but not
  canonical, naming the canonical form. Checked with
  Intl.getCanonicalLocales; gsw, yue, fil are valid (the template's
  two-letter rule rejected them).
- W-LANG-PAIR-SAME and W-SET-TITLE-NATIVE for language sets (errors in
  the template, warnings here: both can be intended).
- W-CARD-BACK-SCRIPT: card backs with letters but none in the CLDR
  likely script of a non-Latin source language, one warning per
  lesson, for every language instead of a table of six.
  validateLesson / validateLessonRules take the set's source language
  as options.sourceLanguage; a lesson's own source_language wins.

Measured with this build over the 53 sets and 631 lessons of the ten
content repositories: 0 findings; seeded faults in real content are
found.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@astrapi69
astrapi69 merged commit 9b73ad4 into main Sep 25, 2026
4 checks passed
@astrapi69 astrapi69 mentioned this pull request Sep 25, 2026
@astrapi69
astrapi69 deleted the feat/190-language-rules branch September 25, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Language-pair and set-metadata rules live in the template's validator

2 participants