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

Validator 报错,到底该改译文还是改规则?一次 A Tour of Go 韩语翻译实战

图 2:英文 source 与当时发生 validation failure 的 ko-KR candidate 对比

作者:

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 页又发现了哪些真实问题

图 2:首批 10 个普通页面经过试跑与流程校准后全部进入 ready

(10) A Tour of Go 多语言翻译项目:首批 10 个普通页面试跑,从 2 个 blocked 到全部 ready

图 2:flowcontrol/6 最终中文页面,return 与 v 为适应自然中文语序发生整体换位

(11) A Tour of Go 中文翻译第二批实跑:从 Inline Code 顺序误判到历史响应重新验证

图 1:flowcontrol/6 首次原始输入实验直接通过统一自动校验

(12) A Tour of Go 多语言翻译项目:从 swap 问题重新审视 Token 保护,原始输入与最小保护的一次真实实验

图 7:moretypes/1 最终中文页面预览。左侧课程正文、静态代码块、行内代码和教学注释均正常渲染,右侧官方 Go 示例继续保持原样。

(13) A Tour of Go 多语言翻译实战:第三批 10 页全部 Ready,继续校准 Protected Token 的结构角色

图 4:上游源码确认共有 103 个课程页面,zh-CN 最终达到 ready=103、pending=0、blocked=0

(14) A Tour of Go 多语言翻译项目:zh-CN 103 个课程页面全部 ready,课程正文翻译阶段完成

图 4:GitHub 与 ChatGPT 已成功连接

(15) ChatGPT 连接 GitHub 实测:让 AI 直接读取 go-tour-i18n 仓库参与译文审核

图 3:中文 Go 术语校准完成后,将确定的译法写入 zh-CN glossary

(16) 为什么 channel 最终选择“通道”:A Tour of Go 中文术语统一的一次实践

图 1:技术含义基本正确,但“返回一个返回……”已经明显影响阅读,因此仍被列入 C 类修订

(17) 103 页逐页审核后,只返修 16 页:A Tour of Go 中文正文发布前质量审计

图 2:中文课程页面以及已经完成本地化的编辑器控制区

(18) A Tour of Go 多语言翻译项目:公共 UI 本地化完成,从课程译文走向完整中文界面

图 2:完整 zh-CN 正式投影成功生成,103 个课程页面被重新组装为 7 个 .article

(19) 从 103/103 ready 到完整站点验收:A Tour of Go 中文版补齐正式投影与课程元数据本地化

图 3:从 production bundle 独立启动的 A Tour of Go 简体中文页面,课程正文、公共 UI 和官方 Go 示例均已进入最终发布形态。

(20) A Tour of Go 多语言翻译项目:103 页全部完成后,我终于生成了可独立部署的 zh-CN Production Bundle

图 1:A Tour of Go 简体中文版正式生产页面,远程运行已经成功输出“Hello, 世界”。

(21) A Tour of Go 多语言翻译项目:zh-CN 正式上线,从生产发布到 go.dev Playground 的完整部署记录

图 1:新生产域名 go-dev.shuijingwanwq.com 已正常访问,Go 示例远程运行成功

(22) A Tour of Go 多语言翻译项目:从 go-tour 到 go-dev,完成 EdgeOne、Nginx 与 HTTPS 生产域名迁移

图 1:A Tour of Go 多语言翻译项目正式生产首页

(23) A Tour of Go 多语言翻译项目正式上线:从项目首页、公共 Footer 到生产发布元数据的完整收尾

图 3:GA4 g/collect 请求返回 HTTP 204,并携带当前课程页面地址,确认 Google Analytics 已产生真实事件上报

(24) A Tour of Go 多语言翻译项目:接入 Google Analytics 与百度统计,从 systemd 注入到 EdgeOne 与真实上报验证

图 1:生产环境 robots.txt 与 sitemap.xml 已正确对外提供,Sitemap 共包含 104 个规范 URL。

(25) A Tour of Go 中文站上线后:补齐 robots.txt、Sitemap,并完成五大搜索引擎提交

图 4:发布生产并刷新 EdgeOne 缓存后,再次通过真实手机访问,顶栏项目名称已经完整保持在一行,最终移动端验收通过。

(26) A Tour of Go 多语言翻译项目:修复手机端首页顶栏标题换行,并完成真机验证

图 3:生产自动部署脚本第一次真实运行时,systemd 已经显示 active,但首次 localhost 探测仍然得到 HTTP 000;随后连续 3 次同时满足 active + HTTP 200 后,脚本才判定部署成功,并继续完成公网 HTTP 200 验收。这次真实运行验证了连续应用层健康检查的必要性。

(27) A Tour of Go 多语言翻译项目:从一次 203/EXEC 故障到生产自动部署脚本落地

图 1:阿里云生产环境访问 go.dev Playground 时出现 TLS 握手超时

(28) A Tour of Go 多语言翻译项目:为 Playground 搭建独立的 ZgoCloud 执行代理

图 3:清除 /tour/script.js 缓存后,Run 已直接请求 ZgoCloud /compile 并返回 HTTP 200

(29) A Tour of Go 多语言翻译项目:将生产 Run / Format 切换到 ZgoCloud 执行节点

【图 1:微信中运行 Go 示例时出现 Error communicating with remote server.】

(30) A Tour of Go 中文版手机端运行与格式化偶发失败排查:从间歇性报错到补强 Nginx 可观测性

【图 4:向 golang-dev 公开邮件列表发送 Playground 使用告知邮件并投递成功】

(31) A Tour of Go 多语言项目:为 Playground 代理增加唯一 User-Agent,并主动告知 Go 官方

【图 3:百度剩余 9 个核心 URL 提交成功,remain 归零】

(32) A Tour of Go 搜索引擎收录实测:从 Google 自然索引到百度主动提交与 Bing IndexNow

【图 1:当前正式中文 methods/24 页面,右侧示例代码运行成功】

(33) A Tour of Go 中文翻译完成后,我为什么重新评估 minimal-protect 翻译模式

【图 3:5 个页面 Request SHA256 完全一致,但 Response SHA256 全部不同】

(34) A Tour of Go 翻译实验:更多原始上下文为什么没有更好?从 157 个保护标记到 30 次重复盲评

图 3:3 个代表页 × 4 个评审模型,共 12 次匿名评审的完整排名与第一名票数。

(35) A Tour of Go 翻译质量再评估:minimal-protect 未达预期后,我开始比较 ChatGPT、Codex 与 GLM-5.2

图 2:GitHub 中的 chatgpt-zh-CN-008 重译批次。一个 Batch 同时保存 inputs、raw-responses、candidates、validation 和 retry 记录,GitHub 成为 ChatGPT 与本地翻译流水线之间的数据交接层。

(36) 没有 OpenAI API,我如何用 ChatGPT + GitHub 完成 A Tour of Go 103 页全量重译

图 1:103 个正式课程页面完成最终语义质量审核,结果为 A=103、B/C/D=0,同时不存在缺失、重复或额外页面。A/B/C/D 是本轮项目内部语义质量分级,并不是自动结构 validator 的结果。

(37) 103 页翻完还不能发布:A Tour of Go ChatGPT 译文的语义审核与 Canonical Promotion

图 3:7 个 article endpoint 的 production origin 与 EdgeOne 公网响应全部 exact match,同时新的 ChatGPT 译文标记存在,旧版译文标记已经消失。

(38) 103/103 与 7/7 Exact Match:A Tour of Go ChatGPT 新译文的最终线上验收

【图 2:upstream source preview】

(39) A Tour of Go 多语言翻译项目首次实现 upstream 同步机制:从一次性翻译到长期维护

图 2:A/B/C/D 质量评级规则,展示正式质量 rubric 与 Promotion gate。

(40) A Tour of Go 多语言翻译项目引入 Translation Quality Review:从自动验证到 AI 翻译质量控制

图 4:翻译后 Playground Example,展示中文注释与保持不变的 Go 代码结构。

(41) A Tour of Go 多语言翻译项目实现 Playground Example 本地化:从代码注释翻译到完整学习体验

图 5:公网页面验证广告脚本输出

(42) A Tour of Go 多语言翻译项目接入广告系统:从 AdSense 专用配置到通用 HTML 注入架构

图 3:百度统计单页应用设置

(43) go-dev 单页网站接入统计与 Google AdSense Auto Ads 验证记录

图 1:GitHub 仓库中的多语言翻译项目结构

(44) A Tour of Go 多语言翻译项目:选择日语作为第二语言并启动翻译验证

图 5:模型身份描述不一致

(45) A Tour of Go 多语言翻译项目中的 AI 协作异常排查:一次长期工程会话失效分析

【图 1:ChatGPT GitHub 写入流程调整说明截图】

(46) go-tour-i18n 翻译流程调整:从 ChatGPT GitHub 写入到本地 artifact 导入

图 4 展示了本次异常状态。

(47) go-tour-i18n 翻译自动化流程复盘:已验证的 ChatGPT 翻译能力在新任务中的稳定性问题

【图 6:全部评审结束以后才读取 secret key,完成最终揭盲】

(48) A Tour of Go 翻译质量再评估:Codex High 已接近 ChatGPT High,我决定调整默认翻译引擎

图 4:ja-JP 生产课程页实际运行 Go 示例

(49) A Tour of Go 日语版正式上线:第二门语言 ja-JP 完成生产发布

图 1:ja-JP 上线以后,先提交“新增语言标准化流程”

(50) A Tour of Go 多语言扩展复盘:为第三门语言建立标准化流程

图 1:课程正文结束以后,页面左侧仍然存在大块视觉空白,但 AdSense 并没有在这里插入展示广告。

(51) A Tour of Go 明明有大片空白,为什么 AdSense 就是不显示展示广告?

图 2:golang/website 当前的 _content/tour/。课程内容、静态资源和模板都在这个上游目录中持续维护。

(52) 为了 AdSense,要不要放弃 SPA?A Tour of Go 页面架构的一次取舍

图 4:最终的手动课程广告真实展示在课程内容区域内,而不是 footer 之后。

(53) 保留 SPA 以后,广告应该放在哪里?从 AdSense 预览到方案 B

图 1:Google Search Console 中,多个不同的 A Tour of Go 课程 URL 被判断为“重复网页,用户未选定规范网页”。

(54) SPA 的 SEO 老问题:为什么最后还是给 103 个 A Tour of Go 课程页生成了 Prerender HTML

图 2:方案 B 生产环境中,源码已经正常,但课程主体高度异常缩短,footer 提前进入第一屏。

(55) 从源码空白到 AdSense 高度污染:A Tour of Go 方案 B 的生产收尾

图 1:A Tour of Go 德语版 de-DE 已正式运行在生产环境

(56) A Tour of Go de-DE 上线复盘:第三门语言用了约 16 小时,下一门如何压到 8 小时?

图 1:fr-FR 首次 production 验收完成后,我没有立即开始 ko-KR,而是先连续完成新增语言与首次生产流程的优化

(57) 法语版上线后,我没有马上开始韩语:A Tour of Go 新增语言流程的一轮完善与简化

图 1:根据 ko-KR 期间 76 次 Git 提交估算实际工作时间

(58) 原本预计 6 小时,最后用了约 16~18 小时:A Tour of Go 韩语版上线复盘

图 2:英文 source 与当时发生 validation failure 的 ko-KR candidate 对比

(59) Validator 报错,到底该改译文还是改规则?一次 A Tour of Go 韩语翻译实战

在 A Tour of Go 韩语版的翻译过程中,我遇到了一个很典型的问题:

automatic validator 报错了,到底应该修改译文,还是应该修改 validator?

以前遇到 validation failure 时,我的第一反应通常比较直接:

既然校验没有通过,那就找到译文中的结构问题,把译文修到通过为止。

这个思路大多数时候没有问题。

毕竟 validator 的重要职责之一,就是保护代码、Go 标识符、链接、present directive、preformatted block 等不应该在翻译过程中被破坏的技术结构。

但韩语这次让我发现:

validator 给出的 FAIL 是 evidence,不一定等于“译文一定错了”。

更重要的是,反过来也不能因为发现 validator 有一次误判,就开始认为:

“后面的失败大概也都是 validator 太严格。”

真正需要做的,是判断每一个 failure 究竟属于哪一种:

Plaintext
译文破坏了技术结构
→ 修改译文

译文技术身份完整,只是目标语言出现了合理语法现象
→ 检查 validator

语言质量本身有问题
→ 走 revision,而不是调整 validator

韩语 codex-ko-KR-004 这个 batch,刚好把这三者之间的边界展示得非常清楚。


一、同一个 batch,13 个 TranslationUnit 里有 3 个 validation failure

事情发生在:

Plaintext
codex-ko-KR-004

这个 batch 一共有 13 个 TranslationUnit。

第一次执行 process 以后:

Plaintext
unit_count: 13
validation_passed: 10
validation_failed: 3
图 1:ko-KR batch 004 首次 validation,13 个 TranslationUnit 中有 3 个失败
图 1:ko-KR batch 004 首次 validation,13 个 TranslationUnit 中有 3 个失败

失败的是:

Plaintext
concurrency/2
concurrency/4
concurrency/6

但仔细看错误原因,会发现它们并不是同一种问题。

concurrency/2

Plaintext
referenced Go identifier count mismatch:
expected 2, actual 0

concurrency/6

Plaintext
referenced Go identifier count mismatch:
expected 1, actual 0

两者都指向:

教学注释中引用的 Go identifier 数量不匹配。

concurrency/4 完全不同:

Plaintext
font span mismatch at index 6:
expected bold, actual program

也就是说,同样是:

Plaintext
validation_failed

背后其实已经至少分成了两类:

Plaintext
identifier recognition

和:

Plaintext
present font structure

这也是后面判断的起点。

不能简单地把三个文件全部重新翻译一次,然后期待模型碰巧让 validator 满意。


二、先看 concurrency/2:Go 标识符真的丢了吗?

concurrency/2 是 Channels 页面。

正式英文 source 中有这样一段教学代码:

Plaintext
ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and
           // assign value to v.

韩语 candidate 则变成:

Plaintext
ch <- v    // v을 채널 ch로 보냅니다.
v := <-ch  // ch에서 값을 받아
           // v에 할당합니다.
图 2:英文 source 与当时发生 validation failure 的 ko-KR candidate 对比
图 2:英文 source 与当时发生 validation failure 的 ko-KR candidate 对比

如果只看技术身份,会发现一个非常关键的事实:

原来的:

Plaintext
v
ch

并没有消失。

Go 代码里的:

Plaintext
ch <- v
v := <-ch

完全没有修改。

教学注释里的 vch 也仍然存在。

变化在于,韩语翻译自然地把韩语语法成分直接接在这些 ASCII identifier 后面,例如:

Plaintext
ch에서
v에

而不是强行写成类似:

Plaintext
ch 에서
v 에

三、对人来说是 ch + 에서,对 lexer 来说却不一定如此

这里真正撞到的是一个“自然语言”和“程序词法分析”之间的边界。

从韩语读者的角度,可以很自然地理解:

Plaintext
ch에서

为:

Plaintext
ch + 에서

其中:

Plaintext
ch

仍然是课程代码中真实存在的 Go identifier。

后面的韩语只是自然语言语法的一部分。

但是 validator 原来的判断方式更加接近程序词法分析。

而 Go identifier 本身支持 Unicode 字母。

于是:

Plaintext
ch에서

对词法分析器来说,有可能被视为一个完整 identifier。

它看到的就不再是:

Plaintext
ch

而是:

Plaintext
ch에서

然后再去和源代码中的 identifier 集合比较:

Plaintext
ch
v

自然就找不到完全相同的:

Plaintext
ch

最终产生:

Plaintext
expected 2
actual 0

这样的结果。

所以这次 failure 的真正含义并不是:

模型把 ch 删除了。

而是:

validator 无法从正常韩语写法中重新识别出那个仍然完整存在的 ASCII Go identifier。


四、这时候如果为了通过 validator 修改韩语,反而可能做错

发现问题以后,其实有一个最简单的“修复办法”。

比如强制要求译文写成:

Plaintext
ch 에서
v 에

甚至要求模型:

  • 插空格;
  • 加反引号;
  • 插入额外名词;
  • 使用其他人为分隔方式。

这样旧 validator 可能就可以重新识别出:

Plaintext
ch
v

然后顺利 PASS。

但这会出现一个非常奇怪的结果:

机器检查通过了,目标语言反而被 validator 的实现细节扭曲了。

而 validator 的设计目标本来应该是保护翻译质量和技术结构。

如果最后变成:

为了让 validator 好解析,要求韩语按照英文词法边界书写。

那方向就反了。

所以这一次我没有选择:

Plaintext
修改正确韩语
→ 迎合旧 validator

而是开始检查:

validator 真正需要保护的,到底是什么?


五、真正需要保护的是 identifier 身份,而不是它必须“词法独立”

对这个场景来说,真正不能发生的是:

Plaintext
v → value
v → v2
v → V
v → _v
v → 被删除
v → 出现两次
v / ch → 顺序发生错误变化

因为这些都会改变教学注释对真实 Go identifier 的引用。

但是:

Plaintext
v + 韩语语法字符
ch + 韩语语法字符

本身并没有改变那个 ASCII identifier 的技术身份。

因此最终规则被重新定义为:

对于 ko-KR teaching comment,完整保留的 ASCII Go identifier 后面,可以按照正常韩语语法直接附着一个或多个 Hangul 字符。

图 3:最终进入正式 Translation Task Spec 的 ko-KR teaching comment 窄规则
图 3:最终进入正式 Translation Task Spec 的 ko-KR teaching comment 窄规则

这里我特别强调“窄规则”。

因为发现 validator 存在误判以后,最危险的做法其实是:

那干脆把 identifier validation 放宽一点。

这很容易把真正应该挡住的问题一起放过去。

所以最终规则仍然要求:

Plaintext
ASCII identifier 字节不变
大小写不变
数量不变
顺序不变
所属教学注释不变

同时明确:

Plaintext
不能追加 ASCII 字母
不能追加数字
不能追加下划线

例如源 identifier 是:

Plaintext
v

下面这些仍然不能因为“韩语特殊规则”而通过:

Plaintext
value
v2
_v
V

更不能允许:

Plaintext
删除 v
重复 v
调换 identifier

六、而且这个例外不能扩散到整个项目

这次规则还有几个非常重要的 scope。

它只适用于:

Plaintext
locale = ko-KR

并且只适用于:

Plaintext
可翻译 teaching comment body

它不适用于真正的 Go source code。

也不放宽:

Plaintext
present directive
link
URL
preformatted non-comment bytes
其他 protected structure

更不会自动变成:

Plaintext
所有 locale 都允许 identifier 后接任意 Unicode 字符

这其实是我现在修改 validator 时很重视的一点:

真实语言现象需要支持,但 exception 的作用范围要尽可能小。

不能因为韩语暴露了一个真实问题,就顺手修改一个全局正则,让中文、日文、法语、德语以及未来所有语言全部改变语义。


七、单靠“我觉得这样没问题”还不够,需要正反测试

规则修改以后,还专门增加了一组测试。

图 4:ko-KR identifier suffix 正向与反向测试全部通过
图 4:ko-KR identifier suffix 正向与反向测试全部通过

第一类测试证明正常韩语形式应该被接受。

例如:

Plaintext
v를
ch로
ch에서
v에
i를
c에서

这些场景中,原始 ASCII Go identifier 仍然完整存在。

第二类测试更加重要:

必须证明 validator 没有因为支持韩语而失去原来的保护能力。

所以测试专门包含:

Plaintext
ASCII_letters_appended
ASCII_digit_appended
ASCII_underscore_prefix
case_changed
identifier_missing
identifier_repeated
identifiers_reordered

也就是:

Plaintext
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 后,结果从:

Plaintext
validation_passed: 10
validation_failed: 3

变成:

Plaintext
validation_passed: 12
validation_failed: 1
图 5:调整 validator 后,两个 identifier failure 消失,但真实 protected structure failure 仍然存在
图 5:调整 validator 后,两个 identifier failure 消失,但真实 protected structure failure 仍然存在

这其实是整个案例里我觉得最重要的一步。

concurrency/2 通过了。

concurrency/6 也通过了。

说明这两个:

Plaintext
referenced Go identifier count mismatch

确实属于 validator 对韩语边界认识不足。

但是:

Plaintext
concurrency/4

仍然失败。

错误还是:

Plaintext
font span mismatch:
expected bold, actual program

也就是说:

validator 修正以后,真正的结构错误并没有一起消失。

这正是我希望看到的结果。


九、concurrency/4 就不能再怪 validator 了

concurrency/4 的 candidate 中有这样一个差异:

Plaintext
-*추가 참고:*
+*추가*참고:*

看起来只移动了一个 *

但对于 present 文本来说,这并不是普通标点变化。

*...* 本身带有 font span 语义。

于是:

Plaintext
*추가 참고:*

和:

Plaintext
*추가*참고:*

对应的结构并不相同。

原 validator 报:

Plaintext
expected bold
actual program

在这里是有意义的。

这种问题不能通过:

“韩语语法比较特殊。”

来解释。

也不能继续扩展 validator:

“那韩语的星号位置也灵活一点吧。”

因为一旦这么做,protected structure validation 就真的开始失去意义了。

所以这一个 failure 的正确处理路径是:

修改 candidate。


十、最后才真正达到 13/13

在修复 concurrency/4 candidate 后,再次验证:

Plaintext
validation_passed: 13
validation_failed: 0

整个 batch 才真正完成。

所以完整过程实际上是:

Plaintext
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

这个过程比:

Plaintext
失败
→ 重翻译三份
→ 再试

复杂一些。

但它留下的东西也完全不同。


十一、为什么还专门增加了 revalidate

这次还有一个流程上的小变化:

Plaintext
feat: 增加 retranslation revalidate 正式流程

原因也来自这个问题本身。

concurrency/2concurrency/6 的 candidate 并没有因为失败而变坏。

后来发生变化的是:

validator。

那么 validator 修正以后,最正确的动作应该是:

Plaintext
使用同一个 candidate
→ 用当前规则重新 validation

而不是:

Plaintext
重新调用模型
→ 生成一份可能完全不同的翻译

否则会出现一个很荒唐的情况:

原译文其实没问题,只因为 validator 修好了,却要求模型重新翻译一次。

不仅浪费额度,还会引入新的语言差异。

所以这里正式增加了:

Plaintext
revalidate

用于:

candidate 不变、validator 发生合法变化以后,对现有 candidate 重新执行 automatic validation。

与此同时,旧的 failure evidence 仍然保留在:

Plaintext
revalidation-history/

而不是被新的 PASS 直接覆盖得像从来没有失败过一样。

这一点对以后排查规则演进也很重要。


十二、Retry、Revalidate 和 Revision 其实是三种完全不同的动作

做到韩语以后,我越来越觉得,这三条路径需要特别明确。

Retry

适用于:

Plaintext
restore_failed
validation_failed

而且是真正需要重新生成或修复受保护 artifact 的场景。

例如 concurrency/4 的 font span 结构错误。

Revalidate

适用于:

candidate 没有变,但 validator 本身经过了有证据支持的修正。

像这次:

Plaintext
concurrency/2
concurrency/6

就是典型案例。

Revision

则完全不同。

如果 Quality Check 或 Final Review 判断:

Plaintext
语言不自然
含义偏差
术语错误
表达质量不够

那就不是 automatic validation 问题。

必须:

Plaintext
re-export
→ Codex 重译
→ process
→ validation
→ Quality Check
→ Final Review

也就是新的 revision batch。

不能修改 validator。

也不能用 retry 绕过语言质量 gate。

这三个概念看起来都像:

“上一轮没过,再来一次。”

但工程含义完全不同。


十三、Automatic validation 从来都不等于 Translation Quality

这也是这个案例另外一个值得记录的点。

Automatic validator 可以检查很多东西:

Plaintext
代码有没有改变
protected token 有没有丢
link 有没有损坏
directive 有没有变化
Go identifier 有没有被改写
present structure 是否保持

但它不能回答:

Plaintext
韩语自然吗?
含义准确吗?
术语统一吗?
教学表达适合韩语读者吗?

反过来也一样。

一个韩语句子非常自然,并不能证明:

Plaintext
Go identifier 没被改
链接没丢
代码没变

所以两种检查本来就是互补关系:

Plaintext
Automatic validation
→ 技术安全

Quality Check / Final Review
→ 语言质量

这次 validator 自己出现语言学边界,也并不意味着 automatic validation 不可靠。

恰恰相反。

正因为它把失败明确报出来,我才有证据继续分析:

是 translation 问题,还是 validator 问题?


十四、Validator 也需要被新的 locale 校准

以前设计 validator 时,很容易有一种默认想法:

只要规则足够严格,就越安全。

做了几门语言以后,我现在更倾向于:

严格应该针对真正需要保护的语义,而不是针对某一种语言的表面形式。

例如真正需要保护的是:

Plaintext
identifier v

那就应该保护:

Plaintext
它还是不是 ASCII 的 v
出现次数有没有变化
大小写有没有变化
顺序有没有变化
是否仍然引用正确代码

而不是保护:

v 后面必须是 ASCII 空格或英文标点。

后者其实只是英文书写方式带来的偶然形式。

如果未来另一门语言出现:

Plaintext
前缀黏着
后缀黏着
Unicode 标点
不同断词习惯

也可能再次暴露类似问题。

这时候不能简单认为:

新语言不符合 validator,所以新语言应该改。

更合理的问题应该是:

当前 validator 检查的到底是技术身份,还是无意中把源语言的书写习惯当成了技术规则?


十五、但“语言差异”也不能成为放宽规则的万能理由

这一点同样重要。

如果只强调:

validator 需要适配自然语言。

很容易走向另一个极端。

以后碰到任何 failure 都可以说:

可能是 locale 特殊情况。

然后不断加 exception。

最终 automatic validation 就会变成:

Plaintext
if zh-CN ...
if ja-JP ...
if de-DE ...
if fr-FR ...
if ko-KR ...

最后什么都能通过。

所以我现在给自己定下来的判断方式更接近:

第一步:确认 protected technical identity 有没有真正改变

如果改变了:

修改 candidate。

第二步:如果 identity 没变,确认 failure 是否来自目标语言正常语法

如果是:

才考虑 validator。

第三步:规则修改必须尽可能窄

例如这次只允许:

Plaintext
ko-KR
+
teaching comment body
+
完整 ASCII Go identifier
+
仅 Hangul suffix

第四步:必须增加反例测试

证明:

真正的技术损坏仍然过不了。

如果做不到这一步,我会更倾向于不修改 validator。


十六、我现在不再把 validator PASS 当成目标本身

这次还有一个思路上的变化。

以前看到:

Plaintext
validation_failed

很容易把目标设成:

我要想办法让它变成 PASS。

现在我觉得更准确的目标应该是:

我要先判断这个 FAIL 是否正确。

如果它是正确的:

Plaintext
保持 validator
修改 candidate

如果它是错误的:

Plaintext
保持 candidate
修改 validator
revalidate

而不是:

哪种方法最快让状态变绿,就用哪一种。

因为状态最终显示:

Plaintext
passed

只是结果。

真正重要的是:

为什么它应该通过。


十七、这个案例最终留下的,不只是一条韩语规则

表面上看,这次修复只是:

Plaintext
支持 ko-KR 注释标识符韩语后缀

但我觉得真正留下来的经验更通用。

第一:

validator 不是绝对真理。

它是实现出来的规则,同样可能带有设计时没有意识到的语言假设。

第二:

发现 validator 误判,不代表应该降低 validation 标准。

正确做法是缩小 exception,让真正技术约束继续存在。

第三:

candidate 和 validator 谁发生变化,要决定后续执行 retry、revalidate 还是 revision。

第四:

failure evidence 应该保留。

不能因为新规则让它通过以后,就把旧失败直接抹掉。

第五:

目标语言质量不能为了 machine validation 让路。

如果一种写法在目标语言中本来就是正常的,而技术 identity 又完全没有改变,就不应该仅仅为了让 tokenizer 更容易识别,强迫译文采用人为格式。


小结

ko-KR batch 004 第一次执行 validation 时:

Plaintext
13 units

10 passed
3 failed

其中:

Plaintext
concurrency/2
concurrency/6

表面上都是:

Plaintext
referenced Go identifier count mismatch

但真正原因是韩语可以把 Hangul 语法字符直接连接到 ASCII Go identifier 后面,而旧 validator 把整个 Unicode 字符串作为新的 identifier 处理。

于是:

Plaintext
修改 validator
→ 增加 ko-KR teaching comment 窄规则
→ 加入正反测试
→ revalidate

结果变成:

Plaintext
12 passed
1 failed

剩下的:

Plaintext
concurrency/4

是真正的:

Plaintext
font span mismatch

这个时候就不能再调整 validator。

而应该修改 candidate。

最后:

Plaintext
13 passed
0 failed

所以这次我真正记住的不是:

“韩语需要一个特殊 validator。”

而是:

当 validator 报错时,正确的问题不是“怎样让它通过”,而是“这一次,到底是谁错了?”

如果译文破坏了技术结构,就修译文。

如果 validator 把目标语言的正常语法误认为技术破坏,就修 validator。

如果语言质量不好,就回 revision。

三者看起来都会让当前流程变慢。

但只有把边界分清楚,automatic validation 才真正是在保护翻译,而不是反过来塑造翻译。

A Tour of Go de-DE 上线复盘:第三门语言用了约 16 小时,下一门如何压到 8 小时?

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 官方无隶属或授权关系。