两年前上线双语。今天:EN 文章上线后每篇更新 5 次,ZH 多数没动过。ZH 页引用过期截图、缺失只在 EN 有的章节、cross-link 指向 EN 里被改名的页面——Search Console 几十对 hreflang 警告。你骄傲启动的双语站点其实是一个站点 + 一份褪色的影子。
双语内容是承诺不是发版事件。漂移会复利:6 个月单边更新 = 1 年补课债务。修法不是”现在全翻一遍”——是审计 + 按对决定,未来更新自动同步,接受有些文章应标单语而不是维护得糟。
常见原因
按命中率从高到低:
1. 翻译只在上线时做了一次,更新只动主语言
上线时翻了 200 篇 EN 到 ZH。之后每次更新只动英文——ZH 时间冻结、EN 持续演进。
如何判断:对同一 translationKey 比 en/ vs zh/ 的 git log——50%+ 的对 en 更新——系统性漂移。
2. 一边改名 translationKey 配对断了
gpt-tips.mdx 在 EN 改成 chatgpt-tips.mdx,ZH 还是 gpt-tips.mdx——不再配对,两页 hreflang 都坏。
如何判断:找只在一边有的 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. 机翻没审就用了
为”补漂移”机翻了缺失的——ZH 听着流畅但 context 错(页面写「click 提交」但 ZH 文字写「submit按钮」)。双语存在但质量比单语差。
如何判断:随机读 5 篇最近 ZH 翻译——有 AI 翻译典型征兆(直译习语、英文残留、UI 词不匹配)——就是没审的机翻。
5. cross-link 指错了语言
EN 文章 link 到 /zh/articles/old-name/——因为 EN 先写、作者 copy-paste link 没改 locale。ZH 读者点 → 404。
如何判断:grep en/ 文件里的 /zh/articles/(反之亦然)——通常错的,除非显式双语引用。数错 locale 的 link。
6. 翻译在内容上分叉了
EN 扩了新例子,ZH 为简洁被精简。现在两边不是翻译——是相关文章。hreflang 暗示它们一样——读者发现不是。
如何判断:字数比——en > 1.5x zh(或反过来),不能用语言冗余解释——内容真分叉了。
最短修复路径
按收益从高到低。Step 1 审计,其余按漂移类型决定怎么办。
Step 1:建漂移审计
脚本检查配对:
# scripts/audit-bilingual.mjs
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const en = collectKeys("src/content/articles/en/troubleshooting");
const zh = collectKeys("src/content/articles/zh/troubleshooting");
console.log("仅 EN:", [...en.keys()].filter(k => !zh.has(k)));
console.log("仅 ZH:", [...zh.keys()].filter(k => !en.has(k)));
console.log("都有,EN 更新:", [...en.keys()].filter(k => zh.has(k) && en.get(k).mtime > zh.get(k).mtime));
function collectKeys(dir) {
const map = new Map();
for (const f of fs.readdirSync(dir)) {
const p = path.join(dir, f);
const { data } = matter(fs.readFileSync(p, "utf8"));
if (data.translationKey) {
map.set(data.translationKey, { path: p, mtime: fs.statSync(p).mtime });
}
}
return map;
}
输出:仅 EN、仅 ZH、EN 较新对——这是你的漂移清单。
Step 2:每对决定翻 / 同步 / 标单语
| 对类型 | 动作 |
|---|---|
| 仅 EN | (a) 翻译到 ZH,或 (b) 标单语 hreflang 声明 |
| 仅 ZH | 同理反向 |
| 两边都有、EN 较新 | 按现 EN 同步 ZH |
| 两边都有、ZH 较新 | 按现 ZH 同步 EN |
| 两边都有、内容分叉 | 选 canonical 同步另一边,或拆成两篇 |
不是每篇都要双语。「标单语」是合理选择——比维护得糟好。
Step 3:先同步高价值对
不要一周末搞 200 对——按流量优先:
# GSC:按 URL 模式过滤 /zh/articles/*
# 按 impression / 点击排序
# Top 20 有流量的 ZH 页优先同步
月 100 impression 的 ZH 值得同步,月 0 impression 的不值。
Step 4:未来更新自动化翻译队列
加 CI check 或 pre-commit hook:
# scripts/check-translation-sync.sh
# en/*.mdx 改了就检查对应 zh/*.mdx
# zh 比 en 前一个版本旧就 fail
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
# ZH 存在,flag 让人 review
echo "::warning::EN 更新:$f——ZH 可能要同步:$zh"
fi
done
不自动翻译,PR 阶段把漂移浮出来让你决定。
Step 5:显式修 hreflang
sitemap / 页面 metadata:
<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>
没对应物的文章不要有 hreflang 条目——单语声明。别做一半。
Step 6:拒盲目机翻
必须自动翻译补课:
- 自动翻到 draft 分支
- 每篇人审 UI 词、习语、tone
- 验证 cross-link 是 localized 的
- 才发
差 ZH 比没 ZH 差。AdSense 和 Google 都检测到未审 MT 内容。
预防建议
- CI 加漂移自动检查——EN 更新但 ZH 没(反之亦然)触发警告
- 翻译债务是追踪指标,月审一次刻意降低
- 没双语维护的文章标单语,不是半维护
- 高流量页强制同步,0 流量页单语就够
- 拒未审机翻——差双语比单语差
translationKey是合同——改名一个文件就要改名另一边