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

A Tour of Go 中文版项目设计冻结:从 101 页到 103 页,从 Gin 转向 Cobra CLI

本地运行的 A Tour of Go「Methods continued」课程页面,左侧为课程说明,右侧为 Go 代码编辑器及运行结果。

作者:

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 简体中文版。

相关文章:

A Tour of Go 中文版项目初步设计

当时的设想还比较宽泛,包括:

  • 使用 Go 开发项目;
  • 使用 Gin 提供管理能力;
  • 调用 GLM-5.2 翻译课程内容;
  • 建立翻译状态和同步机制;
  • 最终部署一个可以公开访问的中文 Tour。

经过几天对官方源码、课程结构、生产运行方式和翻译流程的进一步分析,这个项目的设计已经逐渐收敛。

截至 2026 年 8 月 1 日,页面级翻译契约和实现准备清单都已经冻结。

下一步不再继续扩展设计,而是正式创建 go-tour-i18n 仓库,从第一个最小开发任务开始实施。

一、最初确认的 101 页并不是完整生产页面数

在最开始检查官方源码时,我统计了 _content/tour 目录下的七个 .article 文件。

直接按照原始 article 中的顶层 Section 统计,共有 101 个课程页面。

本地进入官方 tour 目录以后执行:

Bash
go run .

看到的同样是 101 页。

其中 welcome.article 在本地显示三个页面:

  1. Hello, 世界
  2. Go local
  3. Congratulations

但是当前官方 A Tour of Go 网站中的 Welcome 实际上包含五页:

  1. Hello, 世界
  2. Go local
  3. Go offline(可选)
  4. The Go Playground
  5. Congratulations

这说明本地直接启动 Tour 得到的页面结构,与官方生产环境并不完全一致。

二、101 页和 103 页来自两个不同的页面投影

继续分析官方源码后,最终确认:

统计方式页面数
Raw article 显式 Section101
Standalone 本地运行101
Integrated production103

差异主要来自 welcome.article 中的 #appengine: 条件内容。

官方生产环境在解析 article 之前,会先执行一层类似 gaePrepContent 的预处理。

这层预处理会:

  • 去掉 #appengine: 前缀;
  • 保留生产环境需要的标题、段落和指令;
  • 删除与其对应的 standalone 替代内容;
  • 将条件 .play、命令和标题转换成真正的 present 内容。

因此,下面两页只会出现在生产投影中:

  • Go offline(可选)
  • The Go Playground

最终当前 upstream 基线的页面数量为:

Lesson页数
basics17
concurrency11
flowcontrol14
generics3
methods26
moretypes27
welcome5
总计103

这里的 103 只属于当前固定的 upstream commit:

Plaintext
e11dacba76c5aae474746e9eedee19693f492803

它不能成为业务代码中的永久常量。

未来官方新增、删除、拆分或者合并页面后,合法页面数完全可能变成 104 或其他数字。

项目必须根据当前 upstream 动态生成 production manifest,再用 manifest 判断页面数量、顺序和前后关系。

三、翻译单元最终确定为 Production 页面

最开始,我曾经把 raw article 中的顶层 Section 视为翻译单元。

这种设计在 101 页和 103 页差异被发现后已经不再成立。

现在正式冻结的定义是:

对 raw article 执行 integrated production 预处理,再使用固定版本的 present.Context.Parse 解析;最终产生的顶层 present.Section,才是一个完整的翻译页面。

这意味着:

  • Go local 是一个翻译请求;
  • Go offline 是另一个翻译请求;
  • The Go Playground 又是一个独立请求。

不能因为它们在原始源码中存在条件关系,就把它们合并成同一个翻译任务。

页面翻译流程已经固定为:

Plaintext
完整 Production 页面
→ GLM-5.2 整页翻译
→ 保存 Candidate
→ 自动结构和页面校验
├─ 通过 → Ready → 显式发布
└─ 失败 → 有限整页重试 → Blocked

                    ChatGPT 整页翻译

                    同一套自动校验

这里不会采用:

  • 段落级独立翻译;
  • AST 节点级翻译;
  • JSON 多个 text 字段;
  • 页面失败后自动拆分;
  • 常规逐页人工审批。

模型始终一次接收一个完整课程页面,以保留完整上下文。

四、普通段落结构也需要自动保护

整页翻译并不代表译文可以任意改变页面结构。

冻结后的规则要求:

  • 普通段落数量默认保持一致;
  • 段落顺序必须一致;
  • 段落所在的结构位置必须一致;
  • 同一段落内部可以调整中文语序;
  • 同一段落内部可以拆分或合并句子;
  • 默认不能跨段合并;
  • 不能删除或新增段落;
  • 不能把段落移动到列表或其他章节位置。

如果发生这种变化,校验器会返回:

Plaintext
paragraph_structure_changed

只有 manifest 中存在绑定当前 page_idsource_content_hash 的精确例外时,才允许改变某个页面的段落边界。

这样既保留了整页翻译的上下文优势,又防止模型在不知不觉中改变课程结构。

五、工作流状态和线上发布版本必须独立

另一个重要决定,是将当前翻译状态与线上发布版本完全分开。

例如官方页面更新以后,可以同时存在:

YAML
translation:
  status: needs_retranslation

publication:
  published_version: 4

它表示:

  • 官方英文页面已经更新;
  • 新的中文译文尚未完成;
  • 旧的中文 version 4 仍然正常在线。

下面这些状态都不能清除旧的线上版本:

  • needs_retranslation
  • translating
  • translation_failed
  • translated
  • validation_failed
  • blocked

只有一个已经进入 ready 的 Candidate,经过显式发布操作以后,才允许移动 published_version 指针。

这样可以避免一次翻译失败导致原本正常的中文页面下线。

六、曾经计划使用 Gin,最终决定取消

在最早的项目设计中,我曾经希望使用 Gin。

一方面,这是一个 Go 实战项目,我希望尽量练习 Go 生态中的常见框架。

另一方面,我最初以为 Gin 会像 Yii 2 一样,同时提供 Web 和 CLI 两种运行模式。

Yii 2 中可以分别使用:

  • Web Application;
  • Console Application。

但是进一步确认后发现,Gin 本质上是一个 HTTP Web 框架,并没有天然的 Console 模式。

如果坚持使用 Gin,同时又需要 CLI,通常有几种方式:

  • CLI 和 Gin 共享同一套核心业务服务;
  • CLI 作为客户端调用 Gin API;
  • 使用 Cobra 等工具单独实现 CLI;
  • Gin 只在 Web 服务启动时使用。

一度考虑过这样的结构:

Plaintext
CLI
→ Gin 管理 API
→ 核心应用服务

未来可能存在的后台管理界面,也通过同一套 Gin API 管理翻译、校验和发布。

但是继续分析以后,我发现这个项目很可能永远不需要 Web 管理界面。

整个工作流主要是:

  • 获取 upstream;
  • 生成 manifest;
  • 翻译页面;
  • 自动校验;
  • 导出 blocked 页面;
  • 导入 ChatGPT 译文;
  • 生成 article;
  • 发布和回滚。

这些都是非常典型的命令行批处理任务。

我此前处理 WordPress 历史文章翻译、摘要补全和 SyntaxHighlighter 迁移时,也一直主要使用 CLI。

实际使用下来,CLI 已经足够完成:

  • 固定批次;
  • 状态检查;
  • 有限重试;
  • 失败恢复;
  • 只读验证;
  • 批量执行;
  • 日志留存。

如果为了一个可能永远不会出现的后台界面提前引入 Gin,反而会增加:

  • HTTP API 设计;
  • 服务启动和关闭;
  • API 认证;
  • 请求超时;
  • 长任务状态;
  • CLI HTTP 客户端;
  • 服务端和客户端错误码;
  • 本地和 CI 对常驻服务的依赖。

因此,项目最终决定:

第一阶段放弃 Gin,也不再规划 Web 管理界面。

七、正式选择 Go 加 Cobra

项目使用 Go 语言实现这一点没有变化。

CLI 框架正式选择 Cobra。

最终关系是:

Plaintext
Cobra CLI
→ Application Service
→ Upstream、Translation、Validation、Workspace

Cobra 只负责:

  • 命令和子命令;
  • 参数与 Flag;
  • 帮助信息;
  • Shell 补全;
  • 调用应用服务;
  • 输出结果;
  • 返回退出码;
  • 传递 Context 和取消信号。

真正的业务逻辑不会写进 Cobra 的 RunE

例如下面这些行为都属于独立应用服务:

  • BuildManifest
  • TranslatePage
  • ValidatePage
  • ExportBlockedPage
  • ImportCandidate
  • GenerateLocale
  • PublishLocale
  • RollbackLocale

预计最终命令形式包括:

Bash
go-tour-i18n upstream verify
go-tour-i18n manifest build
go-tour-i18n manifest show
go-tour-i18n page translate
go-tour-i18n page validate
go-tour-i18n blocked export
go-tour-i18n candidate import
go-tour-i18n locale generate
go-tour-i18n locale publish
go-tour-i18n locale rollback
go-tour-i18n preview
go-tour-i18n status

第一阶段不会一次实现所有命令,而是随着项目阶段逐步增加。

八、独立仓库不能直接导入官方 internal/tour

新的项目仓库暂定为:

Plaintext
go-tour-i18n

但官方 Tour 的核心实现位于:

Plaintext
golang.org/x/website/internal/tour

Go 的 internal 规则决定了,独立仓库不能直接导入这个包。

因此不能直接调用:

  • gaePrepContent
  • parseLesson
  • initTour
  • 官方未导出的 Handler

冻结后的方案是:

管理和构建侧

go-tour-i18n 中实现一个非常小的、带版本号的 gaePrepContent 等价 projector。

它只实现当前项目真正需要的语义,并通过 golden test 与固定 upstream 行为对比。

预览和运行侧

不重写官方 Tour 服务。

项目会在临时 staging 目录中:

  1. 取得锁定的官方 website 源码;
  2. 写入生成后的中文 .article
  3. 应用少量 UI 和运行配置 Patch;
  4. 构建或启动官方 Tour 兼容服务;
  5. 退出后清理 staging。

这样既避免复制完整官方 Tour,又不会修改 upstream 缓存目录。

九、Upstream 使用 Lock 文件固定

项目不会依赖我当前已经存在的:

Plaintext
~/code/go-website-upstream

正式方案采用:

Plaintext
upstream.lock
+ CLI 管理的本地缓存 Checkout

upstream.lock 会记录:

  • 官方仓库地址;
  • 固定 commit;
  • projector contract;
  • appengine.go 的 hash;
  • present 模块和版本;
  • 课程顺序来源文件和 hash。

本地、CI 和以后发布构建,都必须根据同一份 Lock 获取源码。

当前本地的 ~/code/go-website-upstream 只用于初始分析和方便阅读源码,不能成为项目构建成立的隐含条件。

十、测试采用两层 Fixture

冻结前还发现了一个测试设计问题。

最初首个任务只打算保存 welcome.article,但同时又希望验证完整的 101 和 103 页统计。

单个 Welcome 文件显然无法验证七个 Lesson 的完整页面数。

最终改为两层 Fixture。

Projector 小型 Fixture

只验证逐行预处理行为,例如:

  • #appengine: 条件替代;
  • 条件标题;
  • 缩进命令;
  • 条件 .play
  • 条件 Note。

它们规模很小,适合快速单元测试。

完整 Upstream 基线 Fixture

保存当前 commit 下的七个 .article 文件,用于离线生成完整 manifest。

它必须验证:

  • Raw 总计 101;
  • Integrated 总计 103;
  • 每个 Lesson 的页面数;
  • 官方课程顺序;
  • Lesson 内页面顺序;
  • Previous 和 Next 关系。

这些基线数字只会出现在固定 commit 的测试期望中,不会进入业务代码。

十一、部署方式暂时不提前确定

实现准备阶段曾经一度倾向于把容器镜像作为默认发布方式。

但当前服务器已经有:

  • Linux ECS;
  • Nginx;
  • 现有服务管理和运维环境;
  • EdgeOne。

对于一个单独的 Go Tour 服务,最终最简单的方案可能是:

Plaintext
Linux 二进制
+ systemd
+ 现有 Nginx
+ EdgeOne

也可能在实际测试后发现容器更合适。

因此,现在只确定第一阶段 Release 必须提供:

  • 可独立运行的 Linux amd64 二进制;
  • Release Manifest;
  • Artifact Hash;
  • License 和第三方声明;
  • 运行配置说明。

systemd 和容器在第五阶段根据实际环境再比较,不同时维护两套复杂部署方案。

十二、中文站点域名也已经调整

最初暂定的中文域名是:

Plaintext
zh.go-tour.shuijingwanwq.com

现在调整为:

Plaintext
https://go-tour.shuijingwanwq.com

默认语言就是简体中文,因此不使用 /zh/ 前缀。

例如:

Plaintext
https://go-tour.shuijingwanwq.com/tour/welcome/1
https://go-tour.shuijingwanwq.com/tour/basics/1

未来增加其他语言时,可以使用:

Plaintext
/ja/tour/...
/de/tour/...

默认中文继续使用无语言前缀地址。

不会同时公开:

Plaintext
/tour/welcome/1
/zh/tour/welcome/1

避免产生重复页面。

十三、下一个任务只验证 Upstream 和 Manifest

今天已经完成了方案冻结,暂时不继续创建仓库。

明天开始时,第一个开发任务会严格限制范围:

  1. 创建 go-tour-i18n 本地仓库;
  2. 初始化 Go Module;
  3. 接入 Cobra;
  4. 创建 upstream.lock
  5. 实现最小等价 projector;
  6. 生成 raw-to-output provenance;
  7. 使用 golang.org/x/tools/present v0.48.0
  8. 生成 integrated production manifest;
  9. 实现三个最小命令:
    • upstream verify
    • manifest build
    • manifest show
  10. 完成小型 projector Fixture 和完整基线 Fixture 测试。

当前任务不会:

  • 调用 GLM-5.2;
  • 翻译任何页面;
  • 实现 Candidate;
  • 实现状态机;
  • 实现 Blocked;
  • 实现发布;
  • 实现回滚;
  • 实现 Preview;
  • 部署测试站。

第一步只需要证明:

项目能够从锁定的官方源码中,稳定、离线、可重复地生成正确的 Integrated Production Manifest。

十四、设计阶段终于可以结束

这几天最大的变化,并不是增加了多少功能,而是逐渐删除了不必要的设计。

被删除或推迟的内容包括:

  • Gin;
  • Web 管理 API;
  • Web 管理界面;
  • 数据库;
  • 逐页人工审核;
  • 运行时动态语言切换;
  • 多语言同时上线;
  • 复杂任务队列;
  • 分布式翻译;
  • 自建公网代码沙箱;
  • 同时维护容器和 systemd 两套发布流程。

最终留下来的,是更符合当前真实需求的一条路线:

Plaintext
Go + Cobra
→ 锁定 Upstream
→ Production 页面投影
→ 整页翻译
→ 自动校验
→ 文件状态
→ 官方 Tour 兼容生成与发布

这个设计依然具备多语言扩展能力,但第一阶段只完成 zh-CN

它也仍然是一个完整的 Go 实战项目,只是不再为了使用某个框架而人为增加 Web 管理系统。

接下来要做的事情已经非常明确。

明天开始创建 go-tour-i18n 仓库,从 upstream、projector 和 production manifest 开始,逐步把已经冻结的设计真正实现出来。

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

我是拥有 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 来减少垃圾评论。了解你的评论数据如何被处理