feat(cli): gate generated translation bundles with os i18n extract --check - #3683
Merged
Conversation
…-check` The generated translation bundles had no freshness gate. Nothing failed when they fell behind the schema, so they only ever got fixed when someone happened to re-run the extractor by hand — and by the time anyone did (#3670), three distinct drifts had accumulated at once: - translations left behind for keys the schema had REMOVED (`enable.trash` / `enable.mru`, #2377; agent `visibility`, #1901) — keys the schema now rejects outright; - keys the schema had GAINED with no entry in any locale (`summaryOperations.*`, the ADR-0105 D8 invitation-placement fields, and the better-auth 1.7 columns from #3647); - `sys_migration` committed with EMPTY STRINGS for its labels in ja-JP and es-ES, which renders blank rather than falling back to anything readable. This is the same shape as #3624: an artifact and its source with no gate between them, so the gap is only found by accident. #3647 closed that one for better-auth's columns by deriving the expected surface and failing the build; this does the same for translations. `--check` renders exactly what a real extract would write — both branches iterate one shared rendered set, so the check cannot drift from the writer — then compares against `--out` instead of writing, listing every stale or missing file and printing the regenerate command. It runs in merge mode like any other extract, so it never asks for re-translation: an up-to-date bundle re-extracts byte-identically. The flag lives on the CLI rather than in a repo script, so any consumer that ships generated bundles can gate them the same way. Here it is `pnpm check:i18n`, wired into lint.yml beside the other post-build consumer gates (it reads the built spec dist through the extract config). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UYLC8TfjzHGwatNxZKdX7H
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The docs and the i18n skill both presented `os i18n check` as *the* CI gate. It is the coverage gate — it answers 'are the strings translated?'. For anyone committing generated bundles that leaves the other half unguarded: renaming a label, adding an object, or removing a spec key keeps coverage at 100% while the bundles quietly go stale. Documents the two as a pair, in both surfaces, since the skill is what ships to third parties via `npx skills add`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UYLC8TfjzHGwatNxZKdX7H
os-zhuang
marked this pull request as ready for review
July 27, 2026 14:29
This was referenced Jul 27, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
接 #3670。那个 PR 清掉了翻译 bundle 里积压的漂移,但没有任何东西阻止它再次积压 —— 本 PR 补上这个 gate。
为什么需要
生成的翻译 bundle 此前没有新鲜度检查。落后于 schema 时不会有任何东西变红,所以只有当某个人恰好重跑一次 extractor 才会被发现。等到真有人跑的时候(#3670),已经同时攒了三类漂移:
enable.trash/enable.mru(#2377)、agentvisibility(#1901)—— schema 现在直接拒绝这些 keysummaryOperations.*、ADR-0105 D8 的 invitation-placement 字段、#3647 的 better-auth 1.7 列sys_migration在 ja-JP / es-ES 下的 label —— 渲染成空白,而不是回退到可读文本这和 #3624 是同一个形状:产物和来源之间没有 gate,所以缺口只能靠偶然发现。#3647 用"推导出期望面 + 构建失败"关掉了 better-auth 列的那一个;本 PR 对翻译做同样的事。
实现
--check渲染的内容与真实 extract 完全一致 —— 两个分支迭代的是同一份 rendered set,所以 check 不可能和 writer 产生分歧。它不写文件,而是与--out里已提交的内容比对,列出每个过期/缺失的文件,并打印出该跑的重新生成命令:以 merge 模式运行(和任何一次普通 extract 一样),所以它永远不会要求任何人重新翻译:一个最新的 bundle 重新 extract 出来是逐字节相同的。
flag 放在 CLI 而不是仓库脚本里 —— 任何 ship 生成 bundle 的消费者(cloud、各 app)都能用同样的方式加 gate。本仓库里是
pnpm check:i18n,接在 lint.yml 里其他 post-build consumer gate 旁边(它通过 extract config 读取已构建的 spec dist,所以必须在 build 之后)。文档
os i18n check此前在文档和 skill 里都是以"那个 CI gate"的身份出现的 —— 但它是覆盖率 gate,回答"字符串翻了没有"。对于提交生成 bundle 的人来说,另一半始终没有防护:改个 label、加个对象、删个 spec key,覆盖率仍然 100%,而 bundle 已经悄悄过期。所以在两处都把它们作为一对来写(
content/docs/ui/translations.mdx和skills/objectstack-i18n/SKILL.md—— 后者会通过npx skills add分发给第三方),并在 AGENTS.md 的 Documentation Guardrails 里加了一行标明这些文件是生成的。验证
8 bundle(s) are in syncsys_team.member_count)out of date+ exit 1out of date+ exit 1@objectstack/clicheck:skill-refs三类漂移的两个方向都能抓到。
Refs #3670, #3624, #3647, #2377, #1901。