双语版本逐渐漂移:6 种类型 + 按对决定「翻译 / 同步 / 标单语」

EN 更新 5 次 ZH 才 1 次:截图过期、cross-link 失效、hreflang 被忽略。用脚本审计漂移、按对决定、自动化未来同步。2026 年 6 月。

两年前上线双语。今天:EN 每篇上线后更新了 5 次,ZH 多数没动过。ZH 页引用过期截图、缺失只在 EN 有的章节、链接还指向 EN 那边已经改名的页面。你当初骄傲上线的双语站,其实是一个活着的站 + 一份正在褪色的影子。

最快的修法:别现在全翻一遍。跑一个一次性审计脚本,把 en/zh/ 里每个 translationKey 配上对,按流量给漂移清单排序,只同步真正有点击的页面。其余的要么声明为单语(去掉它的 hreflang 配对),要么先放着。然后加一个 CI 警告,让未来的漂移在 pull request 阶段就浮出来,而不是一年后才发现。

双语内容是承诺,不是发版事件。漂移会复利:6 个月单边更新 = 1 年的补课债务。修法是审计 + 按对决定 + 自动化同步,并接受有些文章应该标成单语,而不是维护得糟糕。

先澄清一件事:Search Console 不再报这个问题

如果你是因为预期能在 Google Search Console 里看到 hreflang 警告才来的,请注意:国际定位报告(International Targeting,也就是 Language / hreflang 错误报告)在 2022 年 9 月之后被 Google 弃用,而且没有替代品。Google 自己的帮助页确认该报告已下线,也没给出迁移路径。截至 2026 年 6 月,GSC 完全不再提供专门的站点级 hreflang 校验报告。所以:

  • 「GSC 里没有 hreflang 警告」不等于你的配对是健康的,只是 GSC 不再告诉你了。
  • 漂移得你自己查:用下面的审计脚本,或用 Screaming Frog(它仍有专门的 hreflang 报告)、Ahrefs、Sitebulb 这类爬虫。
  • Google 还在做的事:它会读 hreflang 标注,并悄悄忽略任何不互指的链接。按 Google 官方文档的说法,“If two pages don’t both point to each other, the tags will be ignored.”(两个页面若不互相指向,标签会被忽略。)配对一断就静默失效,你不会收到任何控制台告警。

常见原因

按命中率从高到低排列。

1. 翻译只在上线时做了一次,更新只动主语言

上线时把 200 篇 EN 翻成了 ZH。之后每次更新只动英文。ZH 时间冻结,EN 持续演进。

如何判断:对同一个 translationKey,比较 en/zh/ 文件的 git log 日期。如果超过一半的对里 en 更新,就是系统性漂移。

# 一对里两边各自最新一次提交
git log -1 --format=%ci -- src/content/articles/en/troubleshooting/chatgpt-tips.mdx
git log -1 --format=%ci -- src/content/articles/zh/troubleshooting/chatgpt-tips.mdx

2. 一边改名后 translationKey 配对断了

EN 里把 gpt-tips.mdx 改成了 chatgpt-tips.mdx,ZH 还是 gpt-tips.mdx。两边不再配对,hreflang 链接变成单向不互指,Google 对两个页面都直接忽略。

如何判断:找出只在一边存在的 translationKey

diff \
  <(grep -h "translationKey:" src/content/articles/en/**/*.mdx | sort -u) \
  <(grep -h "translationKey:" src/content/articles/zh/**/*.mdx | sort -u)

3. 一边发了新文章,另一边一直没翻

你每月加 5 篇 EN,只翻 1-2 篇到 ZH。backlog 每月增长。一年后,50+ 篇仅有 EN。

如何判断en/ 里有 translationKey、但没有对应 zh/ 文件的文章。数它们,这就是你的翻译债务。

4. 机翻没审就用了

为了「补漂移」,把缺失的部分机翻了一遍。现在非英文版读起来流畅、但上下文是错的:UI 显示本地化按钮名,文字里却引用英文按钮名;或者英文名词突兀地夹在段落中间。结果是有双语外壳、质量却比单语还差。

注意这里的细微之处(截至 2026 年 6 月):Google 的垃圾内容政策并不把机翻或 AI 翻译本身当作垃圾。2025 年 6 月 11 日,在 Reddit 的 AI 翻译事件之后,Google 删掉了旧文档里「用 robots.txt 屏蔽自动翻译页」的建议(这只是文档调整,让文档与既有的规模化内容政策对齐)。标准是看价值、不看方式:有用、准确的翻译没问题;单薄或语无伦次的翻译落入规模化内容滥用政策。AdSense 逻辑一样,对 AI 内容偏多的站,被引用最多的拒审理由仍是「Low value content」(低价值内容)。所以规则不是「机翻被禁」,而是「没审的机翻既没收益、还有被处罚的风险」。

如何判断:随机读 5 篇最近的 ZH 翻译。出现 AI 翻译典型征兆(直译习语、英文残留、UI 词不匹配)就说明是没审的机翻。

某篇 EN 文章链接到 /zh/articles/old-name/,因为 EN 先写、作者 copy-paste 链接时没改 locale。ZH 读者在这个 EN 页上点过去时 locale 被翻转;如果 slug 也变了,就直接 404。

如何判断:grep en/ 文件里出现的 /zh/articles/(反向同理)。除非是刻意的跨语言引用,否则这些通常都是错的。

grep -rn "/zh/articles/" src/content/articles/en/ | wc -l
grep -rn "/en/articles/" src/content/articles/zh/ | wc -l

6. 翻译在内容上真的分叉了

EN 扩写了新例子,ZH 为了简洁被精简。两边已经不是互为翻译,而是相关文章。hreflang 暗示它们一样,读者点进来发现并不是。

如何判断:字数比。如果 en > 1.5x zh(或反过来),且差距无法用语言冗余解释,内容就是真分叉了。

漂移类型典型信号检测命令 / 检查
ZH 过期(原因 1)超过 50% 的对里 en 提交更新每边跑 git log -1 --format=%ci
配对断裂(原因 2)translationKey 只在一边对排序后的 translationKey 列表做 diff
翻译 backlog(原因 3)仅 EN 的 translationKey 数量审计脚本的 EN only 输出
没审的机翻(原因 4)直译习语、英文残留手动读 5 篇随机 ZH 页
错 locale 链接(原因 5)en/ 文件里出现 /zh/...grep -rn "/zh/articles/" src/content/articles/en/
内容分叉(原因 6)字数比 > 1.5x每边跑 wc -w

最短修复路径

按收益从高到低。Step 1 审计,其余按漂移类型决定怎么办。

Step 1:建漂移审计

写脚本检查整棵树的配对(这版会递归遍历子目录,不只是单个文件夹):

// scripts/audit-bilingual.mjs
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";

const enKeys = collectKeys("src/content/articles/en");
const zhKeys = collectKeys("src/content/articles/zh");

console.log("EN only:", [...enKeys.keys()].filter(k => !zhKeys.has(k)));
console.log("ZH only:", [...zhKeys.keys()].filter(k => !enKeys.has(k)));
console.log(
  "Both, EN newer:",
  [...enKeys.keys()].filter(
    k => zhKeys.has(k) && enKeys.get(k).mtime > zhKeys.get(k).mtime
  )
);

function collectKeys(dir) {
  const map = new Map();
  for (const f of walk(dir)) {
    const { data } = matter(fs.readFileSync(f, "utf8"));
    if (data.translationKey) {
      map.set(data.translationKey, { path: f, mtime: fs.statSync(f).mtime });
    }
  }
  return map;
}

function walk(dir) {
  return fs.readdirSync(dir, { withFileTypes: true }).flatMap(e => {
    const p = path.join(dir, e.name);
    return e.isDirectory() ? walk(p) : p.endsWith(".mdx") ? [p] : [];
  });
}

有个坑:fs.statSync().mtime 是文件系统修改时间,全新 git clone 会把它重置成检出时间。要可靠判断「哪边更新」,请用 git 提交时间(git log -1 --format=%ct)而不是 mtimemtime 版本只适合在你一直在编辑的工作树上做快速本地排查。

输出:仅 EN、仅 ZH、EN 较新的对。这就是你的漂移清单。

Step 2:每对决定翻译 / 同步 / 标单语

对类型动作
仅 EN(a) 翻译到 ZH,或 (b) 标单语并去掉它的 hreflang 配对
仅 ZH同理反向
两边都有、EN 较新按现在的 EN 同步 ZH
两边都有、ZH 较新按现在的 ZH 同步 EN
两边都有、内容分叉选一个 canonical 同步另一边,或拆成两篇独立文章

不是每篇都要双语。「标单语」是合理选择,比维护得糟糕好。没有 hreflang 标注的单语页完全有效,Google 会正常收录它。

Step 3:先同步高价值的对

不要一个周末就想搞 200 对。按流量优先。因为 GSC 砍掉了国际定位报告,改从标准的**效果报告(Performance)**里按 URL 过滤拿流量。

# GSC -> 效果 -> 搜索结果
# 加过滤:网页(Page)-> 包含 -> /zh/articles/
# 按展示次数(或点击)排序,导出前 20
# 这 20 个 ZH 页优先同步

月展示 100 次的 ZH 页值得同步;月展示 0 次的暂时不值得花这个功夫。

Step 4:未来更新自动化翻译队列

加一个 CI check 或 pre-commit hook,让漂移在 pull request 阶段就浮出来:

# scripts/check-translation-sync.sh
# en/*.mdx 改了,就把对应的 zh/*.mdx 标出来让人 review。

CHANGED=$(git diff --name-only origin/main -- 'src/content/articles/en/' | grep '\.mdx$')
for f in $CHANGED; do
  zh=$(echo "$f" | sed 's/\/en\//\/zh\//')
  if [ -f "$zh" ]; then
    echo "::warning::EN updated: $f - ZH may need sync: $zh"
  else
    echo "::warning::EN updated but no ZH twin exists: $f"
  fi
done

它不自动翻译,而是在改动还在 review 时就让漂移可见,让你在合并前决定。::warning::GitHub Actions 工作流命令,会内联显示在 pull request 上。

Step 5:显式且互指地修 hreflang

互指(reciprocity)是关键:按 Google 文档,如果 A 指向 B 但 B 不回指 A,两边的标注都会被忽略。每个页面还必须列出自己。在 sitemap 或页面 <head> 里:

<url>
  <loc>https://site.com/en/articles/topic/</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://site.com/en/articles/topic/" />
  <xhtml:link rel="alternate" hreflang="zh" href="https://site.com/zh/articles/topic/" />
  <xhtml:link rel="alternate" hreflang="x-default" href="https://site.com/en/articles/topic/" />
</url>

对应的 /zh/articles/topic/ 条目必须带完全相同的一组链接。x-default 用于「未明确列出的任何语言」所看到的版本。没有对应物的文章完全不要hreflang 条目,这就是单语声明。别做一半:单向标注等同于没有。

Step 6:拒绝盲目机翻

如果必须靠自动翻译补课,让它走 review 流程,而不是直接发原始机翻:

1. 自动翻译到一个 draft 分支(不要从它直接发布)。
2. 每个页面由人审 UI 词、习语、语气。
3. 验证 cross-link 是本地化的(zh/ 里没有 /en/...,反之亦然)。
4. 然后才合并、发布。

按上文 2026 年 6 月的政策图景,风险不是「存在机翻」,而是「没价值的机翻」。读起来自然、UI 字符串正确的、经过审校的机翻,Google 和 AdSense 都能接受;没人检查过的原始机翻,正是 AdSense 会拒的那种低价值内容。

如何确认已修好

  1. 重跑 scripts/audit-bilingual.mjs。「EN only」/「ZH only」列表里应只剩你刻意保留为单语的页面;「Both, EN newer」应为空(或只剩刚同步的对)。
  2. 用 Screaming Frog(或 Ahrefs / Sitebulb)爬一遍线上站,打开它的 hreflang 报告,应该是 0 个「missing return link」/ 不互指错误。这是已下线的 GSC 报告的实用替代。
  3. 在浏览器开发者工具里抽查一个 EN 页和它 ZH 双胞胎渲染出的 <head>。两边都应列出 enzhx-default,且两个页面里的 URL 必须完全一致。
  4. 重跑原因 5 的错 locale grep,两个计数都应为 0(或只剩刻意的跨语言引用)。

常见问题

2026 年 Google Search Console 还报 hreflang 错误吗? 不报了。Google 在 2022 年 9 月之后弃用了国际定位报告(里面就是 hreflang / Language 标签),且没有用专门的 hreflang 报告替代它。Google 仍会读取并使用 hreflang,但不再在 GSC 里暴露站点级的 hreflang 校验。这块改用 Screaming Frog、Ahrefs 或 Sitebulb 这类爬虫,它们都仍有 hreflang /「missing return link」报告。

机翻的 ZH 页会让我的站被处罚或被 AdSense 拒吗? 不会自动发生。截至 2026 年 6 月,Google 按价值、而非制作方式来判断翻译内容,并已删除旧的「屏蔽自动翻译页」建议。单薄或语无伦次的翻译仍落入规模化内容滥用;AdSense 最常见的拒审理由仍是「Low value content」。审校过、准确的翻译没问题;没审的原始机翻才是风险。

把一些文章标成单语,会伤 SEO 吗? 不会。没有 hreflang 标注的页会被当作独立页正常收录。真正伤的是坏掉的或单向的 hreflang 配对,因为 Google 会忽略不互指的标注。干净的单语声明比半维护的配对好。

我的 EN 和 ZH 现在确实是不同的文章了,该怎么办? 先决定它们该是一篇还是两篇。如果讲的是同一主题,选一个 canonical 版本,把另一边同步过去,保留 hreflang 配对。如果已经分叉成两篇不同的文章,就拆开:给它们不同的 slug 和 translationKey,并去掉 hreflang 配对,让每篇各自成页。

每一对漂移都要修吗? 不用。按流量给漂移清单排序(Step 3),只同步有展示或点击的页面。0 流量的页可以先维持单语,等它挣到这份工作再说。翻译债务是要刻意降低的指标,不是要一个周末清空的 backlog。

预防建议

  • 自动化一个 CI 漂移检查:EN 文件更新但 ZH 没有对应改动(或反之)时触发。
  • 把翻译债务当作追踪指标:每月复盘、刻意降低。
  • 没有双语维护的文章标成单语,而不是放着半维护。
  • 高流量页强制同步;0 流量页保持单语就够。
  • 别指望 GSC 帮你抓 hreflang 断裂,自己定期跑审计或爬虫。
  • translationKey 就是合同:改名一个文件,就要同时改名另一边。

相关阅读

标签: #内容运营 #站点质量 #站点审计 #排查 #双语