React i18next 国际化落地
React 项目做国际化时,最小闭环是:初始化 i18next、组织资源文件、在组件里读取文案、提供语言切换入口。
依赖安装
bash
npm install i18next react-i18next i18next-browser-languagedetector如果需要按需加载翻译文件,再加入:
bash
npm install i18next-http-backend目录结构
txt
src/
├── i18n/
│ ├── index.ts
│ └── resources/
│ ├── en-US.json
│ └── zh-CN.json
├── components/
│ └── LanguageSwitcher.tsx
└── App.tsx初始化配置
ts
// src/i18n/index.ts
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import enUS from './resources/en-US.json';
import zhCN from './resources/zh-CN.json';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
'en-US': { translation: enUS },
'zh-CN': { translation: zhCN },
},
fallbackLng: 'zh-CN',
supportedLngs: ['zh-CN', 'en-US'],
interpolation: {
escapeValue: false,
},
detection: {
order: ['localStorage', 'navigator'],
caches: ['localStorage'],
},
});
export default i18n;入口文件只需要引入一次:
tsx
// src/main.tsx
import './i18n';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);资源文件组织
中文:
json
{
"common": {
"save": "保存",
"cancel": "取消"
},
"home": {
"title": "首页",
"welcome": "你好,{{name}}"
}
}英文:
json
{
"common": {
"save": "Save",
"cancel": "Cancel"
},
"home": {
"title": "Home",
"welcome": "Hello, {{name}}"
}
}资源组织建议:
- 小项目可以每种语言一个 JSON。
- 中大型项目按业务模块拆 namespace。
- key 保持稳定,不要把整句中文当 key。
- 删除页面时同步删除无用 key,避免翻译文件膨胀。
在组件中使用
tsx
import { useTranslation } from 'react-i18next';
export function Home() {
const { t } = useTranslation();
return (
<main>
<h1>{t('home.title')}</h1>
<p>{t('home.welcome', { name: 'Renouc' })}</p>
<button>{t('common.save')}</button>
</main>
);
}带默认值:
tsx
t('profile.empty', '暂无资料');列表或枚举建议通过固定 key 映射:
ts
const statusText = {
pending: 'order.status.pending',
paid: 'order.status.paid',
closed: 'order.status.closed',
} as const;
t(statusText[status]);不要用接口返回值直接拼 key,除非后端枚举稳定且前端有兜底。
语言切换
tsx
import { useTranslation } from 'react-i18next';
const languages = [
{ label: '中文', value: 'zh-CN' },
{ label: 'English', value: 'en-US' },
];
export function LanguageSwitcher() {
const { i18n } = useTranslation();
return (
<select
value={i18n.resolvedLanguage}
onChange={(event) => i18n.changeLanguage(event.target.value)}
>
{languages.map((language) => (
<option key={language.value} value={language.value}>
{language.label}
</option>
))}
</select>
);
}切换语言后,i18next-browser-languagedetector 会把语言写入 localStorage。下一次进入页面会优先读取用户选择。
命名空间拆分
页面较多时,可以按模块拆资源文件。
txt
public/locales/
├── zh-CN/
│ ├── common.json
│ └── dashboard.json
└── en-US/
├── common.json
└── dashboard.json配置按需加载:
ts
import HttpBackend from 'i18next-http-backend';
i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'zh-CN',
supportedLngs: ['zh-CN', 'en-US'],
ns: ['common'],
defaultNS: 'common',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
interpolation: {
escapeValue: false,
},
});组件中指定 namespace:
tsx
const { t } = useTranslation('dashboard');富文本翻译
带链接或强调文本时,使用 Trans,不要把 HTML 字符串直接塞进页面。
json
{
"terms": "继续使用即表示同意 <link>服务条款</link>"
}tsx
import { Trans } from 'react-i18next';
<Trans
i18nKey="terms"
components={{
link: <a href="/terms" />,
}}
/>;日期和数字
日期、数字、金额优先使用 Intl。i18n 文案只负责句子结构。
ts
const formatter = new Intl.NumberFormat(i18n.resolvedLanguage, {
style: 'currency',
currency: 'CNY',
});
formatter.format(99);日期:
ts
new Intl.DateTimeFormat(i18n.resolvedLanguage, {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date());缺失翻译处理
开发阶段可以打开缺失 key 提示。
ts
i18n.init({
saveMissing: true,
missingKeyHandler(lng, ns, key) {
console.warn(`Missing translation: ${lng}:${ns}:${key}`);
},
});生产环境至少要有 fallbackLng,避免页面直接显示空白。
检查清单
- 初始化文件只执行一次。
- 所有语言的 key 结构保持一致。
- 语言选择有持久化策略。
- 富文本使用
Trans,不渲染未受控 HTML。 - 日期、数字、金额使用
Intl。 - 删除页面时清理对应翻译 key。
结论
React i18n 的核心不是复杂配置,而是稳定的资源结构和清晰的使用边界。先用内置资源完成最小闭环;资源变大后,再引入 namespace 和按需加载。