目录
开篇:当"你好"变成"مرحبا"
你是否遇到过产品需要出海,多语言支持复杂,文本翻译管理混乱,RTL语言适配困难的痛苦场景?前端国际化是产品全球化的必经之路。网上搜到的国际化教程要么太浅,要么没有系统性方案。本文将从原理到实战,给出一个零成本上手方案,包含完整代码和避坑指南。
💡 效率技巧:国际化不是简单的"翻译替换",而是一套完整的产品工程化体系。越早规划,后期成本越低。
第一章:国际化核心概念——别再把i18n当密码
1.1 国际化 vs 本地化
先搞清楚几个容易混淆的概念:
┌─────────────────────────────────────────────────────────────┐
│ 全球化 (Globalization) │
│ = g11n │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 国际化 (i18n) │ │ 本地化 (l10n) │ │
│ │ Internationalization│ │ Localization │ │
│ │ │ │ │ │
│ │ "让产品具备支持 │ │ "让产品适应特定 │ │
│ │ 多语言的能力" │ │ 地区的习惯" │ │
│ │ │ │ │ │
│ │ • 文本抽取 │ │ • 翻译内容 │ │
│ │ • 日期/数字格式 │ │ • 文化适配 │ │
│ │ • 布局方向支持 │ │ • 本地法规 │ │
│ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
简单记忆法:
- i18n = "让产品"能"说"多种语言(能力建设)
- l10n = "让产品"在"某个地方"说得地道(内容适配)
⚠️ 避坑警告:很多团队把i18n和l10n混为一谈,结果开发阶段没考虑RTL布局,等产品要进中东市场时才发现整个UI需要重写!
1.2 Locale 到底是什么?
Locale 是标识特定地区语言文化的字符串,通常格式为:语言[_地区][.编码]
zh_CN.UTF-8
│ │ │
│ │ └── 字符编码
│ └────── 国家/地区
└────────── 语言代码
常见 Locale 示例:
┌──────────┬─────────────────┬────────────────────────┐
│ Locale │ 语言 │ 说明 │
├──────────┼─────────────────┼────────────────────────┤
│ en │ 英语 │ 通用英语 │
│ en_US │ 美式英语 │ 美国地区英语 │
│ en_GB │ 英式英语 │ 英国地区英语 │
│ zh │ 中文 │ 通用中文 │
│ zh_CN │ 简体中文 │ 中国大陆 │
│ zh_TW │ 繁体中文 │ 中国台湾 │
│ ja │ 日语 │ 日本 │
│ ar │ 阿拉伯语 │ 阿拉伯国家 │
│ he │ 希伯来语 │ 以色列 │
└──────────┴─────────────────┴────────────────────────┘
💡 效率技巧:建议使用 BCP 47 语言标签(如 zh-CN、en-US),这是国际标准,各大框架都支持。
1.3 国际化覆盖范围
真正的国际化不只是翻译文本,还包括:
国际化覆盖维度
├── 文本内容
│ ├── UI 文案翻译
│ ├── 错误信息本地化
│ └── 动态内容插值
├── 格式规范
│ ├── 日期时间格式
│ ├── 数字/货币格式
│ ├── 度量单位转换
│ └── 姓名/地址格式
├── 布局方向
│ ├── LTR (Left-to-Right)
│ └── RTL (Right-to-Left)
└── 文化习俗
├── 色彩含义
├── 图标/图片适配
└── 节假日处理
第二章:方案选型——三大框架的"三国杀"
2.1 主流方案对比
┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐
│ 特性 │ react-i18next │ vue-i18n │ FormatJS │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 适用框架 │ React / 通用 │ Vue │ React / 通用 │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 学习曲线 │ 中等 │ 低 │ 较高 │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 功能丰富度 │ 丰富 │ 丰富 │ 中等 │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 包体积 │ ~15KB │ ~10KB │ ~12KB │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ TypeScript │ 优秀 │ 良好 │ 优秀 │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ SSR支持 │ 良好 │ 优秀 │ 良好 │
├─────────────────┼─────────────────┼─────────────────┼─────────────────┤
│ 插件生态 │ 丰富 │ 中等 │ 较少 │
└─────────────────┴─────────────────┴─────────────────┴─────────────────┘
💡 效率技巧:如果你用 React,react-i18next 是社区最活跃的选择;如果是 Vue,vue-i18n 是官方推荐,生态成熟。
2.2 React 实战:react-i18next
安装依赖:
npm install react-i18next i18next i18next-http-backend i18next-browser-languagedetector
初始化配置 (i18n.js):
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import HttpApi from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(HttpApi) // 异步加载翻译文件
.use(LanguageDetector) // 自动检测浏览器语言
.use(initReactI18next) // 绑定 React
.init({
fallbackLng: 'zh', // 默认语言
debug: process.env.NODE_ENV === 'development',
interpolation: {
escapeValue: false, // React 已经做了 XSS 防护
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json', // 翻译文件路径
},
detection: {
order: ['localStorage', 'navigator', 'htmlTag'],
caches: ['localStorage'],
},
});
export default i18n;
翻译文件结构:
public/
└── locales/
├── zh/
│ └── translation.json
├── en/
│ └── translation.json
└── ar/
└── translation.json
translation.json (中文示例):
{
"welcome": "欢迎回来",
"greeting": "你好,{{name}}!",
"items_count": "你有 {{count}} 条消息",
"items_count_plural": "你有 {{count}} 条消息",
"buttons": {
"submit": "提交",
"cancel": "取消",
"save": "保存"
},
"errors": {
"required": "此字段为必填项",
"email_invalid": "请输入有效的邮箱地址"
}
}
组件中使用:
import { useTranslation } from 'react-i18next';
function UserProfile({ user }) {
const { t, i18n } = useTranslation();
return (
<div className="profile">
{/* 简单翻译 */}
<h1>{t('welcome')}</h1>
{/* 带变量的翻译 */}
<p>{t('greeting', { name: user.name })}</p>
{/* 复数处理 */}
<p>{t('items_count', { count: user.messages.length })}</p>
{/* 嵌套键 */}
<button>{t('buttons.save')}</button>
{/* 语言切换 */}
<button onClick={() => i18n.changeLanguage('en')}>
Switch to English
</button>
</div>
);
}
高阶组件 (HOC) 方式:
import { withTranslation } from 'react-i18next';
class LegacyComponent extends React.Component {
render() {
const { t } = this.props;
return <h1>{t('welcome')}</h1>;
}
}
export default withTranslation()(LegacyComponent);
⚠️ 避坑警告:不要在组件渲染时调用
i18n.changeLanguage(),这会导致无限重渲染。应该在用户交互(如点击切换按钮)时调用。
2.3 Vue 实战:vue-i18n
安装:
npm install vue-i18n@9 # Vue 3 版本
初始化 (i18n/index.js):
import { createI18n } from 'vue-i18n';
import zh from './locales/zh.json';
import en from './locales/en.json';
import ar from './locales/ar.json';
const messages = {
zh,
en,
ar,
};
const i18n = createI18n({
legacy: false, // 使用 Composition API 模式
locale: 'zh', // 默认语言
fallbackLocale: 'zh', // 回退语言
messages,
// 复数规则(针对复杂语言)
pluralRules: {
// 阿拉伯语有6种复数形式
ar: (choice) => {
if (choice === 0) return 0;
if (choice === 1) return 1;
if (choice === 2) return 2;
if (choice % 100 >= 3 && choice % 100 <= 10) return 3;
if (choice % 100 >= 11) return 4;
return 5;
},
},
});
export default i18n;
main.js 引入:
import { createApp } from 'vue';
import App from './App.vue';
import i18n from './i18n';
const app = createApp(App);
app.use(i18n);
app.mount('#app');
组件中使用:
<template>
<div class="user-profile">
<!-- 简单翻译 -->
<h1>{{ $t('welcome') }}</h1>
<!-- 带变量的翻译 -->
<p>{{ $t('greeting', { name: user.name }) }}</p>
<!-- 复数处理 -->
<p>{{ $t('messages', user.messages.length) }}</p>
<!-- 嵌套键 -->
<button>{{ $t('buttons.save') }}</button>
<!-- 语言切换 -->
<select v-model="$i18n.locale">
<option value="zh">中文</option>
<option value="en">English</option>
<option value="ar">العربية</option>
</select>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
// Composition API 方式
const { t, locale } = useI18n();
// 编程式切换语言
const switchLang = (lang) => {
locale.value = lang;
};
</script>
翻译文件 (ar.json - 阿拉伯语示例):
{
"welcome": "مرحباً",
"greeting": "مرحباً، {{name}}!",
"messages": "لا رسائل | رسالة واحدة | رسالتان | {count} رسائل | {count} رسالة | {count} رسالة",
"buttons": {
"submit": "إرسال",
"cancel": "إلغاء",
"save": "حفظ"
}
}
💡 效率技巧:vue-i18n 的复数语法使用管道符
|分隔不同形式,阿拉伯语有6种复数形式,务必正确配置!
2.4 FormatJS (React Intl)
适合需要 ICU MessageFormat 标准格式的项目:
import { IntlProvider, FormattedMessage, useIntl } from 'react-intl';
// 使用 Hook
function MyComponent() {
const intl = useIntl();
return (
<div>
<p>
{intl.formatMessage(
{ id: 'greeting', defaultMessage: 'Hello, {name}!' },
{ name: 'World' }
)}
</p>
{/* 或使用组件 */}
<p>
<FormattedMessage
id="items_count"
values={{ count: 5 }}
/>
</p>
</div>
);
}
第三章:翻译文件管理——从JSON到Excel的进化论
3.1 翻译文件格式对比
┌──────────┬──────────┬──────────┬──────────┬─────────────────────────────┐
│ 格式 │ 可读性 │ 易编辑 │ 版本控制 │ 适用场景 │
├──────────┼──────────┼──────────┼──────────┼─────────────────────────────┤
│ JSON │ 良 │ 中 │ 优 │ 开发首选,结构清晰 │
├──────────┼──────────┼──────────┼──────────┼─────────────────────────────┤
│ YAML │ 优 │ 优 │ 良 │ 非技术人员友好 │
├──────────┼──────────┼──────────┼──────────┼─────────────────────────────┤
│ CSV │ 良 │ 优 │ 中 │ 与翻译团队交接 │
├──────────┼──────────┼──────────┼──────────┼─────────────────────────────┤
│ Excel │ 中 │ 优 │ 差 │ 大型项目,多翻译协作 │
├──────────┼──────────┼──────────┼─────────────────────────────┤
│ PO/Gettext│ 中 │ 中 │ 优 │ 传统软件本地化 │
└──────────┴──────────┴──────────┴──────────┴─────────────────────────────┘
3.2 JSON 结构最佳实践
扁平化 vs 嵌套:
// ❌ 不推荐:过度嵌套
{
"pages": {
"home": {
"header": {
"title": "首页",
"subtitle": "欢迎"
}
}
}
}
// ✅ 推荐:适度扁平化
{
"home_title": "首页",
"home_subtitle": "欢迎",
"login_button": "登录",
"login_error_invalid": "用户名或密码错误"
}
// ✅ 或按功能模块分组
{
"common": {
"save": "保存",
"cancel": "取消",
"delete": "删除"
},
"user": {
"profile": "个人资料",
"settings": "设置"
},
"errors": {
"network": "网络错误",
"timeout": "请求超时"
}
}
3.3 Excel 导入导出工具
Node.js 脚本示例:
// scripts/i18n-excel.js
const XLSX = require('xlsx');
const fs = require('fs');
const path = require('path');
const LOCALES_DIR = './src/locales';
const LANGUAGES = ['zh', 'en', 'ja', 'ar'];
// JSON 转 Excel(给翻译人员)
function jsonToExcel() {
const data = [];
// 读取中文作为基准
const zhTranslations = JSON.parse(
fs.readFileSync(path.join(LOCALES_DIR, 'zh.json'), 'utf-8')
);
// 递归展平对象
function flatten(obj, prefix = '') {
const result = {};
for (const key in obj) {
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(result, flatten(obj[key], newKey));
} else {
result[newKey] = obj[key];
}
}
return result;
}
const flatZh = flatten(zhTranslations);
// 构建 Excel 数据
for (const key in flatZh) {
const row = { key, description: '' };
LANGUAGES.forEach(lang => {
const translations = JSON.parse(
fs.readFileSync(path.join(LOCALES_DIR, `${lang}.json`), 'utf-8')
);
const flat = flatten(translations);
row[lang] = flat[key] || '';
});
data.push(row);
}
// 创建工作簿
const ws = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Translations');
XLSX.writeFile(wb, './translations.xlsx');
console.log('✅ Excel 文件已生成: translations.xlsx');
}
// Excel 转 JSON(翻译完成后导入)
function excelToJson() {
const workbook = XLSX.readFile('./translations.xlsx');
const worksheet = workbook.Sheets['Translations'];
const data = XLSX.utils.sheet_to_json(worksheet);
// 按语言分组
const translations = {};
LANGUAGES.forEach(lang => translations[lang] = {});
data.forEach(row => {
const keys = row.key.split('.');
LANGUAGES.forEach(lang => {
let current = translations[lang];
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = row[lang] || '';
});
});
// 写入文件
LANGUAGES.forEach(lang => {
fs.writeFileSync(
path.join(LOCALES_DIR, `${lang}.json`),
JSON.stringify(translations[lang], null, 2)
);
});
console.log('✅ JSON 文件已更新');
}
// 命令行参数
const command = process.argv[2];
if (command === 'export') jsonToExcel();
else if (command === 'import') excelToJson();
else console.log('Usage: node i18n-excel.js [export|import]');
package.json 脚本:
{
"scripts": {
"i18n:export": "node scripts/i18n-excel.js export",
"i18n:import": "node scripts/i18n-excel.js import"
}
}
💡 效率技巧:给 Excel 加一列 “description” 用于说明上下文,避免翻译人员看到"Save"不知道是"保存文件"还是"节省"。
第四章:RTL语言适配——当界面开始"反向行驶"
4.1 什么是 RTL?
RTL (Right-to-Left) 指从右向左书写的语言,主要包括:
- 阿拉伯语 (Arabic)
- 希伯来语 (Hebrew)
- 波斯语 (Persian/Farsi)
- 乌尔都语 (Urdu)
LTR 布局: RTL 布局:
┌──────────────────┐ ┌──────────────────┐
│ Logo Nav │ │ Nav Logo │
├──────────────────┤ ├──────────────────┤
│ ← Text flows ← │ │ → Text flows → │
│ left to right │ │ right to left │
├──────────────────┤ ├──────────────────┤
│ [Cancel] [Save] │ │ [Save] [Cancel] │
└──────────────────┘ └──────────────────┘
4.2 CSS 逻辑属性
现代 CSS 支持逻辑属性,自动适配方向:
/* ❌ 物理属性 - 需要两套样式 */
.ltr .card {
margin-left: 20px;
border-left: 2px solid blue;
text-align: left;
}
.rtl .card {
margin-right: 20px;
border-right: 2px solid blue;
text-align: right;
}
/* ✅ 逻辑属性 - 一套样式走天下 */
.card {
margin-inline-start: 20px; /* 逻辑起始边 */
border-inline-start: 2px solid blue;
text-align: start; /* 逻辑起始对齐 */
}
/* 常用逻辑属性对照表 */
┌─────────────────────┬──────────────────────┐
│ 物理属性 │ 逻辑属性 │
├─────────────────────┼──────────────────────┤
│ margin-left │ margin-inline-start│
│ margin-right │ margin-inline-end │
│ padding-left │ padding-inline-start│
│ padding-right │ padding-inline-end │
│ border-left │ border-inline-start│
│ border-right │ border-inline-end │
│ left: 0 │ inset-inline-start: 0│
│ right: 0 │ inset-inline-end: 0 │
│ text-align: left │ text-align: start │
│ text-align: right │ text-align: end │
└─────────────────────┴──────────────────────┘
4.3 方向感知布局
HTML 设置:
<!-- 根据语言自动设置 dir 属性 -->
<html lang="ar" dir="rtl">
<!-- 或 -->
<html lang="en" dir="ltr">
React 中动态设置:
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur'];
function App() {
const { i18n } = useTranslation();
useEffect(() => {
const isRTL = RTL_LANGUAGES.includes(i18n.language);
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
document.documentElement.lang = i18n.language;
}, [i18n.language]);
return <Router />;
}
CSS 方向感知:
/* 使用 :dir() 伪类 */
.flex-container {
display: flex;
gap: 1rem;
}
/* LTR 时按钮顺序 */
.flex-container:dir(ltr) .submit-btn {
order: 2;
}
/* RTL 时按钮顺序反转 */
.flex-container:dir(rtl) .submit-btn {
order: 1;
}
/* 或使用属性选择器作为降级方案 */
[dir="rtl"] .icon-arrow {
transform: scaleX(-1);
}
4.4 镜像与非镜像元素
不是所有元素都需要镜像:
需要镜像的元素: 不需要镜像的元素:
┌─────────────────┐ ┌─────────────────┐
│ 导航栏顺序 │ │ 进度条方向 │
│ 按钮组顺序 │ │ 视频播放控制 │
│ 边距/内边距 │ │ 图表/图形 │
│ 图标方向 │ │ 数字键盘布局 │
│ 文字对齐 │ │ 时间线方向 │
└─────────────────┘ └─────────────────┘
图标处理:
/* 自动镜像需要翻转的图标 */
.icon-arrow,
.icon-back,
.icon-forward {
/* 在 RTL 环境下水平翻转 */
[dir="rtl"] & {
transform: scaleX(-1);
}
}
/* 不翻转的图标(如 Logo) */
.icon-logo {
/* 不应用翻转 */
}
⚠️ 避坑警告:数字、数学公式、URL、代码片段在 RTL 语言中仍然保持 LTR 方向,不要强制翻转!
第五章:动态切换与持久化——让用户说了算
5.1 语言切换组件
React 实现:
import { useTranslation } from 'react-i18next';
const LANGUAGES = [
{ code: 'zh', name: '中文', flag: '🇨🇳' },
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' },
{ code: 'ar', name: 'العربية', flag: '🇸🇦', rtl: true },
];
function LanguageSwitcher() {
const { i18n } = useTranslation();
const currentLang = i18n.language;
const handleChange = (langCode) => {
i18n.changeLanguage(langCode);
// 持久化到 localStorage(i18next-browser-languagedetector 已自动处理)
// 可添加额外逻辑:上报统计、刷新页面等
};
return (
<div className="language-switcher">
<select
value={currentLang}
onChange={(e) => handleChange(e.target.value)}
>
{LANGUAGES.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
</div>
);
}
5.2 语言持久化方案
持久化策略优先级:
┌─────────────────────────────────────────────────────────┐
│ 1. URL 参数 (?lang=en) - 最高优先级,可分享链接 │
│ 2. 已登录用户 → 后端存储的用户偏好设置 │
│ 3. localStorage - 本地缓存 │
│ 4. Cookie - 服务端渲染时读取 │
│ 5. 浏览器语言 (navigator.language) - 默认 │
│ 6. 应用默认语言 - 兜底 │
└─────────────────────────────────────────────────────────┘
自定义持久化插件:
// plugins/i18n-persistence.js
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
export function useLanguagePersistence() {
const { i18n } = useTranslation();
useEffect(() => {
// 1. 从 URL 读取
const urlParams = new URLSearchParams(window.location.search);
const urlLang = urlParams.get('lang');
// 2. 从 localStorage 读取
const storedLang = localStorage.getItem('user-language');
// 3. 从用户配置读取(如果已登录)
const userLang = window.__USER_CONFIG__?.language;
// 优先级:URL > 用户配置 > localStorage > 浏览器
const targetLang = urlLang || userLang || storedLang;
if (targetLang && targetLang !== i18n.language) {
i18n.changeLanguage(targetLang);
}
}, []);
useEffect(() => {
// 语言变化时同步到各存储
const handleLanguageChanged = (lng) => {
// localStorage
localStorage.setItem('user-language', lng);
// Cookie(用于 SSR)
document.cookie = `language=${lng};path=/;max-age=31536000`;
// URL(可选,保持分享一致性)
const url = new URL(window.location);
url.searchParams.set('lang', lng);
window.history.replaceState({}, '', url);
// 同步到后端(如果已登录)
syncLanguageToServer(lng);
};
i18n.on('languageChanged', handleLanguageChanged);
return () => i18n.off('languageChanged', handleLanguageChanged);
}, [i18n]);
}
5.3 语言切换动画
import { motion, AnimatePresence } from 'framer-motion';
function App() {
const { i18n } = useTranslation();
return (
<AnimatePresence mode="wait">
<motion.div
key={i18n.language}
initial={{ opacity: 0, x: i18n.dir() === 'rtl' ? -20 : 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: i18n.dir() === 'rtl' ? 20 : -20 }}
transition={{ duration: 0.2 }}
>
<Router />
</motion.div>
</AnimatePresence>
);
}
第六章:实战案例——从1种语言到20种的蜕变
6.1 项目背景
某 SaaS 产品从仅支持中文,扩展到支持 20 种语言,服务全球用户:
改造前:
• 硬编码中文文本遍布代码
• 每次修改文案需要发版
• 新增语言 = 2周工作量
• 翻译管理靠邮件往来
改造后:
• 100% 文本外置化
• 翻译独立部署,无需发版
• 新增语言 = 2天工作量
• 翻译管理效率提升 3 倍
6.2 迁移策略
第一步:文本提取
# 使用 i18next-scanner 自动提取
npm install --save-dev i18next-scanner
# 配置 i18next-scanner.config.js
module.exports = {
input: ['src/**/*.{js,jsx,ts,tsx}'],
output: './public/locales',
options: {
debug: true,
func: {
list: ['i18next.t', 't'],
},
trans: {
component: 'Trans',
},
lngs: ['zh', 'en'],
defaultLng: 'zh',
resource: {
loadPath: 'public/locales/{{lng}}/{{ns}}.json',
savePath: 'public/locales/{{lng}}/{{ns}}.json',
},
},
};
第二步:代码改造
// 迁移前(硬编码)
function LoginButton() {
return <button>登录</button>;
}
// 迁移后
function LoginButton() {
const { t } = useTranslation();
return <button>{t('login_button')}</button>;
}
第三步:翻译工作流
开发流程:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 开发新功能 │ → │ 提取文案 │ → │ 导出Excel │ → │ 翻译团队 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
↓
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 上线发布 │ ← │ 集成测试 │ ← │ 导入翻译 │ ← │ 翻译完成 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
6.3 性能优化
按需加载翻译文件:
// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
i18n
.use(Backend)
.use(initReactI18next)
.init({
fallbackLng: 'zh',
ns: ['common', 'home', 'user'], // 命名空间
defaultNS: 'common',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
// 组件中按需加载命名空间
function UserPage() {
const { t } = useTranslation('user'); // 只加载 user.json
return <div>{t('profile_title')}</div>;
}
预加载关键语言包:
<!-- index.html -->
<link rel="preload" href="/locales/zh/common.json" as="fetch" crossorigin>
<link rel="preload" href="/locales/en/common.json" as="fetch" crossorigin>
文末三件套
📦 源码获取
关注此系列获取后续更新,后台回复「国际化」获取完整源码和配套工具。
🤔 思考题
你的产品准备好出海了吗?在评论区分享你的国际化踩坑经历!
📢 系列预告
下一篇《前端动画与交互实战》,带你从零打造丝滑的用户体验。
总结
本文从前端国际化的核心概念出发,系统性地介绍了:
- i18n/l10n/locale 的区别与联系
- react-i18next/vue-i18n/FormatJS 三大方案的选型与实战
- JSON/YAML/Excel 翻译文件的管理策略
- RTL 语言适配 的技术细节
- 动态语言切换 与持久化方案
- 真实项目改造 的完整流程
关键数据回顾:
- ✅ 支持语言从 1 种扩展到 20 种
- ✅ 翻译管理效率提升 3 倍
- ✅ 国际化覆盖率 100%
💡 最后的话:国际化不是"锦上添花",而是产品走向世界的必经之路。越早规划,成本越低。希望这份指南能帮你少走弯路!
标签: 前端国际化, i18n, 多语言, React, Vue, 本地化, 全球化

1291

被折叠的 条评论
为什么被折叠?



