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

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

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

作者:

, ,

Go Tour 中文版开发实战

图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 结构问题

最近,我继续推进 go-tour-i18n 项目的简体中文翻译。

这个项目的目标并不只是把 A Tour of Go 的英文正文替换成中文,而是建立一套可以长期维护、同步官方上游、扩展到多种语言,并且能够自动校验和发布的完整流程。

目前,项目将浏览器左侧显示的一个完整课程页面,也就是一个顶层 present.Section,作为最小翻译单元。每次由 GLM-5.2 翻译一个完整页面,再经过结构比较、语法解析、术语检查和实际页面预览。

在完成 Welcome 章节和 Basics 前三个页面以后,我没有继续按课程顺序直接翻译 basics/4,而是先从不同章节选择了六个代表页面,用于进一步校准翻译流程。

这次首先处理的是:

Plaintext
generics/1 — Type parameters

最终,这个看起来并不复杂的泛型入门页面,前后经历了五次开发模式 attempt,才完成翻译、校验和浏览器预览。

整个过程暴露的问题,比页面译文本身更有价值。

一、为什么首先选择 generics/1

generics/1 的英文标题是:

Plaintext
Type parameters

页面主要介绍:

  • Go 函数如何使用类型参数处理多种类型;
  • 类型参数在函数声明中的位置;
  • T 与内置约束 comparable 的关系;
  • comparable 为什么允许使用 ==!=
  • 泛型 Index 函数如何适用于所有支持比较的类型。

这个页面长度适中,结构也比较常规:

  • 一个标题;
  • 三段正文;
  • 一段预格式化代码;
  • 多个 legacy present 行内代码;
  • 一个 .play 指令。

它没有图片、链接或复杂练习结构,因此比较适合作为新一轮校准的起点。出现问题时,也更容易判断问题来自模型翻译、提示词、结构保护,还是 validator。

二、第一次尝试:不是翻译失败,而是网络权限失败

第一次执行开发模式翻译时,GLM-5.2 API 根本没有被成功调用。

错误信息为:

Plaintext
dial tcp: lookup open.bigmodel.cn on 127.0.0.53:53:
dial udp 127.0.0.53:53: socket: operation not permitted

这是 Codex 受限沙箱禁止网络访问导致的基础设施失败。

项目仍然完整保存了这次 attempt 的:

  • request.json
  • response.json
  • validation.json

页面状态继续保持为 pending

这次记录没有被删除或覆盖。开发阶段的目的就是暴露真实问题,因此即使没有产生模型输出,这次 attempt 仍然具有审计价值。

后续执行翻译命令时,直接使用具备外部网络访问权限的环境,避免先在受限沙箱中失败一次。

三、第二次尝试:模型重复输出了一个保护 Token

第二次调用 GLM-5.2 成功,HTTP 状态为 200,模型也正常返回了完整中文内容。

但 protected token 校验失败。

英文原文中有这样一句:

Plaintext
This declaration means that `s` is a slice of any type `T` that fulfills the
built-in constraint `comparable`.

保护后的 T 对应一个唯一 token。模型为了让中文更自然,将句子改写为:

Plaintext
任意类型 `T` 的切片,且 `T` 满足内置约束 `comparable`

问题在于,英文源文中的 `T` 只出现了一次,而模型在中文中把它使用了两次。

因此,预期 10 个 token,模型实际输出了 11 个。代表 `T` 的 token 出现了两次。

从人类阅读角度看,这句话的技术含义没有错;但对自动恢复流程来说,同一个唯一占位符不能复制。否则恢复后可能残留 token,也无法确定多出来的位置是否安全。

validator 因此拒绝了这次输出。

四、强化保护 Token 提示词

分析后确认,原来的 system prompt 已经包含英文规则:

Plaintext
Preserve every protection token exactly once and in order.

也就是每个 token 必须恰好出现一次,并保持顺序。

不过,对 GLM-5.2 来说,这条规则仍然不够醒目。尤其是在模型为了自然中文补充显式主语时,容易把 token 当成可以重复引用的技术术语,而不是唯一占位符。

因此,我在 user prompt 中增加了更明确的中文规则:

Plaintext
重要:下文每个形如 ⟪GTI18N_...⟫ 的保护 token 都是唯一占位符。
本页共有 10 个保护 token,输出中也必须恰好包含 10 个。
每个 token 必须原样输出且恰好输出一次,并严格保持输入顺序。
不得复制、不得复用、不得删除、不得改写或交换任何 token。
调整中文语序时,请使用“该类型”“该值”“前者”等普通中文承接,不要再次输出已有 token。

其中 token 数量不是硬编码,而是根据当前页面实际生成的 token map 动态写入。

五、第三次尝试:Token 全部正确,但 Present 结构仍然失败

第三次尝试中,10 个 token 全部:

  • 数量正确;
  • 每个只出现一次;
  • 没有未知 token;
  • 顺序完全正确。

前一次的 token 重复问题已经解决。

但是,恢复后的中文出现了:

Plaintext
`comparable`。`x`

validator 报告:

Plaintext
inline code mismatch at index 3:
expected "`comparable`",
actual "`comparable`。`x`"

这看起来只是中文句号后少了一个空格,但实际上涉及 legacy present 的行内代码解析规则。

六、Legacy Present 为什么依赖空格

A Tour of Go 当前使用的 legacy present 字体语法,并不是简单地把每一对反引号都识别为独立行内代码。

它会先按照 Unicode 空白切分 word,然后在每个 word 中识别字体 span。

英文原文是:

Plaintext
`comparable`. `x`

句号后有一个 ASCII 空格,因此会被切分为两个 word,最终得到两个独立代码单元:

Plaintext
`comparable`
`x`

模型翻译为中文以后,按照普通中文排版习惯删除了句号后的空格:

Plaintext
`comparable`。`x`

这样整段内容落在同一个非空白 word 中。present 会把第一个反引号到最后一个反引号识别为一个整体,中文句号也被包含进去。

类似问题还可能出现在:

Plaintext
`s`是`T`
`==`和`!=`
`f`、`x`、`y`

这些写法在人类看来没有问题,但对 legacy present 来说,多个行内代码可能被合并为一个 span。

更麻烦的是,项目中还存在这样的特殊写法:

Plaintext
`package`rand`

它本来就应该作为一个完整代码单元处理,语义内容为:

Plaintext
package rand

因此,不能在 token 恢复后简单扫描反引号并自动拆分,否则会破坏官方 present 的既有语义。

七、增加元数据驱动的边界规范化

最终采用的方案是:

在严格 token 校验全部通过以后、恢复 token 以前,根据 token 类型,对独立 inline-code token 的外部边界进行确定性规范化。

项目现在会为 protected token 记录类型,例如:

  • inline code;
  • directive;
  • link target;
  • glossary 或 keep word;
  • 其他保护类型。

规范化逻辑只处理完整 inline-code token 的外部普通文本,不检查,也不修改 token 内部的反引号。

例如,模型输出:

Plaintext
TOKEN_COMPARABLE。TOKEN_X

会被规范化为:

Plaintext
TOKEN_COMPARABLE。 TOKEN_X

恢复后成为:

Plaintext
`comparable`。 `x`

对于:

Plaintext
TOKEN_S是TOKEN_T

则会规范化为:

Plaintext
TOKEN_S 是 TOKEN_T

恢复后成为:

Plaintext
`s` 是 `T`

而下面这种原本就是单一 legacy span 的内容:

Plaintext
`package`rand`

始终只对应一个 token,内部内容完全不变。

需要特别说明的是,规范化不会绕过严格 token 校验。

处理顺序仍然是:

  1. 检查 token 总数;
  2. 检查未知 token;
  3. 检查每个 token 是否恰好出现一次;
  4. 检查 token 顺序;
  5. 全部通过后才进行边界规范化;
  6. 恢复 token;
  7. 继续执行 present 和结构校验。

缺失、重复、未知或乱序的 token,不会被自动“修复”。

八、第四次尝试:模型为了自然中文交换了 Token 顺序

第四次尝试中,10 个 token 全部存在,每个也只出现一次,但 `T``comparable` 对应的 token 被交换了。

英文顺序是:

Plaintext
`T` → `comparable`

模型翻译成:

Plaintext
满足内置约束 `comparable` 的任意类型 `T`

因此实际顺序变成:

Plaintext
`comparable` → `T`

这段中文自然,技术含义也基本正确,但违反了当前全局严格顺序要求。

当时我进一步检查提示词,才发现一直被简称为“中文 system prompt”的提示词,实际上是:

英文核心规则 + 中文详细翻译规则

user prompt 和 glossary 控制说明中也保留了一些英文标签,例如:

Plaintext
Mandatory glossary rules:
Complete protected page:

这种中英混合并不是经过对照测试后刻意设计的,而是项目逐步开发时形成的历史结果:

  1. 最初使用英文建立最小 API 契约;
  2. 后来为了改善中文质量,追加中文详细要求;
  3. 再根据真实失败,追加中文 token 规则。

九、将固定提示词统一为简体中文

为了减少变量,我没有立即放宽 token 顺序,也没有加入针对 Tcomparable 的页面专用示例。

这次只做了一项修改:

将固定 system prompt、user prompt 标签、glossary 控制说明和前次失败提示统一为简体中文。

新的 system prompt 开头为:

Plaintext
请将一个完整的《Go 语言之旅》present.Section 从英文翻译为中国大陆简体中文。

只返回完整且可由 present 解析的 .article 内容。必须保留每个保护 token,使其原样出现、恰好出现一次,并严格保持输入顺序。必须使用术语表中的强制译法;对应的、应当翻译的英文显示文本不得残留;不得简化、遗漏或改变原文含义。

user prompt 标签也改为:

Plaintext
强制术语表与译法规则:
需要翻译的完整受保护页面:

术语表中的英文 source、present.Section.article、token、directive、link target、代码和路径仍然保留,因为这些属于技术标识或源数据,不需要为了形式上的“全中文”强行翻译。

十、第五次尝试终于成功

第五次尝试只调用了一次 API,没有重试,也没有人工修改模型输出。

结果为:

  • HTTP 200;
  • 10 个 token 全部存在;
  • 没有缺失、重复或未知 token;
  • token 顺序完全正确;
  • `T` 位于 `comparable` 之前;
  • inline-code 边界规范化实际修复了一处;
  • present 解析通过;
  • Section 结构通过;
  • directive 通过;
  • 行内代码通过;
  • 预格式化代码通过;
  • glossary 检查通过;
  • candidate 成功生成;
  • 页面进入 ready

这次模型仍然输出了:

Plaintext
TOKEN_COMPARABLE。TOKEN_X

边界规范化自动将其调整为:

Plaintext
TOKEN_COMPARABLE。 TOKEN_X

最终恢复为:

Plaintext
`comparable`。 `x`

这证明新增的边界规范化不只是单元测试通过,也在真实 GLM-5.2 输出中发挥了作用。

不过,一次成功并不能证明固定提示词中文化已经彻底消除了 token 换序问题。当前仍然保留严格顺序策略,后续还需要在其他代表页面中继续观察。

十一、人工润色后的最终译文

模型 candidate 通过自动校验以后,我又进行了少量人工润色。

最终页面内容为:

Plaintext
* 类型参数

Go 函数可以通过类型参数处理多种类型。函数的类型参数写在参数列表之前的方括号中。

  func Index[T comparable](s []T, x T) int

该声明表示,`s` 是元素类型为 `T` 的切片,而该类型满足内置约束 `comparable`。 `x` 也是该类型的值。

`comparable` 是一个很有用的约束,允许对该类型的值使用 `==` 和 `!=` 运算符。在本例中,我们用它将一个值与切片中的各个元素进行比较,直到找到匹配项。`Index` 函数适用于任何支持比较的类型。

.play generics/index.go

其中:

Plaintext
`comparable`。 `x`

中文句号后的 ASCII 空格不能删除。它不是普通排版错误,而是 legacy present 区分两个独立行内代码 span 所需的结构性空白。

润色后重新执行 candidate validator,完整自动校验继续通过。

十二、同步沉淀泛型术语

这次还在 zh-CN glossary 中新增了三个 mandatory 术语:

Plaintext
constraint: 约束
type parameter: 类型参数
type parameters: 类型参数

当前 glossary 匹配不进行单复数归一化,因此:

Plaintext
type parameter
type parameters

需要分别记录。

没有加入:

  • built-in constraint
  • comparable
  • supports comparison
  • slice element
  • generic function

其中,comparable 是 Go 的预声明约束名称,在页面中以代码形式保留;其余内容可以由模型结合上下文自然翻译,没有必要把普通句式全部写入术语表。

十三、实际浏览器预览

自动校验通过后,我又启动了 generics/1 的本地预览。

实际访问地址为:

Plaintext
http://127.0.0.1:3999/tour/generics/1

预览使用 /tmp 中的临时 Tour 内容副本,只替换目标中文 Section,不修改仓库正式 _content

页面请求返回 HTTP 200,右侧 index.go 代码正常加载,静态资源和模板没有报错。

实际 HTML 中,关键内容为:

HTML
该声明表示,<code>s</code> 是元素类型为 <code>T</code> 的切片,而该类型满足内置约束 <code>comparable</code><code>x</code> 也是该类型的值。

可以确认:

  • comparablex 是两个独立 <code> 元素;
  • 中文句号没有进入代码字体;
  • 页面中没有残留 protected token;
  • 函数声明和右侧示例完整;
  • 浏览器实际显示正常。
图 1:generics/1 简体中文页面本地预览
图 1:generics/1 简体中文页面本地预览

十四、最终提交

本轮改动最终以一个提交完成:

Plaintext
8f9f001 feat: 完成 generics/1 翻译校准并增强结构保护

提交内容包括:

  • 固定翻译提示词中文化;
  • protected token 唯一性和动态数量规则;
  • token kind 元数据;
  • legacy inline-code 外部边界规范化;
  • 相应单元测试;
  • generics/1 五次 attempt 记录;
  • 人工润色后的 candidate;
  • 泛型 glossary;
  • 状态测试;
  • PROJECT_STATE.md 更新。

提交统计为:

Plaintext
25 个文件,724 行新增,36 行删除

提交已经推送到 GitHub 的 main 分支,工作树保持干净。

十五、当前项目状态

截至 2026 年 8 月 5 日:

  • 正式发布页面:103;
  • ready:9;
  • pending:94;
  • blocked:0;
  • 另有 2 条单独保留的 #appengine: 条件源审计记录。

目前完成的页面包括:

Plaintext
welcome/1~welcome/5
basics/1~basics/3
generics/1

这次只完成了代表页面校准的第一页,还不能说明项目已经具备稳定的批量翻译能力。

后续代表页还包括:

  • flowcontrol/8
  • methods/16
  • methods/20
  • concurrency/7
  • concurrency/10

这些页面将继续覆盖:

  • 长篇练习说明;
  • 数学表达;
  • methods 与 interfaces;
  • 特殊 legacy present 语法;
  • .image
  • concurrency。

只有这些代表页稳定以后,才会开始试跑一批普通 pending 页面。

十六、这次校准带来的几点认识

这次最重要的收获,并不是终于翻译完了一个泛型页面,而是进一步明确了自动翻译系统中几种不同问题的边界。

第一,人类可读的正确译文,不一定是结构上可发布的译文。

重复一次 T 或交换 Tcomparable,对人类来说可能完全合理,但会破坏确定性恢复和结构签名。

第二,技术文档中的空格可能承担语法作用。

中文排版通常会删除行内代码附近的空格,但 legacy present 恰恰依赖空白划分代码单元。这里的空格不是审美选择,而是结构的一部分。

第三,提示词只能降低错误概率,不能替代确定性校验。

即使提示词已经明确要求 token 不得复制或交换,模型仍然可能为了自然语言改写而违反约束。因此,token 检查、present 解析和结构比较仍然不可缺少。

第四,自动修复必须建立在明确的结构元数据之上。

这次的 inline-code 空格规范化之所以安全,是因为系统知道哪些 token 代表完整行内代码。它不会在恢复后的普通字符串中猜测反引号结构,也不会误伤 `package`rand` 这样的 legacy 写法。

第五,一次成功只能证明当前样本通过。

attempt-005 成功以后,仍然需要在其他章节和结构页面中继续观察。现在还不应该为了单次成功就放宽 validator,或者宣布流程已经稳定。

结语

最初选择 generics/1,只是因为它篇幅适中、结构常规,适合作为泛型章节的校准起点。

实际推进以后,这一个页面却连续暴露了网络权限、token 重复、legacy present 空白边界、token 换序和提示词语言不统一等问题。

从结果来看,这五次 attempt 并不是简单的重复消耗。每一次失败都推动流程补上了一个此前没有被真实页面验证过的环节。

现在,generics/1 已经完成翻译、人工润色、自动校验、术语沉淀和浏览器预览,但整个项目仍处于代表页面校准阶段。

接下来是否能够进入普通页面试跑和批量翻译,还要看后续几类高风险页面能否继续稳定通过同一套流程。

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

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan

评论

发表回复

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

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