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

A Tour of Go in Chinese: Practical Development with an English Baseline, Online Execution, and a 101-Page Upstream Sync System

作者:

在

,

In the previous article, I completed the design freeze for the Chinese version of A Tour of Go, focusing on issues such as page count, translation units, state workflows, and command-line architecture. (Shuijing Wanwq)

After the design freeze, I did not immediately call GLM-5.2 to start translating.

Once I actually entered the implementation phase, I first completed three more fundamental tasks:

  1. Investigate the official Tour and multiple existing translation projects;
  2. Extract an independently runnable English baseline from a fixed official source code commit;
  3. Build a machine-readable table of contents, persistent identities, and upstream change previews for the 101 lesson pages.

During this phase, I also specifically verified how the Go example code on the right side runs.

A Tour of Go is not just a set of static lesson articles. Readers can directly modify the example code, format the code, and click Run. How to retain this capability without running untrusted code on an ordinary web server is an unavoidable problem for this project.

This article does not repeat the full-page translation principles already explained in the previous article. Instead, it records what these designs ultimately became once they entered the code implementation phase.

1. Before starting development, I investigated six translation projects

Before importing the official source code, I conducted a read-only investigation of six representative A Tour of Go translation projects:

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

These projects cover several common approaches:

ApproachRepresentative form
Full copy of golang/websiteChinese website project
Copy of legacy single-language TourChinese, Japanese, Indonesian, and Brazilian Portuguese projects
Generate static pages from legacy TourJapanese static project
Independent deployment for each languageMost community translation sites

These projects are not without value.

On the contrary, they proved that basic directions are feasible: full .article translation, separating lesson text from the public UI, using /socket for local execution, and the official website calling a remote Playground.

However, after years of maintenance, several common problems gradually emerged.

2. Single-language copying enables a quick launch, but incurs high long-term maintenance costs

The approach adopted by most legacy translation projects can be summarized as follows:

Plaintext
Copy a Tour server
→ Copy templates and static assets
→ Copy all Go examples
→ Translate lesson text
→ Deploy a separate language site

The advantages of this approach are obvious.

When a project starts, maintainers only need to copy the official code, modify .article and UI copy, and they can quickly get a runnable translation site.

The problem is that every added language introduces an independently maintained set of server code, templates, JavaScript, example code, and deployment configurations.

After the official Tour updates, every translation project needs to sync separately:

  • Server code;
  • Frontend assets;
  • Lesson pages;
  • Go examples;
  • Third-party dependencies;
  • Deployment runtime.

If a language project stops being maintained, it often does not immediately fail completely.

A more common scenario is that the site remains temporarily accessible, but the lesson content, dependencies, and deployment methods gradually fall behind.

Therefore, this project chose not to copy a permanently independent Tour for Simplified Chinese, nor does it plan to copy more servers for Japanese, German, or other languages in the future.

The currently established principles are:

  • Share the Go server as much as possible;
  • Share the base template structure and static assets as much as possible;
  • Maintain only a single official baseline for Go examples;
  • Independently maintain only the lesson translations, UI copy, glossary, and page status for each language.

3. Copying the entire website creates another set of problems

The official A Tour of Go is currently located in the golang/website repository.

The most straightforward approach seems to be forking the entire website and then modifying the Tour inside it.

But golang/website contains not only the Tour, but also:

  • The official Go website service;
  • Blog and documentation content;
  • Download pages;
  • Package documentation logic;
  • Official build and deployment configurations;
  • App Engine and Cloud Build files;
  • A large amount of static assets and tools unrelated to the Tour.

If the goal is only to maintain a multilingual Tour, copying the entire website would mix a large number of irrelevant changes into every upstream sync.

Therefore, I ultimately adopted a different approach:

Extract the minimal source code closure required for the Tour to run independently from a fixed official commit.

The currently fixed official baseline commit is:

e11dacba76c5aae474746e9eedee19693f492803

The initial import involved 199 upstream source files:

  • 192 files remain byte-for-byte identical;
  • 7 files underwent minimal modifications to adapt to the standalone repository.

The project uses UPSTREAM_MANIFEST.tsv to record the following for each source file:

  • Upstream path;
  • Local path;
  • File type;
  • Whether it is kept as-is;
  • SHA-256;
  • Necessary adaptation notes.

This avoids both copying the entire website and the network dependencies and non-reproducibility caused by temporarily downloading source code during every build.

4. Long-term problems observed in other translation projects

Beyond redundant server maintenance, this investigation also exposed several more noteworthy issues.

1. Many projects lack a clear upstream version

Having a lesson translation in a Git repository does not directly indicate which official commit it corresponds to.

Recent commits do not guarantee that the lesson text has been synced.

Therefore, this project simultaneously records:

  • Fixed upstream commit;
  • File-level SHA-256;
  • Page-level source_sha256;
  • Page identity and current path;
  • Categorized results of future upstream changes.

2. Deployment platform retirement can take an entire language site offline

Multiple legacy translation projects are deeply tied to the App Engine runtime.

When the legacy runtime was retired, a batch of community translation sites went offline simultaneously. Although the official Go project provided entry points for these languages on its pages, these sites were owned by community maintainers, and the official project could not directly restore deployments on the maintainers’ behalf.

This demonstrates that:

Source code still existing does not equate to the online service being able to run long-term.

Therefore, the current project does not treat a specific cloud platform’s proprietary deployment files as core architecture, nor does it write personal project IDs, account configurations, or fixed runtimes into the general workflow.

3. Without automated validation, errors rely primarily on readers to be discovered

Existing translation projects typically have basic Go tests, but rarely possess:

  • Structural comparison between English pages and translations;
  • .play path integrity checks;
  • Link target checks;
  • Inline code checks;
  • Page identity checks;
  • Translation status gates;
  • Upstream change previews;
  • Blocking publication of broken translations.

This means that even if a translation breaks the lesson structure, it might not be noticed until a maintainer or reader stumbles upon it.

4. Example code easily diverges across languages

Some translation projects modified comments or strings in the example code.

In the short term, this makes the code appear more localized; in the long term, it leads to different example baselines for different languages.

After the official code is modified, maintainers must not only sync the code but also judge whether the modifications in each language version still apply.

Therefore, the first phase decided:

  • Go examples referenced by .play remain exactly as the official original;
  • Variable names, function names, and output strings are not translated;
  • A different set of examples is not copied for each language;
  • Hashes and tests are used continuously to confirm that no unintended changes occur in the examples.

5. Purely static pages are easier to deploy, but lose full interactivity

The static Japanese project proved that lesson text can be pre-generated into ordinary HTML.

This approach is easy to migrate and does not depend on a continuously running Go service.

However, static pages usually cannot fully preserve:

  • Editor;
  • Run;
  • Format;
  • Kill;
  • Interaction with the Playground.

Therefore, static pages can serve as a read-only backup or a graceful degradation product in the future, but they are not suitable as the sole official form for the current project.

5. The English Tour baseline can now run independently

After completing the minimal source code import, the project no longer depends on the full golang/website repository to run the Tour.

The current English baseline has completed the following verifications:

  • Go Module resolves correctly;
  • All Go tests pass;
  • Seven .article can be parsed by present;
  • Example code completes the original build and run tests;
  • Tour HTTP service starts normally;
  • Pages, CSS, JavaScript, Logo, and favicon are accessible;
  • /_/fmt formats code correctly;
  • Local /socket completes the WebSocket handshake;
  • No listening ports or temporary files remain after the process exits.

The current source code structure statistics are:

ItemCount
Top-level .article7
Standalone regular pages101
Regular .play references92
.image references1
#appengine: conditional pages2

This set of 7 / 101 / 92 / 1 data has become part of the current fixed English baseline.

6. Why the previous article had 103 pages, but the current status file only has 101

The previous article counted 103 pages from the official integrated production projection perspective.

The two extra pages were:

  • Go offline (optional)
  • The Go Playground

They come from the #appengine: conditional content in welcome.article.

Upon actually starting the standalone Tour import, I decided to first establish the first-phase status using the 101 regular pages in the current standalone runtime mode, while separately recording the two conditional pages in:

data/tour-conditional-pages.tsv

Therefore, the current structure does not ignore those two pages; rather:

Plaintext
101 standalone regular pages
→ Enter zh-CN page status

2 appengine conditional pages
→ Registered separately
→ Not entering first-phase translation status for now

This allows the first-phase page table of contents to remain consistent with the current standalone runtime baseline, while preserving the source, title, and hash of the conditional pages.

When actually implementing the production projection or equivalent pages later, I will explicitly decide how to handle these two pages, rather than mixing the standalone and App Engine runtime modes into the same status file now.

7. Where exactly does the Go example code on the right run

One of the most important features of A Tour of Go is that readers can directly modify the code on the right and click Run.

However, “clicking Run” does not mean that all environments run the code the same way.

Local development mode

When starting the Tour locally, the execution flow is roughly:

Plaintext
Code editor in browser
→ WebSocket /socket
→ Local Tour process
→ Local Go toolchain
→ Return execution result

This approach is suitable for local learning and development on personal computers.

After importing the English baseline, the current project also retains this upstream behavior and actually verified that /socket can complete the WebSocket 101 Switching Protocols handshake.

However, /socket calls the Go toolchain on the machine hosting the Tour to execute code, so it can only be used in a trusted local development environment.

It cannot directly serve as a code execution interface for a public website.

Official production mode

The current official Tour production flow is not the browser running Go locally, nor is it the go.dev ordinary web process directly executing user code.

The actual flow is:

Plaintext
Browser
→ Same-origin /_/compile
→ go.dev server proxy
→ play.golang.org/compile
→ Go Playground sandbox
→ Return execution result

Go Playground compiles and runs code in a restricted sandbox, limiting execution time, CPU, memory, and external access. The official project also allows third-party services beneficial to the Go community to use the Playground, but requires callers to contact them in advance and provide a recognizable, unique User-Agent. (Go)

Approach planned for the current project in the future

The official website still plans to keep Run, but will not expose the public /socket to the internet.

The planned official flow is:

Plaintext
Browser
→ Project same-origin lightweight proxy
→ Official Go Playground
→ Return execution result

The “proxy” here is only responsible for forwarding restricted requests.

It does not mean that user code runs on the project’s web server.

This retains the interactive learning experience while avoiding the need to take on malicious code isolation, system call restrictions, and full sandbox operations ourselves.

However, this part is currently still just the established official plan; the proxy has not yet been implemented and is not publicly online.

8. The 101 lesson pages now have a machine-readable table of contents

After completing the English baseline, the project did not immediately start translating, but first generated:

data/tour-pages.tsv

Each page records:

  • page_id
  • Parent article;
  • Current Section number;
  • Current route;
  • English title;
  • Full page source_sha256;
  • .play count;
  • .image count.

For example, a page can have the following relationships:

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

The hash here is not generated based solely on the title, nor is it generated from an abstract syntax tree.

It corresponds to the full top-level page source text exported from the English article, including:

  • Page title;
  • Regular body text;
  • present directives;
  • Code references;
  • Trailing newline.

When the official project modifies a page later, this project can accurately determine whether the English source has changed, rather than relying solely on file modification times or manual visual comparison.

9. Pages can be exported individually by full Section

The current maintenance tool can already export a complete English page based on page_id:

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

Export results:

  • Contain only one complete top-level Section;
  • Do not include other pages from the same article;
  • Maintain the original present format;
  • Can be re-parsed;
  • SHA-256 matches the page table of contents;
  • Do not overwrite existing files by default.

In the future, GLM-5.2 will receive such complete pages, rather than a few sentences or JSON fields extracted from a page.

This design principle was already detailed in the previous article and will not be repeated here.

10. The 101 zh-CN pages are currently all pending

The project has created an independent Simplified Chinese language space:

locales/zh-CN/

It separately maintains:

  • Locale metadata;
  • Page status;
  • Future full page translations;
  • UI copy boundaries;
  • Glossary.

Currently, status.tsv has exactly 101 records:

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

This means the project knows which pages need to be processed, but has not yet generated any official Chinese translations.

No English content is masquerading as Chinese pages, nor were empty .article files created prematurely just to show progress.

11. Structural validation no longer locks down regular paragraphs

During the design phase in the previous article, I leaned towards strictly preserving the count, order, and position of regular paragraphs.

After actually analyzing the 101 pages and implementing the candidate validator, I adjusted this rule.

The principle is now:

Only strictly protect content that is genuinely dangerous or untranslatable; do not restrict normal Chinese expression just to match the form of English paragraphs.

Content that must currently be preserved includes:

  • .play directives and target paths;
  • .image directives and target paths;
  • Link target URLs;
  • JavaScript link targets;
  • Inline code;
  • Preformatted code;
  • present directives;
  • The count and order of protected content;
  • The count of top-level Sections.

Content allowed to change includes:

  • Page titles;
  • Regular body text;
  • Chinese word order;
  • Sentence count;
  • Regular paragraph count;
  • Regular text line breaks;
  • Link display text.

For example, the link display text below can be translated:

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

However, the link target /cmd/gofmt/ cannot be modified by the model.

Similarly, .play welcome/hello.go can appear in the translated page, but the paths inside must remain exactly as they are.

This preserves the context needed for full-page translation while avoiding awkward Chinese phrasing caused by pursuing mechanical structural consistency.

12. page_id cannot always equal the page position

When the page table of contents was initially generated, page_id looked completely identical to the current route:

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

But if the official project later inserts a page before Basics page 5, the original page 5 will move to page 6.

If IDs continue to be regenerated based on position, all subsequent pages will change:

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

This would further cause:

  • Translation file path changes;
  • Loss of association in status records;
  • Inability to match historical Candidates;
  • Identity drift in published pages;
  • A large number of meaningless changes in sync results.

Therefore, the current 101 page_id have been officially frozen as persistent identities.

The fields now have different responsibilities:

FieldMeaning
page_idInternal persistent project identity
routeCurrent access path
section_numberCurrent upstream location
source_titleIdentification and diagnostic information
source_sha256Current English source version

In the future, even if a page moves, route and section_number can change, while page_id remains unchanged.

New pages will not insert a number and reshuffle old pages; instead, they will be explicitly assigned a never-before-used new ID.

13. Upstream changes can now be previewed first

The project added a read-only upstream change preview command:

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

The preview results categorize pages into six types:

TypeMeaning
unchangedPage content and position are unchanged
content_changedCan be confirmed as the same page, but English content has been updated
movedFull source content is identical, but position or route has changed
addedNew page added upstream
removedPage removed upstream
ambiguousInsufficient evidence, cannot be safely matched automatically

The matching strategy remains conservative.

If the full source hash is uniquely identical, a page move can be confirmed.

If the current route still exists and the protected structure remains compatible, a change in regular content can be confirmed.

However, the following situations will not be automatically migrated:

  • A page moves while simultaneously undergoing significant structural changes;
  • .play path changes;
  • Multiple pages share duplicate characteristics;
  • Matching can only be determined by identical titles;
  • Matching can only be determined by similar regular body text;
  • Moving across articles without unique evidence.

These cases will enter ambiguous, awaiting explicit handling.

The preview command will not:

  • Modify the official table of contents;
  • Modify status.tsv;
  • Delete language status;
  • Create new page IDs;
  • Move Candidates;
  • Automatically overwrite translations.

14. Preview results for the current fixed baseline

Running the preview twice using the current fixed upstream yields completely identical output:

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

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

conditional unchanged:2

This indicates that the official page table of contents is completely consistent with the current English source code, and also proves that the preview output is deterministic.

At the same time:

  • 101 page_id have not changed;
  • status.tsv remains byte-for-byte unchanged;
  • The two conditional page directories have not changed;
  • English articles have not changed;
  • Go examples have not changed.

15. Multilingual domain names have also shifted from a path scheme to subdomains

During the design phase, using paths under the same domain was considered:

Plaintext
/ja/
/de/

After actually considering CDN and deployment, I switched to using subdomains by language.

The current plan is:

Plaintext
go-tour.<actual-domain>
→ Default Simplified Chinese
→ EdgeOne

en.go-tour.<actual-domain>
→ English
→ Cloudflare

ja.go-tour.<actual-domain>
→ Future Japanese
→ Cloudflare

The default site directly provides zh-CN, without creating zh. or zh-cn. subdomains.

Project introduction, language selection, upstream baseline, and GitHub entry will be placed on the /about/ page of the default site.

This way, different languages can independently choose in the future:

  • CDN;
  • Deployment region;
  • Caching strategy;
  • Release time;
  • Failure handling method.

Currently, these are just recorded domain and deployment plans; no actual DNS, certificates, or CDN configurations have been created.

16. This phase still has not translated any official pages

As of now, the project has completed:

  • Minimal English Tour source code closure import;
  • 199 source file tracking;
  • Independent English Tour execution;
  • Full parsing of seven articles;
  • 101 regular page table of contents;
  • Two conditional page table of contents;
  • 92 .play reference validations;
  • Example code build and run tests;
  • Single-page full export;
  • zh-CN status file;
  • Candidate structural validation;
  • 101 persistent page_id;
  • Upstream change preview;
  • Multilingual domain and CDN boundary planning.

But the current status is still:

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

This is not project progress stagnating.

On the contrary, it means that before the first translation appears, the project has already established:

  • Reproducible English sources;
  • Traceable page identities;
  • Verifiable structural boundaries;
  • Previewable upstream changes;
  • A status foundation that does not rely on human memory.

17. The next step is where GLM-5.2 is actually called

The next phase will enter the official translation pipeline:

Plaintext
Export full English page based on page_id
→ Protect dangerous structures
→ Call GLM-5.2 for full-page translation
→ Save Candidate
→ present re-parsing
→ protected structure comparison
→ Update attempt count and page status

This phase still will not immediately batch process all 101 pages.

A more prudent approach is:

  1. First implement the single-page translation executor;
  2. Complete automated testing using a mock service;
  3. Select a shorter page for the first real translation;
  4. Verify Candidate persistence and failure recovery;
  5. Verify ready and blocked state transitions;
  6. Finally decide when to start batch translation.

Only then will this project actually generate the first Simplified Chinese lesson page.

And the work completed now ensures that after the first translation appears, it will not become an isolated text whose source cannot be confirmed, whose expiration cannot be judged, and whose structure cannot be automatically validated.

系列导航

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

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

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

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

关于我  ·  GitHub  ·  邮件联系