现代前端国际化架构设计与优化 — 万字详解
一、总体实施流程
-
评估现状
-
识别硬编码文本
-
确定目标 locale 列表
-
分析 SSR/SPA 架构
-
检查微前端架构
-
-
设计资源组织与 key 规范
-
按模块/页面拆分
-
制定 key 命名规范
-
ICU 语法标准化
-
-
搭建提取与校验流程
-
提取 → 上传 TMS → 导入 → CI 校验
-
-
代码引入与初始化
-
React/Vue/Next.js 初始化
-
实现按需加载与缓存
-
-
测试与监控
-
单元测试、E2E、视觉回归
-
缺失 key、ICU 错误监控
-
-
逐步迁移
-
按页面/路由推进
-
灰度发布策略
-
二、提取硬编码字符串(自动化)
基于 Babel AST 的提取脚本:
javascript
// tools/extract-i18n.js
const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const glob = require('glob');
function extractFromFile(file) {
const code = fs.readFileSync(file, 'utf8');
const ast = parser.parse(code, {
sourceType: 'module',
plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining']
});
const results = [];
traverse(ast, {
JSXText(path) {
const text = path.node.value.trim();
if (text) results.push(text);
},
StringLiteral(path) {
const val = path.node.value;
if (val && val.length > 1 && !/^(https?:\/\/|\/|\.\/)/.test(val)) {
const p = path.parent;
if (p && p.type === 'CallExpression' && p.callee && p.callee.name === 't') {
// skip keyed t('xxx')
} else {
results.push(val);
}
}
},
TemplateLiteral(path) {
if (path.node.quasis && path.node.quasis.length) {
path.node.quasis.forEach(q => {
const v = q.value && q.value.cooked && q.value.cooked.trim();
if (v) results.push(v);
});
}
}
});
return results;
}
const root = process.argv[2] || './src';
const files = glob.sync(`${root}/**/*.{js,jsx,ts,tsx}`);
const out = {};
files.forEach(f => {
try {
const arr = extractFromFile(f);
if (arr && arr.length) out[f] = Array.from(new Set(arr));
} catch (e) {
console.warn('skip', f, e.message);
}
});
console.log(JSON.stringify(out, null, 2));
运行方式:node tools/extract-i18n.js ./src > messages.json
三、消息格式与 key 设计(ICU)
Key 规范:module.page.element(如 checkout.payment.title)
消息格式示例:
json
{
"checkout.payment.title": {
"message": "You will be charged {amount, number, USD} on {date, date, long}",
"description": "Title shown on payment confirmation. amount is in USD, date is the scheduled date."
},
"notifications.count": {
"message": "{count, plural, =0 {You have no notifications} one {You have one notification} other {You have # notifications}}",
"description": "Notification count summary"
}
}
四、前端 i18n 初始化
1. React (i18next + react-i18next)
javascript
// src/i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
const defaultNS = 'common';
async function loadLocale(locale) {
if (locale === 'en') {
return import('../locales/en/common.json');
}
return import(/* webpackChunkName: "locale-[request]" */ `../locales/${locale}/common.json`);
}
i18n.use(initReactI18next).init({
lng: detectLocaleFromBrowserOrCookie(),
fallbackLng: 'en',
ns: [defaultNS],
defaultNS,
resources: { en: { common: require('../locales/en/common.json') } },
interpolation: { escapeValue: false },
react: { useSuspense: true }
});
export async function changeLocale(locale) {
if (!i18n.hasResourceBundle(locale, defaultNS)) {
const messages = (await loadLocale(locale)).default;
i18n.addResourceBundle(locale, defaultNS, messages, true, true);
}
await i18n.changeLanguage(locale);
}
export default i18n;
2. Vue 3 (vue-i18n)
javascript
import { createI18n } from 'vue-i18n';
import en from '../locales/en/common.json';
export const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: { en }
});
export async function changeLocale(locale) {
if (!i18n.global.availableLocales.includes(locale)) {
const msgs = (await import(`../locales/${locale}/common.json`)).default;
i18n.global.setLocaleMessage(locale, msgs);
}
i18n.global.locale.value = locale;
}
3. Next.js (SSR/SSG)
javascript
// pages/index.js
export async function getStaticProps({ locale }) {
const messages = await import(`../locales/${locale}/common.json`);
return { props: { initialI18n: messages.default } };
}
五、构建与打包优化
-
配置 webpack/rollup/vite 的 code-splitting
-
动态 import 语言包,chunkName 带 locale
-
生产环境开启 gzip/brotli 压缩
-
SSR/SSG 为每个 locale 生成独立页面
-
语言包添加版本哈希便于缓存管理
六、Service Worker 缓存策略
javascript
// sw.js
const CACHE_NAME = 'i18n-v1';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
'/locales/en/common.json',
'/locales/zh/common.json'
]);
})
);
});
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/locales/')) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
}
});
七、CI 校验与自动化
package.json 脚本:
json
{
"scripts": {
"i18n:extract": "node tools/extract-i18n.js ./src > i18n_messages.json",
"i18n:lint": "node tools/i18n-lint.js ./locales",
"i18n:check": "npm run i18n:extract && npm run i18n:lint"
}
}
GitHub Actions 示例:
yaml
name: i18n Check
on: [pull_request]
jobs:
i18n:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run i18n:check
八、测试策略
单元测试 (Jest)
javascript
import messages_en from '../locales/en/common.json';
import messages_zh from '../locales/zh/common.json';
test('all keys present in zh', () => {
expect(Object.keys(messages_zh)).toEqual(
expect.arrayContaining(Object.keys(messages_en))
);
});
E2E 测试 (Playwright)
javascript
// test/i18n.spec.ts
test('rtl layout for Arabic', async ({ page }) => {
await page.goto('/?lang=ar');
await expect(page.locator('html')).toHaveAttribute('dir', 'rtl');
await expect(page.locator('nav')).toHaveScreenshot('nav-rtl.png');
});
九、监控与遥测
javascript
function t(key, vars) {
try {
return i18n.t(key, vars);
} catch (e) {
Sentry.captureException(e, {
extra: {
key,
locale: i18n.language,
route: window.location.pathname
}
});
return `[MISSING:${key}]`;
}
}
十、迁移策略
-
制定优先级页面列表
-
小范围引入 i18n 框架,灰度测试
-
CI 阶段阻止新增硬编码文本
-
替换后立即运行 UI 自动化测试
-
设置语言包版本化回滚策略
十一、微前端协作
主应用通信:
javascript
// 主应用
window.dispatchEvent(new CustomEvent('languageChange', {
detail: { locale: 'zh' }
}));
// 子应用
window.addEventListener('languageChange', (e) => {
changeLocale(e.detail.locale);
});
十二、RTL 特殊处理
HTML 设置:
html
<html lang="ar" dir="rtl">
CSS 适配:
css
.element {
margin-inline-start: 10px; /* 替代 margin-left */
margin-inline-end: 10px; /* 替代 margin-right */
}
.rtl-icon {
transform: scaleX(-1);
}
十三、翻译工作流自动化
-
提取 → 生成 XLIFF/CSV
-
上传翻译平台 (Crowdin/Lokalise)
-
翻译/审校 → 导回 JSON
-
CI 校验 → 合并发布
十四、回滚与版本管理
-
语言包版本号:
i18n-20251030-1 -
CDN 目录版本控制
-
配置中心控制语言包版本 URL
十五、AI 辅助翻译
-
机器翻译生成初稿
-
LLM 检查上下文一致性
-
AI 自动生成翻译上下文
十六、验收检查清单
-
默认语言无 FOUC
-
语言切换立即生效
-
ICU 表达式通过校验
-
RTL 页面方向正确
-
CI 无新增未国际化文本
-
监控面板记录正常
十七、工具清单
| 类别 | 推荐工具 |
|---|---|
| AST 提取 | @babel/parser, @babel/traverse |
| ICU 校验 | @formatjs/icu-messageformat-parser |
| 翻译平台 | Lokalise, Crowdin, Transifex |
| i18n 库 | react-i18next, vue-i18n, next-intl |
| 缓存 | Service Worker, IndexedDB |
| 测试 | Jest, Playwright, Percy |
| 监控 | Sentry, Datadog, Prometheus |
更多推荐



所有评论(0)