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:
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.
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:
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
tscshim over a Go binary and nothing else:No
createSourceFile, noScriptTarget, no AST. Every script inscripts/that parses source has been throwingCannot read properties of undefined (reading 'Latest')since5.9.3 → 7.0.2.i18n-catalog-checki18n-literalsi18n-coveragei18n-stringsi18n-extractConfirmed on a clean
maincheckout, so it wasn't from #327.Why an alias rather than a rewrite
There is no official TS7 API package —
@typescript/astand@typescript/apiare 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-astis an npm alias for the last TypeScript carrying the JS API. It parses;typescriptstill 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.Dev-only. Nothing changes at runtime or in the image.
What the gate says now it can speak
Catalogues are green —
i18n:checkexits 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:
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
npm run typecheck— clean, both packagesnpm test— 1160 web tests, 168 server tests, all passingnpm run build— cleanWorth knowing
No workflow runs any of these.
ci.ymlhas one job, a Docker build. That's why a dead gate for nine languages went unnoticed — the only signal was running it by hand. Wiringi18n:checkinto 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.