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

有英文页面,不代表一定应该有本地页面:v2 如何处理本地内容覆盖、canonical 与真实 404

v2 本地内容覆盖判断流程图:官方英文页面先检查是否已有正式本地覆盖,有覆盖时使用本地 URL,没有覆盖时直接链接 go.dev,并保持本地路径真实 404。

作者:

在

Go 学习与文档多语言本地化项目

图3:访问过去的 Go Tour 简体中文站 tour.go-zh.org 时,当前已经无法正常建立连接。

(1) 从「A Tour of Go 中文版」这个搜索词开始:我决定做一个持续维护的 Go Tour 中文版

本地运行的 A Tour of Go「Methods continued」课程页面,左侧为课程说明,右侧为 Go 代码编辑器及运行结果。

(2) A Tour of Go 中文版项目设计冻结:从 101 页到 103 页,从 Gin 转向 Cobra CLI

A Tour of Go 中文版开发实战:英文基线、在线运行与 101 页上游同步体系

(3) A Tour of Go 中文版开发实战:英文基线、在线运行与 101 页上游同步体系

go-tour-i18n 项目完成首个简体中文课程页面 welcome/1 的整页翻译、结构校验和本地预览。

(4) A Tour of Go 多语言翻译项目实录:完成首个 zh-CN 页面翻译闭环

图 1:DeepSeek 官方更新日志显示,本次只更新 DeepSeek-V4-Flash,DeepSeek-V4-Pro API 与 APP、Web 模型均未更新

(5) A Tour of Go 中文翻译项目进展:完成前 8 页、修复 present 语法,并暂缓 DeepSeek 对比

图 1:generics/1 简体中文页面本地预览

(6) 一个页面重试五次:使用 GLM-5.2 翻译 A Tour of Go 时遇到的 Token 与 Present 结构问题

图 1:methods/16 简体中文页面浏览器预览

(7) A Tour of Go 中文翻译实录:如何只翻译教学代码注释,而不破坏 Go 代码

图 2:A Tour of Go methods/20「练习:错误」中文候选页面最终渲染效果

(8) A Tour of Go 多语言翻译:当正确的中文语序被受保护标记顺序校验误判

图 3:methods/24 中文页面,静态代码、Bounds 行内代码和链接内的 image.Rectangle 均正确渲染

(9) A Tour of Go 中文翻译完成 7 个代表页校准:最后 3 页又发现了哪些真实问题

图 2:首批 10 个普通页面经过试跑与流程校准后全部进入 ready

(10) A Tour of Go 多语言翻译项目:首批 10 个普通页面试跑,从 2 个 blocked 到全部 ready

图 2:flowcontrol/6 最终中文页面,return 与 v 为适应自然中文语序发生整体换位

(11) A Tour of Go 中文翻译第二批实跑:从 Inline Code 顺序误判到历史响应重新验证

图 1:flowcontrol/6 首次原始输入实验直接通过统一自动校验

(12) A Tour of Go 多语言翻译项目:从 swap 问题重新审视 Token 保护,原始输入与最小保护的一次真实实验

图 7:moretypes/1 最终中文页面预览。左侧课程正文、静态代码块、行内代码和教学注释均正常渲染,右侧官方 Go 示例继续保持原样。

(13) A Tour of Go 多语言翻译实战:第三批 10 页全部 Ready,继续校准 Protected Token 的结构角色

图 4:上游源码确认共有 103 个课程页面,zh-CN 最终达到 ready=103、pending=0、blocked=0

(14) A Tour of Go 多语言翻译项目:zh-CN 103 个课程页面全部 ready,课程正文翻译阶段完成

图 4:GitHub 与 ChatGPT 已成功连接

(15) ChatGPT 连接 GitHub 实测:让 AI 直接读取 go-tour-i18n 仓库参与译文审核

图 3:中文 Go 术语校准完成后,将确定的译法写入 zh-CN glossary

(16) 为什么 channel 最终选择“通道”:A Tour of Go 中文术语统一的一次实践

图 1:技术含义基本正确,但“返回一个返回……”已经明显影响阅读,因此仍被列入 C 类修订

(17) 103 页逐页审核后,只返修 16 页:A Tour of Go 中文正文发布前质量审计

图 2:中文课程页面以及已经完成本地化的编辑器控制区

(18) A Tour of Go 多语言翻译项目:公共 UI 本地化完成,从课程译文走向完整中文界面

图 2:完整 zh-CN 正式投影成功生成,103 个课程页面被重新组装为 7 个 .article

(19) 从 103/103 ready 到完整站点验收:A Tour of Go 中文版补齐正式投影与课程元数据本地化

图 3:从 production bundle 独立启动的 A Tour of Go 简体中文页面,课程正文、公共 UI 和官方 Go 示例均已进入最终发布形态。

(20) A Tour of Go 多语言翻译项目:103 页全部完成后,我终于生成了可独立部署的 zh-CN Production Bundle

图 1:A Tour of Go 简体中文版正式生产页面,远程运行已经成功输出“Hello, 世界”。

(21) A Tour of Go 多语言翻译项目:zh-CN 正式上线,从生产发布到 go.dev Playground 的完整部署记录

图 1:新生产域名 go-dev.shuijingwanwq.com 已正常访问,Go 示例远程运行成功

(22) A Tour of Go 多语言翻译项目:从 go-tour 到 go-dev,完成 EdgeOne、Nginx 与 HTTPS 生产域名迁移

图 1:A Tour of Go 多语言翻译项目正式生产首页

(23) A Tour of Go 多语言翻译项目正式上线:从项目首页、公共 Footer 到生产发布元数据的完整收尾

图 3:GA4 g/collect 请求返回 HTTP 204,并携带当前课程页面地址,确认 Google Analytics 已产生真实事件上报

(24) A Tour of Go 多语言翻译项目:接入 Google Analytics 与百度统计,从 systemd 注入到 EdgeOne 与真实上报验证

图 1:生产环境 robots.txt 与 sitemap.xml 已正确对外提供,Sitemap 共包含 104 个规范 URL。

(25) A Tour of Go 中文站上线后:补齐 robots.txt、Sitemap,并完成五大搜索引擎提交

图 4:发布生产并刷新 EdgeOne 缓存后,再次通过真实手机访问,顶栏项目名称已经完整保持在一行,最终移动端验收通过。

(26) A Tour of Go 多语言翻译项目:修复手机端首页顶栏标题换行,并完成真机验证

图 3:生产自动部署脚本第一次真实运行时,systemd 已经显示 active,但首次 localhost 探测仍然得到 HTTP 000;随后连续 3 次同时满足 active + HTTP 200 后,脚本才判定部署成功,并继续完成公网 HTTP 200 验收。这次真实运行验证了连续应用层健康检查的必要性。

(27) A Tour of Go 多语言翻译项目:从一次 203/EXEC 故障到生产自动部署脚本落地

图 1:阿里云生产环境访问 go.dev Playground 时出现 TLS 握手超时

(28) A Tour of Go 多语言翻译项目:为 Playground 搭建独立的 ZgoCloud 执行代理

图 3:清除 /tour/script.js 缓存后,Run 已直接请求 ZgoCloud /compile 并返回 HTTP 200

(29) A Tour of Go 多语言翻译项目:将生产 Run / Format 切换到 ZgoCloud 执行节点

【图 1:微信中运行 Go 示例时出现 Error communicating with remote server.】

(30) A Tour of Go 中文版手机端运行与格式化偶发失败排查:从间歇性报错到补强 Nginx 可观测性

【图 4:向 golang-dev 公开邮件列表发送 Playground 使用告知邮件并投递成功】

(31) A Tour of Go 多语言项目:为 Playground 代理增加唯一 User-Agent,并主动告知 Go 官方

【图 3:百度剩余 9 个核心 URL 提交成功,remain 归零】

(32) A Tour of Go 搜索引擎收录实测:从 Google 自然索引到百度主动提交与 Bing IndexNow

【图 1:当前正式中文 methods/24 页面,右侧示例代码运行成功】

(33) A Tour of Go 中文翻译完成后,我为什么重新评估 minimal-protect 翻译模式

【图 3:5 个页面 Request SHA256 完全一致,但 Response SHA256 全部不同】

(34) A Tour of Go 翻译实验:更多原始上下文为什么没有更好?从 157 个保护标记到 30 次重复盲评

图 3:3 个代表页 × 4 个评审模型,共 12 次匿名评审的完整排名与第一名票数。

(35) A Tour of Go 翻译质量再评估:minimal-protect 未达预期后,我开始比较 ChatGPT、Codex 与 GLM-5.2

图 2:GitHub 中的 chatgpt-zh-CN-008 重译批次。一个 Batch 同时保存 inputs、raw-responses、candidates、validation 和 retry 记录,GitHub 成为 ChatGPT 与本地翻译流水线之间的数据交接层。

(36) 没有 OpenAI API,我如何用 ChatGPT + GitHub 完成 A Tour of Go 103 页全量重译

图 1:103 个正式课程页面完成最终语义质量审核,结果为 A=103、B/C/D=0,同时不存在缺失、重复或额外页面。A/B/C/D 是本轮项目内部语义质量分级,并不是自动结构 validator 的结果。

(37) 103 页翻完还不能发布:A Tour of Go ChatGPT 译文的语义审核与 Canonical Promotion

图 3:7 个 article endpoint 的 production origin 与 EdgeOne 公网响应全部 exact match,同时新的 ChatGPT 译文标记存在,旧版译文标记已经消失。

(38) 103/103 与 7/7 Exact Match:A Tour of Go ChatGPT 新译文的最终线上验收

【图 2:upstream source preview】

(39) A Tour of Go 多语言翻译项目首次实现 upstream 同步机制:从一次性翻译到长期维护

图 2:A/B/C/D 质量评级规则,展示正式质量 rubric 与 Promotion gate。

(40) A Tour of Go 多语言翻译项目引入 Translation Quality Review:从自动验证到 AI 翻译质量控制

图 4:翻译后 Playground Example,展示中文注释与保持不变的 Go 代码结构。

(41) A Tour of Go 多语言翻译项目实现 Playground Example 本地化:从代码注释翻译到完整学习体验

图 5:公网页面验证广告脚本输出

(42) A Tour of Go 多语言翻译项目接入广告系统:从 AdSense 专用配置到通用 HTML 注入架构

图 3:百度统计单页应用设置

(43) go-dev 单页网站接入统计与 Google AdSense Auto Ads 验证记录

图 1:GitHub 仓库中的多语言翻译项目结构

(44) A Tour of Go 多语言翻译项目:选择日语作为第二语言并启动翻译验证

图 5:模型身份描述不一致

(45) A Tour of Go 多语言翻译项目中的 AI 协作异常排查:一次长期工程会话失效分析

【图 1:ChatGPT GitHub 写入流程调整说明截图】

(46) go-tour-i18n 翻译流程调整:从 ChatGPT GitHub 写入到本地 artifact 导入

图 4 展示了本次异常状态。

(47) go-tour-i18n 翻译自动化流程复盘:已验证的 ChatGPT 翻译能力在新任务中的稳定性问题

【图 6:全部评审结束以后才读取 secret key,完成最终揭盲】

(48) A Tour of Go 翻译质量再评估:Codex High 已接近 ChatGPT High,我决定调整默认翻译引擎

图 4:ja-JP 生产课程页实际运行 Go 示例

(49) A Tour of Go 日语版正式上线:第二门语言 ja-JP 完成生产发布

图 1:ja-JP 上线以后,先提交“新增语言标准化流程”

(50) A Tour of Go 多语言扩展复盘:为第三门语言建立标准化流程

图 1:课程正文结束以后,页面左侧仍然存在大块视觉空白,但 AdSense 并没有在这里插入展示广告。

(51) A Tour of Go 明明有大片空白,为什么 AdSense 就是不显示展示广告?

图 2:golang/website 当前的 _content/tour/。课程内容、静态资源和模板都在这个上游目录中持续维护。

(52) 为了 AdSense,要不要放弃 SPA?A Tour of Go 页面架构的一次取舍

图 4:最终的手动课程广告真实展示在课程内容区域内,而不是 footer 之后。

(53) 保留 SPA 以后,广告应该放在哪里?从 AdSense 预览到方案 B

图 1:Google Search Console 中,多个不同的 A Tour of Go 课程 URL 被判断为“重复网页,用户未选定规范网页”。

(54) SPA 的 SEO 老问题:为什么最后还是给 103 个 A Tour of Go 课程页生成了 Prerender HTML

图 2:方案 B 生产环境中,源码已经正常,但课程主体高度异常缩短,footer 提前进入第一屏。

(55) 从源码空白到 AdSense 高度污染:A Tour of Go 方案 B 的生产收尾

图 1:A Tour of Go 德语版 de-DE 已正式运行在生产环境

(56) A Tour of Go de-DE 上线复盘:第三门语言用了约 16 小时,下一门如何压到 8 小时?

图 1:fr-FR 首次 production 验收完成后,我没有立即开始 ko-KR,而是先连续完成新增语言与首次生产流程的优化

(57) 法语版上线后,我没有马上开始韩语:A Tour of Go 新增语言流程的一轮完善与简化

图 1:根据 ko-KR 期间 76 次 Git 提交估算实际工作时间

(58) 原本预计 6 小时,最后用了约 16~18 小时:A Tour of Go 韩语版上线复盘

图 2:英文 source 与当时发生 validation failure 的 ko-KR candidate 对比

(59) Validator 报错,到底该改译文还是改规则?一次 A Tour of Go 韩语翻译实战

图 2:法语 A Tour of Go 仓库目前已经归档,页面仍保留 553 Commits 和 61 Contributors 的历史

(60) 从法语 553 次提交到韩语两代离线:A Tour of Go 社区翻译为什么难以长期维护

【图 4:es-ES 首次 Production 收口后连续完成的流程优化 commit】

(61) 从 es-ES 首次上线复盘:我把 A Tour of Go 新增语言的 Production 流程又收敛了一轮

图 1:旧公网验收链路连续出现 curl 28 超时,最终导致 public-machine 阶段失败

(62) A Tour of Go Production 公网验收踩坑:从跨境 SOCKS 链路改为 zgocloud direct

图 1:两次 Production Machine Acceptance 都已经通过,但 Headless Chrome 分别在 / 和 /tour/ 判定页面没有完成渲染

(63) 页面明明能打开,为什么 Headless Chrome 仍然判定 Production 失败?

图 3:Bing 从 Google Search Console 中识别到新的 it-IT 站点,并同时发现已有的 1 个 sitemap

(64) GSC → Bing Webmaster Tools:多语言站点如何减少重复验证

图 3:英文站 3 小时内出现 565 个 Cloudflare 来源 URL

(65) IndexNow 实战:手工提交长期不显示,Cloudflare Crawler Hints 却很快出现在 Bing Webmaster Tools

【图 1:A Tour of Go 项目首页目前的语言版本列表】

(66) 从 nl-NL 到 tr-TR:A Tour of Go 连续上线三门语言后,我又优化了哪些流程

【图 4:Go local 页面中已经出现 French、German、Korean、Simplified Chinese】

(67) 从邮件无人回复到进入 Go 官方 Go local:我的 4 个 A Tour of Go 翻译站终于被收录

图8:最终只保留一把启用的新 API Key

(68) 腾讯云 EdgeOne API 自动化:从 CAM 子用户到最小权限 API Key 的完整配置

【图 7:Production Release Batch 最终汇总】

(69) 从同步 Go 官方上游到一个命令发布 10 个语言站:A Tour of Go Production 流程的再次收敛

【图 3:Cloudflare Smart Tiered Cache 已启用】

(70) Cloudflare 缓存 HIT 很快,MISS 却不稳定:回源阿里云杭州的一次真实优化

【图 3:简体中文站 Support 页面】

(71) 广告之外,社区项目如何长期活下去?我给 A Tour of Go 多语言项目加了 Support

图5:评论成功发布

(72) 从 Go Weekly 到 Reddit、DEV 和 Go Forum:第一次系统推广 A Tour of Go 多语言项目

【图 4:RTL 桌面课程布局修复后的效果】

(73) 第一次把 A Tour of Go 做成 RTL:阿拉伯语版本上线,以及我踩过的几个坑

多语言项目应该先做哪些语言?我的 66 个语言版本商业优先级排序

(74) 多语言项目应该先做哪些语言?我的 66 个语言版本商业优先级排序

当术语一致性遇到性能瓶颈:Go 多语言翻译项目的一次架构取舍

(75) 当术语一致性遇到性能瓶颈:Go 多语言翻译项目的一次架构取舍

从 A Tour of Go 走向 Go Learning & Documentation:为什么这个多语言项目需要 v2

(76) 从 A Tour of Go 走向 Go Learning & Documentation:为什么这个多语言项目需要 v2

Markdown 明明可以拆成很多段,为什么 v2 仍然坚持一个 Page 就是一个 TranslationUnit

(77) Markdown 明明可以拆成很多段,为什么 v2 仍然坚持一个 Page 就是一个 TranslationUnit

Tour、Learn、Docs 为什么不应该各维护一份术语表:v2 的统一 Glossary 设计

(78) Tour、Learn、Docs 为什么不应该各维护一份术语表:v2 的统一 Glossary 设计

v2 术语表变更后的页面影响范围判断流程:区分 Compatible 与 Affected,只处理真正受影响的页面。

(79) 术语表改了,为什么不应该让所有页面重新翻译?v2 的影响范围判断

CLI 性能优化示意图:通过 Prepare Once、Reuse Many Times,将执行时间从 6m54.554s 降至 10.335s,约提升 40.1 倍。

(80) 从 6 分 54 秒到 10.3 秒:Go 多语言项目一次 CLI 性能优化实战

历史为什么值得保留:复用已经完成的翻译成果,而不是重新做一遍

(81) 历史为什么值得保留:复用已经完成的翻译成果,而不是重新做一遍

历史状态通过 Trusted Checkpoint 建立可信断点,旧记录继续保留用于审计和诊断,后续工作流从当前状态继续向前,不再要求无限重建过去。

(82) 历史应该被保留,而不是被反复重建:v2 为什么最终需要一个“断点”

v2 依赖与失效传播流程图:从内容变化开始,评估依赖关系和真实影响,将结果区分为“仍然有效”和“需要失效”,只让真正受影响的状态重新进入流程。

(83) 一次内容变化,到底应该让哪些状态失效?v2 的依赖与失效传播设计

v2 本地内容覆盖判断流程图:官方英文页面先检查是否已有正式本地覆盖,有覆盖时使用本地 URL,没有覆盖时直接链接 go.dev,并保持本地路径真实 404。

(84) 有英文页面,不代表一定应该有本地页面:v2 如何处理本地内容覆盖、canonical 与真实 404

当 Go Learning & Documentation v2 从 A Tour of Go 逐渐扩展到 Learn、Docs 和完整站点以后,我遇到了一个以前不太明显的问题:

go.dev 上存在一个英文页面,是不是就意味着每一个本地语言站点都应该存在对应的本地 URL?

一开始看,这个问题似乎很好回答。

既然英文站点有:

Plaintext
https://go.dev/某个路径

那么中文站点似乎也应该有:

Plaintext
https://中文站点/某个路径

哪怕中文内容暂时还没有完成,也可以先创建一个页面。

或者访问这个本地 URL 时,再跳转到英文 go.dev。

这样至少不会出现 404。

听起来很合理。

但真正把 Learn、Docs、导航、canonical、sitemap 和搜索引擎收录放到一起以后,我越来越觉得:

“没有 404”并不等于“站点结构正确”。

有时候,一个真实的 404,反而比一个假的本地页面更正确。

这篇就整理 v2 在这个问题上的最终思路。


最容易产生的误区:英文页面存在,所以本地页面也应该存在

假设 go.dev 上有一个正式页面。

本地语言暂时还没有翻译。

最直接的想法可能是:

Plaintext
英文页面存在 → 创建对应的本地 URL

至于内容,可以以后再补。

甚至可以先让这个本地 URL 自动跳回英文页面。

这样做的好处非常明显:

用户不会看到 404。

站内链接也不容易“断”。

路由看起来比较完整。

搜索引擎爬到这个 URL 时,也能得到一个正常响应。

但是这里其实混淆了两件事:

官方英文页面存在。

和:

这个本地语言站点已经正式覆盖这个页面。

它们并不是同一件事。


v2 开始把“本地页面存在”当成一种正式承诺

以前如果只是 Tour,站点内容范围相对固定。

但扩展到整个 Go Learning & Documentation 以后,页面种类越来越多。

包括:

  • Tour;
  • Learn;
  • Docs;
  • Site shell;
  • 安全文档;
  • 其他后续可能加入的内容包。

这时,本地 URL 是否存在就不能再只是一个路由层面的决定。

它实际上代表了一项更强的承诺:

这个页面已经属于该语言版本的正式内容覆盖范围。

也就是说,只要本地路由存在,用户很自然地会认为:

这个页面是这个语言站点的一部分。

搜索引擎也会根据实际响应、canonical、sitemap 和站内链接,判断这个页面属于谁。

所以 v2 后来把“有没有这个本地页面”提高成了一个正式状态问题:

只有具备正式本地内容覆盖时,才应该建立对应的本地页面。


什么叫“正式本地内容覆盖”

这里的“覆盖”,并不只是数据库或者文件系统里存在一份内容。

至少从 v2 的工作流来看,一个内容要真正成为本地站点的一部分,需要经过完整的质量链路。

大致包括:

Plaintext
内容进入翻译范围
→ 生成候选译文
→ 机器校验
→ 独立质量审核
→ 修订与复审
→ 全 A 定稿
→ 内容包封存
→ 整合站点审核
→ 激活
→ 预览与生产环境验证

所以:

“已经有译文草稿”不等于“已经有正式本地内容覆盖”。

甚至:

“翻译单元已经得到 A”也不自动等于这个 URL 已经可以进入生产环境。

因为页面最终还需要进入内容包、整合站点、激活和生产验证。

这也是上一篇里提到的:

每一层证明的事情不同。


没有本地覆盖时,应该继续链接官方英文页面

最终我更认可的一条规则是:

只有正式存在本地覆盖的页面,才使用本地链接。

如果还没有正式本地覆盖,就继续指向官方英文页面:

Plaintext
https://go.dev/<canonical>

例如一个导航项对应的文档尚未翻译完成。

此时不应该为了“让链接看起来都是本地的”,强行改成:

Plaintext
https://本地站点/<canonical>

而应该继续链接:

Plaintext
https://go.dev/<canonical>

等这部分内容真正完成翻译、审核、整合和激活以后,再把链接切换到本地站点。

这意味着本地站点的内容范围不是:

“英文站点有什么,我就先把 URL 全部复制过来。”

而是:

“我正式完成了哪些内容,就公开哪些本地 URL。”


为什么不应该创建“假的本地页面”

所谓假的本地页面,可能有几种形式。

例如:

页面 URL 存在,但内容实际上还是英文。

或者:

页面只有一句:

本页面暂未翻译。

或者:

页面立即跳转到 go.dev。

这些方案看起来都能避免 404。

但它们都有一个共同问题:

本地 URL 存在了,却没有真正对应的本地内容。

这样会让“URL 存在”与“本地覆盖存在”这两个概念脱离。

以后系统就很难再回答:

哪些页面真的已经完成本地化?


“先创建一个空壳”会让覆盖率失真

假设英文站点有 100 个页面。

真正完成中文翻译的只有 60 个。

如果为了路由完整,先把 100 个本地 URL 全部创建出来,那么从路由角度看:

Plaintext
本地页面数量 = 100

但真正的翻译覆盖其实只有:

Plaintext
正式本地内容 = 60

这时系统里会同时存在两种“覆盖率”。

一个是路由覆盖。

一个是内容覆盖。

久而久之,很容易出现:

系统认为页面已经存在,所以导航、sitemap、canonical 都开始把它当成本地内容处理。

但实际上,这 40 个页面根本还没有完成本地化。

所以 v2 更希望:

URL 是否存在,本身就尽量反映真实内容覆盖。


自动跳转到英文页面,也不是真正的本地覆盖

另一种常见做法是:

Plaintext
访问本地 URL → 302 跳转到 go.dev 英文页面

这样用户至少能看到内容。

但从语义上看,这个本地 URL 仍然不是一个真正的本地页面。

它只是一个中转入口。

这会带来几个问题。

首先,用户看到的是一个本地语言域名。

自然会预期:

点击以后还是这个语言版本。

但结果突然进入英文 go.dev。

这种体验其实并不比直接链接英文页面更自然。

其次,站点内部会产生一个并不存在真实内容的 URL。

以后导航、sitemap、canonical、搜索引擎爬取都需要为这个“虚拟页面”增加额外规则。

最后,最麻烦的是:

系统很难再通过路由是否存在,判断页面是否真的具备本地内容。

所以 v2 选择了更简单、也更诚实的方式:

没有本地内容,就不要伪造本地页面。


真实 404 不是失败,而是在表达真实状态

这也是我这段时间对 404 最大的认识变化之一。

以前很容易认为:

网站出现 404,就是有问题。

但实际上,404 的含义只是:

这个 URL 所代表的资源不存在。

如果某个页面本来就没有正式进入本地内容覆盖,那么访问对应的本地 URL:

Plaintext
https://本地站点/尚未覆盖的路径

返回真实 404,反而是正确的。

因为系统是在准确表达:

这个本地页面目前不存在。

真正的问题不是 404 本身。

而是:

明明应该存在的页面却返回 404。

或者:

明明不存在的页面却伪装成存在。

这两个问题要区分开。


这和“软 404”其实是相反的思路

如果一个 URL 返回 HTTP 200,但页面内容实际上只是:

暂无内容。

或者:

请访问英文页面。

那么从用户和搜索引擎角度看,它很可能并不是真正有价值的页面。

相比之下:

Plaintext
没有正式内容 → 真实 404

反而更加明确。

所以 v2 不追求:

所有可能的本地 URL 都返回 200。

而是追求:

返回状态应该准确反映内容是否真实存在。


canonical 不能脱离真实内容覆盖

到了这里,就会碰到另一个很重要的问题:

canonical。

canonical 可以简单理解成:

这个页面告诉搜索引擎,哪一个 URL 才是它的正式版本。

如果一个本地页面确实存在,并且属于正式本地覆盖,那么它应该拥有符合当前站点规则的 canonical。

但如果页面本身根本不存在,那么就不能为了“结构完整”提前制造一个 canonical。

否则会出现非常奇怪的状态:

Plaintext
页面不存在
但 sitemap 里有它
canonical 也声明它存在
导航还链接到它

这实际上是在同时向不同系统发送相互矛盾的信息。

所以 v2 的原则是:

canonical 必须建立在真实、完整的本地内容覆盖之上。


sitemap 也不能变成“未来计划清单”

同样的道理也适用于 sitemap。

sitemap 的意义并不是:

“这些 URL 将来可能会有内容。”

它应该告诉搜索引擎:

这些是当前真正希望被发现和索引的页面。

所以没有正式本地覆盖的页面,不应该仅仅因为 go.dev 上存在同路径页面,就提前进入本地 sitemap。

否则就会出现:

Plaintext
sitemap 声明页面存在
→ 搜索引擎访问
→ 本地没有真实内容

或者:

Plaintext
sitemap 声明本地页面
→ 页面再跳到英文站点

这些状态都会让站点的内容边界变得模糊。

所以 v2 更倾向:

sitemap 只包含真正完成本地覆盖的页面。


导航也必须遵守同样的规则

导航是用户最直接接触到的地方。

假设官方英文站点导航里有某个文档。

本地版本尚未翻译。

这时候其实有两种选择:

第一种:

Plaintext
导航 → 本地 URL → 404 或跳转英文

第二种:

Plaintext
导航 → 官方英文 go.dev

我更认可第二种。

因为它更加直接。

用户点击之前看到的是一个可用的导航入口。

点击以后直接到真正存在的内容。

而不是先进入一个假的本地路径,再由系统替用户决定跳去哪里。

这也让链接状态变得非常清晰:

Plaintext
存在正式本地覆盖 → 链接本地页面
没有正式本地覆盖 → 链接 go.dev 官方页面

这样可以避免“路由存在”和“内容存在”脱节

我觉得这一条非常重要。

一个长期维护的多语言站点,最好让下面几个概念尽可能一致:

Plaintext
本地路由存在
本地内容存在
本地内容已经通过正式质量流程
导航可以链接本地页面
sitemap 可以包含这个页面
canonical 可以声明这个页面

如果这些状态长期不同步,系统就会越来越复杂。

例如:

路由存在,但是内容不存在。

内容存在,但是还没审核。

审核通过,但是没有激活。

已经激活,但是 sitemap 还没更新。

导航又提前链接了未来页面。

这样每一个地方都需要额外判断。

而且越往后,越难知道:

究竟哪一层才代表真实状态。

所以 v2 更希望建立一个比较清晰的边界:

正式本地覆盖,是本地页面进入站点表面的前提。


/doc/security/** 让我更重视 canonical 规则本身

在 Docs 扩展过程中,还有一个比较具体的例子:

Security 文档的正式 canonical 路径是:

Plaintext
/doc/security/**

这类规则看起来只是路径差异。

但它让我进一步意识到:

canonical 并不能靠“看起来差不多”来猜。

一个内容页面真正对应什么 canonical,必须服从当前正式内容结构。

否则本地站点很容易因为:

  • 路径拼接;
  • 历史旧路由;
  • 页面分类变化;
  • 内容包扩展;

而生成一个看似合理、实际上并不是正式 canonical 的 URL。

所以 v2 对这类路径规则更倾向于:

读取正式内容权威,而不是根据页面名称推测。


本地覆盖其实也是一种状态机

做到这里以后,我觉得“页面有没有本地版本”已经不能简单用:

Plaintext
true / false

来理解。

更接近的是:

Plaintext
英文官方内容存在
→ 尚未进入本地翻译范围
→ 已进入翻译流程
→ 翻译与审核完成
→ 内容包完成
→ 整合站点通过
→ 激活
→ 本地覆盖正式生效

其中任何一个中间阶段,都不能提前等价成:

“生产环境已经存在正式本地页面。”

这和前面几篇博客讨论的 current、历史复用、失效传播,本质上其实是一套逻辑。

系统需要知道:

什么已经完成,什么只是正在进行,什么已经真正成为当前生产状态。


Surface PASS 也不代表 Production ready

这一点在这个问题上尤其明显。

假设整合站点审核已经通过。

这证明:

当前组合出来的站点内容,在审核范围内没有发现需要阻止继续推进的问题。

但生产环境还有真实路由、canonical、sitemap 和浏览器行为。

因此:

Plaintext
整合站点审核 PASS ≠ 已经可以宣布生产环境正确

生产验证仍然需要确认:

  • 应该存在的本地页面确实存在;
  • 不应该存在的本地页面确实返回 404;
  • 本地覆盖存在时链接到本地页面;
  • 没有本地覆盖时链接官方 go.dev;
  • canonical 指向正确;
  • sitemap 没有提前暴露未完成页面。

所以:

整合站点审核是进入生产之前的重要门禁,但不能代替生产环境验证。


“页面不存在”也应该被测试

以前测试站点时,很自然会重点测试:

哪些页面应该打开成功?

但做到 v2 以后,我觉得还应该测试另外一件事:

哪些页面应该明确不存在?

例如:

某个英文页面尚未进入本地正式覆盖。

那么它对应的本地 URL:

Plaintext
应该返回真实 404

而不是:

Plaintext
200 空页面

也不是:

Plaintext
302 跳回英文页面

这类“负向测试”其实很重要。

因为只有这样,才能确认本地内容边界没有被无意扩大。


一个成熟的本地化站点,不应该假装自己翻译完了所有内容

我觉得这背后还有一个比技术实现更重要的问题。

多语言站点很容易产生一种压力:

既然是中文版,那么所有页面最好都有中文版 URL。

但实际翻译工作一定是逐步推进的。

特别是在:

  • 内容持续更新;
  • 新内容包不断加入;
  • 审核要求较高;
  • 需要独立复审;
  • 生产上线还要经过多层验证;

这样的系统里,不可能要求所有内容永远同时完成。

所以比起“看起来完整”,我更希望站点做到:

已经完成的部分明确本地化。

还没有完成的部分明确链接官方来源。

而不是制造一种:

“所有内容好像都有本地版本。”

的假象。


这也能让后续新增内容包更简单

假设未来又加入一个新的 Go 内容包。

如果架构要求:

所有官方英文 URL 都必须立刻在本地存在。

那么每增加一个内容包,就要先扩展大量路由、跳转、canonical、sitemap 特例。

然后再慢慢填内容。

整个系统会先产生一大批“未来页面”。

但如果采用正式覆盖原则:

Plaintext
新内容进入正式翻译流程
→ 完成以后建立本地覆盖
→ 激活以后进入本地站点

那么新内容包的扩展会自然很多。

站点只公开已经完成的部分。

没有完成的继续使用官方英文来源。


本地覆盖、canonical、sitemap 和 404 其实必须是一套规则

现在回头看,我觉得这些原本看起来彼此独立的问题:

  • 本地内容覆盖;
  • 路由;
  • 导航;
  • canonical;
  • sitemap;
  • 404;
  • 官方英文回退;

实际上不能分别设计。

它们必须围绕同一个事实:

这个页面现在是否具备正式本地覆盖?

如果答案是“有”,那么:

Plaintext
使用本地 URL
→ 本地页面真实存在
→ canonical 使用正确的本地正式地址
→ sitemap 可以包含
→ 导航可以链接本地页面

如果答案是“没有”,那么:

Plaintext
不伪造本地页面
→ 本地路径真实 404
→ sitemap 不包含
→ 不生成假的本地 canonical
→ 需要访问内容时直接链接 go.dev 官方页面

这样整个系统才不会互相打架。


真实状态比“看起来完整”更重要

这一篇最后其实又回到了 v2 很多设计都会遇到的同一个问题:

我们到底是在优化表面完整度,还是在维护真实状态?

如果追求表面完整度,很容易得到:

Plaintext
所有 URL 都存在
所有链接都是本地域名
所有页面都返回 200

看起来非常漂亮。

但其中可能有大量页面并没有真正完成本地化。

而 v2 更想追求的是:

Plaintext
完成了 → 明确存在
没完成 → 明确不存在

如果没有本地覆盖,就老老实实使用官方英文来源。

如果本地 URL 不应该存在,就返回真实 404。

如果页面已经正式完成,就让导航、canonical、sitemap 和生产环境一起承认它。

这套规则可能没有“所有页面都有本地 URL”那么漂亮。

但它更加诚实。

也更加容易长期维护。


最终形成的原则

现在这部分设计可以总结成几条比较稳定的规则。

第一:

官方英文页面存在,不自动等于本地页面应该存在。

第二:

只有正式完成本地内容覆盖,才建立对应的本地 URL。

第三:

没有本地覆盖时,继续直接链接官方 https://go.dev/<canonical>。

第四:

不要使用假的本地页面,也不要用 302 跳转伪装本地覆盖。

第五:

缺少正式本地覆盖的本地 URL,应该返回真实 404。

第六:

canonical 和 sitemap 只承认已经完整建立的本地内容覆盖。

第七:

正式 canonical 必须来自当前内容权威,不能根据页面名称或历史路径猜测。

第八:

整合站点审核通过,不等于生产环境已经验证完成。

最终目标不是:

让本地站点看起来拥有和 go.dev 一模一样数量的页面。

而是:

让每一个公开存在的本地页面,都真正代表一份已经完成、已经审核、并且可以对用户负责的本地内容。

一次内容变化,到底应该让哪些状态失效?v2 的依赖与失效传播设计

Go 学习与文档多语言本地化项目

本系列完整记录 Go 学习与文档多语言本地化项目从 A Tour of Go 起步,逐步扩展到站点首页、项目说明页、学习内容、文档以及未来内容包的实际开发过程,包括架构设计、统一术语治理、整页翻译、结构保护、自动校验、独立质量审核、生产发布与长期维护。

项目源码:
✅ GitHub:shuijingwan/go-tour-i18n

语言版本:

当前跨项目目标语言池共包含 66 个语言版本。Go 项目本身目前提供 65 个社区 Production 语言版本,下面每一门 Go 语言版本都直接链接到其当前公开站点。语言列表沿用项目首页现有的展示顺序。

英语(美国)仅作为跨项目目标语言纳入列表,并不是单独的 Go Production 语言版本。当前 Go 的英文源内容仍为官方 generic English,因此英语(美国)链接到 Go 官方站点,而不是不存在的本地社区部署。

  1. 阿姆哈拉语
  2. 阿拉伯语
  3. 孟加拉语
  4. 葡萄牙语(巴西)
  5. 保加利亚语
  6. 加泰罗尼亚语
  7. 克罗地亚语
  8. 捷克语
  9. 丹麦语
  10. 荷兰语
  11. 英语(美国)(Go 官方英文站点)
  12. 英语(澳大利亚)
  13. 英语(加拿大)
  14. 英语(印度)
  15. 英语(新加坡)
  16. 英语(南非)
  17. 英语(英国)
  18. 爱沙尼亚语
  19. 菲律宾语
  20. 芬兰语
  21. 法语(法国)
  22. 法语(加拿大)
  23. 德语(德国)
  24. 德语(奥地利)
  25. 德语(瑞士)
  26. 希腊语
  27. 古吉拉特语
  28. 希伯来语
  29. 印地语
  30. 匈牙利语
  31. 印度尼西亚语
  32. 意大利语
  33. 日语
  34. 卡纳达语
  35. 哈萨克语
  36. 韩语
  37. 西班牙语(拉丁美洲)
  38. 拉脱维亚语
  39. 立陶宛语
  40. 马来语
  41. 马拉雅拉姆语
  42. 马拉地语
  43. 书面挪威语(Bokmål)
  44. 波斯语
  45. 波兰语
  46. 葡萄牙语(葡萄牙)
  47. 旁遮普语(果鲁穆奇文,印度)
  48. 罗马尼亚语
  49. 俄语
  50. 塞尔维亚语(西里尔文)
  51. 简体中文(中国大陆)
  52. 简体中文(新加坡)
  53. 斯洛伐克语
  54. 斯洛文尼亚语
  55. 西班牙语(西班牙)
  56. 斯瓦希里语(坦桑尼亚)
  57. 瑞典语
  58. 泰米尔语
  59. 泰卢固语
  60. 泰语
  61. 繁体中文(台湾)
  62. 繁体中文(香港)
  63. 土耳其语
  64. 乌克兰语
  65. 乌尔都语
  66. 越南语

项目正在持续扩展新的 Go 学习与文档内容,并长期维护各语言版本的术语一致性、翻译质量、独立审核、生产发布与后续更新。

本项目属于非官方社区多语言本地化项目,与 Go 官方无隶属、授权或背书关系。