fix(i18n): refine Chinese prose and prompt validation

This commit is contained in:
Tianyi Cui
2026-07-15 20:39:24 +08:00
parent 168a3b9fe7
commit 3383c20c63
12 changed files with 42 additions and 26 deletions
+1 -1
View File
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
README.md: 17bb1eeb67b4f5119a698fca23f12490c9378a7f
README.zh.md: 2b44ad702c68c0bfb748b25a4f62252aa3dbdc98
README.zh.md: c957a82bf420a942e2249942a2d9afc54ad950cf
+2 -2
View File
@@ -26,7 +26,7 @@
1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。
2. 任何已存在的配对——无论是否 required——都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致——标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。
3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。
4. 日期等于或晚于 manifest(元数据清单)中 `requiredSince` 分界日期的每篇日期命名文档(`yyyy-mm-dd-*.md`)都有完整配对——新的日期命名 RFC 从创建起就要求双语齐备
4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,都必须有完整配对——新的日期命名 RFC 从创建起便须配齐中英文
`pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态——missing、out-of-sync 或 ok——是翻译批次的工作清单。它从不失败;它只报告。
@@ -49,4 +49,4 @@
## 分工
这里的对侧译文由运行 [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 拆分规则。
对侧译文由运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 的 agent 生成,再由人评审在这里推理(inference)很便宜,评审注意力才是稀缺资源。门禁负责检查配对是否完整、记录的 hash、语言切换行以及本文列出的结构签名;翻译质量、术语签名未涵盖的结构要求仍由评审把关。prompt 契约也有可执行实现[scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) 会把权威规则渲染到英译中或中译英的 prompt 中,并严格解析含三个字段的 XML 响应;`doc-sync` 中的 `verify-translation-prompt` 会检查两个渲染方向、仓库内示例与 CDATA 拆分规则。
+5 -5
View File
@@ -16,7 +16,7 @@
> This document covers **behavior**; type shapes live in [core-data-structures/](../core-data-structures/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events.md), per-package contracts in the package READMEs ([map](../../packages/README.md)).
本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](../core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events.md);各 package 的对外契约写在对应 package 的 README[索引](../../packages/README.md))。
本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](../core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events.md);各包(package的对外契约写在相应的 README[索引](../../packages/README.md))。
## ② 防御模式规则
@@ -26,11 +26,11 @@
> **Dispose must reach quiescence, not just request it** — A teardown that issues kills/aborts but returns before the work stops leaves orphans. Make cleanup async and await the children's exit (kill → await `done`), and close listener/notification registries BEFORE killing so late completions stay silent. Tests prove disposal waited (pid gone right after `await fiber.dispose()`), not merely that the process eventually dies.
**dispose(资源释放)必须等待所有任务完全停稳,不能仅下发终止指令就返回**——若清理逻辑仅发送终止中断信号,不等任务停止就直接退出,会产生孤儿进程。清理逻辑需设为异步,等待所有子任务彻底退出(先发终止信号,再等待执行完成);在执行终止操作前先关闭监听器与通知注册表,延迟到达的完成事件不再触发任何通知。测试要证 dispose 确实完成等待:执行完 `await fiber.dispose()` 后进程 PID 立即消失,不能仅校验进程最终会自行消亡。
**dispose(资源释放)必须等待所有任务完全停稳,不能仅下发终止指令就返回**:如果清理过程只发出终止中断信号,不等任务停止就返回,就会留下孤儿进程。清理应采用异步方式,等待所有子任务彻底退出(先发终止信号,再等待退出);发出信号前应先关闭监听器与通知注册表,使延迟到达的完成事件不再触发通知。测试要证 dispose 的确等到清理完成:执行完 `await fiber.dispose()` 后进程 PID 立即消失,不能只检查进程最终会自行消亡。
> **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`),不要根据操作次数推断操作与轮次一一对应。
## ③ 测试政策清单
@@ -60,7 +60,7 @@
> The gate's limit, stated plainly: a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound. It checks hashes and shape; it cannot judge whether the two sides actually say the same thing — that is the reviewer's half of the contract. A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review.
明确门禁校验边界:门禁校验通过,仅代表每侧文件当前 blob hash 与伴随记录中的对应值一致,且两侧结构签名相符,不代表译文内容准确无误。门禁无法判断双语表意是否统一——译文质量把关是评审人的责任。即便译文粗糙、表意偏差,只要两侧当前 blob hash 各自匹配记录值,门禁就会放行,但这类 PR 绝不能通过人工评审。
门禁的边界很明确:通过门禁只说明两侧文件当前 blob hash 与伴随记录吻合,并且结构签名一致,也就是说,这组内容曾被确认一致;它不代表这次确认可靠。门禁无法判断两种语言是否真正表达了相同的意思;这部分契约要由评审人把关。即使译文粗糙、表意有误,重新记录配对后仍能通过门禁,但绝不能通过人工评审。
## ⑥ RFC 论证
@@ -72,7 +72,7 @@
> **Rollout**: date-named RFCs don't wait for a batch — one dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so each new date-named RFC is bilingual from birth. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. […] Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it.
**推进**新增的日期命名 RFC 不再走批量翻译流程。若其标注日期等于或晚于 manifest(元数据清单)`requiredSince` 分界时间,提交合入时必须配套双语文件,因此每篇新的日期命名 RFC 从创建起就要求双语齐备。对存量文档manifest 内的强制翻译列表只是当执行红线,并非最终目标。(……)文档完成双语配对等同于一份长期约束承诺:后续只要修改任一版本,就必须同步更新对应另一语种文件。因此强制翻译范围的推进节奏,要匹配翻译评审实际可投入人力,切勿超前铺开
**推进**:日期命名 RFC 无需等待批量翻译。只要文件名中的日期不早于 manifest(元数据清单)的 `requiredSince` 分界日期,合入时必须配齐中英文,因此此类 RFC 从创建起就要求双语齐备。对存量文档manifest 中的 `required` 列表只是当前的执行红线,并非最终目标。(……)一旦文档完成配对,后续修改任一侧都必须同步更新另一侧。因此,应根据实际可投入的翻译评审能力逐步扩展执行红线,不能超前
## 从样例提炼的要点
+6 -4
View File
@@ -1,6 +1,6 @@
# Translation prompt (pipeline asset)
本文件是自动翻译流水线的 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 评审。
本文件是自动翻译流水线使用的 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 评审。
## 占位符契约
@@ -15,11 +15,13 @@
| `{{source_filename}}` | 源文档的 basename(如 `foo.md``foo.zh.md`) | 由流水线从待译文件路径取得 |
| `{{source_filename_zh}}` | 中文侧 basename(如 `foo.zh.md` | 英文源追加 `.zh`;中文源使用自身 basename |
流水线仅支持上表占位符,并按整篇文档翻译。它不支持 `{{to}}``{{title_prompt}}``{{summary_prompt}}``{{terms_prompt}}``{{imt_style_guide}}` `%%` 分段协议。输出是一个以 `<dsh-translation-response>` 为根元素的 XML 文档,三个子元素的任意 Markdown 内容都放在 CDATA 中;内容出现 `]]>` 时写成 `]]]]><![CDATA[>`XML 解析后仍得到原文
例如,英译中时若源文件是 `foo.md``{{source_filename}}``foo.md``{{source_filename_zh}}` `foo.zh.md`;中译英时若源文件是 `foo.zh.md`,两个占位符都填 `foo.zh.md`
流水线只识别上表中的占位符,并且一次翻译整篇文档。它不支持 `{{to}}``{{title_prompt}}``{{summary_prompt}}``{{terms_prompt}}``{{imt_style_guide}}``%%` 分段协议。输出必须是一个以 `<dsh-translation-response>` 为根元素的 XML 文档;三个子元素中的 Markdown 内容都放在 CDATA 中。内容出现 `]]>` 时写成 `]]]]><![CDATA[>`XML 解析后仍会还原为原文。
## Few-shot 金标
流水线的 few-shot 是**整文档**的中英对照,不是模板内嵌的句子级正误例。few-shot 集取自以下 5 组经人工评审的配对文档,以仓库当前版本为准随仓库更新:
流水线使用**整文档**的中英对照作为 few-shot,不是模板内嵌的句子级正误例。以下 5 组配对文档均经过人工评审,并以仓库当前版本为准随仓库一同更新:
- `README.md``README.zh.md`
- `docs/development.md``docs/development.zh.md`
@@ -27,7 +29,7 @@
- `docs/i18n/translation-rules.md``docs/i18n/translation-rules.zh.md`
- `docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md` ↔ 对应 `.zh.md`
注入时按当前翻译方向选择每组的源侧与目标侧:user 消息源文档全文,assistant 消息使用模板正文规定的同一 XML 协议;`translation``final` 都放目标文档全文,`review` `- [None] No corrections.`。CDATA 使用上文的 `]]>` 拆分规则。上下文紧张时按上列顺序从后往前裁剪组数。这 5 组也是评审校准锚点;改动任何一组改变流水线行为。
注入时按当前翻译方向选择每组的源侧与目标侧:user 消息包含源文档全文,assistant 消息用模板正文规定的 XML 协议;`translation``final` 都放目标文档全文,`review` `- [None] No corrections.`。CDATA 遵循上文的 `]]>` 拆分规则。上下文不足时,按上列顺序从后往前删减示例组数。这 5 组也是评审校准锚点;改动任何一组都会改变流水线行为。
## 模板正文
+2 -2
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: c55c928efb64608176b6a9abff86fe228d5e71b1
translation-rules.zh.md: a50d9fb97e5b6cd95dfd3210ccb70921c7169725
translation-rules.md: fb6aa9ac05bebe68ff9213af99f64457bdb1ad6f
translation-rules.zh.md: 04dd0a704e19502c676ea0966437870c5af0624f
+1
View File
@@ -44,6 +44,7 @@ These rules govern the Chinese side; the English side follows the repo's normal
- MUST put one half-width space between Chinese text and Latin words, and between Chinese text and numerals: `每个 plugin 注册 3 个 tool`。No space between a full-width punctuation mark and anything.
- MUST use full-width (Chinese) punctuation in Chinese prose: `,。:;?!()「」`. Half-width punctuation stays inside code spans, inside complete English sentences quoted as-is, and in numbers (`3.5`, `1,024`).
- Chinese prose *SHOULD* prefer colons, periods, commas, or parentheses over em dashes. Keep an em dash only when no other punctuation preserves the sentence naturally.
- Enumeration commas: a Chinese list of parallel items uses 顿号(、), not commas.
- MUST NOT use full-width digits or full-width Latin letters — `123` never, `123` always.
- Proper nouns keep their canonical casing: GitHub, TypeScript, DeepSeek — never `github`/`Github` unless quoting code.
+9 -8
View File
@@ -12,16 +12,16 @@
## 行文
- 语体以 [style-samples.md](style-samples.md) 为校准锚点——人工定稿的金标样例按文体各一组,译文必须对齐最接近样例目标语言一侧;样例的语体与本文的语体规则冲突,以样例为准。中文目标使用规范的技术制度文,英文目标使用简洁、专业的开发者文档语体。
- 语体以 [style-samples.md](style-samples.md) 为校准锚点人工定稿的金标样例按文体各一组,译文必须参照文体最接近样例,采用其中目标语言一侧的语体;如果样例与本文的行文规则冲突,以样例为准。译成中文时,采用规范的技术制度文;译成英文时,采用简洁、专业的开发者文档语体。
- 以母语技术作者的身份重述内容,而不是逐句转写的译者。写完后逐句对照原文核验:不添加、不遗漏——流畅永远不是丢掉语义成分的理由。
- 目标语言会模糊执行主体时,请补出实际执行者;翻译为中文时,将含糊的被动句或抽象主语改由「系统、门禁、评审人」等实际执行者主语。
- 优先使用目标语言的工程惯用语而非直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻做本地化替换而不是移植,并按目标语言需要展开名词链
- 如果直译会让执行主体含糊,请明确写出实际执行者;译成中文时,由「系统、门禁、评审人」等实际执行者作主语,避免含糊的被动句或抽象主语。
- 优先用目标语言中通行的工程表达,避免生硬直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻应自然改写,名词链则按目标语言的习惯拆开
- 长段按语义单元拆分,一段一件事。段落边界可以与原文不同;结构签名不比对段落数。
- 翻译为中文时,类别名词使用中文并在首现括注英文(实操手册(cookbook));翻译为英文时,使用通行的英文类别名。指目录或文件本身时保留代码体英文。
## 结构保持
配对门禁检查标题深度、围栏代码块、表格行列数、列表类型、有序列表起始编号、列表项数量与链接目标。其余框架由译者手工保持;配对的两个文件必须在以下方面一一对应:
配对门禁检查标题深度、围栏代码块、表格行列数、列表类型、有序列表起始编号、列表项数量与链接目标;门禁未覆盖的结构仍需人工核对。两个配对文件必须在以下方面一一对应:
- 标题层级(相同级别、相同顺序;标题的**文字**要翻译);
- 列表形态与编号;
@@ -34,9 +34,9 @@
## 术语
- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;表内术语必须遵守对应行与「不要译作」禁项。中文目标使用「中文」列「首次出现」括注;英文目标使用「English」列,不加中文括注。
- 翻译为中文时,表中没有的技术术语只有在主中文 OSS 或厂商资料已有成型译法时才可以翻译(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档),并在 PR 中注明出处。没有先例时必须保留英文,并在 PR 描述的「待定术语」中出建议译法。
- 翻译为英文时,使用已确立的英文技术术语。源术语没有明确的通行对应词,保留原词并附简短说明,同时列入「待定术语」。两个方向都禁止就地发明译法;确定下来的术语在同一个 PR 或后续 PR 中入 [terminology.md](terminology.md)。
- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;表内术语必须遵守对应行与「不要译作」禁项。译成中文时,采用「中文」列,并按「首次出现」括注;译成英文时,采用「English」列,不加中文括注。
- 译成中文时,术语表未收录的技术术语只有在主中文 OSS 文档或厂商资料已有通行译法时才可以翻译(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档),并在 PR 中注明出处;否则必须保留英文,并在 PR 描述的「待定术语」中出建议译法。
- 译成英文时,采用通行的英文技术术语。如果源术语没有明确的通行对应词,保留原词、附上简短说明,列入「待定术语」。两个方向都不得自行创造译法;确定的术语在同一个 PR 或后续 PR 中入 [terminology.md](terminology.md)。
## 排版
@@ -44,6 +44,7 @@
- 必须在中文与拉丁词之间、中文与数字之间各留一个半角空格:`每个 plugin 注册 3 个 tool`。全角标点与任何字符之间不加空格。
- 中文行文必须使用全角(中文)标点:`,。:;?!()「」`。半角标点保留在代码内、按原样引用的完整英文句子内、以及数字内(`3.5``1,024`)。
- 中文行文*应当*优先使用冒号、句号、逗号或括号,尽量不用破折号;只有其他标点都无法自然表达时才保留破折号。
- 顿号:中文的并列项之间使用顿号(、),而非逗号。
- 禁止使用全角数字或全角拉丁字母:永远不写 `123`,永远写 `123`
- 专有名词保持规范大小写:GitHub、TypeScript、DeepSeek。除非引用代码,否则绝不写 `github``Github`
@@ -54,7 +55,7 @@
- 一对文档的完成标准:一位双语工程师只读其中任一文件,能获得与另一文件读者完全相同的信息(相同的事实、相同的告诫、相同的语气),并且没有任何多余的内容。
- 交付前,请对照本文自查一遍,并**单独通读对侧文件**,不与源侧对照;不对照原文时,更容易察觉别扭的表达。
- 请运行 `pnpm run verify-translation-pairing``doc-sync` 的其余门禁检查一致性记录、切换行、标题深度、代码块、表格行列数、列表类型、有序列表起始编号、列表项数量、链接及仓库 Markdown 规则列表与表格顺序、非常规列表编号、行内代码、强调标记、语义、术语和语体仍需工核对。
- 请运行 `pnpm run verify-translation-pairing``doc-sync` 的其余门禁。这些门禁会检查一致性记录、切换行、标题深度、代码块、表格行列数、列表类型、有序列表起始编号、列表项数量、链接及仓库 Markdown 规则列表与表格顺序、非常规列表编号、行内代码、强调标记、语义、术语和语体仍需工核对。
## 参考资料
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-02-bilingual-docs-and-pairing-gate.md: 68c0f3bbc0472b0c96f9d64fc6b1b24ac7008795
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 66a24dbfa9e516bf341d42d10b797fb70dcd715d
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 2cf8f9b9c17d8a521d8674833e909b34f315cfe0
@@ -12,7 +12,7 @@ Status: implemented
- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../i18n/terminology.md)。
- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。
- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:required 的配对必须存在;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)保持不配对;日期等于或晚于 manifest(元数据清单)中 `requiredSince` 分界日期的文档必须有完整配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单只进不退:每个合并的翻译批次将自己的文件加入其中,覆盖面只增不减。
- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:required 的配对必须存在;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)不得配对;凡文件名以日期开头且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,也必须有完整配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单只进不退:每个合并的翻译批次将自己的文件加入其中,覆盖面只增不减。
- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../../.agents/skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../../.agents/skills/dsh-code-review/SKILL.md) 模式相同:skill(技能)承载工作流,并将文档作为真源。
## 曾考虑的替代方案
@@ -34,5 +34,5 @@ Status: implemented
- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。
- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。
- 生成文档(`cordis-catalog/``tool-catalog/``module-graph.md`)暂被排除;计划中的后续工作是让生成器在输出英文的同时输出中文,届时将这些文件移出排除清单。
- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(待翻清单,`--list`),而非红色的 CI;因此配对按可评审的批次落地,无需一个巨型 PR。日期等于或晚于 manifest 中 `requiredSince` 分界日期的文档必须配齐双语文件,因此新的日期命名 RFC 不会扩大这份 backlog。
- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(待翻清单,`--list`),而非红色的 CI;因此配对按可评审的批次落地,无需一个巨型 PR。凡文件名以日期开头且日期不早于 manifest 中 `requiredSince` 分界日期的文档,都必须配齐双语文件,因此新的日期命名 RFC 不会增加这份 backlog。
- 记录的 hash 兼作更新工具(`git cat-file -p <hash>` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),因此这套机制从不强迫整篇重译。
+9
View File
@@ -40,6 +40,15 @@ describe('translation prompt rendering', () => {
terminology: 'terms',
})).toThrow('does not match source language Chinese')
})
it('rejects malformed template placeholders before injecting rule contents', () => {
expect(() => renderTranslationPrompt(document.replace('{{source_lang}}', '{{source-lang}}'), {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
translationRules: 'A literal {{source_lang}} in injected rules.',
terminology: '| English | 中文 |',
})).toThrow('template contains malformed placeholder syntax')
})
})
describe('translation response XML', () => {
+4
View File
@@ -82,6 +82,10 @@ export function renderTranslationPrompt(document: string, input: TranslationProm
source_filename_zh: sourceFilenameZh,
}
const template = extractTranslationPrompt(document)
const placeholderFreeTemplate = template.replace(PLACEHOLDER, '')
if (placeholderFreeTemplate.includes('{{') || placeholderFreeTemplate.includes('}}')) {
throw new Error('translation prompt: template contains malformed placeholder syntax')
}
const names = [...template.matchAll(PLACEHOLDER)].map(match => match[1] ?? '')
const unknown = names.filter(name => !TRANSLATION_PROMPT_PLACEHOLDERS.includes(name as TranslationPromptPlaceholder))
if (unknown.length > 0) throw new Error(`translation prompt: unsupported placeholder(s): ${[...new Set(unknown)].join(', ')}`)
-1
View File
@@ -37,7 +37,6 @@ try {
translationRules,
terminology,
})
if (englishSource.includes('{{') || chineseSource.includes('{{')) throw new Error('rendered prompt contains an unresolved placeholder')
if (!englishSource.includes('[English](example.md) | 中文')) throw new Error('English-source render does not carry the Chinese switcher instruction')
if (!chineseSource.includes('English | [中文](example.zh.md)')) throw new Error('Chinese-source render does not carry the English switcher instruction')