跳转到主要内容
产品更新

我们如何用 Astro 把网站本地化为 9 种语言

Alex Rivera2 分钟阅读

这个冲刺周期里,我们上线了 9 种语言的本地化路由。以下是这件事真实发生的过程——包括那个在我们注意到之前,已经悄悄影响生产环境的 bug。

发现问题

早在 Sprint 1,我们就搭建好了预期中的 i18n 基础设施:9 种语言(英语、西班牙语、阿拉伯语、德语、法语、日语、葡萄牙语、俄语、中文)对应的本地化 JSON 文件、一个 getTranslation() 辅助函数、用于构建带语言标识 URL 的 getLocalePath(),以及每个页面上指向全部 9 个语言版本的 hreflang 标签。

我们没有搭建的是:这些语言版本 URL 下实际存在的页面。

src/pages/ 下的每个页面都硬编码了 const locale = "en" as const。翻译相关的底层机制是存在的,而且能正常运作——但它背后什么都没有。这意味着 BaseLayout.astro 在每一个页面上都会输出类似 https://proman365.com/es/pricing/ 这样的 hreflang 标签,而这些 URL 在生产环境中实际返回的是 404。

这不是一个无关紧要的表面 bug。搜索引擎依靠 hreflang 标签来判断哪些 URL 是彼此的语言版本。大规模地宣称存在实际上是 404 的页面,是一个实实在在的负面 SEO 信号——它告诉爬虫,你网站的元数据与现实不符,这会削弱爬虫对你所有元数据的信任,而不仅仅是出问题的那一部分。

修复方案:提取模板,而不是复制页面

最简单粗暴的修复方式,是把每个页面复制 8 份,每个非英语语言一份,并在每份副本里硬编码不同的语言常量。我们立刻否决了这个方案——文件数量变成 8 倍,意味着一旦有人改动某个页面却忘了同步改动另外 7 份副本,腐化速度也会变成 8 倍。

我们采用的方法是:把每个页面的内容提取成一个以 locale 作为 prop 的模板组件,然后通过两种类型的路由来渲染这个模板。

对于 12 个一级页面(首页、定价、联系我们、合作伙伴、帮助中心,以及六个功能页面),每一个都从 src/pages/*.astro 迁移到了 src/page-templates/*.astro,原来硬编码的语言常量被替换为:

interface Props {
  locale: Locale;
}
const { locale } = Astro.props;

原来的英语路由变成了一个薄薄的包装层——只有几行代码,负责导入模板并以 locale="en" 渲染它。src/pages/[locale]/ 下新增了一个镜像路由,使用 getStaticPaths() 为 8 个非英语语言各生成一个页面,渲染同一个模板。

这个方案之所以能顺利落地,是因为每个一级页面的逻辑——getTranslation(locale)、meta 标签生成器、结构化数据(schema markup)——早在 Sprint 1 搭建基础设施时,就已经围绕那一个 locale 常量做了参数化处理。我们不是在给这些页面事后补上 i18n,而是终于把一直闲置未用的 i18n 接上了线。

结果是:一级页面上线带来了 96 个新的静态页面(12 个页面 × 8 种语言),随后第二轮又把同样的模式扩展到了 64 个对比页面(8 个竞品 × 8 种语言)。

localized prop 对 hreflang 做门控

修复路由缺口本身并不会自动解决 hreflang 的问题——你仍然需要确保这些标签只声明真实存在的内容。我们在 BaseLayoutPageLayout 中新增了一个 localized?: boolean prop,默认值为 false

localizedfalse 时,页面只会输出两个 hreflang 标签:enx-default。当它为 true 时,会输出完整的 10 个标签(全部 9 种语言加上 x-default)。只有一级模板和对比页面会传入 localized={true}

把默认值设为 false 是一个刻意的安全选择。一个页面必须主动声明自己拥有 9 个语言版本。如果明天有人新增一个页面时忘了考虑语言本地化,它会安全地失败——退化为仅英语的元数据,而不是又制造出一批虚假 URL。

我们刻意没有本地化的部分

并不是所有内容都做了本地化处理,这是一个刻意的决定,而不是疏漏:

  • 博客将无限期保持仅英语。这是 SaaS 营销博客的常见做法,而且机器翻译长篇内容产出的文案质量,往往还不如干脆不翻译。如果日后分析数据能证明有必要投入专门的本地化内容,我们会重新评估。
  • 法律页面(隐私政策、服务条款、Cookie 政策)保持英语。这些文案是由 Termly 生成的法律文本;是否翻译不是我们可以单方面决定的事情。
  • 404 页面保持英语——静态托管无论请求路径是什么,都只提供同一个 404 页面,所以在不改变托管模式的前提下,没有干净的方式可以本地化它。
  • RSS 本来就只有英语,并且已经正确地从 hreflang 中排除,这部分不需要任何改动。

经验清单

如果你打算在一个静态 Astro 网站上做类似的事情:

  1. 动手之前先审计。 检查你的 hreflang 标签指向的页面是否真的存在。如果你的 i18n 基础设施搭建时间早于路由本身,你很可能已经踩了这个坑。
  2. 提取模板,而不是复制页面。 每个页面一个模板,上面套一层薄薄的、按语言区分的包装路由。腐化(如果会发生的话)只会发生在包装层,而包装层的同步成本很低。
  3. 元数据声明默认取最窄的真实范围。 一个默认值为“否”的 localized 标志,意味着新页面不会不小心过度声明。
  4. 刻意决定哪些内容保持不翻译,并把这个决定写下来。 博客、法律页面和错误页面都是合理的排除对象——但这应该是一个有记录的决定,而不是无声的默认状态。
  5. 翻译和路由是两个独立的问题。 上线路由并不要求翻译工作已经全部完成——在一个真实存在的语言路由里显示英语兜底文本,也比一个能正常工作却没有对应路由的翻译 key 更诚实。

我们前面还有不少手动翻译工作要做——非英语语言文件里有大量字符串目前还是等待人工翻译的英语占位文本。但现在这些 URL 是真实存在的,元数据也不再撒谎了。这才是更紧迫的问题。

分享XLinkedIn

Alex Rivera

项目经理兼工作流顾问,帮助团队找到真正适合其工作方式的工具。

保持关注

获取项目管理技巧和产品更新。无垃圾邮件,随时可取消订阅。

准备好简化您的工具栈了吗?

免费试用 Proman — 无需信用卡。

免费开始 →