工程日记・第十八天:10种语言、224条航线、以及 isZh 大清洗——48小时全球化
问题:为什么10种语言不是"加几个 JSON 文件"那么简单
VOLO 最初以英文和中文两种语言发布,后来又轻量级地加上了法语和西班牙语。i18n 层使用了 next-intl,这是正确的工具选择。但实现中有一个致命的捷径嵌入在数十个页面文件中:isZh 模式。
页面没有使用 useTranslations() 或 getTranslations() 来获取本地化字符串,而是这样做的:
const isZh = locale === 'zh';
// 散布在每个渲染函数中:
<h2>{isZh ? '我们的机队' : 'Our Fleet'}</h2>
<p>{isZh ? '探索200+飞机' : 'Explore 200+ aircraft'}</p>
这对2种语言有效。对10种语言则完全崩溃。这个反模式嵌入在代码库的81个文件中:机队页面、目的地页面、航线页面、洞察页面、运营商页面、机场页面、博客页面、案例研究页面、预订组件和营销组件。
从4种语言扩展到10种语言,意味着我们必须先消灭 isZh——在所有地方,一次性完成。
第一步:基础设施——10语言管道
在触碰任何页面文件之前,我们先打好了地基:
路由与配置
- i18n 配置:添加 ja、ko、ar、de、pt、ru 到语言数组。更新语言显示名称、文字方向(阿拉伯语 RTL)和日期格式偏好。
- 路由:更新所有10种语言的路径模式。
- 中间件:为6种新语言添加 Accept-Language 检测,让日本访客访问
/时自动跳转到/ja。 - 语言切换器:重新设计以处理10个选项——国旗、母语脚本标签、按地区分组。
- 内容类型:扩展
MultilingualText以支持跨航线、目的地、机队、服务等的10语言字符串对象。
6个新翻译文件
每个语言 JSON 有约1,131个键,涵盖26+个命名空间。我们为日语、韩语、阿拉伯语、德语、葡萄牙语和俄语创建了完整翻译——不是机器翻译的占位符,而是尊重每种语言惯例的本地化字符串。
阿拉伯语需要特别注意:RTL 文字方向、正确的数字格式,以及没有直接阿拉伯语对应词的航空术语的文化适应。
提交:6eb909a — 61个文件,+5,504/-198行。当时 VOLO 历史上最大的单次提交。
第二步:isZh 大清洗——81个文件,一个反模式
这是最乏味但最重要的步骤。每一个 isZh 模式都必须替换为适当的 t('namespace.key') 调用。重构涉及:
| 类别 | 重构文件数 |
|---|---|
| 机队页面和组件 | 12个文件 |
| 洞察页面 | 18个文件 |
| 航线页面和组件 | 5个文件 |
| 目的地页面和组件 | 5个文件 |
| 机场和 FBO 页面 | 5个文件 |
| 博客和案例研究页面 | 6个文件 |
| 运营商页面 | 4个文件 |
| 预订和营销组件 | 8个文件 |
| 空腿航班页面 | 3个文件 |
| 工具和体验页面 | 5个文件 |
| 翻译文件(10种语言) | 10个文件 |
每次替换的模式都是相同的:
// 之前(isZh 反模式):
const isZh = locale === 'zh';
<h2>{isZh ? '详细规格' : 'Detailed Specifications'}</h2>
// 之后(next-intl):
const t = await getTranslations('fleet');
<h2>{t('detailedSpecs')}</h2>
总计:81个文件变更,+5,130/-2,222行。我们添加了约300个新翻译键来支持之前硬编码的字符串。提交:daf0119。
第三步:224条航线 × 10种语言
航线的 i18n 有独特挑战。航线数据文件包含需要用每种语言的母语脚本渲染的城市名称对:
- 英语:New York → London
- 中文:纽约 → 伦敦
- 日语:ニューヨーク → ロンドン
- 韩语:뉴욕 → 런던
- 阿拉伯语:نيويورك → لندن
- 俄语:Нью-Йорк → Лондон
这不仅是翻译——是音译。日语使用片假名,韩语使用韩文字母,俄语使用西里尔字母,阿拉伯语使用阿拉伯文字。224条航线中的每条都有出发地和目的地城市,每个城市名需要10种语言变体。
我们更新了所有7个航线数据文件,为每条航线包含带本地化城市名的 MultilingualText 对象。即 224条航线 × 2个城市 × 10种语言 = 4,480个本地化城市名。
提交:01b8d4c — 7个文件,+5,757/-607行。
第四步:Core Web Vitals 优化
在 i18n 工作进行的同时,我们也解决了 Lighthouse 和真实用户指标标记的性能问题:
- Hero LCP:为 Hero 主内容添加
fetchPriority="high"。React 19 原生支持fetchPriority——不需要@ts-expect-error。 - 延迟加载:首屏以下的非关键组件(GlobalMapSection、EmptyLegsTeaser、案例研究卡片)现在在初次绘制后加载。
- 自适应预加载器:VOLO 预加载器现在检测连接速度,在慢连接上跳过动画。
- 服务端地图数据:GlobalMapSection 之前在客户端获取航线坐标。我们将数据计算移到服务端组件,消除了布局偏移。
在同一个过程中,我们将航线目录从约100条扩展到224条,包含完整的双语描述(中英文)、真实 ICAO 代码和市场化定价。
提交:cf8256d — 11个文件,+8,258/-68行。
第五步:Agent 模式 i18n + 无障碍打磨
面向 AI Agent 的 UI(e2b.dev 风格设计)之前完全是英文的。有了10种语言支持后,我们国际化了每个 Agent 组件,添加了95+个新 i18n 键。
无障碍修复
- QuickQuoteModal:添加了
role="dialog"、aria-modal="true"、焦点陷阱和 Escape 键处理。 - ExitIntentPopup:相同的模态框无障碍模式,加上
aria-live="polite"。 - AirportAutocomplete:添加了
role="listbox"、aria-activedescendant、方向键导航和屏幕阅读器结果数量播报。 - Header:修复了移动菜单、浮动 CTA 和弹窗之间的 z-index 堆叠冲突。
提交:3f57ec7 — 28个文件,+1,451/-265行。
第六步:Google Search Console 清理
- robots.txt:移除了对 OpenGraph 和 Twitter 图片路由的过度封锁。
- 联系页 canonical:为 GSC 标记为重复的联系页添加了专用 canonical URL。
- 双重 locale 重定向:为
/en/en/...类 URL 添加了 Next.js 重定向规则。
核心数据
| 指标 | 之前 | 之后 |
|---|---|---|
| 支持语言 | 4 (en, zh, fr, es) | 10 (+ ja, ko, ar, de, pt, ru) |
| 每语言翻译键 | ~739 | ~1,131 |
| 总翻译键 | ~2,956 | ~11,310 |
| 航线数 | ~100 | 224 |
| 本地化城市名对 | ~400 | 4,480 |
| 页面 × 语言 | 86 × 4 = 344 | 86 × 10 = 860 |
| isZh 出现次数 | 200+ | 0 |
| 总变更文件 | — | 153 |
| 新增行数 | — | 27,184 |
| 删除行数 | — | 2,705 |
提交记录
两天内的8次提交:
6eb909a— i18n: 从4种扩展到10种语言(61个文件,+5,504/-198)a2e076c— i18n: 完成 ja、ko、de、pt、ru 翻译03c8829— i18n: 完成阿拉伯语全部30个命名空间翻译cf8256d— perf: Core Web Vitals 优化 + 航线扩展到224条(+8,258/-68)01b8d4c— i18n: 为224条航线添加8种语言城市名翻译(+5,757/-607)daf0119— i18n: 全面重构——移除 isZh 反模式(81个文件,+5,130/-2,222)3d5c681— fix: 解决 Google Search Console 覆盖率验证问题3f57ec7— ui/ux: 全面打磨——i18n、无障碍、视觉改进(28个文件,+1,451/-265)
经验教训
- 尽早消灭反模式。isZh 快捷方式在只有2种语言时省了10分钟,在需要10种语言时花了我们几个小时。每个硬编码的三元表达式都是一种带复利的技术债。
- 城市名比 UI 字符串难。翻译按钮标签很直接。把"圣保罗"音译成日语片假名(サンパウロ)需要翻译 API 有时会出错的文化知识。
- RTL 不只是
dir="rtl"。阿拉伯语支持需要审查网站上每个 flex 容器、padding/margin 方向和文本对齐。 - React 19 + next-intl v4 是好的技术栈。服务端组件使用
getTranslations()意味着静态翻译字符串的 JavaScript 包大小为零。
下一步
- 翻译质量审计——让母语使用者审查翻译
- 每语言独立 SEO 元描述
- RTL 布局测试——对阿拉伯语进行全面视觉 QA
- Hreflang 验证——确保10种语言替代链接正确
- 内容本地化——博客文章、案例研究、洞察报告多语言版本
准备好飞行了吗?几秒钟获取个性化包机报价。
订阅资讯
空腿航班优惠、新航线与航空洞察,直达您的邮箱。