While continuing the A Tour of Go multilingual translation project, I was originally just preparing to complete the next representative calibration page methods/20.
The title of this page is:
Exercise: Errors
Which translates to “Exercise: Errors”.
Structurally, the page is not particularly complex: regular body text, inline code, a cross-section link, two preformatted Go code blocks, a bolded Note paragraph, and the final .play example.
Previously, the project had progressively refined the protection and validation of inline code, legacy present syntax, preformatted code, instructional code comments, and emphasis structures through several representative pages. I originally judged that this page could go straight into the first development translation.
Unexpectedly, the first translation failed again.
This time, however, the real problem was not GLM-5.2’s translation, but a rule I had previously designed that seemed reasonable but was actually too strict for multilingual translation:
All protected tokens must strictly maintain their original global order.
This troubleshooting ultimately led me to redraw a very important boundary:
Structural validation should be responsible for structural safety that machines can reliably determine, rather than misjudging normal word order changes between languages as structural damage.
1. methods/20 First Translation: API Normal, but Automatic Validation Failed
The official original text corresponding to methods/20 is as follows:
* Exercise: Errors
Copy your `Sqrt` function from the [[/tour/flowcontrol/8][earlier exercise]] and modify it to return an `error` value.
`Sqrt` should return a non-nil error value when given a negative number, as it doesn't support complex numbers.
Create a new type
type ErrNegativeSqrt float64
and make it an `error` by giving it a
func (e ErrNegativeSqrt) Error() string
method such that `ErrNegativeSqrt(-2).Error()` returns `"cannot`Sqrt`negative`number:`-2"`.
*Note:* A call to `fmt.Sprint(e)` inside the `Error` method will send the program into an infinite loop. You can avoid this by converting `e` first: `fmt.Sprint(float64(e))`. Why?
Change your `Sqrt` function to return an `ErrNegativeSqrt` value when given a negative number.
.play methods/exercise-errors.go
The first development translation was executed:
go run ./cmd/tour-i18n translate run --locale zh-CN --id methods/20 --dev
The GLM-5.2 request itself completed normally:
Attempt: 1
finish_reason: stop
But automatic validation failed:
{
"attempt": 1,
"api_success": true,
"token_valid": false,
"present_valid": false,
"failures": [
"protected token order mismatch at 1",
"protected token order mismatch at 2",
"protected token order mismatch at 6",
"protected token order mismatch at 7",
"protected token order mismatch at 10",
"protected token order mismatch at 11"
],
"passed": false
}
The page therefore remained in:
pending
At first glance, this looked like another case of the model failing to follow the Protected Token rules.
But upon further inspection, I found that was completely not the case.
2. 16 Protected Tokens, Not a Single One Lost
A total of 16 protected tokens were generated for this page.
GLM-5.2:
- Did not delete any tokens;
- Did not duplicate any tokens;
- Did not fabricate unknown tokens;
- All 16 tokens were fully preserved.
The only thing that actually changed was that three groups of tokens swapped their order of appearance:
Sqrtand the link target;errorand a preformatted code block;fmt.Sprint(e)andError.
In other words, this failure was not:
The model destroying protected content.
But rather:
The model rearranging some protected content according to natural Chinese word order.
The nature of these two problems is completely different.
3. First Reorder: Link Appears First, Sqrt Appears Later
Original text:
Copy your
Sqrtfunction from the earlier exercise…
In English word order, this roughly translates to:
Copy
→ your Sqrt function
→ from the earlier exercise
And the Chinese generated by the model was:
从之前的练习中复制你的
Sqrt函数……
The more natural Chinese word order is clearly:
From the earlier exercise
→ copy
→ your Sqrt function
Therefore, in the model’s output, the protected token corresponding to the link target appeared before Sqrt.
What the old validator saw was:
Source order:
Sqrt
→ link target
The model output became:
link target
→ Sqrt
So it judged:
protected token order mismatch
But in terms of translation quality:
“从之前的练习中复制你的
Sqrt函数”
Not only is this completely correct, but it is also clearly more natural than forcibly maintaining the English order.
4. Second Reorder: error Moved After the Code Block
This part of the original text is rather special:
and make it an `error` by giving it a
func (e ErrNegativeSqrt) Error() string
method such that ...
The English expresses:
By giving it the following method, make it an
error
After the model translated it, it became:
并为其实现
func (e ErrNegativeSqrt) Error() string
方法,使它成为一个 `error`
Thus:
`error`
→ preformatted code block
Became:
preformatted code block
→ `error`
If we only look at the global order of protected tokens, this certainly changed.
But further analysis of the present structure revealed:
- The second code block remained in its original code block position;
- The code content did not change by a single byte;
- The two preformatted code blocks were not swapped;
- Only the
errorin the regular body text moved from before the code block to after it.
In other words:
The code block did not move at all.
Only the organization of the natural language before and after the code block changed.
After final manual polishing, I adjusted this paragraph to:
并为其实现
func (e ErrNegativeSqrt) Error() string
方法,使其实现error接口……
Not only is the structure correct, but the technical expression is also more accurate than “make it an error“.
5. Third Reorder: Error and fmt.Sprint(e)
Original text:
A call to
fmt.Sprint(e)inside theErrormethod will send the program into an infinite loop.
The English order is:
fmt.Sprint(e)
→ Error
And the model translated it as:
在
Error方法中调用fmt.Sprint(e)会导致程序陷入无限循环。
In Chinese, it naturally becomes:
Error
→ fmt.Sprint(e)
The old rule thus judged the token order as incorrect again.
But there is no technical problem with this Chinese sentence.
On the contrary, if we were to forcibly maintain the English token order, writing something like:
调用
fmt.Sprint(e)在Error方法中……
The Chinese would clearly become worse.
6. This Was Not the First Time: generics/1 Had Already Exposed the Same Problem
After continuing to review previous translation records, I found that this was not the first time this had happened.
In a historical translation attempt for generics/1, the following also occurred:
T
→ comparable
Became:
comparable
→ T
The original text roughly expresses:
type
Tthat fulfills the built-in constraintcomparable
Natural Chinese, however, is better written as:
满足内置约束
comparable的任意类型T
This is also a very typical difference between English and Chinese word order.
At the time, I only treated it as an isolated token reorder failure.
It wasn’t until this methods/20 page had three similar cases in a row that I had enough evidence to confirm:
The problem was not that the model occasionally failed to follow the rules, but that the rule “all Protected Tokens must maintain global order” is inherently unsuitable as a multilingual structural invariant.
7. What Exactly Needs to Be Protected?
This raises a very crucial design question.
Assuming two inline code snippets swap positions after translation, can the validator determine based solely on the present AST or token order:
Is this a normal Chinese word order change?
Or:
Has the technical semantics actually been incorrectly reversed?
In many cases, the answer is no.
For example:
T / comparable
And:
fmt.Sprint(e) / Error
From the perspective of the present structure, they are both just program spans in regular body text.
Relying solely on static structural information, there is no general algorithm that can simultaneously:
- Allow correct cross-lingual word order changes;
- And accurately reject all incorrect transpositions in technical semantics.
If forced to implement this, it would likely devolve into an ever-growing set of heuristic rules tailored to English, Chinese, or even specific sentence patterns.
This runs counter to the project’s future goal of supporting more languages.
Therefore, I redefined the validator’s responsibilities:
The validator is responsible for the structural safety that machines can reliably verify.
Whether cross-lingual technical semantics are correct should not be disguised as a static structural validation capability.
8. Before Restoration: No Longer Requiring Global Protected Token Order Consistency
The modified Protected Token restoration rules remain very strict.
The model output must satisfy:
- Correct total token count;
- No unknown tokens allowed;
- Each expected token appears exactly once;
- No missing tokens allowed;
- No duplicate tokens allowed;
- Token payloads are still restored verbatim according to the deterministic mapping.
The only thing removed was:
All tokens must strictly maintain a single global original order.
At the same time, the boundary normalization for inline tokens was adjusted to be based on:
The token order in the model’s actual output
It is executed this way, and the corresponding rules are judged based on each token’s own type.
This allows the model to adjust positions for natural expression in the target language, but it still cannot:
- Delete code;
- Duplicate code;
- Modify code;
- Fabricate tokens;
- Substitute protected content.
9. After Restoration: Letting present Structural Validation Truly Take Responsibility for Structural Safety
Removing the global order restriction does not mean abandoning strict validation.
On the contrary, the post-restoration structural validation was simultaneously enhanced.
Inline Code
Previously, it required:
Content, count, and order all completely identical.
Now it is adjusted to:
Content and count multiset consistency, while still must correctly parse as a program span.
Therefore:
T / comparable
Can change due to target language word order.
But the following cases must still fail:
- An inline code snippet disappears;
- An extra inline code snippet appears;
- Content is modified;
- A legacy program span is broken.
10. Legacy Program Spans Still Strictly Protected
methods/20 also contains a very typical legacy present syntax:
`"cannot`Sqrt`negative`number:`-2"`
In present, this is actually a complete program span.
The final page displays it as:
"cannot Sqrt negative number: -2"
This kind of content is still protected as a complete payload.
The model cannot split it, nor can it modify the present encoding within it.
During the final browser check, this section correctly displayed as:
"cannot Sqrt negative number: -2"
There was no internal backtick leakage, and no program span breakage.
11. Links Still Maintain Stricter Rules
Link targets are different from regular inline code.
Currently, they are still strictly checked for:
- Target content;
- Target count;
- Target’s own order;
- Whether the target is still within a valid link structure.
In other words, although a:
link target
can have a cross-category positional change with an adjacent Sqrt due to Chinese word order, multiple link targets still cannot be arbitrarily swapped with each other for now.
This is an intentionally conservative strategy.
There are currently no real cases proving that multiple links must be swapped for target language word order, so there is no need to prematurely broaden the relaxation scope.
12. Preformatted Code Still Strictly Validated Block by Block
For preformatted code, the rules remain strict.
Continuing to check:
- Code block count;
- Code block order;
- Static code block bytes;
- Code structure within translatable instructional comments;
- The Section to which the code block belongs.
Therefore, although this time it allowed:
`error`
→ code block
To be adjusted to:
code block
→ `error`
It absolutely does not allow:
first code block
↔ second code block
To be swapped.
Nor can a code block be moved to another Section.
This is exactly:
Natural language can be reorganized, but structural objects themselves cannot be destroyed.
13. Directives Also Added Section-Level Protection
Directives like .play and .image also continue to be strictly checked for:
- Content;
- Count;
- The directive’s own order;
- The Section to which it belongs.
In addition, a rule that machines can stably determine was added this time:
If the source directive is the last element in its Section, it must also remain the last element in the candidate.
For example:
.play methods/exercise-errors.go
It not only determines the code file, but also determines the position where the example code appears on the instructional page.
If the model moves the .play, originally located at the end of the page, into the middle of the explanatory text, even if present can still parse it, it should be considered a structural protection failure.
However, I did not further require:
Directives must maintain the exact same relative position with all regular text paragraphs.
Because regular natural language may inherently be split, merged, or have its expression order adjusted due to translation.
14. Fixed Translation Prompts Must Also Be Synchronized
Modifying only the validator is not enough.
The previous fixed zh-CN translation prompt also required:
Protected Token count, order, and form consistency.
If the validator already allows natural word order adjustments, but the prompt still requires strict global order consistency, the two sets of rules contradict each other.
Therefore, the prompt was synchronized and modified this time:
- All Protected Tokens must be fully preserved;
- Each token must appear exactly once;
- Must not be modified;
- Must not be deleted;
- Must not be copied;
- Must not be fabricated;
- Necessary positional adjustments for natural target language word order are allowed;
- But structural relationships such as links, code, directives, and preformatted code must not be broken.
This is not a special rule just for Chinese.
It is more suitable as a common principle to follow for future multilingual expansions:
Protect structure, not English word order.
15. The Most Critical Validation: No New GLM-5.2 Call
After the modifications were completed, I did not immediately perform a second translation.
Because if I called GLM-5.2 again, even if the new result passed, it could not directly prove:
The old result was truly only rejected by an incorrect validation rule.
Therefore, I kept the original generated by the first translation:
request.jsonresponse.jsonvalidation.json
Then I directly used the modified production code to perform a deterministic replay on the same original attempt-001 response.
That is to say:
The model output itself remained completely unchanged.
Only the validation rules changed.
Old result:
protected token order mismatch at 1
protected token order mismatch at 2
protected token order mismatch at 6
protected token order mismatch at 7
protected token order mismatch at 10
protected token order mismatch at 11
Under the new rules, the same response:
- token integrity: PASS
- token restoration: PASS
- present parsing: PASS
- inline-code: PASS
- link target: PASS
- preformatted block: PASS
- directive: PASS
- emphasis / font span: PASS
- glossary: PASS
- Section structure: PASS
- candidate validation: PASS
This replay was crucial.
It directly proved:
What failed was not the translation, but the old validation rule.
16. Therefore, No attempt-002 Was Created
Since the original attempt-001:
- API request was normal;
finish_reason=stop;- All 16 tokens were intact;
- Fully passed after fixing the rules;
- Manual review found no technical mistranslations;
Then calling GLM-5.2 again just for the sake of process formality has no practical meaning.
Ultimately, it still retains:
methods/20
ready
Attempts=1
A second model translation that did not actually exist was not fabricated.
The historical record of the first failure was also not overwritten.
The old validation.json still retains the six order failures from that time, serving as complete historical evidence for this rule evolution.
17. Final Manually Polished Translation
The final adopted translation is as follows:
* 练习:错误
从[[/tour/flowcontrol/8][之前的练习]]中复制你的 `Sqrt` 函数,并修改它,让它返回一个 `error` 值。
当传入负数时,`Sqrt` 应当返回一个非 nil 错误值,因为它不支持复数。
创建一个新类型
type ErrNegativeSqrt float64
并为其实现
func (e ErrNegativeSqrt) Error() string
方法,使其实现 `error` 接口,这样 `ErrNegativeSqrt(-2).Error()` 就会返回 `"cannot`Sqrt`negative`number:`-2"`。
*注意:* 在 `Error` 方法中调用 `fmt.Sprint(e)` 会导致程序陷入无限循环。可以先转换 `e` 来避免这个问题:`fmt.Sprint(float64(e))`。为什么?
修改 `Sqrt` 函数,使其在传入负数时返回一个 `ErrNegativeSqrt` 值。
.play methods/exercise-errors.go
Main manual adjustments include:
- Changed “前一个练习” to “之前的练习”;
- Changed “并修改它使其返回” to “并修改它,让它返回”;
- Changed “非 nil 的错误值” to “非 nil 错误值”;
- Changed “使它成为一个
error” to the more accurate “使其实现error接口”.
Then re-executed the formal candidate translation validation:
candidate OK: locale=zh-CN page_id=methods/20
18. Actual Browser Comparison of English and Chinese Pages
After completing the candidate translation validation, I also started the local Tour to perform an actual browser comparison between the methods/20 English original page and the Chinese candidate page.
English Original Page

methods/20 “Exercise: Errors” English original course pageThe English page here can serve as a reference for the final visual structure, including:
- Body text;
- Cross-section link;
- Two preformatted Go code blocks;
- Note prompt;
- Right-side
exercise-errors.goexample.
Chinese Candidate Page

The Chinese page screenshot particularly illustrates intuitively why this modification was necessary.
For example, the old validator once rejected:
fmt.Sprint(e)
→ Error
Becoming:
Error
→ fmt.Sprint(e)
But from the final Chinese page, you can directly see:
在
Error方法中调用fmt.Sprint(e)会导致程序陷入无限循环。
This is exactly normal and natural Chinese expression, and it did not cause any present structural damage.
The actual browser comparison confirmed:
- The “练习:错误” title is normal;
- The “之前的练习” link is normal;
- The positions and content of the two Go code blocks are correct;
"cannot Sqrt negative number: -2"renders normally;- Internal backticks for the legacy present are not exposed;
- “注意:” is correctly bolded;
- The Chinese remains natural after adjusting the order of
Errorandfmt.Sprint(e); - The
exercise-errors.gocorresponding to.playloads normally; - The page has no present parsing, rendering, or runtime exceptions.
Therefore, the final browser results further prove:
The three groups of Protected Token reorders rejected by the old rule did not cause structural problems, but were instead normal cross-lingual word order adjustments.
19. Complete Tests Pass Again
After the fix was completed, and the methods/20 commit status baseline was synchronized, the complete tests passed again:
ok github.com/shuijingwan/go-tour-i18n/internal/i18n 0.499s
git diff --check also all passed.
Final commit:
13d9129b3bb0223b5e39af7679868832352e3b42
Commit message:
feat: 完成 methods/20 并修复受保护标记顺序校验
Subsequently pushed to origin/main.
Project repository:
20. Biggest Takeaway This Time: Structural Safety Does Not Equal Unchanged English Order
This issue made one thing clearer to me.
When developing a multilingual translation system, it is easy to conflate:
“Original structure”
And:
“Original surface order”
But they are not the same thing. For example:
fmt.Sprint(e)
→ Error
Is natural order in English.
In Chinese, however, it is more naturally written as:
Error
→ fmt.Sprint(e)
What truly needs to be maintained is:
Erroris still a correct program span;fmt.Sprint(e)has not been modified;- Neither is missing;
- present can still parse correctly;
- The technical meaning remains correct.
Rather than:
The two strings must forever be arranged in the order they appear in English.
Similarly:
“从之前的练习中复制你的
Sqrt函数”
Should not be judged a structural failure just because the link target appears before Sqrt.
21. Stricter Automatic Validation Does Not Mean Higher Quality
This issue also has a very worthwhile lesson to record: strict validation itself is not the goal.
If a rule can reject a large amount of content but cannot distinguish between:
- Actual structural damage;
- Normal cross-lingual word order adjustments;
Then the stricter the rule, the more false positives it produces.
A good automatic validator should check facts that machines can truly determine as strictly as possible.
For example:
- Whether tokens are missing;
- Whether there are duplicates;
- Whether there are unknown tokens;
- Whether code has changed;
- Whether link targets have been modified;
- Whether directives still belong to the correct Section;
- Whether preformatted code blocks have been swapped;
- Whether present can still parse correctly.
As for:
“After inline code is reordered, is the cross-lingual technical semantics still correct?”
If static structure cannot reliably determine this, this boundary should be acknowledged, rather than designing a seemingly strict but actually unreliable pseudo-semantic rule.
22. Next Step: Complete the Final 3 Representative Pages
Currently, the representative page calibration phase has completed 4 pages:
generics/1flowcontrol/8methods/16methods/20
Next, it has been decided to continue processing another 3 different types of representative pages.
concurrency/7
The only formal page in the official course that contains .image.
Main validations:
.imagedirective;- Image and text mixed layout;
- Non-tail directive;
- JavaScript target link.
concurrency/11
Long, multi-paragraph, high-link-density page.
Main validations:
- Large number of link targets;
- Cross-paragraph links;
slidesforced term labels;- Long text stability without code and directives.
methods/24
API documentation-style page.
Main validations:
- Longer static interface code block;
- Multiple API links;
- Emphasis paragraphs;
- Tail
.play.
If these 3 pages also pass stably, the total representative pages will reach 7.
At that point, we will be ready to end this phase of page-by-page calibration and enter:
Automatic trial run of 10 regular pending pages.
If the 10-page trial run continues to be stable, we will then enter the batch translation phase for the remaining pages.
Conclusion
methods/20 was originally just another planned representative calibration page.
Ultimately, however, it helped this project discover and fix a more fundamental issue:
The global order of Protected Tokens is not a reliable multilingual structural invariant.
The most convincing part this time is that I did not bypass the problem by:
“Translating again until it passes”
Instead, I kept the original failed response, fixed the validator, and then let the same model output go through the complete production validation again.
This ultimately proved: GLM-5.2 had actually translated successfully the first time.
What truly needed correction was the automatic validation system’s definition of “structural safety”.
For a project preparing to expand from Simplified Chinese to more languages, this kind of issue is more important than completing any specific page itself.
Because what truly needs to be built is not a set of rules that “tries to make translations maintain the surface order of the English original”.
But rather a multilingual translation process that allows different languages to express themselves naturally, while still strictly protecting code, links, directives, and present structures.
