While maintaining the Go Learning & Documentation Translations multilingual project, I have always held a basic principle:
Translations that have already undergone independent review, and whose content has not been genuinely affected, should not be mechanically retranslated and re-reviewed due to subsequent project expansion.
There is no problem with this principle itself.
The problem arises here: as the project gradually expanded from the initial A Tour of Go to the Site shell, Learn, Docs, and more future content packages, the evidence chain that the machine must maintain to prove “an existing translation can still be safely reused” has become increasingly complex.
Recently, while handling zh-CN, this issue finally shifted from “architectural complexity” to a very real performance problem.
Why should a task that only requires reviewing 1 page take 6 minutes to run?
After the glossary for this zh-CN was uniformly refreshed and independently reviewed, most of the original Tour translations remained reliable.
The project currently uses a glossary compatibility mechanism.
Simply put, when the glossary changes, it does not directly invalidate all old review results. Instead, it calculates:
Old glossary
↓
Semantic delta
↓
All current translation contexts
↓
Which contexts are genuinely affected
↓
Only re-review the affected scopeThe goal of this design is very clear:
A minor glossary change should not trigger a re-review of the entire language pack.
In the current zh-CN evidence, this mechanism has indeed proven effective.
The entire compatibility inventory contains 470 contexts, of which only 32 are judged to be affected; looking solely at TranslationUnits, there are only 3 TUs affected, 119 TUs compatible.
From a language review cost perspective, this is ideal.
But then a completely unacceptable problem emerged:
quality-check scope
quality-check reviewer-bundle
quality-check reviewer-bundle-check
quality-check finalize
retranslation exportThese commands, which should inherently be deterministic mechanical operations, began frequently requiring around 6 minutes to complete.
A real quality-check scope test took:
wall time: 5:58.68
user CPU: 400.28sAnd the final output was merely the QC scope for 122 TranslationUnits.
This is no longer just a matter of the “CLI being a bit slow.”
If every revision, Reviewer Bundle, and finalization requires waiting several minutes, then as the 65 locales, Learn/Docs, and future content packages continue to expand, this workflow itself will gradually become unmaintainable.
Thus, two options emerged.
Option 1: Preserve glossary compatibility, but thoroughly optimize the implementation
The first option is to retain the current semantic-delta / compatibility architecture.
That is, still adhering to:
Glossary changed
↓
Machine calculates the genuinely affected scope
↓
Trusted unchanged A continues to carry
↓
Only re-review affected TranslationUnitsInitially, I was worried that the 6-minute delay might be an unavoidable cost of this precise compatibility model itself.
If that were the case, continuing to optimize would not make much sense.
However, after further inspecting the code, the situation was more optimistic than anticipated.
Currently, when BuildQualityCheckScope iterates over TranslationUnits, it makes a call per Unit:
ResolveGlossaryCompatibility(
root,
locale,
previousGlossarySHA,
currentGlossarySHA,
"tu:"+unitID,
catalog,
)The problem is that this API appears to be querying:
Is
tu:concurrency/6compatible?
But what it actually does internally is closer to:
Rebuilding the entire glossary compatibility world, and finally querying
concurrency/6.
Every call across glossary SHAs re-verifies the current Glossary Review, rebuilds the full 470-context inventory, rescans TranslationUnit source/candidate, re-reads compatibility evidence, reloads archived glossaries, and recalculates semantic delta and classification, only to finally return the result for a single scope.
So what should originally have been similar to:
Build compatibility state once
+
122 map lookupsturned into approximately:
122 × full compatibility reconstructionThe current complexity is evaluated as approximately:
O(N × M)By introducing an invocation-local prepared state, it could be reduced to approximately:
O(M + N)This means the full compatibility world is built only once per command, and all subsequent Units share it.
This implies that a large portion of the current minute-level latency is not an inherent cost of the compatibility concept itself, but rather the same expensive computation being repeatedly executed over a hundred times.
Therefore, Option 1 is still worth trying.
Moreover, this optimization must be shared across all languages.
zh-CN only exposed the problem first because its current evidence and lineage are sufficiently complex. The underlying implementation itself is a generic shared code path and does not require separate optimizations for Chinese, Spanish, or German.
The correct design should be:
locale
old glossary SHA
new glossary SHA
current inventory identity
↓
Prepare once
↓
Shared by all TranslationUnitsInstead of:
if locale == "zh-CN" {
fastPath()
}This point can be clarified now: if compatibility is ultimately retained, its optimization will be a one-time repository-level optimization, and all locales will automatically benefit.
Option 2: Fall back to the simplest glossary SHA gate
The second option is much more aggressive.
Directly abandon the precise affected-scope carry after a glossary change.
The rules become:
Glossary SHA unchanged
→ Unchanged A can carry normally
Glossary SHA changed
→ All old QC A no longer automatically carry
→ Full QC
→ Reviewer re-reviews all current TranslationUnits
→ Only Units that are genuinely B/C/D enter revisionThere is an important distinction here:
Full QC does not equal full regeneration.
Assume there are 122 Tour TranslationUnits after a glossary change.
It does not mean retranslating all 122.
It means re-reviewing all 122.
If 119 of them are still correct, the Reviewer continues to give them an A; only the 3 genuinely affected by the new terminology policy enter targeted revision.
The biggest advantage of this option is its simplicity.
The machine no longer needs to work hard to prove:
Why can this A from three months ago still be trusted after two glossary changes?
It only needs to determine:
SHA same?If not, re-review.
From a fail-closed and recovery perspective, this design is very easy to understand and unlikely to produce false carries.
However, further evaluation revealed that Option 2 is not as cheap as imagined.
Current compatibility does not solely serve TranslationUnit QC carry.
Processes like Course SEO, Surface Review, sitecontent package, historical closure, and protected-input recovery also rely on similar compatibility / provenance mechanisms.
Therefore, switching to hash-only does not mean we can simply delete the entire compatibility subsystem.
A more realistic outcome would likely be:
New QC workflow
→ Uses simple SHA gate
Historical evidence / Course SEO / Surface / recovery
→ Continues to retain compatibility legacy pathThis ultimately forms a dual-track system where “the new rules are simple, but the old rules remain complex.”
This is why the current assessment considers Option 2’s migration complexity to still be very high.
The crucial common prerequisite: stabilize the glossary first
While discussing both options, I became increasingly certain of one thing:
Whether we ultimately retain compatibility or fall back to the glossary SHA gate, we should first stabilize the glossary as much as possible.
The project is no longer just Tour.
A locale permanently has only one:
locales/<locale>/glossary.yamlIt serves the entire Go learning and documentation system, not just a single version.
If the glossary is still built like this:
Working on Tour
→ Add Tour terms
Working on Learn
→ Add Learn terms
Working on Docs
→ Add Docs terms
Adding future package
→ Add another round of termsThen the glossary SHA will inevitably change frequently.
This is bad for both options.
For Option 1, it means compatibility assessment and lineage will constantly grow.
For Option 2, it means continuously triggering full QC.
A more reasonable direction is to expand the terminology discovery scope of glossary generation in advance.
This does not mean turning the glossary into a massive English-Chinese technical dictionary, but rather:
Complete Go / go.dev terminology discovery corpus
↓
Discover terms across the entire ecosystem that may require long-term governance
↓
Filter
↓
Retain only genuinely high-value project-level terminology decisionsThat is to say:
Expand the corpus, not the definition of the glossary.
The project’s existing principles remain unchanged:
The glossary should be “as small as possible, but must be valuable.”
What truly deserves to enter the glossary should still be words like these:
- Concepts unique to Go that are prone to technical misunderstanding;
- Words with multiple reasonable translations that require a unified project choice;
- Concepts that appear repeatedly across Pages / surfaces and are prone to terminology drift;
- Technical names that must retain their original identity;
- High-impact expressions like UI, navigation, and project naming.
Ordinary, natural, and clearly unambiguous words should not mechanically enter the glossary just because they appear in the corpus.
The current frozen corpus already covers Tour, Site shell, Learn YAML, Learn/Docs Pages, Course SEO, and other current official content, comprising 403 contributors and 3,004 contexts; however, it is still not a complete Go/go.dev terminology discovery corpus.
In the future, a more ideal discovery scope could continue to cover the language specification, memory model, modules, workspace, toolchain, testing, benchmarking, fuzzing, profiling, tracing, PGO, security and supply chain, as well as high-value cross-ecosystem concepts in the standard library.
These discovery-only sources do not necessarily mean the corresponding pages have already been localized.
They simply help the glossary make long-term terminology decisions in advance.
This way, when a future content package is actually added, the following situation will not occur:
“A new page was added today, so 5 more words need to be added to the glossary.”
Adding a new page itself should not be a reason to modify the glossary.
The glossary should only be modified when a new technical area emerges, a genuine terminology conflict occurs, or it is discovered that a concept truly warrants long-term unified governance.
Current decision: give Option 1 a chance first
Based on this evaluation, I have temporarily chosen Option 1.
This is not because semantic compatibility is necessarily more correct than a simple SHA gate, but because there is now fairly clear evidence that:
The current 6-minute performance issue is primarily an implementation problem, not something the architecture itself inherently requires 6 minutes for.
If, within a single invocation, we can turn:
122 full compatibility reconstructionsinto:
1 prepared compatibility state
+
122 lightweight lookupsthen the Reviewer cost savings brought by compatibility remain highly valuable.
Especially in the current real-world case:
122 TranslationUnits
3 affected
119 compatibleAbandoning 119 trusted carries merely because the implementation was written inefficiently would be too premature.
However, this decision comes with a clear stop-loss condition.
I will not continue to introduce the following just to preserve compatibility:
persistent cache
cache invalidation protocol
new receipt
new schema
locale-specific tuningThis time, I only accept:
A locale-agnostic, invocation-local prepared-state refactor with no new persistent state.
If, after completing this clean refactor:
quality-check scope
reviewer-bundle
reviewer-bundle-check
finalize
retranslation exportstill remain noticeably at the minute level, then Option 2 will be reopened.
At that point, the issue will no longer be “a certain computation was repeated a hundred times,” but rather that the long-term cost of this architecture itself has exceeded the Reviewer costs it saves.
Long-term maintainability is more important than performance
This issue made me realize one thing again.
The biggest scaling challenge for this project is not that a single locale has 122 TranslationUnits.
The true scale is:
65 locales
×
Tour
×
Site shell
×
Learn
×
Docs
×
future content packages
×
long-term upstream evolutionAt this scale, a design must not only answer:
“Can it be precisely reused in theory?”
It must also answer:
“Can it still be understood, recovered, verified, and maintained three years from now?”
Therefore, the principles ultimately retained this time are actually more important than Option 1 or Option 2 themselves:
First, stabilize the glossary as much as possible.
Second, machine optimizations should serve all locales, rather than patching a specific language.
Third, do not infinitely increase the complexity of the evidence state machine just to save a small amount of Reviewer cost.
Fourth, having already invested a lot of development effort is not a reason to continue retaining a complex design.
If compatibility can be cleanly optimized to the second level, then continue using it.
If not, fall back to the simpler, more conservative, and more easily recoverable glossary SHA gate.
An architecture is not better just because it is more precise.
In a project that requires long-term maintenance across dozens of languages, explainability, recoverability, and verifiability are themselves part of quality.
