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

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

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

作者:

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 中文版补齐正式投影与课程元数据本地化

A Tour of Go 多语言翻译项目的简体中文课程正文,终于进入了一个非常明确的阶段:

Plaintext
ready=103
pending=0
blocked=0

103 个正式课程页面全部拥有 canonical candidate,也都已经通过前面建立起来的 present 解析、结构比较、页面验证等自动校验。

原本我以为,接下来已经可以直接进入正式发布。

但真正开始做“发布前完整预览”之后,我才发现还有两层之前没有暴露出来的问题:

  1. 仓库虽然支持单页 candidate 预览,却根本没有“将 103 个 ready 页面重新组装成完整中文 Tour”的正式投影能力;
  2. 即使完整投影和 103/103 HTTP 验收全部通过,浏览器里仍然残留了 7 个英文课程标题——进一步排查后才发现,还有同层的 7 个英文副标题没有进入原来的翻译模型。

最终,这一轮不仅补齐了完整语言正式投影,还把课程正文、课程元数据和公共 UI 三层本地化真正拆分清楚了。


一、103 个页面全部 ready,并不代表已经可以发布

前面的翻译阶段完成以后,当前 zh-CN 状态已经是:

Plaintext
ready=103
pending=0
blocked=0

catalog 也已经固定为:

Plaintext
103 published pages
2 conditional source records

103 个 page_id 与冻结 catalog 完全一致,没有缺失、额外或重复页面。

这些页面覆盖 7 个 .article

Plaintext
welcome.article       5
basics.article       17
flowcontrol.article  14
moretypes.article    27
methods.article      26
generics.article      3
concurrency.article  11

合计正好 103 页。

但这时出现了一个很现实的问题:

如何证明这 103 个独立 candidate 最终能够重新组成一个真正可运行的完整中文 A Tour of Go?

此前仓库中的预览命令实际上只有单页模式:

Bash
go run -mod=readonly ./cmd/tour-i18n preview \
    --locale zh-CN \
    --id methods/17

它会复制官方 Tour 内容,然后只替换指定页面所属的 Section。

这个能力非常适合翻译开发阶段,但不能拿来代替正式发布前验收。

因为正式发布时面对的是:

103 个页面同时进入 7 个 .article 以后,整个站点是否仍然正确?


二、第一次准备完整发布预览时,直接发现能力缺口

我原本让 Codex 做一次“103 页完整 zh-CN 正式发布投影预览”。

结果它没有强行拼一个临时方案,而是在检查仓库现状后直接停止了。

原因很明确:

当前仓库根本不存在完整正式投影实现。

没有 build,也没有完整语言 preview

现有的 BuildCandidatePreview 强制要求单个 page_id,只负责替换一个 Section。

这反而让我确认了一件事:

不能因为 103 个 candidate 分别通过校验,就直接假设最终发布产物也一定正确。

于是这一轮首先补的不是 publish,而是它前面更基础的一层:

完整语言正式投影。


三、新增 BuildLocaleProjection:一次生成完整语言站点

这次新增了统一的 BuildLocaleProjection

它的正式输入不是某个开发 attempt,而是:

  • catalog;
  • locale status;
  • canonical ready candidate;
  • upstream Tour 内容。

构建时会严格要求:

  • 正式页面全部为 ready
  • 每一页都有 canonical candidate;
  • candidate 文件实际存在;
  • page_id 集合与 catalog 完全一致;
  • 不允许 pending;
  • 不允许 blocked;
  • 不允许缺页;
  • 不允许多页;
  • 不允许偷偷退回 upstream 英文内容。

103 个页面会先按 .article 聚合,再统一替换。

这样也避免了同一个 article 中多次替换 Section 时,后一次写入覆盖前一次结果的问题。

与此同时,原来的单页预览与新的完整投影也尽量复用了同一套:

  • Section 定位;
  • candidate 加载;
  • welcome 特殊投影;
  • present 解析;
  • 结构校验。

没有为了正式发布再复制一套新的拼装逻辑。


四、完整 build 和完整 preview 都补上了

现在仓库已经支持完整语言构建:

Bash
go run -mod=readonly ./cmd/tour-i18n build \
    --locale zh-CN

也支持完整语言预览:

Bash
go run -mod=readonly ./cmd/tour-i18n preview \
    --locale zh-CN

原来的单页模式仍然保留:

Bash
go run -mod=readonly ./cmd/tour-i18n preview \
    --locale zh-CN \
    --id methods/17

也就是说现在两种用途已经分开:

  • --id:翻译开发阶段的单页 candidate preview;
  • --id:全部 canonical ready candidate 组成的完整语言预览。

真实构建结果为:

Plaintext
locale=zh-CN
ready=103
pending=0
blocked=0
pages=103
articles=7
图 1:完整 A Tour of Go 简体中文预览站点,课程页面、课程元数据和公共 UI 均已完成本地化
图 1:完整 A Tour of Go 简体中文预览站点,课程页面、课程元数据和公共 UI 均已完成本地化

这张完整预览与以前的单页 preview 最大的区别在于:

此时浏览器看到的是 103 个 canonical ready candidate 共同组成的站点,而不是只替换了当前页面。


五、103 个页面重新组成了 7 个 .article

完整构建以后,最终得到:

Plaintext
ready=103
pending=0
blocked=0
pages=103
articles=7

生成内容位于独立临时投影目录,不修改 candidate、status 或正式仓库内容。

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

构建完成以后还会重新使用 present 解析最终 .article

这一步很重要。

以前是:

candidate 自己是否合法?

现在进一步变成:

candidate 被重新装回最终 article 以后,整个文档是否仍然合法?

包括此前已经处理过的一些特殊结构,也全部重新进入最终投影:

  • welcome/1 的远程服务器分支;
  • welcome/4welcome/5#appengine: 条件 Section;
  • .play
  • directive;
  • 行内代码;
  • 链接;
  • 静态预格式化代码;
  • emphasis / font span;
  • Section 拓扑。

其中最终的 welcome.article 中也已经不存在应该去掉的 #appengine: 前缀。


六、不能只验证 build,还要真的访问全部 103 个页面

完整投影生成成功以后,我又做了一次 HTTP 页面级全量验收。

不是抽几个代表页面,而是直接从:

Plaintext
data/tour-pages.tsv

读取全部正式 page_id,逐一请求:

Plaintext
http://127.0.0.1:3999/tour/<page_id>

最终结果:

Plaintext
total=103
passed=103
failed=0

另外几个此前比较特殊或有代表性的页面也再次单独检查:

Plaintext
welcome/1     PASS
welcome/4     PASS
welcome/5     PASS
moretypes/18  PASS
methods/17    PASS
图 3:完整 zh-CN 投影的 103 个课程页面全部通过 HTTP 页面级验收,103 成功、0 失败
图 3:完整 zh-CN 投影的 103 个课程页面全部通过 HTTP 页面级验收,103 成功、0 失败

公共 UI 同样正常:

HTML
<html lang="zh-CN">

浏览器标题为:

Plaintext
Go 语言之旅

公共 UI 中也能看到:

Plaintext
切换主题

CSS、JavaScript、图片等代表性静态资源全部返回 HTTP 200。

到这里为止,我一度认为:

103/103 已经可以作为最终完整预览验收结果。

结果下一步准备博客截图时,又发现了一个之前自动验收完全没有覆盖到的问题。


七、准备截图时才发现:导航里怎么还有英文?

我打开:

Plaintext
http://127.0.0.1:3999/tour/methods/17

准备截一张完整中文版的浏览器效果图。

结果看了一眼右侧导航,就发现很不对劲。

页面正文已经是中文,公共 UI 也是中文,大量 Section 标题也已经完成翻译。

但课程导航中仍然能看到:

Plaintext
Welcome!
Packages, variables, and functions.
Flow control statements: for, if, else, switch and defer
More types: structs, slices, and maps.
Methods and interfaces
Generics
Concurrency
图 4:首次完整预览时,课程导航仍残留 7 个 upstream 英文标题
图 4:首次完整预览时,课程导航仍残留 7 个 upstream 英文标题

最开始我还以为只是“基础”下面几个标题漏翻。

继续往下滚以后才发现:

GenericsConcurrency 也仍然是英文。

这时出现了一个非常明显的规律:

刚好 7 条,而且当前也刚好有 7 个 .article

这已经不像偶然漏翻,更像是翻译数据模型漏掉了一整层。


八、根因不是 Section,而是 present.Doc.Title

进一步检查后确认,这 7 条文字分别就是 7 个 .article 的第一行。

例如:

Plaintext
welcome.article
Welcome!
Plaintext
basics.article
Packages, variables, and functions.
Plaintext
methods.article
Methods and interfaces

它们在 present 数据结构中不是 present.Section,而是:

Plaintext
present.Doc.Title

Tour 服务读取完整 .article 以后,会映射为:

Plaintext
doc.Title

lesson.Title

而 103 个现有 page_id 对应的是:

Plaintext
doc.Sections

lesson.Pages[]

换句话说:

103 个 candidate 只覆盖了顶层 present.Section,从来没有覆盖 .article 根级 metadata。

这正好解释了为什么此前所有流程都不会报错:

Plaintext
candidate validate

只检查 Section

BuildLocaleProjection

只替换 catalog 中的 Section

HTTP 103/103

只证明 103 个页面能正常路由、渲染

没有任何一层要求:

Plaintext
present.Doc.Title

必须已经本地化。


九、继续排查后发现,还不只是 7 个 Title

问题其实比截图中看到的还多一层。

每一个 .article 除了:

Plaintext
present.Doc.Title

还有:

Plaintext
present.Doc.Subtitle

Tour 中它最终会成为:

Plaintext
lesson.Description

当前 7 个 .article 也分别保留着一条 upstream 英文 Subtitle。

例如 basics

Plaintext
Learn the basic components of any Go program.

generics

Plaintext
Go supports generic programming using type parameters.
This lesson shows some examples for employing generics in your code.

因此真正遗漏的不是 7 条,而是:

Plaintext
7 个 Doc.Title
+
7 个 Doc.Subtitle
=
14 条根级课程 metadata

这说明只修导航中肉眼看到的 7 个标题还不够。


十、没有把这 14 条硬塞进 103 个页面

这里我最终没有修改:

Plaintext
ready=103
pending=0
blocked=0

因为这个状态本身没有错。

103 代表的就是:

103 个正式 present.Section 的 candidate 状态。

如果为了补 7 个 .article metadata,突然把项目改成“110 页”,反而会破坏原有模型。

最终我把现在的多语言内容明确拆成了三层:

Plaintext
课程页面
103 / 103 ready

课程元数据
7 / 7 localized
title    7 / 7
subtitle 7 / 7

公共 UI
localized

这样以后再增加其他语言时,每一种资源的职责都比较清晰。


十一、新增独立的 article metadata 本地化资源

最终新增了:

Plaintext
locales/zh-CN/article-metadata.json

每一个 article 独立维护:

Plaintext
article
title
subtitle

当前 zh-CN 的 7 个课程标题为:

  • 欢迎!
  • 包、变量和函数
  • 流程控制语句:for、if、else、switch 和 defer
  • 更多类型:结构体、切片和映射
  • 方法和接口
  • 泛型
  • 并发

同时也补齐了对应的 7 个中文 Subtitle。

例如:

basics

学习任意 Go 程序的基本组成部分。

moretypes

学习如何基于现有类型定义新类型:本课涵盖结构体、数组、切片和映射。

generics

Go 通过类型参数支持泛型编程。本课展示如何在代码中使用泛型。

concurrency

Go 在语言层面提供并发构造。本课介绍这些构造及其使用方式。


十二、metadata 同样采用“缺失即失败”

这层虽然数据量不大,但没有采用“找不到就继续显示英文”的策略。

正式 article 集合由 catalog 动态推导。

构建时严格要求:

Plaintext
正式 article 集合
==
locale metadata article 集合

下面任何一种情况都会直接导致完整 build 失败:

  • 缺少 article;
  • 出现额外 article;
  • article 重复;
  • title 缺失;
  • title 为空;
  • subtitle 缺失;
  • subtitle 为空;
  • metadata 文件不存在;
  • metadata JSON 无法解析。

也就是说:

没有 upstream 英文 fallback。

否则以后新增一种语言时,很容易再次出现:

Plaintext
103 个页面全部通过
但课程导航悄悄混入英文

这种“技术上成功,视觉上却是半成品”的状态。


十三、单页 preview 也同步修复

这次没有只处理完整 build。

原来的:

Bash
go run -mod=readonly ./cmd/tour-i18n preview \
    --locale zh-CN \
    --id methods/17

同样会应用 article metadata。

这样就不会出现:

  • 完整 preview 显示中文 lesson title;
  • 单页 preview 却仍然显示英文 lesson title。

完整 projection 和单页 preview 共用同一个 metadata 应用入口。


十四、修复后再次完整验收

article metadata 修复以后,再次执行:

Bash
go test ./...

通过。

catalog:

Plaintext
103 published pages
2 conditional source records

status:

Plaintext
103 pages for zh-CN

完整 build:

Plaintext
ready=103
pending=0
blocked=0
pages=103
articles=7

metadata:

Plaintext
article metadata localized=7/7
title localized=7/7
subtitle localized=7/7

浏览器重新打开 methods/17 后,原来看到的:

Plaintext
Welcome!
Packages, variables, and functions.
Methods and interfaces
Generics
Concurrency

已经分别变成:

Plaintext
欢迎!
包、变量和函数
方法和接口
泛型
并发

至此,课程正文、课程元数据和公共 UI 三个层面的 zh-CN 本地化才真正闭合起来。


十五、这次最值得记录的,不只是 103/103

这一轮最初的目标其实很简单:

给 103 个已经 ready 的页面做一次正式发布前完整预览。

但真正执行以后连续发现了两个“最后一公里”问题。

第一个是:

根本还没有完整语言正式投影。

第二个是:

103 个 Section 全部正确,并不等于整个 .article 的根级 metadata 也已经翻译。

尤其第二个问题,是在:

Plaintext
HTTP total=103
passed=103
failed=0

已经出现以后,才通过人工浏览器截图发现的。

这也再次说明:

自动化校验应该尽可能覆盖结构,但最终真实产物仍然值得做一次人工视觉检查。

人工检查不需要重新逐页校对 103 页译文。

但它非常适合发现:

  • 导航层漏翻;
  • 元数据漏层;
  • 页面整体布局;
  • 公共 UI;
  • 真实站点拼装后才会出现的问题。

十六、本轮两个提交

完整语言正式投影与预览完成后提交:

Plaintext
eb3d817 feat: 增加完整语言正式投影与预览

随后补齐 article/lesson metadata:

Plaintext
22e0f6a feat: 增加课程元数据多语言本地化

两个提交都已经推送至 main,最终工作区保持干净。


十七、当前真正的项目状态

到这一阶段,简体中文已经可以明确拆成三部分:

Plaintext
Section candidate
103 / 103 ready

Article metadata
7 / 7 localized
title    7 / 7
subtitle 7 / 7

Public UI
localized

完整语言投影:

Plaintext
pages=103
articles=7

HTTP 页面级验收:

Plaintext
total=103
passed=103
failed=0

所以现在已经不再只是:

“103 页翻译完成”。

而是已经证明:

103 个正式页面能够通过统一正式投影重新组成完整 zh-CN A Tour of Go,并以真实 Tour 服务完整运行。

不过,还有最后一个非常明确的边界:

production publish 目前仍未实现。

下一阶段将不再处理翻译 candidate,也不再处理完整站点拼装,而是真正进入:

  • 发布产物结构;
  • 持久化发布目录;
  • 原子发布;
  • 失败恢复;
  • upstream / locale 发布元数据;
  • 生产环境 /socket 安全边界;
  • 发布后验收。

这部分我准备单独继续,不再塞进这一篇。


结语

做到 ready=103 的时候,我原本已经觉得简体中文翻译阶段基本结束了。

但这次完整发布预览再次证明:

“所有翻译单元都完成”与“最终产品已经完整”其实是两件不同的事。

Section candidate、article metadata、公共 UI,本质上是三个不同层级。

只有把最终真实站点构建出来,才能发现其中有没有哪一层根本没有进入翻译模型。

比较巧的是,这次最后一个明显问题并不是测试发现的,而是为了写博客准备截图时,我自己在浏览器里看到的。

也正因为这一眼,最终又补上了:

Plaintext
present.Doc.Title
present.Doc.Subtitle

这一整层多语言资源。

现在,103 个课程页面、7 个课程元数据以及公共 UI 都已经进入统一的 zh-CN 完整投影。

接下来,终于可以真正开始考虑:

如何把已经验证过的完整投影安全地变成正式发布产物。

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

A Tour of Go 多语言翻译项目

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

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

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

评论

发表回复

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

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