React 国际化 (i18n) 最佳实践
中等 🟡React 生态
4 个标签
预计阅读时间:21 分钟
React国际化i18n本地化
React 国际化 (i18n) 最佳实践
国际化(Internationalization,简称 i18n)是构建全球应用的重要环节。一个优秀的国际化方案不仅要支持多语言翻译,还需要处理日期时间格式、数字格式、货币符号、复数规则、文字方向等复杂问题。React 提供了多种国际化方案,选择合适的方案对项目的可维护性和用户体验至关重要。
国际化库对比
i18next - 功能最全面的国际化方案:
•功能丰富,生态完整,支持 React、Vue、Angular 等多种框架
•支持复数、插值、嵌套翻译、命名空间等高级功能
•提供浏览器语言检测、后端加载、缓存等插件
•支持服务端渲染(SSR)和静态站点生成(SSG)
•社区活跃,文档完善,是 React 国际化的首选方案
typescriptCode
// i18next 配置示例
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';
i18n
.use(HttpBackend) // 从服务器加载翻译文件
.use(LanguageDetector) // 检测用户语言
.use(initReactI18next) // 绑定 react-i18next
.init({
fallbackLng: 'en', // 默认语言
supportedLngs: ['en', 'zh', 'ja', 'ko'],
ns: ['common', 'home', 'about'], // 命名空间
defaultNS: 'common',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json', // 翻译文件路径
},
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
caches: ['cookie', 'localStorage'],
},
interpolation: {
escapeValue: false, // React 已经处理 XSS
},
react: {
useSuspense: true, // 使用 Suspense 加载翻译
},
});
export default i18n;react-intl - Airbnb 出品的国际化方案:
•Airbnb 开发的 React 国际化解决方案,专注于格式化
•基于 ICU 消息格式,支持复杂的复数和性别规则
•提供 FormattedMessage、FormattedDate、FormattedNumber 等组件
•支持 React 和 React Native,API 设计一致
•适合需要复杂格式化的应用,如电商、金融类应用
typescriptCode
// react-intl 配置示例
import { IntlProvider, FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';
const messages = {
en: {
greeting: 'Hello, {name}!',
items: '{count, plural, one {# item} other {# items}}',
income: '{gender, select, male {He earns} female {She earns} other {They earn}} {amount}',
},
zh: {
greeting: '你好,{name}!',
items: '{count} 个项目',
income: '{gender, select, male {他赚} female {她赚} other {他们赚}} {amount}',
},
};
function App() {
const [locale, setLocale] = useState('en');
return (
<IntlProvider locale={locale} messages={messages[locale]}>
<div>
<FormattedMessage id="greeting" values={{ name: 'Alice' }} />
<FormattedNumber value={1234.56} style="currency" currency="USD" />
<FormattedDate value={new Date()} year="numeric" month="long" day="numeric" />
</div>
</IntlProvider>
);
}Format.js - 底层格式化工具集:
•一套国际化工具集,react-intl 的底层依赖
•支持格式化日期、数字、货币、相对时间等
•提供 ICU 消息格式的完整实现
•可以独立使用,也可以与其他国际化库配合
实现策略
消息管理最佳实践:
•集中管理翻译文件,使用 JSON 或 YAML 格式存储
•按功能模块划分命名空间,避免单个文件过大
•支持动态加载翻译文件,减少初始加载体积
•使用翻译管理工具(如 Crowdin、Lokalise)协作翻译
typescriptCode
// 翻译文件结构示例
// /locales/en/common.json
{
"welcome": "Welcome to our app",
"navigation": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"errors": {
"required": "This field is required",
"invalidEmail": "Please enter a valid email address"
}
}
// /locales/zh/common.json
{
"welcome": "欢迎使用我们的应用",
"navigation": {
"home": "首页",
"about": "关于我们",
"contact": "联系我们"
},
"errors": {
"required": "此字段为必填项",
"invalidEmail": "请输入有效的电子邮件地址"
}
}
// 组件中使用翻译
import { useTranslation } from 'react-i18next';
function Navigation() {
const { t } = useTranslation('common');
return (
<nav>
<Link to="/">{t('navigation.home')}</Link>
<Link to="/about">{t('navigation.about')}</Link>
<Link to="/contact">{t('navigation.contact')}</Link>
</nav>
);
}语言检测策略:
•从 URL 参数检测(如 ?lang=zh),适合 SEO 和分享链接
•从浏览器设置检测(navigator.language),提供默认语言
•从用户偏好检测(localStorage/cookie),记住用户选择
•优先级:URL 参数 > 用户偏好 > 浏览器设置 > 默认语言
typescriptCode
// 语言切换组件
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
// 保存用户偏好
localStorage.setItem('preferredLanguage', lng);
// 更新 URL 参数
const url = new URL(window.location.href);
url.searchParams.set('lang', lng);
window.history.replaceState({}, '', url.toString());
};
return (
<div className="language-switcher">
<button
onClick={() => changeLanguage('en')}
className={i18n.language === 'en' ? 'active' : ''}
>
English
</button>
<button
onClick={() => changeLanguage('zh')}
className={i18n.language === 'zh' ? 'active' : ''}
>
中文
</button>
</div>
);
}文本提取与翻译工作流:
•使用 i18next-scanner 或 babel-plugin-react-intl 自动提取文本
•支持批量翻译,导出为 XLIFF 或 CSV 格式
•与翻译服务集成,自动化翻译流程
•使用 CI/CD 检查翻译完整性
javascriptCode
// i18next-scanner 配置
module.exports = {
input: ['src/**/*.{js,jsx,ts,tsx}'],
output: 'public/locales',
options: {
debug: true,
sort: true,
func: {
list: ['t', 'i18n.t'],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
trans: {
component: 'Trans',
i18nKey: 'i18nKey',
defaultsKey: 'defaults',
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
lngs: ['en', 'zh', 'ja'],
ns: ['common', 'home'],
defaultLng: 'en',
defaultNs: 'common',
},
};性能优化
懒加载翻译文件:
•按需加载翻译文件,减少初始加载体积
•使用命名空间分隔翻译,按页面或功能加载
•配合 React Suspense 提供加载状态
typescriptCode
// 懒加载翻译配置
i18n
.use(HttpBackend)
.use(initReactI18next)
.init({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
// 只加载需要的命名空间
partialBundledLanguages: true,
// 预加载常用语言
preload: ['en'],
});
// 动态加载命名空间
function AdminPanel() {
const { t } = useTranslation('admin', { useSuspense: true });
return <div>{t('title')}</div>;
}
// 使用 Suspense 处理加载状态
function App() {
return (
<Suspense fallback={<Loading />}>
<AdminPanel />
</Suspense>
);
}缓存翻译文件:
•使用 Service Worker 缓存翻译文件
•利用浏览器 HTTP 缓存
•使用 localStorage 缓存已加载的翻译
typescriptCode
// 使用 localStorage 缓存
const localStorageBackend = {
type: 'localStorage',
prefix: 'i18next_',
expirationTime: 7 * 24 * 60 * 60 * 1000, // 7天
};
i18n.use(initReactI18next).init({
backend: {
backends: [
localStorageBackend, // 优先从缓存读取
HttpBackend, // 缓存未命中时从服务器加载
],
},
});优化渲染性能:
•避免不必要的重新渲染,使用 memo 包裹组件
•使用 useMemo 缓存翻译结果
•避免在渲染中动态生成翻译 key
typescriptCode
// 优化前:每次渲染都创建新对象
function UserCard({ user }) {
const { t } = useTranslation();
return (
<div>
<h2>{t('user.greeting', { name: user.name })}</h2>
<p>{t('user.role', { role: user.role })}</p>
</div>
);
}
// 优化后:使用 memo 和 useMemo
const UserCard = memo(function UserCard({ user }) {
const { t } = useTranslation();
const values = useMemo(() => ({ name: user.name }), [user.name]);
return (
<div>
<h2>{t('user.greeting', values)}</h2>
<p>{t('user.role', { role: user.role })}</p>
</div>
);
});最佳实践
组件化翻译:
•创建国际化组件封装翻译逻辑
•提高代码复用性,统一翻译风格
•便于后期维护和修改
typescriptCode
// 封装翻译组件
interface TranslatedTextProps {
id: string;
values?: Record<string, string | number>;
defaultValue?: string;
}
const TranslatedText: React.FC<TranslatedTextProps> = ({
id,
values,
defaultValue
}) => {
const { t } = useTranslation();
return <>{t(id, values, { defaultValue })}</>;
};
// 使用示例
<TranslatedText id="welcome.message" values={{ name: 'Alice' }} />占位符和插值:
•使用占位符处理动态内容,避免字符串拼接
•支持复数形式,不同语言复数规则不同
•支持性别变化,某些语言需要根据性别调整
typescriptCode
// 插值示例
// en.json
{
"greeting": "Hello, {{name}}!",
"items": "{{count}} item",
"items_plural": "{{count}} items"
}
// zh.json
{
"greeting": "你好,{{name}}!",
"items": "{{count}} 个项目"
}
// 使用
t('greeting', { name: 'Alice' }) // "Hello, Alice!" / "你好,Alice!"
t('items', { count: 1 }) // "1 item" / "1 个项目"
t('items', { count: 5 }) // "5 items" / "5 个项目"日期和时间本地化:
•使用本地化的日期和时间格式
•考虑时区问题,显示用户本地时间
•使用 Intl.DateTimeFormat 或库处理
typescriptCode
// 使用 Intl API 格式化日期
const formatDate = (date: Date, locale: string) => {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
}).format(date);
};
// en: "Saturday, March 15, 2026"
// zh: "2026年3月15日星期六"
// 使用 react-intl
import { FormattedDate, FormattedRelativeTime } from 'react-intl';
<FormattedDate
value={new Date()}
year="numeric"
month="long"
day="numeric"
/>
<FormattedRelativeTime
value={-5}
unit="minute"
numeric="auto"
/>
// "5 minutes ago" / "5分钟前"数字和货币本地化:
•使用本地化的数字格式
•正确处理货币符号和位置
•使用 Intl.NumberFormat 或库处理
typescriptCode
// 使用 Intl API 格式化数字
const formatNumber = (number: number, locale: string) => {
return new Intl.NumberFormat(locale).format(number);
};
// en: "1,234,567.89"
// zh: "1,234,567.89"
// de: "1.234.567,89"
// 格式化货币
const formatCurrency = (amount: number, locale: string, currency: string) => {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
}).format(amount);
};
// en-US, USD: "$1,234.56"
// zh-CN, CNY: "¥1,234.56"
// ja-JP, JPY: "¥1,235"RTL(从右到左)布局支持:
•测试 RTL 布局,如阿拉伯语、希伯来语
•使用 CSS 逻辑属性(start/end 代替 left/right)
•提供布局方向切换功能
typescriptCode
// RTL 支持
function App() {
const { i18n } = useTranslation();
const isRTL = ['ar', 'he', 'fa'].includes(i18n.language);
return (
<div dir={isRTL ? 'rtl' : 'ltr'} className={isRTL ? 'rtl' : 'ltr'}>
{/* 内容 */}
</div>
);
}
// CSS 逻辑属性
.card {
padding-inline-start: 16px; /* LTR: padding-left, RTL: padding-right */
margin-inline-end: 8px;
border-start-start-radius: 8px; /* LTR: top-left, RTL: top-right */
}测试国际化:
•测试不同语言的渲染效果
•测试文本长度变化对布局的影响
•测试 RTL 布局
•测试日期、数字、货币格式化
typescriptCode
// 国际化测试示例
import { render, screen } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';
describe('Navigation', () => {
it('renders in English', () => {
i18n.changeLanguage('en');
render(
<I18nextProvider i18n={i18n}>
<Navigation />
</I18nextProvider>
);
expect(screen.getByText('Home')).toBeInTheDocument();
});
it('renders in Chinese', () => {
i18n.changeLanguage('zh');
render(
<I18nextProvider i18n={i18n}>
<Navigation />
</I18nextProvider>
);
expect(screen.getByText('首页')).toBeInTheDocument();
});
});