fix(i18n): make translation contracts executable

This commit is contained in:
Tianyi Cui
2026-07-14 23:09:01 +08:00
parent 883948d1d5
commit 3caaa43797
25 changed files with 615 additions and 243 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 38474b353820876c4ec859d3cea863f5ef82658f
README.zh.md: e047680ad273a7066515de0b61c73b7e00ff96e3
README.md: 17bb1eeb67b4f5119a698fca23f12490c9378a7f
README.zh.md: 2b44ad702c68c0bfb748b25a4f62252aa3dbdc98

View File

@@ -17,14 +17,14 @@ This repo's documentation is read by people and agents both inside and outside t
Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p <hash>`), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. After bringing the pair back in line, `pnpm run verify-translation-pairing --write` re-records both hashes; that yaml diff is the reviewable act of confirming consistency.
- **Language switcher.** Both files link to each other immediately after their H1 heading: the English file carries `English | [中文](foo.zh.md)` and the Chinese file carries `[English](foo.md) | 中文`.
- **Structure mirrors the counterpart.** Heading depths and order, list kinds, table columns, link targets, and verbatim code blocks match one to one across the pair — see [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).
- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, link targets, and verbatim code blocks match one to one across the pair — see [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).
## The gate: verify-translation-pairing
`pnpm run verify-translation-pairing` (part of `doc-sync`, so CI and the pre-push hook run it) enforces the contract mechanically:
1. Every file listed as `required` in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair.
2. Every pair that exists at all — required or not — is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table column counts, list kinds, and every link target apart from the switcher.
2. Every pair that exists at all — required or not — is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and every link target apart from the switcher.
3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all.
4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named RFCs merge bilingual from birth.
@@ -49,4 +49,4 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co
## Division of labor
Counterparts here are produced by an agent running [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) and reviewed by a human — inference is cheap here, review attention is the scarce resource. The gate checks pair completeness, recorded hashes, switchers, and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode.
Counterparts here are produced by an agent running [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) and reviewed by a human — inference is cheap here, review attention is the scarce resource. The gate checks pair completeness, recorded hashes, switchers, and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode. The prompt contract is executable: [scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) renders the canonical rules into either direction and strictly parses the three-field XML response, while `verify-translation-prompt` exercises both render directions, the checked-in example, and the CDATA split rule in `doc-sync`.

View File

@@ -7,7 +7,7 @@
## 配对契约
- **两种语言同权。**一篇文档可以先用任一语言撰写和评审——先写中文的 RFC 与先写英文的一样正当——另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。
- **一对文档是三个同目录文件。**英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`都在同一目录。不用语言目录不用独立翻译仓库不用中英混排的单文件。配对整体合入PR 永远不会只带一种语言而缺其余两个文件。
- **一对文档是三个同目录文件。**英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`都在同一目录。不用语言目录不用独立翻译仓库不用中英混排的单文件。配对整体合入PRPull Request永远不会只带一种语言而缺其余两个文件。
- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 git blob hash
```yaml
@@ -17,14 +17,14 @@
用 blob hash 而不是 commit hash这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。记录的 hash 还能还原任一侧上次确认时的确切文本(`git cat-file -p <hash>`),所以失去同步的配对是「把被改的一侧与其上次确认状态做 diff、再最小化地修补另一侧」——从不整篇重译。两侧对齐后`pnpm run verify-translation-pairing --write` 重新记录两个 hash那份 yaml diff 就是「确认一致」这个动作本身,可以被评审。
- **语言切换行。**两个文件在各自 H1 标题之后立即互链:英文文件带 `English | [中文](foo.zh.md)`,中文文件带 `[English](foo.md) | 中文`。
- **结构与另一侧一一对应。**标题深度与顺序、列表类型、表格列、链接目标与逐字节一致的代码块在配对两侧一一对应——完整保持规则见 [translation-rules.md](translation-rules.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。
- **结构与另一侧一一对应。**标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、链接目标与逐字节一致的代码块在配对两侧一一对应——完整保持规则见 [translation-rules.md](translation-rules.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。
## 门禁verify-translation-pairing
`pnpm run verify-translation-pairing``doc-sync`(文档同步门禁)的一环,因此 CI 和 pre-push 钩子都会运行)机械地强制执行这份契约:
1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。
2. 任何已存在的配对——无论是否 required——都完整且一致三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致——标题深度、逐字节一致的代码块(信息字符串与内容)、表格列数、列表类型,以及除切换行之外的每个链接目标。
2. 任何已存在的配对——无论是否 required——都完整且一致三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致——标题深度、逐字节一致的代码块(信息字符串与内容)、表格列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。
3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。
4. 日期等于或晚于 manifest元数据清单中 `requiredSince` 分界日期的每篇日期命名文档(`yyyy-mm-dd-*.md`)都有完整配对——新增的日期命名 RFC 从创建起就要求双语齐备。
@@ -49,4 +49,4 @@
## 分工
这里的对侧译文由运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 的 agent 产出、由人评审——在这里推理inference很便宜评审注意力才是稀缺资源。门禁机械检查配对完整性、记录的 hash、切换行与文档所列的结构签名翻译质量、术语以及签名未编码的结构要求仍由评审把关。
这里的对侧译文由运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 的 agent 产出、由人评审——在这里推理inference很便宜评审注意力才是稀缺资源。门禁机械检查配对完整性、记录的 hash、切换行与文档所列的结构签名翻译质量、术语以及签名未编码的结构要求仍由评审把关。prompt 契约可以直接执行:[scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) 将规范真源渲染到两个翻译方向,并严格解析含三个字段的 XML 响应;`doc-sync` 中的 `verify-translation-prompt` 会检查两个渲染方向、仓库内示例与 CDATA 拆分规则。

View File

@@ -30,7 +30,7 @@
> **Async state is not synchronous state** — `agent.send()` does not flip status before returning; a background task's completion races turn boundaries; `reader.close()` fires for both EOF and disposal. Never gate control flow on a status you only just requested — drive lifecycle off the events/promises that actually fire (`agent/status`, `task.done`), and observe the transition (saw `running` THEN `idle`) rather than counting actions you assume map 1:1 to turns.
**异步状态不等同于同步瞬时状态**——调用 `agent.send()` 不会在返回前同步更新状态;后台任务完成时机与轮次边界存在竞态;调用 `reader.close()` 既可能是读到文件末尾,也可能是资源销毁触发。切勿仅凭刚查询到的状态来阻断流程;生命周期逻辑应基于真实触发的事件与 promise 驱动(`agent/status``task.done`),观测完整状态切换(先 `running`、再 `idle`),而非主观认定操作和执行轮次一一对应(主循环会批量处理排队消息)
**异步状态不等同于同步瞬时状态**——调用 `agent.send()` 不会在返回前同步更新状态;后台任务完成时机与轮次边界存在竞态;调用 `reader.close()` 既可能是读到文件末尾,也可能是资源销毁触发。切勿根据刚刚请求切换的状态来控制流程;生命周期逻辑应基于真实触发的事件与 promise 驱动(`agent/status``task.done`),观测完整状态切换(先 `running`、再 `idle`),而非通过操作次数推断轮次一一对应
## ③ 测试政策清单

View File

@@ -1,6 +1,6 @@
# Translation prompt (pipeline asset)
本文件是自动翻译流水线的 prompt 模板;自 `# Translation Prompt` 起的正文逐字进入模型请求,因此不参与双语配对(见 [README.md](README.md) 排除清单)。渲染时,[terminology.md](terminology.md) 整表填入 `{{terminology}}`。[style-samples.md](style-samples.md) 定义文体,模板内嵌的 Examples 仅抽样问题类型;术语表、忠实性与结构规则优先于样例,样例在这些硬约束内决定文体。修改本文件即修改翻译行为,需按正常 PR 评审。
本文件是自动翻译流水线的 prompt 模板;自 `# Translation Prompt` 起的正文逐字进入模型请求,因此不参与双语配对(见 [README.md](README.md) 排除清单)。渲染时,[translation-rules.md](translation-rules.md) 全文填入 `{{translation_rules}}`[terminology.md](terminology.md) 整表填入 `{{terminology}}`,避免模板另存一份会漂移的规则副本。[style-samples.md](style-samples.md) 定义文体,模板内嵌的 Examples 仅抽样问题类型;术语表、忠实性与结构规则优先于样例,样例在这些硬约束内决定文体。修改本文件即修改翻译行为,需按正常 PR 评审。
## 占位符契约
@@ -10,6 +10,7 @@
|---|---|---|
| `{{source_lang}}` | 源语言名(`English` / `Chinese` | 由改动侧文件推断:`.zh.md` 被改则为 `Chinese` |
| `{{target_lang}}` | 目标语言名(`Chinese` / `English` | 与 `{{source_lang}}` 相对 |
| `{{translation_rules}}` | [translation-rules.md](translation-rules.md) 全文Markdown 原文) | 渲染时读取仓库当前版本,不缓存 |
| `{{terminology}}` | [terminology.md](terminology.md) 的完整表格Markdown 原文) | 渲染时读取仓库当前版本,不缓存 |
| `{{source_filename}}` | 源文档的 basename`foo.md``foo.zh.md` | 由流水线从待译文件路径取得 |
| `{{source_filename_zh}}` | 中文侧 basename`foo.zh.md` | 英文源追加 `.zh`;中文源使用自身 basename |
@@ -35,46 +36,20 @@
You are a senior technical translator specializing in LLM and agent development documentation. Translate the complete source document from {{source_lang}} to {{target_lang}} as natural, professional technical prose.
## Quality Requirements
## Binding Translation Rules
### Structure and Format Preservation
- Preserve the complete document frame: heading hierarchy, list item count and numbering, table row and column order, link targets, fenced code blocks, inline code spans, and emphasis spans.
- Fenced code blocks must be byte-identical to the source, including every comment, info string, and line break. Never translate a code-block comment.
- Inline code spans (commands, flags, paths, API names, event names, configuration keys, and version numbers) remain byte-identical and in the same order.
- Every relative link keeps the same target. Translate link text, not link targets.
- The source basename is `{{source_filename}}`. When translating into Chinese, write `[English]({{source_filename}}) | 中文` immediately after the H1. When translating into English, write `English | [中文]({{source_filename_zh}})` immediately after the H1. Emit the switcher for a new pair and flip an existing switcher; never copy it unchanged.
- Preserve every source emphasis marker on the corresponding translated span. Do not add italics, bold, quotation marks, or other emphasis absent from the source.
- After a closing bold marker `**`, add a half-width space only when the next character is a Latin letter or digit. Never add one before full-width punctuation.
The canonical repository rules below are injected verbatim. Apply every direction-appropriate requirement. In those rules, the authored document is the source for this request and the generated document is its counterpart.
### Faithfulness and Voice
- Preserve every behavior, condition, prerequisite, warning, version claim, example, exception, and modal verb. Add none and drop none.
- Write as a native technical author in the target language, not as a word-for-word translator. Restructure sentences where target-language grammar requires it while preserving the author's register.
- Use precise, established developer terminology. Do not vary a term merely to avoid repetition, and do not collapse two distinct source concepts into one target term.
- Do not add politeness, certainty, emphasis, rationale, or examples that the source does not contain.
{{translation_rules}}
#### When translating into Chinese
- Use institutional technical Chinese: complete sentences, explicit actors where a passive would be vague, and established Chinese engineering idiom rather than calques.
- When a number modifies a noun, include a natural classifier or measure word. Example: `three-package seam` → `由三个 package 构成的 seam`, not `三 package seam`.
- Use full-width Chinese punctuation in prose: `,。:;?!()「」`. Prefer colons, periods, commas, or parentheses over em dashes; use 顿号between parallel items.
- Put one half-width space between Chinese text and Latin words or numbers. Do not put spaces around full-width punctuation.
- Render RFC 2119 keywords as 必须、禁止、应当、可以 while preserving the source emphasis exactly; plain source text remains plain.
## Request-Specific Structure
#### When translating into English
- Use concise professional developer English. Convert Chinese topic-comment order, implicit subjects, and nominalizations into idiomatic English without dropping their meaning.
- Use normal half-width English punctuation and spacing. Convert enumeration commas (、) to English commas and 「」 quotation marks to English double quotes, except inside verbatim Chinese text.
- Render RFC 2119 keywords as MUST, MUST NOT, SHOULD, and MAY while preserving the source emphasis exactly.
- Use established English engineering idiom rather than literal transliteration (误报 → false positive, 执行红线 → enforcement frontier), consulting the terminology table first.
- Use direct English imperatives for instructions unless the source's politeness carries substantive meaning.
- The source basename is `{{source_filename}}`. When translating into Chinese, write `[English]({{source_filename}}) | 中文` immediately after the H1. When translating into English, write `English | [中文]({{source_filename_zh}})` immediately after the H1.
- Emit the switcher for a new pair and flip an existing switcher; never copy it unchanged.
## Terminology
## Binding Terminology
The table below is binding:
- For a Chinese target, use the `中文` column and apply the `首次出现` form once; later occurrences use the text before its parentheses.
- For an English target, use the `English` column. Do not copy Chinese first-occurrence glosses into English prose.
- Respect every `不要译作` prohibition in both directions.
- For an unlisted term in a Chinese target, use a citable established Chinese OSS or vendor rendering and record the precedent in `<review>`; otherwise keep the English term and record it as `[Pending term]` with a suggested rendering.
- For an unlisted term in an English target, use the established English technical term. If no unambiguous equivalent exists, preserve the source term with a short gloss and record it as `[Pending term]`.
- Never invent a technical rendering inline.
Apply the current table below exactly as required by the injected translation rules.
{{terminology}}
@@ -99,32 +74,7 @@ Return exactly one well-formed XML document with this root and these three child
## Self-Review Instructions
After writing `<translation>`, re-read it in the target language without looking at the source. Then compare it with the source clause by clause and record actual corrections in English inside `<review>`.
**Structure**
- Do heading levels, list item counts and numbering, table rows and columns, links, code blocks, inline code spans, and emphasis spans correspond exactly?
- Are all fenced code blocks byte-identical, comments included?
- Is the language switcher present and pointed in the correct direction?
**Faithfulness**
- Did every condition, warning, modal verb, exception, and example survive?
- Did the translation add any claim, rationale, emphasis, or certainty absent from the source?
**Tone and sentences**
- Does every sentence read as native target-language developer documentation?
- Are passive constructions, topic chains, or run-on sentences unnatural in the target language?
**Terminology**
- Does every tabled term use the target-language column and avoid forbidden forms?
- For a Chinese target, are first-occurrence glosses present once and only once?
- Are unlisted terms handled under the direction-specific precedent and pending-term rules?
**Punctuation**
- For Chinese, are punctuation, mixed-script spacing, classifiers, and 顿号 correct?
- For English, are punctuation and spacing idiomatic and free of Chinese-only padding?
- Do RFC 2119 keywords preserve the source emphasis rather than adding italics?
Apply every recorded correction in `<final>`. If no correction is needed, write only `- [None] No corrections.` in `<review>` and copy `<translation>` unchanged into `<final>`.
After writing `<translation>`, re-read it in the target language without looking at the source. Then apply the injected translation rules as a clause-by-clause comparison against the source and record actual corrections in English inside `<review>`. Apply every recorded correction in `<final>`. If no correction is needed, write only `- [None] No corrections.` in `<review>` and copy `<translation>` unchanged into `<final>`.
## Examples

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
translation-rules.md: b48b1680da2e7a9a4d744659339398af81d60308
translation-rules.zh.md: 6e8f26509e1db104eb341668c3e77bd6ea0780e1
translation-rules.md: 490f12d7fec929e5d3d5682deb92ed373fffbde5
translation-rules.zh.md: b013dcb2d8a892606ae5b1c0837152a771a57da5

View File

@@ -21,7 +21,7 @@ How to translate between the two sides of a documentation pair in this repo. Bot
## Structure preservation
The pairing gate checks heading depths, fenced code blocks, table column counts, list kinds, and link targets. Preserve the rest of the frame manually; the paired files MUST match one to one in:
The pairing gate checks heading depths, fenced code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, and link targets. Preserve the rest of the frame manually; the paired files MUST match one to one in:
- heading hierarchy (same levels, same order — heading TEXT is translated),
- list shape and numbering,
@@ -54,7 +54,7 @@ These rules govern the Chinese side; the English side follows the repo's normal
- A pair is done when a bilingual engineer reading either file alone gets everything a reader of the other gets — same facts, same caveats, same tone — and nothing extra.
- Before handing off, self-check the result against this file and re-read the counterpart ALONE, without the source side by side; awkward phrasing is easier to hear without the source anchoring you.
- Run `pnpm run verify-translation-pairing` and the rest of `doc-sync` for records, switchers, heading depths, code blocks, table column counts, list kinds, links, and repository Markdown rules. Manually verify list item counts and numbering, table row counts and order, inline code, emphasis, meaning, terminology, and tone.
- Run `pnpm run verify-translation-pairing` and the rest of `doc-sync` for records, switchers, heading depths, code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, links, and repository Markdown rules. Manually verify list and table order, noncanonical list numbering, inline code, emphasis, meaning, terminology, and tone.
## References

View File

@@ -21,7 +21,7 @@
## 结构保持
配对门禁检查标题深度、围栏代码块、表格列数、列表类型与链接目标。其余框架由译者手工保持;配对的两个文件必须在以下方面一一对应:
配对门禁检查标题深度、围栏代码块、表格列数、列表类型、有序列表起始编号、列表项数量与链接目标。其余框架由译者手工保持;配对的两个文件必须在以下方面一一对应:
- 标题层级(相同级别、相同顺序;标题的**文字**要翻译);
- 列表形态与编号;
@@ -54,7 +54,7 @@
- 一对文档的完成标准:一位双语工程师只读其中任一文件,能获得与另一文件读者完全相同的信息(相同的事实、相同的告诫、相同的语气),并且没有任何多余的内容。
- 交付前,请对照本文自查一遍,并**单独通读对侧文件**,不与源侧对照;不对照原文时,更容易察觉别扭的表达。
- 请运行 `pnpm run verify-translation-pairing``doc-sync` 的其余门禁,检查一致性记录、切换行、标题深度、代码块、表格列数、列表类型、链接及仓库 Markdown 规则。列表项数量与编号、表格行数与顺序、行内代码、强调标记、语义、术语和语体仍需手工核对。
- 请运行 `pnpm run verify-translation-pairing``doc-sync` 的其余门禁,检查一致性记录、切换行、标题深度、代码块、表格列数、列表类型、有序列表起始编号、列表项数量、链接及仓库 Markdown 规则。列表与表格顺序、非常规列表编号、行内代码、强调标记、语义、术语和语体仍需手工核对。
## 参考资料