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:
go run .
also shows 101 pages.
Among them, welcome.article displays three pages locally:
- Hello, 世界
- Go local
- Congratulations
However, the Welcome section on the current official A Tour of Go website actually contains five pages:
- Hello, 世界
- Go local
- Go offline (optional)
- The Go Playground
- 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 Method | Page Count |
|---|---|
| Raw article explicit Section | 101 |
| Standalone local run | 101 |
| Integrated production | 103 |
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:
| Lesson | Pages |
|---|---|
| basics | 17 |
| concurrency | 11 |
| flowcontrol | 14 |
| generics | 3 |
| methods | 26 |
| moretypes | 27 |
| welcome | 5 |
| Total | 103 |
The 103 here only applies to the currently fixed upstream commit:
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-levelpresent.Sectionconstitutes 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:
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
textfields; - 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:
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:
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_retranslationtranslatingtranslation_failedtranslatedvalidation_failedblocked
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:
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:
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:
BuildManifestTranslatePageValidatePageExportBlockedPageImportCandidateGenerateLocalePublishLocaleRollbackLocale
The expected final command forms include:
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:
go-tour-i18n
But the core implementation of the official Tour is located at:
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:
gaePrepContentparseLessoninitTour- 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:
- Fetch the locked official website source code;
- Write the generated Chinese
.article; - Apply a few UI and runtime configuration patches;
- Build or start the official Tour compatible service;
- 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:
~/code/go-website-upstream
The official approach adopts:
upstream.lock
+ CLI-managed local cache Checkout
upstream.lock will record:
- Official repository address;
- Fixed commit;
- Projector contract;
- Hash of
appengine.go; presentmodule 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:
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:
zh.go-tour.shuijingwanwq.com
It is now adjusted to:
https://go-tour.shuijingwanwq.com
The default language is Simplified Chinese, so the /zh/ prefix is not used.
For example:
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:
/ja/tour/...
/de/tour/...
The default Chinese will continue to use the address without a language prefix.
It will not simultaneously expose:
/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:
- Create the
go-tour-i18nlocal repository; - Initialize the Go Module;
- Integrate Cobra;
- Create
upstream.lock; - Implement the minimal equivalent projector;
- Generate raw-to-output provenance;
- Use
golang.org/x/tools/present v0.48.0; - Generate the integrated production manifest;
- Implement three minimal commands:
upstream verifymanifest buildmanifest show
- 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:
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.

