Give the i18n scripts a parser again #328

Closed
opened 2026-09-10 21:21:59 +00:00 by jcoffey-dev · 0 comments
Owner

Four of the five i18n scripts have been dead since the TypeScript 7 bump. Found while adding catalogue entries in #327, where the gate that should have checked them couldn't run.

Cause

TypeScript 7 is the native port. Its package ships a tsc shim over a Go binary and nothing else:

$ node -e "const ts=require('typescript'); console.log(Object.keys(ts))"
[ 'version', 'versionMajorMinor' ]

No createSourceFile, no ScriptTarget, no AST. Every script in scripts/ that parses source has been throwing Cannot read properties of undefined (reading 'Latest') since 5.9.3 → 7.0.2.

script before after
i18n-catalog-check broken
i18n-literals broken
i18n-coverage broken
i18n-strings broken
i18n-extract

Confirmed on a clean main checkout, so it wasn't from #327.

Why an alias rather than a rewrite

There is no official TS7 API package — @typescript/ast and @typescript/api are both 404. The alternative was rewriting 493 lines across 5 files using 25 distinct AST calls, including the JSX type guards, against a different tree shape — in tooling that has no tests of its own.

So typescript-ast is an npm alias for the last TypeScript carrying the JS API. It parses; typescript still type-checks and builds. Two entries, two jobs. Each script says so at the import, so the next reader doesn't delete one as a stray duplicate.

"typescript": "^7.0.2",
"typescript-ast": "npm:typescript@^5.9.3"

Dev-only. Nothing changes at runtime or in the image.

What the gate says now it can speak

Catalogues are greeni18n:check exits 0 and coverage is 100% (964 wrapped, 0 remaining).

The "16 falling back to English" reported for every locale are not rot. They are placeholders, example domains, a product name, a licence identifier and the quote glyph:

"ihasmail.org"  "example.com"  "[email protected]"  "[email protected]"
"https://"  "https://…"  "https://meet.example.com/…"  "•••"
"Stalwart Mail Server"  "AGPL-3.0-or-later · {source}"  "ihasmail test"

All correctly untranslated — translating them would be the bug.

The 41 stale keys per locale (225 lines across the nine files) are real dead weight — entries translated but never looked up, mostly weekday names and a shortcuts list that has since changed. Left for their own change rather than bundled here.

Checks

  • all five scripts run
  • npm run typecheck — clean, both packages
  • npm test — 1160 web tests, 168 server tests, all passing
  • npm run build — clean

Worth knowing

No workflow runs any of these. ci.yml has one job, a Docker build. That's why a dead gate for nine languages went unnoticed — the only signal was running it by hand. Wiring i18n:check into CI would stop it rotting again, but that's a call about what CI is for rather than part of this fix, so it isn't here.

Merged 2026-09-10 as coffey-labs/ihasmail@3c4f6a9f8e

Rebuilt from: GH Archive, git history, session transcript.

Four of the five i18n scripts have been dead since the TypeScript 7 bump. Found while adding catalogue entries in #327, where the gate that should have checked them couldn't run. ## Cause TypeScript 7 is the native port. Its package ships a `tsc` shim over a Go binary and nothing else: ``` $ node -e "const ts=require('typescript'); console.log(Object.keys(ts))" [ 'version', 'versionMajorMinor' ] ``` No `createSourceFile`, no `ScriptTarget`, no AST. Every script in `scripts/` that parses source has been throwing `Cannot read properties of undefined (reading 'Latest')` since `5.9.3 → 7.0.2`. | script | before | after | |---|---|---| | `i18n-catalog-check` | broken | ✅ | | `i18n-literals` | broken | ✅ | | `i18n-coverage` | broken | ✅ | | `i18n-strings` | broken | ✅ | | `i18n-extract` | ✅ | ✅ | Confirmed on a clean `main` checkout, so it wasn't from #327. ## Why an alias rather than a rewrite There is no official TS7 API package — `@typescript/ast` and `@typescript/api` are both 404. The alternative was rewriting **493 lines across 5 files using 25 distinct AST calls**, including the JSX type guards, against a different tree shape — in tooling that has no tests of its own. So `typescript-ast` is an npm alias for the last TypeScript carrying the JS API. **It parses; `typescript` still type-checks and builds.** Two entries, two jobs. Each script says so at the import, so the next reader doesn't delete one as a stray duplicate. ```json "typescript": "^7.0.2", "typescript-ast": "npm:typescript@^5.9.3" ``` Dev-only. Nothing changes at runtime or in the image. ## What the gate says now it can speak Catalogues are **green** — `i18n:check` exits 0 and coverage is **100%** (964 wrapped, 0 remaining). The "16 falling back to English" reported for every locale are not rot. They are placeholders, example domains, a product name, a licence identifier and the quote glyph: ``` "ihasmail.org" "example.com" "[email protected]" "[email protected]" "https://" "https://…" "https://meet.example.com/…" "•••" "Stalwart Mail Server" "AGPL-3.0-or-later · {source}" "ihasmail test" ``` All correctly untranslated — translating them would be the bug. The **41 stale keys per locale** (225 lines across the nine files) are real dead weight — entries translated but never looked up, mostly weekday names and a shortcuts list that has since changed. Left for their own change rather than bundled here. ## Checks - all five scripts run - `npm run typecheck` — clean, both packages - `npm test` — 1160 web tests, 168 server tests, all passing - `npm run build` — clean ## Worth knowing **No workflow runs any of these.** `ci.yml` has one job, a Docker build. That's why a dead gate for nine languages went unnoticed — the only signal was running it by hand. Wiring `i18n:check` into CI would stop it rotting again, but that's a call about what CI is for rather than part of this fix, so it isn't here. **Merged** 2026-09-10 as coffey-labs/ihasmail@3c4f6a9f8ee4 <sub>Rebuilt from: GH Archive, git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.