31 欧元,我们为整个网站加上了日语和巴西葡萄牙语
9 月 29 日,我们上线了 globalize.now 的日语和巴西葡萄牙语版本:所有页面、所有定价表、全部 61 篇博客文章。翻译本身用了 8 分钟,应用计费约 31 欧元。globalize.now 是 AI 驱动的本地化基础设施,下面就是在自家网站上跑一遍的实况,每个数字都来自任务页面和 Git 历史,而不是凭记忆。
我们那篇 2017 年本地化增长手册要花多少钱 的结尾说,我们暂时还没公布自己的数据。这是第一份。它是成本数据,不是增长数据:还没人来得及访问 /ja/。
我们到底翻译了什么?
整个网站,两种语言。以下是按仓库统计的清单:
- 界面: 分布在五个消息目录(核心站点、定价、对比页、集成页和翻译示例)中的 1,782 个英文字符串,约 2.1 万词。
- 博客: 61 篇文章,约 11 万词。
- 目标语言: 日语(
ja)和巴西葡萄牙语(pt-BR)。
也就是说,约 13 万源语言单词被译成了两种语言。这个 PR 涉及 170 个文件,新增 34,681 行。合并后,站点地图在 11 个 locale 下共列出 811 个 URL,站点构建出 962 个静态页面。
花了多长时间?
8 分钟翻译,外加更长的质量检查。我们的 CTO Arturs 把两种语言添加到项目中,任务在 main 的 UTC 08:56 启动。任务页面给出了明细:
| 阶段 | 耗时 |
|---|---|
| 翻译(11,264 个新字符串,每种语言 5,632 个) | 8 分 1 秒 |
| 自动化 QA 审查 | 27 分 11 秒 |
| 交付到 PR | 5 分 29 秒 |
包含两种语言的提交在 UTC 09:29 落地。任务中另外 45,106 个字符串直接来自翻译记忆库,因为已有的八种语言没有变化。
Arturs 在团队群里提到时,估计的是 10 分钟。翻译这部分他说得没错,但对整个任务就过于乐观了,差出来的时间就是 QA。我们宁愿花 27 分钟机器时间做检查,也不想跳过它。
自动化 QA 发现了什么?
11,264 个新字符串中有 72 条标记,全部评级为轻微。11,192 个一次通过,QA 过程中还自动应用了 182 处修复。
我们查看的标记,大多是长度检查对日语过于谨慎。“Pricing”译成 料金,只有两个字,长度比 0.29 刚好低于 0.3 的下限。这是正确的译文触发了为字母语言设计的启发式规则,并不是错误。不过我们还是保留这些标记不隐藏:一个从不触发的检查,等于没有检查。
花了多少钱?
约 31 欧元:项目的按语言成本视图显示,日语 15.72 欧元,巴西葡萄牙语 16.15 欧元。这是应用的实际收费,和客户翻译同等规模内容看到的一样,不是我们的内部模型成本,也不是折扣价。
按约 26 万翻译单词算,每 1,000 词约 12 美分。除此之外没有任何额外计费:没有按语言收费,也没有按席位收费,所以加第十二种语言和加第三种语言的成本一样。当前套餐见 定价页。
工程上到底做了什么?
一个 PR,当天下午就合并了,而且其中没有一项是翻译。PR 创建后,翻译任务在上面又跑了一遍,56,390 个字符串中有 56,370 个直接取自翻译记忆库,所以已翻译的目录文件 7 分钟就进了新文件。自动化的是翻译这一环。在 Next.js 站点里注册新 locale 仍然是你自己的代码,规划之前最好先弄清楚“你自己的代码”究竟指什么。
给 next-intl 添加一个 locale,只需在路由配置里加一行。其余的 diff,是所有构建 URL、读取 locale 或列出 locale 的地方:
- 路由: locale 列表,加上
pt-BR的 URL 前缀覆盖(见下一节)。 - SEO 元数据: hreflang 集群、canonical、
og:locale(ja_JP、pt_BR),以及元数据辅助函数遍历的 locale 列表。 - 格式化:
Intl为ja-JP和pt-BR添加标签,使日期和数字正确渲染。 - 语言切换器: 链接到每个 locale 的真实
<a href hreflang>,让爬虫能够顺着抓取。 - 站点地图与检查: 站点地图生成器、lastmod 检查、内部链接检查和翻译校验器,都需要识别这两个新代码。
- 目录文件: 为两个 locale 新建仅含文件头的空
.po文件,并在同一个 PR 中用翻译记忆库填充。
如果你的站点没有我们这么多定制,这份清单上大部分只是配置。但只要你在任何地方手写拼接 URL(大多数站点多少都有),就会在链接第一次出错时逐个发现这些地方。开发者指南 介绍了配置方法,AI 本地化 Agent 究竟做什么 则说明了自动化与非自动化部分的分工。
为什么巴西葡萄牙语是 /pt-br/,而不是 /pt-BR/?
因为语言标签和 URL 段是两回事,各司其职。
按 BCP 47,正确的标签是 pt-BR。凡是机器读取语言的地方,我们都用它:文件名、<html lang>、hreflang、og:locale 和 Intl。只有 URL 改成小写:
// i18n/routing.ts (trimmed)
const prefixes = {
"pt-BR": "/pt-br",
} as const;
export const routing = defineRouting({
locales: ["en", "it", "de", "fr", "es", "lv", "hi", "lt", "et", "ja", "pt-BR"],
defaultLocale: "en",
localePrefix: { mode: "always", prefixes },
});
/** The URL path segment for a locale: `pt-BR` → `pt-br`, `de` → `de`. */
export function localeUrlSegment(locale: string): string {
const prefix = (prefixes as Record<string, string | undefined>)[locale];
return prefix ? prefix.slice(1) : locale;
}
大小写混用的路径容易拼错,而一个同时响应 /pt-BR/ 和 /pt-br/ 的站点,等于每个页面都有两份副本在互相竞争。所以 /pt-BR/ 会重定向到 /pt-br/,localeUrlSegment() 则根据前缀映射表构建所有手写 URL。只有一个事实来源,语言切换器、canonical 和站点地图就不会各说各话。
为什么选日语和巴西葡萄牙语?
它们是我们此前没有覆盖的大体量网络语言。按 W3Techs 在我们上线当天的统计,日语是 4.9% 网站的内容语言,葡萄牙语是 4.1%。
做这件事的理由,比我们的历史还要久远。CSA Research 在 2020 年调查了 29 个国家的 8,709 名消费者,76% 表示更愿意购买提供母语信息的产品。这是需求侧。变化出在供给侧:两种语言过去意味着要找供应商,现在只需 31 欧元。
我们自己挑选语言的规则,就是 增长手册文章 里写的那一条:先看流量已经来自哪里,加两种语言,其他一概不动,然后观察一个季度。这两种语言我们就照此执行。
还有哪些没验证?
母语审校。目前还没有以日语或巴西葡萄牙语为母语的人通读过输出结果。
已经验证的部分:每个字符串都过了自动化 QA,构建通过了所有 prebuild 检查,我们在预发布环境抽查的页面都返回 200,lang、canonical 和 hreflang 正确,页面渲染完整。构建无法判断的,是一句话读起来像不像人写的。我们审计自己的德语和法语页面时就吃过这个亏,发现整页的语体都不对。
所以接下来是审校,我们会把审校发现的问题,包括所有错误,更新到这篇文章里。
你的站点也能这样做吗?
如果字符串已经放在语言目录里,可以,翻译只需几分钟。如果还硬编码在组件里,得先转换。转换只需在应用内的连接流程里运行一次,之后由推送任务翻译新增的字符串。vibe coder 指南介绍了用 Lovable、Bolt 或 Cursor 构建的应用,这类应用里硬编码字符串是常态。
成本取决于翻译的字数,与语言数量和团队人数无关。所以“值不值得加一种语言”这个问题的答案变了:现在做一次度量,比开一次讨论会还便宜。
在你自己的仓库上试试
两种语言,我们花了 8 分钟翻译,成本约 31 欧元。连接仓库,选两种分析数据已经指向的语言,看看你自己的账单长什么样。
globalize.now 把硬编码的应用文案转换为可直接翻译的语言文件,并在你持续发布时保持同步更新。
免费试用 globalize.now