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

A Tour of Go Chinese Edition Project Design Frozen: Pages 101 to 103 Shift From Gin to Cobra CLI

A Tour of Go Chinese Edition Project Design Frozen: Pages 101 to 103 Shift From Gin to Cobra CLI

作者:

在

Previously, I wrote an article preliminarily discussing how to develop a maintainable Simplified Chinese version of A Tour of Go.

Related article:

A Tour of Go Chinese Version Project Preliminary Design

The initial ideas were fairly broad, including:

  • Developing the project using Go;
  • Using Gin to provide management capabilities;
  • Calling GLM-5.2 to translate course content;
  • Establishing translation status and synchronization mechanisms;
  • Ultimately deploying a publicly accessible Chinese Tour.

After several days of further analysis of the official source code, course structure, production runtime, and translation workflow, the project design has gradually converged.

As of August 1, 2026, the page-level translation contract and implementation preparation checklist have been frozen.

The next step is no longer to expand the design, but to officially create the go-tour-i18n repository and begin implementation with the first minimal development task.

1. The initially confirmed 101 pages is not the complete production page count

When I first inspected the official source code, I counted the seven .article files under the _content/tour directory.

Counting directly by the top-level Sections in the raw article, there are 101 course pages.

After navigating to the official tour directory locally, executing:

Bash
go run .

also shows 101 pages.

Among them, welcome.article displays three pages locally:

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

However, the Welcome section on the current official A Tour of Go website actually contains five pages:

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

This indicates that the page structure obtained by starting the Tour locally is not entirely consistent with the official production environment.

2. The 101 pages and 103 pages come from two different page projections

After continuing to analyze the official source code, I finally confirmed:

Counting MethodPage Count
Raw article explicit Section101
Standalone local run101
Integrated production103

The difference mainly comes from the conditional content in #appengine: within welcome.article.

Before parsing the article, the official production environment first executes a preprocessing layer similar to gaePrepContent.

This preprocessing layer will:

  • Remove the #appengine: prefix;
  • Keep the titles, paragraphs, and instructions needed for the production environment;
  • Delete the corresponding standalone alternative content;
  • Convert conditional .play, commands, and titles into actual present content.

Therefore, the following two pages only appear in the production projection:

  • Go offline (optional)
  • The Go Playground

Ultimately, the page count for the current upstream baseline is:

LessonPages
basics17
concurrency11
flowcontrol14
generics3
methods26
moretypes27
welcome5
Total103

The 103 here only applies to the currently fixed upstream commit:

Plaintext
e11dacba76c5aae474746e9eedee19693f492803

It cannot become a permanent constant in the business code.

After the official source adds, deletes, splits, or merges pages in the future, the valid page count could very well become 104 or another number.

The project must dynamically generate a production manifest based on the current upstream, and then use the manifest to determine the page count, order, and previous/next relationships.

3. The translation unit is ultimately determined to be the Production page

Initially, I treated the top-level Section in the raw article as the translation unit.

This design no longer holds after the discrepancy between 101 and 103 pages was discovered.

The now officially frozen definition is:

Performing integrated production preprocessing on the raw article, and then parsing it with a fixed version of present.Context.Parse; the resulting top-level present.Section constitutes a complete translation page.

This means:

  • Go local is one translation request;
  • Go offline is another translation request;
  • The Go Playground is yet another independent request.

They cannot be merged into the same translation task just because they have a conditional relationship in the original source code.

The page translation workflow is now fixed as:

Plaintext
Complete Production page
→ GLM-5.2 full-page translation
→ Save Candidate
→ Automatic structure and page validation
├─ Pass → Ready → Explicit publish
└─ Fail → Limited full-page retry → Blocked
                           ↓
                    ChatGPT full-page translation
                           ↓
                    Same set of automatic validation

The following will not be adopted here:

  • Paragraph-level independent translation;
  • AST node-level translation;
  • Multiple JSON text fields;
  • Automatic splitting upon page failure;
  • Routine manual page-by-page approval.

The model always receives one complete course page at a time to preserve the full context.

4. Ordinary paragraph structure also requires automatic protection

Full-page translation does not mean the translation can arbitrarily change the page structure.

The frozen rules require:

  • The number of ordinary paragraphs must remain consistent by default;
  • The paragraph order must be consistent;
  • The structural position of paragraphs must be consistent;
  • Chinese word order can be adjusted within the same paragraph;
  • Sentences can be split or merged within the same paragraph;
  • Cross-paragraph merging is not allowed by default;
  • Paragraphs cannot be deleted or added;
  • Paragraphs cannot be moved to list or other section positions.

If such changes occur, the validator will return:

Plaintext
paragraph_structure_changed

Changing the paragraph boundaries of a page is only allowed when an exact exception binding the current page_id and source_content_hash exists in the manifest.

This preserves the contextual advantage of full-page translation while preventing the model from inadvertently altering the course structure.

5. Workflow status and the live published version must be independent

Another important decision is to completely separate the current translation status from the live published version.

For example, after an official page update, the following can exist simultaneously:

YAML
translation:
  status: needs_retranslation

publication:
  published_version: 4

This indicates:

  • The official English page has been updated;
  • The new Chinese translation is not yet complete;
  • The old Chinese version 4 remains live and functioning normally.

None of the following statuses can clear the old live version:

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

Only after a Candidate that has entered ready goes through an explicit publish operation is it allowed to move the published_version pointer.

This prevents a single translation failure from taking an otherwise functional Chinese page offline.

6. Gin was originally planned, but ultimately dropped

In the earliest project design, I had hoped to use Gin.

On one hand, this is a Go practical project, and I wanted to practice common frameworks in the Go ecosystem as much as possible.

On the other hand, I initially thought Gin would, like Yii 2, provide both Web and CLI runtime modes.

In Yii 2, you can separately use:

  • Web Application;
  • Console Application.

However, upon further confirmation, I found that Gin is essentially an HTTP Web framework and does not have a native Console mode.

If I insisted on using Gin while also needing a CLI, there were usually a few options:

  • The CLI and Gin share the same core business services;
  • The CLI acts as a client calling the Gin API;
  • Using tools like Cobra to independently implement the CLI;
  • Using Gin only when starting the Web service.

I briefly considered this structure:

Plaintext
CLI
→ Gin Management API
→ Core Application Service

A potential future backend management interface would also manage translation, validation, and publishing through the same Gin API.

But after further analysis, I realized that this project might never need a Web management interface.

The entire workflow mainly consists of:

  • Fetching upstream;
  • Generating a manifest;
  • Translating pages;
  • Automatic validation;
  • Exporting blocked pages;
  • Importing ChatGPT translations;
  • Generating articles;
  • Publishing and rollback.

These are all very typical command-line batch processing tasks.

When I previously handled WordPress historical article translation, summary completion, and SyntaxHighlighter migration, I also primarily used a CLI.

In actual use, the CLI is sufficient to accomplish:

  • Fixed batches;
  • Status checks;
  • Limited retries;
  • Failure recovery;
  • Read-only validation;
  • Batch execution;
  • Log retention.

Introducing Gin prematurely for a backend interface that might never appear would instead add:

  • HTTP API design;
  • Service startup and shutdown;
  • API authentication;
  • Request timeouts;
  • Long-running task status;
  • CLI HTTP client;
  • Server and client error codes;
  • Local and CI dependency on resident services.

Therefore, the project ultimately decided:

Drop Gin in the first phase, and no longer plan for a Web management interface.

7. Officially choosing Go plus Cobra

The project’s use of the Go language remains unchanged.

Cobra is officially chosen as the CLI framework.

The final relationship is:

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

Cobra is only responsible for:

  • Commands and subcommands;
  • Arguments and flags;
  • Help messages;
  • Shell completion;
  • Calling application services;
  • Outputting results;
  • Returning exit codes;
  • Passing Context and cancellation signals.

The actual business logic will not be written into Cobra’s RunE.

For example, the following behaviors belong to independent application services:

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

The expected final command forms include:

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

The first phase will not implement all commands at once, but will gradually add them as the project phases progress.

8. An independent repository cannot directly import the official internal/tour

The new project repository is tentatively named:

Plaintext
go-tour-i18n

But the core implementation of the official Tour is located at:

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

Go’s internal rules dictate that an independent repository cannot directly import this package.

Therefore, it cannot directly call:

  • gaePrepContent
  • parseLesson
  • initTour
  • Official unexported Handlers

The frozen approach is:

Management and build side

Implement a very small, versioned gaePrepContent equivalent projector in go-tour-i18n.

It only implements the semantics actually needed by the current project, and compares against fixed upstream behavior via golden tests.

Preview and runtime side

Do not rewrite the official Tour service.

The project will, in a temporary staging directory:

  1. Fetch the locked official website source code;
  2. Write the generated Chinese .article;
  3. Apply a few UI and runtime configuration patches;
  4. Build or start the official Tour compatible service;
  5. Clean up staging upon exit.

This avoids copying the complete official Tour while not modifying the upstream cache directory.

9. Upstream is fixed using a Lock file

The project will not depend on my currently existing:

Plaintext
~/code/go-website-upstream

The official approach adopts:

Plaintext
upstream.lock
+ CLI-managed local cache Checkout

upstream.lock will record:

  • Official repository address;
  • Fixed commit;
  • Projector contract;
  • Hash of appengine.go;
  • present module and version;
  • Course order source file and hash.

Local, CI, and future release builds must all fetch the source code based on the same Lock file.

The current local ~/code/go-website-upstream is only for initial analysis and convenient source code reading, and cannot become an implicit condition for the project build to succeed.

10. Testing adopts a two-tier Fixture

A testing design issue was also discovered before the freeze.

Initially, the first task only intended to save welcome.article, but at the same time hoped to validate the full 101 and 103 page counts.

A single Welcome file obviously cannot validate the full page count of the seven Lessons.

It was ultimately changed to a two-tier Fixture.

Projector mini Fixture

Only validates line-by-line preprocessing behavior, such as:

  • #appengine: conditional substitution;
  • Conditional titles;
  • Indented commands;
  • Conditional .play;
  • Conditional Notes.

They are very small in scale, suitable for quick unit tests.

Complete Upstream baseline Fixture

Saves the seven .article files under the current commit, used to offline generate the complete manifest.

It must validate:

  • Raw total 101;
  • Integrated total 103;
  • Page count for each Lesson;
  • Official course order;
  • Page order within a Lesson;
  • Previous and Next relationships.

These baseline numbers will only appear in the test expectations for the fixed commit, and will not enter the business code.

11. Deployment method is temporarily not determined in advance

During the implementation preparation phase, there was a brief inclination towards making the container image the default release method.

But the current server already has:

  • Linux ECS;
  • Nginx;
  • Existing service management and operations environment;
  • EdgeOne.

For a standalone Go Tour service, the ultimately simplest approach might be:

Plaintext
Linux binary
+ systemd
+ existing Nginx
+ EdgeOne

It is also possible that containers are found to be more suitable during actual testing.

Therefore, for now it is only confirmed that the first phase Release must provide:

  • A standalone Linux amd64 binary;
  • Release Manifest;
  • Artifact Hash;
  • License and third-party notices;
  • Runtime configuration instructions.

systemd and containers will be compared in the fifth phase based on the actual environment, without maintaining two complex deployment approaches simultaneously.

12. The Chinese site domain name has also been adjusted

The initially tentative Chinese domain was:

Plaintext
zh.go-tour.shuijingwanwq.com

It is now adjusted to:

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

The default language is Simplified Chinese, so the /zh/ prefix is not used.

For example:

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

When adding other languages in the future, you can use:

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

The default Chinese will continue to use the address without a language prefix.

It will not simultaneously expose:

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

This avoids creating duplicate pages.

13. The next task only validates Upstream and Manifest

The design freeze has been completed today, and the repository will temporarily not be created yet.

When starting tomorrow, the first development task will strictly limit the scope to:

  1. Create the go-tour-i18n local repository;
  2. Initialize the Go Module;
  3. Integrate Cobra;
  4. Create upstream.lock;
  5. Implement the minimal equivalent projector;
  6. Generate raw-to-output provenance;
  7. Use golang.org/x/tools/present v0.48.0;
  8. Generate the integrated production manifest;
  9. Implement three minimal commands:
    • upstream verify
    • manifest build
    • manifest show
  10. Complete the mini projector Fixture and full baseline Fixture tests.

The current task will not:

  • Call GLM-5.2;
  • Translate any pages;
  • Implement Candidate;
  • Implement the state machine;
  • Implement Blocked;
  • Implement publishing;
  • Implement rollback;
  • Implement Preview;
  • Deploy a test site.

The first step only needs to prove:

The project can stably, offline, and reproducibly generate the correct Integrated Production Manifest from the locked official source code.

14. The design phase can finally conclude

The biggest change over these past few days is not how many features were added, but rather the gradual removal of unnecessary designs.

Items deleted or postponed include:

  • Gin;
  • Web management API;
  • Web management interface;
  • Database;
  • Page-by-page manual review;
  • Runtime dynamic language switching;
  • Simultaneous multi-language launch;
  • Complex task queues;
  • Distributed translation;
  • Self-built public code sandbox;
  • Maintaining both container and systemd release pipelines simultaneously.

What remains in the end is a route that better fits the current actual needs:

Plaintext
Go + Cobra
→ Locked Upstream
→ Production page projection
→ Full-page translation
→ Automatic validation
→ File state
→ Official Tour compatible generation and publishing

This design still retains multi-language extensibility, but the first phase will only complete zh-CN.

It also remains a complete Go practical project, just no longer artificially adding a Web management system just to use a certain framework.

What needs to be done next is already very clear.

Starting tomorrow, I will create the go-tour-i18n repository, beginning with upstream, projector, and production manifest, to gradually implement the frozen design for real.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系