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

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

作者:

在

,

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 韩语翻译实战

图 2:法语 A Tour of Go 仓库目前已经归档,页面仍保留 553 Commits 和 61 Contributors 的历史

(60) 从法语 553 次提交到韩语两代离线:A Tour of Go 社区翻译为什么难以长期维护

【图 4:es-ES 首次 Production 收口后连续完成的流程优化 commit】

(61) 从 es-ES 首次上线复盘:我把 A Tour of Go 新增语言的 Production 流程又收敛了一轮

图 1:旧公网验收链路连续出现 curl 28 超时,最终导致 public-machine 阶段失败

(62) A Tour of Go Production 公网验收踩坑:从跨境 SOCKS 链路改为 zgocloud direct

图 1:两次 Production Machine Acceptance 都已经通过,但 Headless Chrome 分别在 / 和 /tour/ 判定页面没有完成渲染

(63) 页面明明能打开,为什么 Headless Chrome 仍然判定 Production 失败?

图 3:Bing 从 Google Search Console 中识别到新的 it-IT 站点,并同时发现已有的 1 个 sitemap

(64) GSC → Bing Webmaster Tools:多语言站点如何减少重复验证

图 3:英文站 3 小时内出现 565 个 Cloudflare 来源 URL

(65) IndexNow 实战:手工提交长期不显示,Cloudflare Crawler Hints 却很快出现在 Bing Webmaster Tools

【图 1:A Tour of Go 项目首页目前的语言版本列表】

(66) 从 nl-NL 到 tr-TR:A Tour of Go 连续上线三门语言后,我又优化了哪些流程

【图 4:Go local 页面中已经出现 French、German、Korean、Simplified Chinese】

(67) 从邮件无人回复到进入 Go 官方 Go local:我的 4 个 A Tour of Go 翻译站终于被收录

图8:最终只保留一把启用的新 API Key

(68) 腾讯云 EdgeOne API 自动化:从 CAM 子用户到最小权限 API Key 的完整配置

【图 7:Production Release Batch 最终汇总】

(69) 从同步 Go 官方上游到一个命令发布 10 个语言站:A Tour of Go Production 流程的再次收敛

【图 3:Cloudflare Smart Tiered Cache 已启用】

(70) Cloudflare 缓存 HIT 很快,MISS 却不稳定:回源阿里云杭州的一次真实优化

【图 3:简体中文站 Support 页面】

(71) 广告之外,社区项目如何长期活下去?我给 A Tour of Go 多语言项目加了 Support

图5:评论成功发布

(72) 从 Go Weekly 到 Reddit、DEV 和 Go Forum:第一次系统推广 A Tour of Go 多语言项目

【图 4:RTL 桌面课程布局修复后的效果】

(73) 第一次把 A Tour of Go 做成 RTL:阿拉伯语版本上线,以及我踩过的几个坑

多语言项目应该先做哪些语言?我的 66 个语言版本商业优先级排序

(74) 多语言项目应该先做哪些语言?我的 66 个语言版本商业优先级排序

当术语一致性遇到性能瓶颈:Go 多语言翻译项目的一次架构取舍

(75) 当术语一致性遇到性能瓶颈:Go 多语言翻译项目的一次架构取舍

从 A Tour of Go 走向 Go Learning & Documentation:为什么这个多语言项目需要 v2

(76) 从 A Tour of Go 走向 Go Learning & Documentation:为什么这个多语言项目需要 v2

Markdown 明明可以拆成很多段,为什么 v2 仍然坚持一个 Page 就是一个 TranslationUnit

(77) Markdown 明明可以拆成很多段,为什么 v2 仍然坚持一个 Page 就是一个 TranslationUnit

Tour、Learn、Docs 为什么不应该各维护一份术语表:v2 的统一 Glossary 设计

(78) Tour、Learn、Docs 为什么不应该各维护一份术语表:v2 的统一 Glossary 设计

v2 术语表变更后的页面影响范围判断流程:区分 Compatible 与 Affected,只处理真正受影响的页面。

(79) 术语表改了,为什么不应该让所有页面重新翻译?v2 的影响范围判断

CLI 性能优化示意图:通过 Prepare Once、Reuse Many Times,将执行时间从 6m54.554s 降至 10.335s,约提升 40.1 倍。

(80) 从 6 分 54 秒到 10.3 秒:Go 多语言项目一次 CLI 性能优化实战

Markdown Page 经过 Parse / Protect、Generation 和 Reconstruct / Validate 的流程示意图,强调保护 inline code、链接、代码等机器语义,同时允许译文按照自然语言调整语序。

(81) Markdown 不是纯文本:Learn / Docs 为什么需要新的 parser 与结构保护

历史为什么值得保留:复用已经完成的翻译成果,而不是重新做一遍

(82) 历史为什么值得保留:复用已经完成的翻译成果,而不是重新做一遍

历史状态通过 Trusted Checkpoint 建立可信断点,旧记录继续保留用于审计和诊断,后续工作流从当前状态继续向前,不再要求无限重建过去。

(83) 历史应该被保留,而不是被反复重建:v2 为什么最终需要一个“断点”

v2 依赖与失效传播流程图:从内容变化开始,评估依赖关系和真实影响,将结果区分为“仍然有效”和“需要失效”,只让真正受影响的状态重新进入流程。

(84) 一次内容变化,到底应该让哪些状态失效?v2 的依赖与失效传播设计

v2 本地内容覆盖判断流程图:官方英文页面先检查是否已有正式本地覆盖,有覆盖时使用本地 URL,没有覆盖时直接链接 go.dev,并保持本地路径真实 404。

(85) 有英文页面,不代表一定应该有本地页面:v2 如何处理本地内容覆盖、canonical 与真实 404

v2 代码注释完整性审计流程图:从源页面建立代码块清单,分类教学注释、机器语义和无法判断的内容,再与当前译文精确对应,确认 missing、extra 和 protected mutation 均为 0。

(86) 代码块里的注释为什么不能只靠肉眼检查?v2 如何统计并验证代码注释翻译完整性

结构化语义输入扩展示意图:同一份 books.yaml 原始文件保持不变,旧 parser 识别 10 个可本地化单元,当前 parser 识别 12 个,并新增 PDF 和 GitHub,确认旧单元无删除无修改后重新进入 Generation。

(87) 原始文件一个字没变,为什么还要重新翻译?从 books.yaml 10→12 看语义输入扩展

v2 Activation 流程图:Surface PASS 后先执行只读 activation-preflight 获取 prior_sha256,activation-apply 时通过 CAS 再次比较当前状态,只有 current 等于 prior 才提交 result_sha256,否则停止并重新 preflight。

(88) 审核都通过了,为什么上线前还要再检查一次?v2 的 Activation、prior SHA 与 CAS 设计

v2 分层验证流程图:TranslationUnit、Package Closure 和 Integrated Surface 均通过后进入 Activation,再由 Preview 检查 LoadContent、Compose、Resolver 和 Renderer 的真实运行链;如果实际使用错误 renderer,即使前置内容全部通过,Preview 仍然失败。

(89) Surface 已经 PASS,为什么 Preview 还是会失败?从一次 wrong renderer 看分层验证的价值

更快不一定更好:为什么我主动撤销了一次已经明显变快的性能优化

(90) 更快不一定更好:为什么我主动撤销了一次已经明显变快的性能优化

TranslationUnit 同类问题完整性示意图:左侧 Reviewer 只列出 3 个 examples,Generation 只修这 3 处后仍留下 5 个同类问题,re-QC 再次判 B;右侧 Reviewer 明确 defect category 和 bounded scope,Generation 执行 same-class complete sweep,最终同类残留为 0 并进入 re-QC A。

(91) 为什么修完 Reviewer 指出的所有例子,下一轮还是会失败?从 B→revision→B 看同类问题完整性

immutable source 与 replacement successor 示意图:左侧直接把旧 canonical source A 覆盖成 source B,会使旧 Generation、Review 和 Closure evidence 失去准确对应;右侧保留 source A 作为 immutable predecessor,为新正文建立 source B successor,并重新进入 corpus、glossary review、Generation、QC、closure、Surface 和 Activation 生命周期。

(92) 页面明明是我自己的,为什么不能直接重写?从 /translation/ 改版看 immutable source 与 replacement successor

protected representation 修订能力示意图:左侧 http.Client 被正确保护为 token;中间普通自然语言 Documentation 被错误保护为 opaque token,导致合法 revision 被阻塞;右侧保留旧 representation,通过 representation successor 建立新的可编辑表示,再继续 Generation 和独立 re-QC。

(93) 保护机制也会保护错东西:为什么一个 Documentation 能阻塞整页修订?

一个候选快照原本只应包含 1 个首页翻译单元,却因内容包范围未显式绑定而错误纳入 122 个翻译单元;通过 --content-package site-v2-shell 明确内容包后恢复到正确范围。

(94) 一个候选快照为什么突然多出 122 个单元?从隐式范围到显式内容包契约

Shell 自动化中,当前性检查命令失败并打印 RESULT=FAILED,但由于顶层子 Shell 中错误使用 return,后续命令仍继续执行,最终又打印出错误的 RESULT=PASS。

(95) 命令明明失败了,为什么最后还是 PASS?一次 Shell 自动化的 fail-closed 教训

【图8:Google AI 概览——引用网站的课程目录页】

(96) 同一个 Go 语言之旅网站,Bing、Google 和百度的搜索表现差距有多大?

上一篇文章中,我完成了 A Tour of Go 中文版项目的设计冻结,重点讨论了页面数量、翻译单元、状态流程和命令行架构等问题。(Shuijing Wanwq)

设计冻结以后,我并没有立即调用 GLM-5.2 开始翻译。

真正进入实现阶段后,我首先完成了三件更基础的工作:

  1. 调查官方 Tour 和多个现有翻译项目;
  2. 从固定的官方源码中提取可独立运行的英文基线;
  3. 为 101 个课程页面建立机器可读目录、持久身份和上游变化预览。

这一阶段还专门确认了右侧 Go 示例代码的运行方式。

A Tour of Go 不只是一组静态课程文章。读者可以直接修改示例代码、格式化代码并点击 Run。如何保留这项能力,同时又不在普通 Web 服务器上运行陌生代码,是这个项目绕不开的问题。

本文不再重复上一篇文章已经说明的完整页面翻译原则,而是记录这些设计进入代码实现以后,最终形成了什么。

一、开始开发以前,我先调查了六个翻译项目

在导入官方源码之前,我先对六个具有代表性的 A Tour of Go 翻译项目进行了只读调查:

  • Go-zh/website
  • Go-zh/tour
  • atotto/go-tour-jp
  • golang-id/tour
  • golangbr/go-tour-br
  • suzusime/go-tour-jp

这些项目覆盖了几种常见路线:

路线代表形式
完整复制 golang/website中文 website 项目
复制旧版单语言 Tour中文、日语、印尼语和巴西葡萄牙语项目
在旧 Tour 上生成静态页面日语静态项目
每种语言独立部署多数社区翻译站

这些项目并不是没有价值。

相反,它们证明了完整 .article 翻译、课程正文与公共 UI 分离、本地运行使用 /socket、正式网站调用远程 Playground 等基本方向是可行的。

但经过多年维护以后,一些共同问题也逐渐显现出来。

二、单语言复制能够快速上线,但长期维护成本很高

多数旧翻译项目采用的方式可以概括为:

Plaintext
复制一套 Tour 服务端
→ 复制模板和静态资源
→ 复制全部 Go 示例
→ 翻译课程正文
→ 单独部署一个语言站

这种方式的优点很明显。

项目刚开始时,维护者只需要复制官方代码、修改 .article 和 UI 文案,就能较快得到一个可以运行的翻译站。

问题在于,每增加一种语言,就会增加一套独立维护的服务端、模板、JavaScript、示例代码和部署配置。

官方 Tour 更新以后,各个翻译项目都需要分别同步:

  • 服务端代码;
  • 前端资源;
  • 课程页面;
  • Go 示例;
  • 第三方依赖;
  • 部署运行时。

如果某个语言项目停止维护,它往往不会立即完全失效。

更常见的情况是:网站暂时还能打开,但课程内容、依赖和部署方式已经逐渐落后。

因此,这次项目没有选择为简体中文复制一套永久独立的 Tour,也没有计划以后为日语、德语或其他语言继续复制更多服务端。

当前确定的原则是:

  • Go 服务端尽量共享;
  • 模板基础结构和静态资源尽量共享;
  • Go 示例只维护一份官方基准;
  • 每种语言只独立维护课程译文、UI 文案、术语表和页面状态。

三、完整复制 website 又会产生另一类问题

当前官方 A Tour of Go 已经位于 golang/website 仓库中。

最直接的方案似乎是 Fork 整个 website,然后在里面修改 Tour。

但 golang/website 不只包含 Tour,还包含:

  • Go 官方网站服务;
  • 博客和文档内容;
  • 下载页面;
  • 包文档相关逻辑;
  • 官方构建和部署配置;
  • App Engine 与 Cloud Build 文件;
  • 与 Tour 无关的大量静态资源和工具。

如果只想维护一个多语言 Tour,复制整个 website 会让每次上游同步都混入大量无关变化。

因此,我最终采用的是另一种方式:

从固定的官方提交中,提取 Tour 能够独立运行所需要的最小源码闭包。

当前固定的官方基线提交为:

e11dacba76c5aae474746e9eedee19693f492803

首次导入共涉及 199 个上游来源文件:

  • 192 个文件保持逐字节一致;
  • 7 个文件为了适配独立仓库进行了最小修改。

项目使用 UPSTREAM_MANIFEST.tsv 记录每个来源文件的:

  • 上游路径;
  • 本地路径;
  • 文件类型;
  • 是否保持原样;
  • SHA-256;
  • 必要的适配说明。

这样既避免了复制整个 website,也避免了每次构建时临时下载源码造成的网络依赖和不可重复性。

四、从其他翻译项目中看到的几个长期问题

除了重复维护服务端以外,这次调查还暴露出几个更值得注意的问题。

1. 很多项目没有明确的上游版本

Git 仓库中有一份课程译文,并不能直接说明它对应官方的哪个提交。

最近有提交,也不代表课程正文已经同步。

因此,本项目同时记录:

  • 固定 upstream commit;
  • 文件级 SHA-256;
  • 页面级 source_sha256;
  • 页面身份和当前路径;
  • 未来上游变化的分类结果。

2. 部署平台退役可能导致整个语言站下线

多个旧翻译项目与 App Engine runtime 深度绑定。

当旧 runtime 退役以后,一批社区翻译站曾经同时离线。官方 Go 项目虽然在页面中提供了这些语言入口,但这些站点由社区维护者拥有,官方无法直接替维护者恢复部署。

这说明:

源码仍然存在,不等于线上服务能够长期运行。

因此,当前项目不会把某个云平台的专有部署文件作为核心架构,也不会把个人项目 ID、账号配置或固定运行时写入通用流程。

3. 缺少自动校验时,错误主要依赖读者发现

现有翻译项目通常拥有基础 Go 测试,但很少具备:

  • 英文页面与译文结构比较;
  • .play 路径完整性检查;
  • 链接目标检查;
  • 行内代码检查;
  • 页面身份检查;
  • 翻译状态门禁;
  • 上游变化预览;
  • 损坏译文阻止发布。

这意味着译文即使破坏了课程结构,也可能要等到维护者或读者偶然发现。

4. 示例代码很容易产生语言分叉

部分翻译项目修改过示例代码中的注释或字符串。

短期看,这样可以让代码显得更本地化;长期看,却会导致不同语言拥有不同的示例基准。

官方修改代码以后,维护者不仅要同步代码,还要判断每个语言版本中的修改是否仍然适用。

因此,第一阶段决定:

  • .play 引用的 Go 示例保持官方原样;
  • 不翻译变量名、函数名和输出字符串;
  • 不为每种语言复制一套不同的示例;
  • 使用哈希和测试持续确认示例没有发生意外变化。

5. 纯静态页面更容易部署,但会失去完整交互

静态日语项目证明,课程正文可以预生成成普通 HTML。

这种方式迁移方便,也不依赖持续运行的 Go 服务。

但静态页面通常无法完整保留:

  • 编辑器;
  • Run;
  • Format;
  • Kill;
  • 与 Playground 的交互。

因此,静态页面未来可以作为只读备份或故障降级产物,但不适合作为当前项目唯一的正式形态。

五、英文 Tour 基线已经可以独立运行

完成最小源码导入以后,项目已经不再依赖完整的 golang/website 仓库才能运行 Tour。

当前英文基线已经完成以下验证:

  • Go Module 可以正常解析;
  • 所有 Go 测试通过;
  • 七个 .article 可以被 present 解析;
  • 示例代码可以完成原有构建和运行测试;
  • Tour HTTP 服务可以正常启动;
  • 页面、CSS、JavaScript、Logo 和 favicon 可以访问;
  • /_/fmt 可以正常格式化代码;
  • 本地 /socket 可以完成 WebSocket 握手;
  • 进程退出后没有监听端口或临时文件残留。

当前源码结构统计为:

项目数量
顶层 .article7
standalone 普通页面101
普通 .play 引用92
.image 引用1
#appengine: 条件页面2

这组 7 / 101 / 92 / 1 数据,已经成为当前固定英文基线的一部分。

六、为什么上一篇是 103 页,现在状态文件却只有 101 页

上一篇文章从官方 integrated production 投影视角统计,得到了 103 个页面。

其中多出的两个页面是:

  • Go offline (optional)
  • The Go Playground

它们来自 welcome.article 中的 #appengine: 条件内容。

真正开始导入独立 Tour 以后,我决定先以当前 standalone 运行方式中的 101 个普通页面建立第一阶段状态,同时将两个条件页面单独记录在:

data/tour-conditional-pages.tsv

因此,现在的结构并不是忽略那两个页面,而是:

Plaintext
101 个 standalone 普通页面
→ 进入 zh-CN 页面状态

2 个 appengine 条件页面
→ 单独登记
→ 暂不进入第一阶段翻译状态

这样可以让第一阶段的页面目录与当前独立运行基线保持一致,同时保留条件页面的来源、标题和哈希。

以后真正实现生产投影或者等价页面时,再明确决定如何处理这两个页面,而不是现在把 standalone 与 App Engine 两种运行模式混在同一个状态文件中。

七、右侧的 Go 示例代码到底在哪里运行

A Tour of Go 最重要的特点之一,是读者可以直接修改右侧代码并点击 Run。

但是,“点击 Run”并不意味着所有环境都以相同方式运行代码。

本地开发模式

本地启动 Tour 时,执行链路大致是:

Plaintext
浏览器中的代码编辑器
→ WebSocket /socket
→ 本地 Tour 进程
→ 本机 Go 工具链
→ 返回运行结果

这种方式适合个人电脑上的本地学习和开发。

当前项目导入英文基线以后,也保留了这项上游行为,并实际验证了 /socket 能够完成 WebSocket 101 Switching Protocols 握手。

但 /socket 会调用 Tour 所在机器上的 Go 工具链执行代码,因此只能用于可信的本地开发环境。

它不能直接作为公开网站的代码执行接口。

官方生产模式

当前官方 Tour 的生产链路并不是浏览器直接在本地运行 Go,也不是让 go.dev 普通 Web 进程直接执行用户代码。

实际链路是:

Plaintext
浏览器
→ 同源 /_/compile
→ go.dev 服务端代理
→ play.golang.org/compile
→ Go Playground 沙箱
→ 返回运行结果

Go Playground 会在受限沙箱中编译和运行代码,并对执行时间、CPU、内存和外部访问进行限制。官方也允许有益于 Go 社区的第三方服务使用 Playground,但要求调用方事先联系并提供可识别的独特 User-Agent。(Go)

当前项目未来采用的方式

正式网站仍然计划保留 Run,但不会把公开 /socket 暴露到互联网。

计划中的正式链路是:

Plaintext
浏览器
→ 项目同源轻量代理
→ Go 官方 Playground
→ 返回运行结果

这里的“代理”只负责转发受限制的请求。

它不代表用户代码在项目 Web 服务器上运行。

这样可以继续保留交互式学习体验,同时避免自己承担恶意代码隔离、系统调用限制和完整沙箱运维。

不过,这部分目前仍然只是已经确定的正式方案,代理尚未开始实现,也没有公开上线。

八、101 个课程页面已经拥有机器可读目录

完成英文基线以后,项目没有直接开始翻译,而是先生成:

data/tour-pages.tsv

其中每个页面都记录:

  • page_id
  • 所属 article;
  • 当前 Section 编号;
  • 当前 route;
  • 英文标题;
  • 完整页面 source_sha256;
  • .play 数量;
  • .image 数量。

例如,一个页面可以拥有如下关系:

Plaintext
page_id: welcome/1
article: welcome.article
section_number: 1
route: /welcome/1
source_sha256: 491d9386ffb917083a1e556bc9849d57894dd8b29bd243ac855cd9e99f701893

这里的哈希不是只根据标题生成,也不是根据抽象语法树生成。

它对应的是从英文 article 中导出的完整顶层页面源文本,包括:

  • 页面标题;
  • 普通正文;
  • present 指令;
  • 代码引用;
  • 结尾换行。

以后官方修改某个页面时,项目可以准确判断英文源是否发生变化,而不是只依赖文件修改时间或人工肉眼比较。

九、页面可以按完整 Section 单独导出

当前维护工具已经可以根据 page_id 导出一个完整英文页面:

Bash
go run -mod=readonly ./cmd/tour-i18n page export \
  --id welcome/1 \
  --output /tmp/welcome-1.article

导出结果:

  • 只包含一个完整顶层 Section;
  • 不包含同一个 article 中的其他页面;
  • 保持原始 present 格式;
  • 可以重新解析;
  • SHA-256 与页面目录一致;
  • 默认不会覆盖已有文件。

未来 GLM-5.2 接收的就是这样的完整页面,而不是从页面中拆出的若干句子或 JSON 字段。

这项设计原则已经在上一篇文章中详细说明,本文不再重复展开。

十、zh-CN 的 101 个页面目前全部是 pending

项目已经创建独立的简体中文语言空间:

locales/zh-CN/

其中分别维护:

  • locale 元数据;
  • 页面状态;
  • 未来的完整页面译文;
  • UI 文案边界;
  • 术语表。

当前 status.tsv 中恰好有 101 条记录:

Plaintext
pending:101
candidate:0
ready:0
blocked:0
published:0

这意味着项目已经知道需要处理哪些页面,但还没有生成任何正式中文译文。

没有英文内容冒充中文页面,也没有为了展示进度提前创建空的 .article 文件。

十一、结构校验不再锁死普通段落

上一篇设计阶段曾经倾向于严格保持普通段落的数量、顺序和位置。

真正分析 101 个页面并实现 candidate 校验器以后,我调整了这项规则。

现在的原则变为:

只严格保护真正危险或不可翻译的内容,不因为英文段落形式限制正常的中文表达。

当前必须保持的内容包括:

  • .play 指令及目标路径;
  • .image 指令及目标路径;
  • 链接目标 URL;
  • JavaScript 链接目标;
  • 行内代码;
  • 预格式化代码;
  • present 指令;
  • protected 内容的数量和顺序;
  • 顶层 Section 数量。

允许变化的内容包括:

  • 页面标题;
  • 普通正文;
  • 中文语序;
  • 句子数量;
  • 普通段落数量;
  • 普通文本换行;
  • 链接显示文字。

例如,下面的链接显示文字可以翻译:

Plaintext
[[/cmd/gofmt/][gofmt tool]]

但是链接目标 /cmd/gofmt/ 不能被模型修改。

同样,.play welcome/hello.go 可以出现在译文页面中,但其中的路径必须保持原样。

这样既保留整页翻译所需要的上下文,也避免为了追求机械结构一致而产生生硬中文。

十二、page_id 不能永远等于页面位置

最初生成页面目录时,page_id 看起来与当前路由完全一致:

Plaintext
page_id: basics/5
route: /basics/5

但如果官方以后在 Basics 的第 5 页前面插入一页,原来的第 5 页就会移动到第 6 页。

如果继续根据位置重新生成 ID,后续所有页面都会发生变化:

Plaintext
basics/5 → basics/6
basics/6 → basics/7
basics/7 → basics/8

这会进一步导致:

  • 译文文件路径变化;
  • 状态记录失去关联;
  • 历史 Candidate 无法匹配;
  • 已发布页面身份漂移;
  • 同步结果产生大量无意义变化。

因此,当前 101 个 page_id 已经正式冻结为持久身份。

各字段现在具有不同职责:

字段含义
page_id项目内部持久身份
route当前访问路径
section_number当前上游位置
source_title识别和诊断信息
source_sha256当前英文源版本

未来即使页面移动,route 和 section_number 可以变化,page_id 仍然保持不变。

新增页面也不会插入编号并重排旧页面,而是显式分配一个从未使用的新 ID。

十三、上游变化现在可以先预览

项目新增了只读的上游变化预览命令:

Bash
go run -mod=readonly ./cmd/tour-i18n upstream preview \
  --source-root /path/to/website

预览结果将页面分为六种类型:

类型含义
unchanged页面内容和位置都没有变化
content_changed可以确认是同一页面,但英文内容已更新
moved完整源内容相同,但位置或 route 发生变化
added上游新增页面
removed上游删除页面
ambiguous证据不足,不能安全自动匹配

匹配策略保持保守。

完整源哈希唯一相同,可以确认页面发生了移动。

当前 route 仍然存在,且 protected structure 保持兼容,可以确认普通内容发生了变化。

但以下情况不会自动迁移:

  • 页面移动的同时又发生较大结构变化;
  • .play 路径发生变化;
  • 多个页面具有重复特征;
  • 只能依靠标题相同判断;
  • 只能依靠普通正文相似判断;
  • 跨 article 移动但没有唯一证据。

这些情况会进入 ambiguous,等待明确处理。

预览命令不会:

  • 修改正式目录;
  • 修改 status.tsv;
  • 删除语言状态;
  • 创建新页面 ID;
  • 移动 Candidate;
  • 自动覆盖译文。

十四、当前固定基线的预览结果

使用当前固定 upstream 运行两次预览,输出完全一致:

Plaintext
普通页面:101
条件页面:2

unchanged:101
content_changed:0
moved:0
added:0
removed:0
ambiguous:0

conditional unchanged:2

这说明正式页面目录与当前英文源码完全一致,也证明预览输出具有确定性。

与此同时:

  • 101 个 page_id 没有变化;
  • status.tsv 保持逐字节不变;
  • 两个条件页面目录没有变化;
  • 英文 article 没有变化;
  • Go 示例没有变化。

十五、多语言域名也从路径方案调整为子域名

设计阶段曾经考虑在同一个域名下使用:

Plaintext
/ja/
/de/

真正考虑 CDN 和部署以后,我改为按语言使用子域名。

当前规划是:

Plaintext
go-tour.<实际域名>
→ 默认简体中文
→ EdgeOne

en.go-tour.<实际域名>
→ 英文
→ Cloudflare

ja.go-tour.<实际域名>
→ 未来日语
→ Cloudflare

默认站点直接提供 zh-CN,不再创建 zh. 或 zh-cn. 子域。

项目介绍、语言选择、上游基线和 GitHub 入口则放在默认站点的 /about/ 页面。

这样不同语言以后可以独立选择:

  • CDN;
  • 部署区域;
  • 缓存策略;
  • 发布时间;
  • 故障处理方式。

目前这些都只是已经记录的域名和部署规划,尚未创建真实 DNS、证书或 CDN 配置。

十六、这一阶段仍然没有翻译任何正式页面

截至目前,项目已经完成:

  • 最小英文 Tour 源码闭包导入;
  • 199 个来源文件追踪;
  • 英文 Tour 独立运行;
  • 七个 article 全量解析;
  • 101 个普通页面目录;
  • 两个条件页面目录;
  • 92 个 .play 引用验证;
  • 示例代码构建和运行测试;
  • 单页完整导出;
  • zh-CN 状态文件;
  • candidate 结构校验;
  • 101 个持久 page_id;
  • 上游变化预览;
  • 多语言域名和 CDN 边界规划。

但当前仍然是:

Plaintext
正式中文页面:0
Candidate:0
Ready:0
Blocked:0
Published:0

这不是项目进度停滞。

恰恰相反,这意味着在第一篇译文出现以前,项目已经先建立了:

  • 可重复的英文源;
  • 可追溯的页面身份;
  • 可验证的结构边界;
  • 可预览的上游变化;
  • 不依赖人工记忆的状态基础。

十七、下一步才开始真正调用 GLM-5.2

下一阶段将进入正式翻译流水线:

Plaintext
根据 page_id 导出完整英文页面
→ 保护危险结构
→ 调用 GLM-5.2 整页翻译
→ 保存 Candidate
→ present 重新解析
→ protected structure 比较
→ 更新尝试次数和页面状态

这一阶段仍然不会立即批量处理全部 101 页。

更稳妥的方式是:

  1. 先实现单页翻译执行器;
  2. 使用模拟服务完成自动测试;
  3. 选择一个较短页面进行第一次真实翻译;
  4. 验证 Candidate 落盘和失败恢复;
  5. 验证 ready 与 blocked 状态迁移;
  6. 最后再决定何时开始批量翻译。

到那时,这个项目才会真正生成第一篇简体中文课程页面。

而现在完成的工作,是确保第一篇译文出现以后,它不会成为一份无法确认来源、无法判断是否过期、也无法自动验证结构的孤立文本。

A Tour of Go 中文版项目设计冻结:从 101 页到 103 页,从 Gin 转向 Cobra CLI A Tour of Go 多语言翻译项目实录:完成首个 zh-CN 页面翻译闭环

Go 学习与文档多语言本地化项目

本系列完整记录 Go 学习与文档多语言本地化项目从 A Tour of Go 起步,逐步扩展到站点首页、项目说明页、学习内容、文档以及未来内容包的实际开发过程,包括架构设计、统一术语治理、整页翻译、结构保护、自动校验、独立质量审核、生产发布与长期维护。

项目源码:
✅ GitHub:shuijingwan/go-tour-i18n

语言版本:

当前跨项目目标语言池共包含 66 个语言版本。Go 项目本身目前提供 65 个社区 Production 语言版本,下面每一门 Go 语言版本都直接链接到其当前公开站点。语言列表沿用项目首页现有的展示顺序。

英语(美国)仅作为跨项目目标语言纳入列表,并不是单独的 Go Production 语言版本。当前 Go 的英文源内容仍为官方 generic English,因此英语(美国)链接到 Go 官方站点,而不是不存在的本地社区部署。

  1. 阿姆哈拉语
  2. 阿拉伯语
  3. 孟加拉语
  4. 葡萄牙语(巴西)
  5. 保加利亚语
  6. 加泰罗尼亚语
  7. 克罗地亚语
  8. 捷克语
  9. 丹麦语
  10. 荷兰语
  11. 英语(美国)(Go 官方英文站点)
  12. 英语(澳大利亚)
  13. 英语(加拿大)
  14. 英语(印度)
  15. 英语(新加坡)
  16. 英语(南非)
  17. 英语(英国)
  18. 爱沙尼亚语
  19. 菲律宾语
  20. 芬兰语
  21. 法语(法国)
  22. 法语(加拿大)
  23. 德语(德国)
  24. 德语(奥地利)
  25. 德语(瑞士)
  26. 希腊语
  27. 古吉拉特语
  28. 希伯来语
  29. 印地语
  30. 匈牙利语
  31. 印度尼西亚语
  32. 意大利语
  33. 日语
  34. 卡纳达语
  35. 哈萨克语
  36. 韩语
  37. 西班牙语(拉丁美洲)
  38. 拉脱维亚语
  39. 立陶宛语
  40. 马来语
  41. 马拉雅拉姆语
  42. 马拉地语
  43. 书面挪威语(Bokmål)
  44. 波斯语
  45. 波兰语
  46. 葡萄牙语(葡萄牙)
  47. 旁遮普语(果鲁穆奇文,印度)
  48. 罗马尼亚语
  49. 俄语
  50. 塞尔维亚语(西里尔文)
  51. 简体中文(中国大陆)
  52. 简体中文(新加坡)
  53. 斯洛伐克语
  54. 斯洛文尼亚语
  55. 西班牙语(西班牙)
  56. 斯瓦希里语(坦桑尼亚)
  57. 瑞典语
  58. 泰米尔语
  59. 泰卢固语
  60. 泰语
  61. 繁体中文(台湾)
  62. 繁体中文(香港)
  63. 土耳其语
  64. 乌克兰语
  65. 乌尔都语
  66. 越南语

项目正在持续扩展新的 Go 学习与文档内容,并长期维护各语言版本的术语一致性、翻译质量、独立审核、生产发布与后续更新。

本项目属于非官方社区多语言本地化项目,与 Go 官方无隶属、授权或背书关系。