在 A Tour of Go 韩语版的翻译过程中,我遇到了一个很典型的问题:
automatic validator 报错了,到底应该修改译文,还是应该修改 validator?
以前遇到 validation failure 时,我的第一反应通常比较直接:
既然校验没有通过,那就找到译文中的结构问题,把译文修到通过为止。
这个思路大多数时候没有问题。
毕竟 validator 的重要职责之一,就是保护代码、Go 标识符、链接、present directive、preformatted block 等不应该在翻译过程中被破坏的技术结构。
但韩语这次让我发现:
validator 给出的 FAIL 是 evidence,不一定等于“译文一定错了”。
更重要的是,反过来也不能因为发现 validator 有一次误判,就开始认为:
“后面的失败大概也都是 validator 太严格。”
真正需要做的,是判断每一个 failure 究竟属于哪一种:
译文破坏了技术结构
→ 修改译文
译文技术身份完整,只是目标语言出现了合理语法现象
→ 检查 validator
语言质量本身有问题
→ 走 revision,而不是调整 validator韩语 codex-ko-KR-004 这个 batch,刚好把这三者之间的边界展示得非常清楚。
一、同一个 batch,13 个 TranslationUnit 里有 3 个 validation failure
事情发生在:
codex-ko-KR-004这个 batch 一共有 13 个 TranslationUnit。
第一次执行 process 以后:
unit_count: 13
validation_passed: 10
validation_failed: 3
失败的是:
concurrency/2
concurrency/4
concurrency/6但仔细看错误原因,会发现它们并不是同一种问题。
concurrency/2:
referenced Go identifier count mismatch:
expected 2, actual 0concurrency/6:
referenced Go identifier count mismatch:
expected 1, actual 0两者都指向:
教学注释中引用的 Go identifier 数量不匹配。
但 concurrency/4 完全不同:
font span mismatch at index 6:
expected bold, actual program也就是说,同样是:
validation_failed背后其实已经至少分成了两类:
identifier recognition和:
present font structure这也是后面判断的起点。
不能简单地把三个文件全部重新翻译一次,然后期待模型碰巧让 validator 满意。
二、先看 concurrency/2:Go 标识符真的丢了吗?
concurrency/2 是 Channels 页面。
正式英文 source 中有这样一段教学代码:
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.韩语 candidate 则变成:
ch <- v // v을 채널 ch로 보냅니다.
v := <-ch // ch에서 값을 받아
// v에 할당합니다.
如果只看技术身份,会发现一个非常关键的事实:
原来的:
v
ch并没有消失。
Go 代码里的:
ch <- v
v := <-ch完全没有修改。
教学注释里的 v 和 ch 也仍然存在。
变化在于,韩语翻译自然地把韩语语法成分直接接在这些 ASCII identifier 后面,例如:
ch에서
v에而不是强行写成类似:
ch 에서
v 에三、对人来说是 ch + 에서,对 lexer 来说却不一定如此
这里真正撞到的是一个“自然语言”和“程序词法分析”之间的边界。
从韩语读者的角度,可以很自然地理解:
ch에서为:
ch + 에서其中:
ch仍然是课程代码中真实存在的 Go identifier。
后面的韩语只是自然语言语法的一部分。
但是 validator 原来的判断方式更加接近程序词法分析。
而 Go identifier 本身支持 Unicode 字母。
于是:
ch에서对词法分析器来说,有可能被视为一个完整 identifier。
它看到的就不再是:
ch而是:
ch에서然后再去和源代码中的 identifier 集合比较:
ch
v自然就找不到完全相同的:
ch最终产生:
expected 2
actual 0这样的结果。
所以这次 failure 的真正含义并不是:
模型把
ch删除了。
而是:
validator 无法从正常韩语写法中重新识别出那个仍然完整存在的 ASCII Go identifier。
四、这时候如果为了通过 validator 修改韩语,反而可能做错
发现问题以后,其实有一个最简单的“修复办法”。
比如强制要求译文写成:
ch 에서
v 에甚至要求模型:
- 插空格;
- 加反引号;
- 插入额外名词;
- 使用其他人为分隔方式。
这样旧 validator 可能就可以重新识别出:
ch
v然后顺利 PASS。
但这会出现一个非常奇怪的结果:
机器检查通过了,目标语言反而被 validator 的实现细节扭曲了。
而 validator 的设计目标本来应该是保护翻译质量和技术结构。
如果最后变成:
为了让 validator 好解析,要求韩语按照英文词法边界书写。
那方向就反了。
所以这一次我没有选择:
修改正确韩语
→ 迎合旧 validator而是开始检查:
validator 真正需要保护的,到底是什么?
五、真正需要保护的是 identifier 身份,而不是它必须“词法独立”
对这个场景来说,真正不能发生的是:
v → value
v → v2
v → V
v → _v
v → 被删除
v → 出现两次
v / ch → 顺序发生错误变化因为这些都会改变教学注释对真实 Go identifier 的引用。
但是:
v + 韩语语法字符
ch + 韩语语法字符本身并没有改变那个 ASCII identifier 的技术身份。
因此最终规则被重新定义为:
对于 ko-KR teaching comment,完整保留的 ASCII Go identifier 后面,可以按照正常韩语语法直接附着一个或多个 Hangul 字符。

这里我特别强调“窄规则”。
因为发现 validator 存在误判以后,最危险的做法其实是:
那干脆把 identifier validation 放宽一点。
这很容易把真正应该挡住的问题一起放过去。
所以最终规则仍然要求:
ASCII identifier 字节不变
大小写不变
数量不变
顺序不变
所属教学注释不变同时明确:
不能追加 ASCII 字母
不能追加数字
不能追加下划线例如源 identifier 是:
v下面这些仍然不能因为“韩语特殊规则”而通过:
value
v2
_v
V更不能允许:
删除 v
重复 v
调换 identifier六、而且这个例外不能扩散到整个项目
这次规则还有几个非常重要的 scope。
它只适用于:
locale = ko-KR并且只适用于:
可翻译 teaching comment body它不适用于真正的 Go source code。
也不放宽:
present directive
link
URL
preformatted non-comment bytes
其他 protected structure更不会自动变成:
所有 locale 都允许 identifier 后接任意 Unicode 字符这其实是我现在修改 validator 时很重视的一点:
真实语言现象需要支持,但 exception 的作用范围要尽可能小。
不能因为韩语暴露了一个真实问题,就顺手修改一个全局正则,让中文、日文、法语、德语以及未来所有语言全部改变语义。
七、单靠“我觉得这样没问题”还不够,需要正反测试
规则修改以后,还专门增加了一组测试。

第一类测试证明正常韩语形式应该被接受。
例如:
v를
ch로
ch에서
v에
i를
c에서这些场景中,原始 ASCII Go identifier 仍然完整存在。
第二类测试更加重要:
必须证明 validator 没有因为支持韩语而失去原来的保护能力。
所以测试专门包含:
ASCII_letters_appended
ASCII_digit_appended
ASCII_underscore_prefix
case_changed
identifier_missing
identifier_repeated
identifiers_reordered也就是:
v → value
v → v2
v → _v
v → V
v → 消失
v → 重复
identifier 顺序被改变这些情况必须继续失败。
同时还验证:
同一规则不能因为实现方便,就顺便让其他 locale 也接受这种行为。
真正的非注释 Go code 被修改时,也仍然应该被拒绝。
这样才算完成了一次安全的 validator 调整。
八、最关键的证据:修改 validator 后,没有变成 13/13
如果这篇文章到这里结束,很容易产生另一个错误印象:
原来 batch 004 的 3 个 validation failure 都是 validator 的问题。
事实并不是这样。
修改 validator 并正式执行 revalidate 后,结果从:
validation_passed: 10
validation_failed: 3变成:
validation_passed: 12
validation_failed: 1
这其实是整个案例里我觉得最重要的一步。
concurrency/2 通过了。
concurrency/6 也通过了。
说明这两个:
referenced Go identifier count mismatch确实属于 validator 对韩语边界认识不足。
但是:
concurrency/4仍然失败。
错误还是:
font span mismatch:
expected bold, actual program也就是说:
validator 修正以后,真正的结构错误并没有一起消失。
这正是我希望看到的结果。
九、concurrency/4 就不能再怪 validator 了
concurrency/4 的 candidate 中有这样一个差异:
-*추가 참고:*
+*추가*참고:*看起来只移动了一个 *。
但对于 present 文本来说,这并不是普通标点变化。
*...* 本身带有 font span 语义。
于是:
*추가 참고:*和:
*추가*참고:*对应的结构并不相同。
原 validator 报:
expected bold
actual program在这里是有意义的。
这种问题不能通过:
“韩语语法比较特殊。”
来解释。
也不能继续扩展 validator:
“那韩语的星号位置也灵活一点吧。”
因为一旦这么做,protected structure validation 就真的开始失去意义了。
所以这一个 failure 的正确处理路径是:
修改 candidate。
十、最后才真正达到 13/13
在修复 concurrency/4 candidate 后,再次验证:
validation_passed: 13
validation_failed: 0整个 batch 才真正完成。
所以完整过程实际上是:
13 units
↓
10 passed / 3 failed
↓
分析 failure
↓
发现 concurrency/2、concurrency/6 属于韩语 identifier boundary
↓
修改 validator
↓
增加正反测试
↓
正式 revalidate
↓
12 passed / 1 failed
↓
确认 concurrency/4 是真实 protected structure 问题
↓
修改 candidate
↓
13 passed / 0 failed这个过程比:
失败
→ 重翻译三份
→ 再试复杂一些。
但它留下的东西也完全不同。
十一、为什么还专门增加了 revalidate
这次还有一个流程上的小变化:
feat: 增加 retranslation revalidate 正式流程原因也来自这个问题本身。
concurrency/2 和 concurrency/6 的 candidate 并没有因为失败而变坏。
后来发生变化的是:
validator。
那么 validator 修正以后,最正确的动作应该是:
使用同一个 candidate
→ 用当前规则重新 validation而不是:
重新调用模型
→ 生成一份可能完全不同的翻译否则会出现一个很荒唐的情况:
原译文其实没问题,只因为 validator 修好了,却要求模型重新翻译一次。
不仅浪费额度,还会引入新的语言差异。
所以这里正式增加了:
revalidate用于:
candidate 不变、validator 发生合法变化以后,对现有 candidate 重新执行 automatic validation。
与此同时,旧的 failure evidence 仍然保留在:
revalidation-history/而不是被新的 PASS 直接覆盖得像从来没有失败过一样。
这一点对以后排查规则演进也很重要。
十二、Retry、Revalidate 和 Revision 其实是三种完全不同的动作
做到韩语以后,我越来越觉得,这三条路径需要特别明确。
Retry
适用于:
restore_failed
validation_failed而且是真正需要重新生成或修复受保护 artifact 的场景。
例如 concurrency/4 的 font span 结构错误。
Revalidate
适用于:
candidate 没有变,但 validator 本身经过了有证据支持的修正。
像这次:
concurrency/2
concurrency/6就是典型案例。
Revision
则完全不同。
如果 Quality Check 或 Final Review 判断:
语言不自然
含义偏差
术语错误
表达质量不够那就不是 automatic validation 问题。
必须:
re-export
→ Codex 重译
→ process
→ validation
→ Quality Check
→ Final Review也就是新的 revision batch。
不能修改 validator。
也不能用 retry 绕过语言质量 gate。
这三个概念看起来都像:
“上一轮没过,再来一次。”
但工程含义完全不同。
十三、Automatic validation 从来都不等于 Translation Quality
这也是这个案例另外一个值得记录的点。
Automatic validator 可以检查很多东西:
代码有没有改变
protected token 有没有丢
link 有没有损坏
directive 有没有变化
Go identifier 有没有被改写
present structure 是否保持但它不能回答:
韩语自然吗?
含义准确吗?
术语统一吗?
教学表达适合韩语读者吗?反过来也一样。
一个韩语句子非常自然,并不能证明:
Go identifier 没被改
链接没丢
代码没变所以两种检查本来就是互补关系:
Automatic validation
→ 技术安全
Quality Check / Final Review
→ 语言质量这次 validator 自己出现语言学边界,也并不意味着 automatic validation 不可靠。
恰恰相反。
正因为它把失败明确报出来,我才有证据继续分析:
是 translation 问题,还是 validator 问题?
十四、Validator 也需要被新的 locale 校准
以前设计 validator 时,很容易有一种默认想法:
只要规则足够严格,就越安全。
做了几门语言以后,我现在更倾向于:
严格应该针对真正需要保护的语义,而不是针对某一种语言的表面形式。
例如真正需要保护的是:
identifier v那就应该保护:
它还是不是 ASCII 的 v
出现次数有没有变化
大小写有没有变化
顺序有没有变化
是否仍然引用正确代码而不是保护:
v后面必须是 ASCII 空格或英文标点。
后者其实只是英文书写方式带来的偶然形式。
如果未来另一门语言出现:
前缀黏着
后缀黏着
Unicode 标点
不同断词习惯也可能再次暴露类似问题。
这时候不能简单认为:
新语言不符合 validator,所以新语言应该改。
更合理的问题应该是:
当前 validator 检查的到底是技术身份,还是无意中把源语言的书写习惯当成了技术规则?
十五、但“语言差异”也不能成为放宽规则的万能理由
这一点同样重要。
如果只强调:
validator 需要适配自然语言。
很容易走向另一个极端。
以后碰到任何 failure 都可以说:
可能是 locale 特殊情况。
然后不断加 exception。
最终 automatic validation 就会变成:
if zh-CN ...
if ja-JP ...
if de-DE ...
if fr-FR ...
if ko-KR ...最后什么都能通过。
所以我现在给自己定下来的判断方式更接近:
第一步:确认 protected technical identity 有没有真正改变
如果改变了:
修改 candidate。
第二步:如果 identity 没变,确认 failure 是否来自目标语言正常语法
如果是:
才考虑 validator。
第三步:规则修改必须尽可能窄
例如这次只允许:
ko-KR
+
teaching comment body
+
完整 ASCII Go identifier
+
仅 Hangul suffix第四步:必须增加反例测试
证明:
真正的技术损坏仍然过不了。
如果做不到这一步,我会更倾向于不修改 validator。
十六、我现在不再把 validator PASS 当成目标本身
这次还有一个思路上的变化。
以前看到:
validation_failed很容易把目标设成:
我要想办法让它变成 PASS。
现在我觉得更准确的目标应该是:
我要先判断这个 FAIL 是否正确。
如果它是正确的:
保持 validator
修改 candidate如果它是错误的:
保持 candidate
修改 validator
revalidate而不是:
哪种方法最快让状态变绿,就用哪一种。
因为状态最终显示:
passed只是结果。
真正重要的是:
为什么它应该通过。
十七、这个案例最终留下的,不只是一条韩语规则
表面上看,这次修复只是:
支持 ko-KR 注释标识符韩语后缀但我觉得真正留下来的经验更通用。
第一:
validator 不是绝对真理。
它是实现出来的规则,同样可能带有设计时没有意识到的语言假设。
第二:
发现 validator 误判,不代表应该降低 validation 标准。
正确做法是缩小 exception,让真正技术约束继续存在。
第三:
candidate 和 validator 谁发生变化,要决定后续执行 retry、revalidate 还是 revision。
第四:
failure evidence 应该保留。
不能因为新规则让它通过以后,就把旧失败直接抹掉。
第五:
目标语言质量不能为了 machine validation 让路。
如果一种写法在目标语言中本来就是正常的,而技术 identity 又完全没有改变,就不应该仅仅为了让 tokenizer 更容易识别,强迫译文采用人为格式。
小结
ko-KR batch 004 第一次执行 validation 时:
13 units
10 passed
3 failed其中:
concurrency/2
concurrency/6表面上都是:
referenced Go identifier count mismatch但真正原因是韩语可以把 Hangul 语法字符直接连接到 ASCII Go identifier 后面,而旧 validator 把整个 Unicode 字符串作为新的 identifier 处理。
于是:
修改 validator
→ 增加 ko-KR teaching comment 窄规则
→ 加入正反测试
→ revalidate结果变成:
12 passed
1 failed剩下的:
concurrency/4是真正的:
font span mismatch这个时候就不能再调整 validator。
而应该修改 candidate。
最后:
13 passed
0 failed所以这次我真正记住的不是:
“韩语需要一个特殊 validator。”
而是:
当 validator 报错时,正确的问题不是“怎样让它通过”,而是“这一次,到底是谁错了?”
如果译文破坏了技术结构,就修译文。
如果 validator 把目标语言的正常语法误认为技术破坏,就修 validator。
如果语言质量不好,就回 revision。
三者看起来都会让当前流程变慢。
但只有把边界分清楚,automatic validation 才真正是在保护翻译,而不是反过来塑造翻译。
A Tour of Go 多语言翻译项目
本系列完整记录 A Tour of Go 多语言翻译项目从架构设计、整页翻译、结构保护、自动校验,到生产发布与后续维护的实际开发过程。
项目入口:
✅ 简体中文:A Tour of Go 简体中文版
✅ 日语:A Tour of Go 日语版
✅ 德语:A Tour of Go 德语版
✅ 法语:A Tour of Go 法语版
✅ 项目源码:GitHub:shuijingwan/go-tour-i18n
当前已上线简体中文、日语、德语和法语版本。项目属于非官方社区多语言翻译项目,与 Go 官方无隶属或授权关系。
