没有不值得去解决的问题,也没有不值得去学习的技术!

A Tour of Go 中文翻译完成 7 个代表页校准:最后 3 页又发现了哪些真实问题

图 3:methods/24 中文页面,静态代码、Bounds 行内代码和链接内的 image.Rectangle 均正确渲染

作者:

,

A Tour of Go 多语言翻译项目

图3:访问过去的 Go Tour 简体中文站 tour.go-zh.org 时,当前已经无法正常建立连接。

(1) 从「A Tour of Go 中文版」这个搜索词开始:我决定做一个持续维护的 Go Tour 中文版

本地运行的 A Tour of Go「Methods continued」课程页面,左侧为课程说明,右侧为 Go 代码编辑器及运行结果。

(2) A Tour of Go 中文版项目设计冻结:从 101 页到 103 页,从 Gin 转向 Cobra CLI

A Tour of Go 中文版开发实战:英文基线、在线运行与 101 页上游同步体系

(3) A Tour of Go 中文版开发实战:英文基线、在线运行与 101 页上游同步体系

go-tour-i18n 项目完成首个简体中文课程页面 welcome/1 的整页翻译、结构校验和本地预览。

(4) A Tour of Go 多语言翻译项目实录:完成首个 zh-CN 页面翻译闭环

图 1:DeepSeek 官方更新日志显示,本次只更新 DeepSeek-V4-Flash,DeepSeek-V4-Pro API 与 APP、Web 模型均未更新

(5) A Tour of Go 中文翻译项目进展:完成前 8 页、修复 present 语法,并暂缓 DeepSeek 对比

图 1:generics/1 简体中文页面本地预览

(6) 一个页面重试五次:使用 GLM-5.2 翻译 A Tour of Go 时遇到的 Token 与 Present 结构问题

图 1:methods/16 简体中文页面浏览器预览

(7) A Tour of Go 中文翻译实录:如何只翻译教学代码注释,而不破坏 Go 代码

图 2:A Tour of Go methods/20「练习:错误」中文候选页面最终渲染效果

(8) A Tour of Go 多语言翻译:当正确的中文语序被受保护标记顺序校验误判

图 3:methods/24 中文页面,静态代码、Bounds 行内代码和链接内的 image.Rectangle 均正确渲染

(9) A Tour of Go 中文翻译完成 7 个代表页校准:最后 3 页又发现了哪些真实问题

上一篇文章结束时,我已经把代表页校准阶段接下来的计划确定了下来。

当时已经完成 4 个代表页:

  1. generics/1
  2. flowcontrol/8
  3. methods/16
  4. methods/20

并且明确选定最后 3 个代表页:

  • concurrency/7
  • concurrency/11
  • methods/24

计划也已经很清楚:如果这 3 页也稳定通过,总代表页将达到 7 页。

然后就结束逐页校准阶段,进入:10 个普通 pending 页面自动试跑。

所以这一次并不是重新选择代表页,也不是继续无限增加测试样本。

目标从一开始就已经确定:把上一篇留下的最后 3 个代表页真正跑完。

原本我以为,经过前 4 个代表页不断修正以后,最后 3 页可能更多只是验证已有机制。

实际结果却再次说明,真实页面往往比人为设计的测试更容易暴露边界问题。

concurrency/7concurrency/11methods/24 不但没有简单地“一次翻译通过”,反而又陆续发现了:

  • 非尾部指令位置校验缺口;
  • standalone 条件源码投影范围过窄;
  • 普通英语动词 Go 被误当成 Go 语言名称保护;
  • 链接显示文本中的行内代码没有受到完整保护;
  • 中文全角标点与行内代码之间仍可能缺少 legacy present 所需的结构性空格。

这些问题全部解决以后,原定的 7 个代表页终于真正完成。

这篇文章就接着上一篇的计划,记录最后 3 个代表页是怎样一步一步跑完的。


一、前 4 页完成后,为什么还坚持把最后 3 页做完

做到前 4 个代表页以后,整个翻译流程其实已经比较稳定。

当时已经真实覆盖了很多关键结构,包括:

  • 泛型和较复杂的技术说明;
  • 普通行内代码和旧式程序字体片段;
  • 普通链接;
  • 受保护内容按照自然中文语序重新排列;
  • 静态预格式化代码;
  • 可翻译的教学代码注释及其中的标识符;
  • 强调与字体样式片段;
  • 练习页面;
  • .play
  • Section 拓扑;
  • 尾部指令。

而且此前还刚刚解决了一个非常重要的问题:受保护标记的全局顺序,并不是可靠的多语言结构不变量。

也就是说,中文可以合理改变受保护内容的出现顺序,只要真正的代码、链接、指令和 present 结构没有遭到破坏。

到了这里,如果只是为了“多翻译几页”,确实已经可以开始批量处理。

但上一篇已经确定,最后还要用 3 个不同类型的页面补齐真实覆盖。

现在回头看,这个决定很值得。

因为接下来发现的问题,大多数都不是靠普通单元测试很容易提前想到的。


二、concurrency/7.image 内容没变,位置却可能悄悄跑掉

第 5 个代表页是:

Plaintext
concurrency/7

对应课程标题:

Plaintext
Exercise: Equivalent Binary Trees

它之所以被选中,是因为页面中包含:

Plaintext
.image /tour/static/img/tree.png

这是当前正式课程页面中非常少见的 .image

而且它不是放在页面最后,而是夹在两段正文之间:

Plaintext
正文 A

.image /tour/static/img/tree.png

正文 B

开始翻译之前,我先让 Codex 做了只读分析。

结果发现,当时的系统虽然能够严格检查:

  • .image 是否丢失;
  • 图片路径是否变化;
  • 指令数量是否变化;
  • 指令内容是否被修改;
  • 指令之间的顺序是否改变。

但还有一个漏洞:如果模型完整保留 .image /tour/static/img/tree.png,只是把它从正文中间移动到了页面开头或者页面末尾,现有校验可能仍然通过。

原因在于,此前已经对尾部指令建立了位置约束,但 .image 在这一页属于非尾部指令

如果整个 Section 里只有这一条指令,那么它即使移动位置,指令数量、内容和指令之间的顺序都没有变化。

于是第 5 个代表页首先发现了:同一个 Section 内,非尾部指令的结构位置还没有真正受到保护。


三、增加 directiveLayout:保护结构关系,而不是锁死中文段落

这个问题不能简单通过记录 .image 的绝对元素编号解决。

因为项目一直坚持:以完整课程页面为单位翻译,而不是要求中英文逐段一一对应。

英文可能是两段,中文翻译以后合理合并成一段;反过来也可能发生。

如果要求:

Plaintext
第 3 个 present 元素必须永远还是第 3 个

就很容易重新退回段落级结构绑定。

最终采用的办法是为含指令的 Section 建立 directiveLayout,也就是指令结构布局

例如一个页面可以抽象成:

Plaintext
正文区域
→ 指令
→ 正文区域
→ 预格式化代码
→ 正文区域

连续的普通自然语言文本会折叠成一个正文区域,而:

  • 预格式化代码;
  • 列表;
  • 嵌套 Section;
  • .play
  • .image
  • 其他指令

则继续作为稳定的结构节点存在。

这样:

Plaintext
正文
→ .image
→ 正文

就不能被随意变成:

Plaintext
.image
→ 正文

或者:

Plaintext
正文
→ .image

与此同时,普通中文段落仍然可以合理拆分、合并和调整措辞。

修复以后,concurrency/7 第一次真实 -dev 翻译就直接通过。

最终中文标题为:

Plaintext
练习:等价二叉树

其中 .image 保持在原来的正文混排位置,页面中的 JavaScript 链接目标:

Plaintext
javascript:click('.next-page')

也完整保留。

这一页最终提交为:

Plaintext
b74b3b36c513b516938d0fe4ea3e09b766aed54d
feat: 完成 concurrency/7 并增强非尾部 directive 位置校验
图 1:concurrency/7 中文页面,.image 正确保持在前后正文之间
图 1:concurrency/7 中文页面,.image 正确保持在前后正文之间

四、concurrency/11:真正的问题首先出在翻译源

第 6 个代表页:

Plaintext
concurrency/11

对应标题:

Plaintext
Where to Go from here...

上一篇已经预期这一页主要用于验证:

  • 长篇、多段正文;
  • 大量链接目标;
  • 跨段链接;
  • slides 强制术语;
  • 没有代码和指令时的长文本稳定性。

结果真正开始之前,却先发现了一个更基础的问题。

固定 upstream 中,这一页包含:

Plaintext
#appengine: You can get started by
#appengine: [[/doc/install/][installing Go]].

#appengine: Once you have Go installed, the
The
[[/doc/][Go Documentation]] is a great place to
#appengine: continue.
start.

其中 #appengine: 行并不是当前 standalone Tour 实际显示的正文。

standalone 页面真正需要的是:

Plaintext
The
[[/doc/][Go Documentation]] is a great place to
start.

项目此前其实已经实现过条件源码投影。

问题在于:当时这套处理只覆盖了 welcome.article

其他 .article 中如果同样存在 #appengine:,原始条件内容仍然可能进入翻译目录。

而校验器本来就有一道严格防线:standalone 源码中只要还存在 #appengine:,候选译文就必须拒绝。

因此,在这种情况下即使 GLM-5.2 翻译得再正确,也不可能通过校验。


五、不是放宽校验器,而是修正 standalone 源码投影

这个问题最终没有通过删除:

Plaintext
standalone source contains #appengine content

这条校验解决。

相反,这条检查继续完整保留。

因为真正的问题不是校验器太严格,而是进入翻译流程的 standalone 源码本身就不应该包含这些条件行。

于是原先只覆盖 welcome.article 的 standalone 条件源码投影被泛化到了其他 Tour .article

固定 upstream 中除了 welcome.article,还发现两个页面受影响:

Plaintext
flowcontrol/10
concurrency/11

两页当时都还是 pending,因此只需要安全更新对应的源码哈希。

这里还专门限制了源码迁移条件:只有旧源码含 #appengine:,并且新源码恰好等于按照规则删除这些条件行后的结果,才允许识别为安全迁移。

普通 upstream 内容变化仍然不会被自动放过。

这样既修正了 standalone 翻译源,也没有放松原本的源码锁。


六、concurrency/11 连续两次失败,却发现是我们保护错了

源码投影修复以后,终于可以开始真实翻译。

投影后的页面共有:

Plaintext
15 个受保护链接目标
12 个必须保留的 Go
总计 27 个受保护标记

第一次:

Plaintext
attempt-001

API 正常成功,GLM-5.2 也返回了完整页面。

但校验失败:

Plaintext
protected token count = 26, want 27
token 1 occurrence count = 0, want 1

丢失的是标题:

Plaintext
Where to Go from here...

中的 Go

模型把标题翻译成:

Plaintext
从这里去往何处...

第二次:

Plaintext
attempt-002

结果几乎完全一样。

仍然是:

Plaintext
26 / 27

仍然只缺标题里的那个 Go

两次模型都认为这里应该翻译成“接下来往哪里走”这一类普通中文含义。

如果停在这里,很容易得出一个结论:GLM-5.2 连续两次违反了受保护标记必须完整保留的规则。

但继续分析以后,结论发生了变化。


七、Where to Go from here... 中的 Go,未必是 Go 语言

项目当时会保护所有独立的大写:

Plaintext
Go

原因很合理。

A Tour of Go 中绝大部分 Go 都明确表示 Go 语言,例如:

Plaintext
Go has...
Go's...
in Go
Go Documentation
Go Blog
A Tour of Go

所以裸 Go 默认需要保护。

但是:

Plaintext
Where to Go from here...

不一样。

这里的 Go 大写,很可能只是因为它处于英文标题中。

从英语语义上看,它就是非常自然的:接下来该去哪里?

我当时还用 Google 翻译简单对照了一下,得到的也是:接下来该去哪里……

随后又检查了固定 upstream 中大约 66 个独立大写 Go

绝大多数确实明确表示 Go 语言。

真正不是明确语言名称的只有极少数,例如:

Plaintext
Where to Go from here...
Go local
Go offline (optional)

其中:

Plaintext
Where to Go from here...

作为普通英语动词的语义最明确。

这说明问题并不是“模型不愿意保留 Go”,而更可能是:我们的词面保护规则,把标题大小写造成的大写普通动词 Go 错误识别成了 Go 语言名称。


八、不取消 Go 保护,只放行高置信度的普通英语动词

解决方案没有改成:

Plaintext
所有 Go 都不保护

这样会让大量真正的 Go 语言名称失去硬保护。

也没有针对:

Plaintext
concurrency/11

或者完整标题写特殊规则。

最终采用了一条非常保守的上下文判断:裸 Go 默认继续保护;只有处于高置信度的 where + to + Go 疑问不定式结构时,才不生成受保护标记。

因此:

Plaintext
Where to Go from here...

中的 Go 不再保护。

而:

Plaintext
Go has...
Go's...
in Go
install Go
Go Documentation
A Tour of Go

仍然受到保护。

甚至:

Plaintext
migrate to Go from another language

也不能因为出现 to Go from 就被简单放行。

这里的 Go 仍然可能明确表示 Go 语言。

至于:

Plaintext
Go local
Go offline

目前继续采用保守策略,没有为了这一个问题试图一次解决所有英语词义消歧。


九、第 3 次遇到网络问题,第 4 次终于成功

修正 Go 的误保护以后,concurrency/11 的受保护标记数量从:

Plaintext
27

减少为:

Plaintext
26

少掉的唯一一个,就是标题中错误保护的 Go

接下来新的真实翻译尝试本应验证这次修复。

不过:

Plaintext
attempt-003

遇到了 DNS/网络沙箱错误,GLM-5.2 实际上没有收到请求。

这一份记录仍然被保留下来。

因为当前代表页校准一直使用开发模式 -dev

开发模式本来就允许针对同一个页面持续调试,不受未来正式运行“三次有限重试”的限制。

网络恢复以后继续:

Plaintext
attempt-004

这一次顺利通过。

26 个受保护标记全部完整。

15 个链接目标全部保留。

3 个 slides 链接显示文本也按照当前强制术语翻译为:

Plaintext
页面

最终标题:

Plaintext
接下来去哪……

没有再为了错误保留一个 Go 而制造生硬中文。

这一页最终提交为:

Plaintext
652839a0e5c14488b1391a61303dad03e8260668
feat: 完成 concurrency/11 并修复条件源码投影与 Go 误保护
图 2:concurrency/11 最终中文页面,“Where to Go from here...”自然翻译为“接下来去哪……”
图 2:concurrency/11 最终中文页面,“Where to Go from here…”自然翻译为“接下来去哪……”

这次经历也说明了一点:受保护标记失败,不一定意味着模型错了,也可能意味着我们保护了错误的东西。


十、最后一个代表页 methods/24,遇到了链接里的行内代码

上一篇已经把最后一个代表页定为:

Plaintext
methods/24

主要目的是验证 API 说明型页面、较长静态接口代码、多个 API 链接、强调文本和尾部 .play

实际分析时,又发现了一个此前没有真正覆盖到的组合:

Plaintext
[[/pkg/image/#Rectangle][`image.Rectangle`]]

这是一个链接。

但它的显示文本不是普通自然语言,而是行内代码:

Plaintext
`image.Rectangle`

此前项目已经能够保护:

  • 普通链接目标;
  • 普通正文中的行内代码。

但这里刚好是:链接显示文本里面又嵌套了一段行内代码。

原来的 presentInlineCodes 会有意跳过链接内部的程序字体片段。

所以:

Plaintext
image.Rectangle

不会成为行内代码受保护标记。

与此同时,校验器虽然能够知道:这里仍然存在一个程序字体片段。

却没有校验这个程序字体片段里面具体还是不是:

Plaintext
image.Rectangle

理论上,如果模型把它改成:

Plaintext
somethingElse

只要反引号结构还在,就可能漏过自动校验。


十一、链接目标和链接内行内代码分别保护

这次没有简单把整个链接显示文本锁死。

因为链接显示文本完全可能同时包含:

  • 可以自由翻译的自然语言;
  • 必须保持不变的技术代码。

最终增加了专门处理链接显示文本内部程序字体片段的逻辑。

例如:

Plaintext
[[/pkg/image/#Rectangle][`image.Rectangle`]]

现在会分别保护:

Plaintext
链接目标

和:

Plaintext
image.Rectangle

概念上相当于:

Plaintext
[[⟪链接目标标记⟫][⟪行内代码标记⟫]]

恢复后仍然得到:

Plaintext
[[/pkg/image/#Rectangle][`image.Rectangle`]]

校验器也不再只检查“这里有没有一个程序字体片段”。

而是会检查:这个具体链接内部的程序字体内容是否仍然保持正确。

因此:

Plaintext
[[/pkg/image/#Rectangle][`image.Rectangle`]]

如果变成:

Plaintext
[[/pkg/image/#Rectangle][`somethingElse`]]

就必须失败。

同时,同一个链接标签如果存在多个行内代码,它们仍然允许根据目标语言语序合理调整。

没有重新引入“整个页面的行内代码必须维持英文原始顺序”的限制。


十二、第一次 15 个受保护标记全部正确,页面却还是失败了

修复完成以后,运行:

Plaintext
attempt-001

API 成功。

这一页共有 15 个受保护标记:

Plaintext
4 个链接目标
1 个尾部指令
1 个静态预格式化代码块
9 个行内代码

15 个全部完整。

刚增加保护的:

Plaintext
image.Rectangle

也正确保留在原链接中。

但是最终页面仍然失败:

Plaintext
font span count mismatch: expected 10, actual 9

也就是:源页面应该有 10 个字体样式片段,恢复后的候选页面却只有 9 个。

最初一度怀疑,是不是静态代码块和后面的:

Plaintext
*Note*

之间缺少了必要空行。

如果当时直接按照这个判断修改预格式化代码逻辑,很可能就会修错地方。


十三、本地回放证明:静态代码块其实没有问题

于是没有马上重新调用 GLM-5.2,而是直接使用已经保存的:

Plaintext
attempt-001

原始模型响应进行本地回放。

结果发现,静态代码块边界其实已经正确恢复:

Plaintext
	type Image interface {
		ColorModel() color.Model
		Bounds() Rectangle
		At(x, y int) color.Color
	}

*注意*:

也就是说:静态预格式化代码的受保护内容本来就已经包含了结束代码块所需的空白。

问题根本不在这一层。

进一步逐项检查字体样式片段以后,真正缺失的是:

Plaintext
Bounds

十四、 后面的 Bounds 看起来没问题,present 却不认识

模型生成的结构类似:

Plaintext
*注意*:⟪BOUNDS_TOKEN⟫ 方法

恢复以后视觉上是:

Plaintext
*注意*:`Bounds` 方法

对于正常 Markdown 阅读来说,这看起来完全没有问题。

但 legacy present 并没有把这里的:

Plaintext
`Bounds`

识别为独立的程序字体片段。

原因最终定位到:Bounds 紧跟全角中文冒号 ,而 legacy present 在这种情况下仍然需要 ASCII 空格作为结构分隔。

这个问题其实与前面 generics/1 已经遇到过的行内代码边界问题属于同一类。

中文自然写作时,我们很容易写:

Plaintext
注意:`Bounds`

但是 legacy present 并不是现代 Markdown 解析器。

对某些组合,它仍然依赖:

Plaintext
注意: `Bounds`

中的那个 ASCII 空格。


十五、这个空格不是排版习惯,而是 present 结构

最终没有增加“所有中文标点后面都加空格”的排版规则。

而是继续扩展已有的行内代码边界归一化

新的处理仍然只作用于普通正文中的受保护行内代码。

当行内代码前面的相邻字符:

  • 不是空白;
  • 也不是 ASCII 标点;

并且 legacy present 确实需要结构边界时,就补一个 ASCII 空格。

因此:

Plaintext
*注意*:`Bounds` 方法

在实际 present 源码中需要恢复成:

Plaintext
*注意*: `Bounds` 方法

类似的中文全角标点包括:

Plaintext




但并不是简单机械地在所有标点前后添加空格。

例如行内代码后面的中文标点,如果 present 已经能够稳定解析,就不会为了“格式统一”再额外加空格。

这里的原则很明确:这些 ASCII 空格不是中文排版风格,而是 legacy present 所需的结构分隔符。

这一点也意味着,后续人工润色时不能只因为“中文冒号后面通常不应该有空格”就把它删除。


十六、第一次失败的原始模型输出,修复程序以后直接通过

这个问题最有价值的一步,是再次利用了已有失败记录。

没有立即运行 attempt-002

而是先拿:

Plaintext
attempt-001

的原始模型输出重新执行恢复和校验。

修复前:

Plaintext
font span = 9

修复后:

Plaintext
font span = 10

Bounds 被重新正确识别成程序字体片段。

同时:

Plaintext
[[/pkg/image/#Rectangle][`image.Rectangle`]]

仍然保持在原来的链接中。

静态 Go 代码逐字保持。

尾部:

Plaintext
.play methods/images.go

也没有任何变化。

然后,对第一次已经失败的模型输出重新执行完整候选译文校验:通过。

这与上一篇 methods/20 的经历非常相似。

不是重新翻译以后碰巧通过。而是:同一份原始模型输出,在修正本地保护、恢复或校验逻辑以后重新通过。

这对判断真正根因非常有帮助。


十七、attempt-002 真实翻译最终通过

本地回放验证以后,再运行:

Plaintext
attempt-002

这一次完整成功。

页面中的 9 个行内代码全部正确恢复,包括:

Plaintext
Image
Bounds
Rectangle
image.Rectangle
image
color.Color
color.Model
color.RGBA
color.RGBAModel

最终中文页面为:

Plaintext
* 图片

[[/pkg/image/#Image][image 包]]定义了 `Image` 接口:

	package image

	type Image interface {
		ColorModel() color.Model
		Bounds() Rectangle
		At(x, y int) color.Color
	}

*注意*: `Bounds` 方法的 `Rectangle` 返回值实际上是
[[/pkg/image/#Rectangle][`image.Rectangle`]],因为该声明位于 `image` 包内。

(详见 [[/pkg/image/#Image][文档]]。)

`color.Color` 和 `color.Model` 类型也是接口,不过我们将使用预定义的实现 `color.RGBA` 和 `color.RGBAModel`,因此这里暂不考虑这一点。这些接口和类型由 [[/pkg/image/color/][image/color 包]]定义。

.play methods/images.go

最终提交:

Plaintext
db62b6331019cc7aad0d08c977e79d55343f9d21
feat: 完成 methods/24 并增强链接内代码与行内边界校验
图 3:methods/24 中文页面,静态代码、Bounds 行内代码和链接内的 image.Rectangle 均正确渲染
图 3:methods/24 中文页面,静态代码、Bounds 行内代码和链接内的 image.Rectangle 均正确渲染

十八、上一篇计划中的 7 个代表页,现在终于全部完成

到这里,上一篇文章结尾确定的计划已经真正执行完毕。

7 个代表页全部完成:

Plaintext
generics/1
flowcontrol/8
methods/16
methods/20
concurrency/7
concurrency/11
methods/24

前 4 页建立了基本的整页翻译、结构保护和校验能力。

最后 3 页又继续补出了几个不同方向的问题。

concurrency/7 证明:指令不仅要保证内容不变,还必须保持合理的结构位置。

concurrency/11 证明:翻译前首先要保证 standalone 源码本身就是正确的发布内容。

同时也证明:不能因为单词写成 Go,就无条件认定它一定表示 Go 语言。

methods/24 证明:链接内部仍然可能嵌套需要独立保护的技术代码。

还再次确认:中文全角标点与 legacy present 行内代码之间的结构边界,需要由本地程序可靠恢复。

这些问题如果留到几十页、上百页批量翻译以后才出现,排查成本会明显更高。


十九、最后 3 页再次证明:失败不能先怪模型

最后 3 个代表页让我更加确定了一件事:模型输出没有通过校验,并不等于模型一定翻译错了。

concurrency/11 连续两次缺少 Go 受保护标记。

表面上看:GLM-5.2 不遵守硬约束。

真正分析以后却发现:我们保护了一个普通英语动词。

methods/24 第一次 15 个受保护标记全部完整,但结构校验还是失败。

表面上很容易认为:模型把代码块或者格式搞坏了。

真正本地回放以后发现:模型输出本身可以成立,是本地的 legacy present 行内边界归一化仍少覆盖了一种中文标点组合。

所以现在我越来越倾向于在失败以后先问:这到底是模型翻译问题,还是翻译系统自己的问题?

可能出问题的层很多:

Plaintext
upstream 源码
→ standalone 源码投影
→ 受保护内容识别
→ 模型整页翻译
→ 受保护内容恢复
→ present 解析
→ 结构校验
→ 页面验证

只有确定问题真正发生在哪一层,才应该修改对应机制。


二十、保存失败响应,本地回放越来越重要

上一篇处理 methods/20 时,我已经体会到保存原始失败响应的价值。

这一次 methods/24 又再次证明了这一点。

现在比较理想的排查方式已经逐渐固定成:

Plaintext
真实整页翻译
→ 自动校验失败
→ 保留原始响应
→ 本地回放
→ 定位最小差异
→ 修改本地通用机制
→ 用同一份响应重新校验
→ 再决定是否需要重新调用模型

这种方式有几个明显好处。

第一,可以节省 GLM-5.2 Tokens。

第二,可以分清:模型是否真的需要重新翻译。

第三,如果修复以后同一份原始响应就能够通过,那么对根因的证明也更加有说服力。

因为这说明:译文没有变化,变化的只是我们对于“什么才算结构安全”的判断或恢复能力。

对于以后扩展到其他语言,这一点尤其重要。


二十一、代表页校准阶段现在可以结束了

最后一个代表页完成以后,项目再次执行了完整验证:

Plaintext
go test ./internal/i18n
go test ./...

全部通过。

课程目录校验:

Plaintext
catalog OK: 103 published pages, 2 conditional source records

简体中文状态校验:

Plaintext
status OK: 103 pages for zh-CN

最后一个代表页提交:

Plaintext
db62b6331019cc7aad0d08c977e79d55343f9d21
feat: 完成 methods/24 并增强链接内代码与行内边界校验

提交并推送以后:

Plaintext
git status --short

再次恢复为空。

至此,原先计划中的 7 个代表页已经全部完成

我认为现在没有必要继续为了追求覆盖率,再人为寻找第 8、第 9、第 10 个复杂代表页。

继续这样做,收益会开始下降。

更重要的是进入下一阶段,让已经经过真实校准的系统面对普通页面。


二十二、下一步:10 个普通 pending 页面自动试跑

这正好承接上一篇文章已经确定的下一阶段。

当时的计划是:如果最后 3 个代表页也稳定通过,总代表页达到 7 页,就结束逐页校准,进入 10 个普通 pending 页面自动试跑。

现在这个前提已经满足。

所以接下来不再刻意寻找:

  • 特殊 .image
  • 特殊链接组合;
  • 特殊代码结构;
  • 特殊条件源码。

而是从剩余 pending 页面中挑选 10 个相对普通的课程页面。

这一批真正要验证的问题已经不同了。

代表页阶段是在问:系统有没有明显的结构盲区?

接下来 10 页普通试跑要问的是:经过这些校准以后,系统面对普通 A Tour of Go 页面时,能不能连续稳定地完成整页翻译和自动校验?

大致流程将是:

Plaintext
10 个普通 pending 页面
→ GLM-5.2 完整页面翻译
→ 自动恢复受保护内容
→ present 解析
→ 结构与术语校验
→ ready / pending / blocked
→ 汇总第一批普通页面实际结果

如果这 10 页也能够比较稳定地通过,就可以进一步降低逐页人工观察的必要性,进入剩余页面的批量翻译阶段。


结语

上一篇结束时,我写下的下一步是:完成最后 3 个代表页。

现在这个目标终于完成了。

但真正得到的并不只是:又多了 3 个中文页面。

concurrency/7concurrency/11methods/24 分别帮助这个项目发现了结构位置、条件源码、词义保护、链接内代码和中文行内边界等问题。

其中几次失败尤其有代表性。

有的问题表面上像模型不听话,最终发现是保护规则错了。

有的问题表面上像模型破坏了格式,最后发现是本地 present 恢复逻辑还缺一个边界。

这让我更加确定,A Tour of Go 多语言翻译项目真正需要建立的,并不是一套“让 AI 尽量少犯错,然后失败就重新翻译”的流程,而是一套能够明确区分哪些内容允许自然翻译,哪些内容必须严格保护,哪些变化属于不同语言的合理表达,哪些变化才真正破坏代码、链接、指令和 present 结构的自动化体系。

7 个代表页已经把这套体系推到了一个新的阶段。

接下来,就不再继续围绕少数特殊页面校准。

而是让它真正面对第一批普通课程页面:10 个普通 pending 页面自动试跑。

如果这一批能够稳定运行,那么这个项目也就真正开始从翻译流程校准进入规模化生成简体中文 A Tour of Go 内容的阶段。

A Tour of Go 中文翻译实录:如何只翻译教学代码注释,而不破坏 Go 代码

A Tour of Go 多语言翻译项目

本系列完整记录 A Tour of Go 多语言翻译项目从架构设计、整页翻译、结构保护、自动校验,到生产发布与后续维护的实际开发过程。

项目入口:
✅ 在线学习:A Tour of Go 简体中文版
✅ 项目源码:GitHub:shuijingwan/go-tour-i18n

当前第一阶段已完成简体中文版本。项目属于非官方社区多语言翻译项目,与 Go 官方无隶属或授权关系。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理