Appearance
react-i18next使用 - React国际化完整实战指南
1. react-i18next概述
1.1 什么是react-i18next
react-i18next是基于i18next的React国际化框架,提供了强大而灵活的国际化解决方案。
核心特性:
- 完整的React集成
- 支持Hooks和HOC
- 服务端渲染(SSR)支持
- 延迟加载翻译资源
- TypeScript支持
- 强大的插件系统
1.2 为什么选择react-i18next
typescript
const advantages = {
ecosystem: '成熟的生态系统,丰富的插件',
features: '功能完整,支持复数、上下文、嵌套等',
performance: '高性能,支持按需加载',
typescript: '完善的TypeScript支持',
community: '活跃的社区,持续维护'
};2. 安装与基础配置
2.1 安装依赖
bash
# 核心库
npm install react-i18next i18next
# 语言检测插件
npm install i18next-browser-languagedetector
# HTTP后端加载器
npm install i18next-http-backend
# TypeScript类型定义
npm install --save-dev @types/react-i18next2.2 基础配置
typescript
// i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
i18n
// 检测用户语言
.use(LanguageDetector)
// 从后端加载翻译
.use(Backend)
// 传递i18n实例给react-i18next
.use(initReactI18next)
// 初始化i18next
.init({
// 默认语言
fallbackLng: 'en',
// 调试模式
debug: process.env.NODE_ENV === 'development',
// 插值配置
interpolation: {
escapeValue: false // React已经处理了XSS
},
// 后端配置
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
},
// 语言检测配置
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
caches: ['localStorage', 'cookie']
}
});
export default i18n;2.3 在React中使用
tsx
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './i18n'; // 导入i18n配置
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);3. 翻译资源组织
3.1 JSON格式翻译文件
json
// public/locales/en/common.json
{
"welcome": "Welcome",
"greeting": "Hello, {{name}}!",
"description": "This is a React i18next demo",
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"button": {
"submit": "Submit",
"cancel": "Cancel",
"save": "Save"
}
}
// public/locales/en/validation.json
{
"required": "This field is required",
"email": "Please enter a valid email",
"minLength": "Minimum {{count}} characters required",
"maxLength": "Maximum {{count}} characters allowed"
}
// public/locales/zh/common.json
{
"welcome": "欢迎",
"greeting": "你好,{{name}}!",
"description": "这是一个React i18next演示",
"nav": {
"home": "首页",
"about": "关于",
"contact": "联系我们"
},
"button": {
"submit": "提交",
"cancel": "取消",
"save": "保存"
}
}3.2 命名空间组织
typescript
// 配置多个命名空间
i18n.init({
ns: ['common', 'validation', 'errors', 'forms'],
defaultNS: 'common',
fallbackNS: 'common'
});
// 目录结构
public/
locales/
en/
common.json
validation.json
errors.json
forms.json
zh/
common.json
validation.json
errors.json
forms.json4. 基础使用
4.1 useTranslation Hook
tsx
// 基础使用
import { useTranslation } from 'react-i18next';
function Welcome() {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('description')}</p>
</div>
);
}
// 带命名空间
function ValidationForm() {
const { t } = useTranslation('validation');
return (
<div>
<span>{t('required')}</span>
<span>{t('email')}</span>
</div>
);
}
// 多个命名空间
function ComplexComponent() {
const { t } = useTranslation(['common', 'validation', 'errors']);
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('validation:required')}</p>
<p>{t('errors:notFound')}</p>
</div>
);
}4.2 插值
tsx
// 简单插值
function Greeting() {
const { t } = useTranslation();
const userName = 'John';
return <h1>{t('greeting', { name: userName })}</h1>;
// 输出: Hello, John!
}
// 多个变量
// Translation: "User {{name}} has {{count}} messages"
function UserMessages() {
const { t } = useTranslation();
return (
<p>{t('userMessages', { name: 'John', count: 5 })}</p>
);
// 输出: User John has 5 messages
}
// 嵌套对象
// Translation: "Welcome {{user.name}}, you are {{user.role}}"
function UserRole() {
const { t } = useTranslation();
const user = { name: 'John', role: 'Admin' };
return <p>{t('userRole', { user })}</p>;
// 输出: Welcome John, you are Admin
}4.3 复数处理
json
// en/common.json
{
"items_one": "{{count}} item",
"items_other": "{{count}} items",
"items_zero": "No items"
}
// zh/common.json
{
"items": "{{count}}个项目"
}tsx
function ItemList() {
const { t } = useTranslation();
return (
<div>
<p>{t('items', { count: 0 })}</p> {/* No items */}
<p>{t('items', { count: 1 })}</p> {/* 1 item */}
<p>{t('items', { count: 5 })}</p> {/* 5 items */}
</div>
);
}4.4 上下文
json
// Translation with context
{
"friend": "A friend",
"friend_male": "A boyfriend",
"friend_female": "A girlfriend"
}tsx
function Friend() {
const { t } = useTranslation();
return (
<div>
<p>{t('friend')}</p> {/* A friend */}
<p>{t('friend', { context: 'male' })}</p> {/* A boyfriend */}
<p>{t('friend', { context: 'female' })}</p> {/* A girlfriend */}
</div>
);
}5. 高级功能
5.1 Trans组件
tsx
import { Trans } from 'react-i18next';
// 包含HTML标签的翻译
// Translation: "Click <1>here</1> to continue"
function LinkText() {
return (
<Trans i18nKey="clickHere">
Click <a href="/continue">here</a> to continue
</Trans>
);
}
// 包含组件的翻译
// Translation: "Welcome <strong>{{name}}</strong>!"
function BoldName() {
const name = 'John';
return (
<Trans i18nKey="welcomeBold" values={{ name }}>
Welcome <strong>{{ name }}</strong>!
</Trans>
);
}
// 复杂嵌套
// Translation: "Go to <1>dashboard</1> or <3>settings</3>"
function Navigation() {
return (
<Trans i18nKey="navigation">
Go to <Link to="/dashboard">dashboard</Link> or <Link to="/settings">settings</Link>
</Trans>
);
}
// 使用components prop
function StyledText() {
return (
<Trans
i18nKey="styledText"
components={{
bold: <strong />,
italic: <em />,
link: <a href="/more" />
}}
/>
);
}5.2 动态命名空间加载
tsx
// 延迟加载命名空间
function DashboardPage() {
const { t, ready } = useTranslation('dashboard', {
useSuspense: false
});
if (!ready) {
return <div>Loading...</div>;
}
return <h1>{t('title')}</h1>;
}
// 使用Suspense
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<DashboardWithSuspense />
</Suspense>
);
}
function DashboardWithSuspense() {
const { t } = useTranslation('dashboard');
return <h1>{t('title')}</h1>;
}5.3 格式化函数
typescript
// 添加自定义格式化
i18n.init({
interpolation: {
format: (value, format, lng) => {
if (format === 'uppercase') {
return value.toUpperCase();
}
if (format === 'lowercase') {
return value.toLowerCase();
}
if (format === 'currency') {
return new Intl.NumberFormat(lng, {
style: 'currency',
currency: 'USD'
}).format(value);
}
return value;
}
}
});
// 使用格式化
// Translation: "Hello {{name, uppercase}}"
function FormattedGreeting() {
const { t } = useTranslation();
return <h1>{t('greeting', { name: 'john' })}</h1>;
// 输出: Hello JOHN
}
// 货币格式化
// Translation: "Price: {{amount, currency}}"
function Price() {
const { t } = useTranslation();
return <p>{t('price', { amount: 99.99 })}</p>;
// 输出: Price: $99.99
}6. 语言切换
6.1 基础语言切换
tsx
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('zh')}>中文</button>
<button onClick={() => changeLanguage('ja')}>日本語</button>
</div>
);
}
// 获取当前语言
function CurrentLanguage() {
const { i18n } = useTranslation();
return <p>Current language: {i18n.language}</p>;
}6.2 高级语言选择器
tsx
function LanguageSelector() {
const { i18n, t } = useTranslation();
const languages = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'zh', name: '中文', flag: '🇨🇳' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' },
{ code: 'ko', name: '한국어', flag: '🇰🇷' },
{ code: 'es', name: 'Español', flag: '🇪🇸' }
];
return (
<select
value={i18n.language}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
);
}
// 下拉菜单样式
function StyledLanguageSelector() {
const { i18n } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const languages = [
{ code: 'en', name: 'English' },
{ code: 'zh', name: '中文' }
];
const currentLang = languages.find(l => l.code === i18n.language);
return (
<div className="relative">
<button onClick={() => setIsOpen(!isOpen)}>
{currentLang?.name} ▼
</button>
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white shadow-lg">
{languages.map(lang => (
<button
key={lang.code}
onClick={() => {
i18n.changeLanguage(lang.code);
setIsOpen(false);
}}
className={lang.code === i18n.language ? 'active' : ''}
>
{lang.name}
</button>
))}
</div>
)}
</div>
);
}6.3 持久化语言设置
typescript
// 扩展语言检测器配置
i18n.use(LanguageDetector).init({
detection: {
// 检测顺序
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
// 缓存用户语言
caches: ['localStorage', 'cookie'],
// Cookie选项
cookieOptions: {
path: '/',
sameSite: 'strict',
maxAge: 365 * 24 * 60 * 60 // 1年
}
}
});
// 手动保存语言
function saveLanguagePreference(lng: string) {
localStorage.setItem('i18nextLng', lng);
document.cookie = `i18nextLng=${lng}; path=/; max-age=${365 * 24 * 60 * 60}`;
}7. TypeScript支持
7.1 类型定义
typescript
// i18next.d.ts
import 'react-i18next';
// 导入翻译资源
import common from '../public/locales/en/common.json';
import validation from '../public/locales/en/validation.json';
declare module 'react-i18next' {
interface CustomTypeOptions {
// 默认命名空间
defaultNS: 'common';
// 资源类型
resources: {
common: typeof common;
validation: typeof validation;
};
}
}7.2 类型安全的使用
tsx
// 自动完成和类型检查
function TypeSafeComponent() {
const { t } = useTranslation();
// ✅ 正确 - 键存在
const text1 = t('welcome');
const text2 = t('nav.home');
// ❌ TypeScript错误 - 键不存在
// const text3 = t('nonexistent');
// 带命名空间
const { t: tValidation } = useTranslation('validation');
const error = tValidation('required');
return <div>{text1}</div>;
}
// 类型安全的插值
function TypeSafeInterpolation() {
const { t } = useTranslation();
// Translation: "Hello {{name}}"
// ✅ 正确
const text1 = t('greeting', { name: 'John' });
// ❌ TypeScript错误 - 缺少必需参数
// const text2 = t('greeting');
return <div>{text1}</div>;
}7.3 自定义Hook类型
typescript
// 创建类型安全的翻译Hook
type TranslationKey = keyof typeof import('../public/locales/en/common.json');
function useTypedTranslation() {
const { t, i18n } = useTranslation();
return {
t: (key: TranslationKey, options?: any) => t(key, options),
i18n
};
}
// 使用
function Component() {
const { t } = useTypedTranslation();
// 自动完成和类型检查
const text = t('welcome');
return <div>{text}</div>;
}8. 服务端渲染(SSR)
8.1 Next.js集成
bash
npm install next-i18nextjavascript
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'zh', 'ja'],
localeDetection: true
},
reloadOnPrerender: process.env.NODE_ENV === 'development'
};javascript
// next.config.js
const { i18n } = require('./next-i18next.config');
module.exports = {
i18n,
// 其他配置...
};tsx
// pages/_app.tsx
import { appWithTranslation } from 'next-i18next';
import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default appWithTranslation(MyApp);tsx
// pages/index.tsx
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import type { GetStaticProps } from 'next';
export default function Home() {
const { t } = useTranslation('common');
return (
<div>
<h1>{t('welcome')}</h1>
</div>
);
}
export const getStaticProps: GetStaticProps = async ({ locale }) => {
return {
props: {
...(await serverSideTranslations(locale ?? 'en', ['common', 'footer']))
}
};
};8.2 动态路由处理
tsx
// pages/blog/[slug].tsx
import { GetStaticPaths, GetStaticProps } from 'next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [],
fallback: 'blocking'
};
};
export const getStaticProps: GetStaticProps = async ({ locale, params }) => {
const slug = params?.slug as string;
return {
props: {
...(await serverSideTranslations(locale ?? 'en', ['common', 'blog'])),
slug
}
};
};
export default function BlogPost({ slug }: { slug: string }) {
const { t } = useTranslation('blog');
return (
<article>
<h1>{t('title')}</h1>
</article>
);
}9. 性能优化
9.1 代码分割
typescript
// 按需加载命名空间
i18n.init({
ns: ['common'], // 初始只加载common
defaultNS: 'common',
// 启用懒加载
partialBundledLanguages: true,
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
});
// 组件中动态加载
function LazyComponent() {
const { t } = useTranslation('lazyNamespace', {
useSuspense: false // 禁用Suspense
});
// 命名空间会在组件挂载时加载
return <div>{t('key')}</div>;
}9.2 缓存翻译
typescript
// 使用i18next-localstorage-backend
import LocalStorageBackend from 'i18next-localstorage-backend';
i18n
.use(LocalStorageBackend)
.init({
backend: {
backends: [
LocalStorageBackend, // 主后端
HttpBackend // 回退后端
],
backendOptions: [{
expirationTime: 7 * 24 * 60 * 60 * 1000, // 7天
defaultVersion: 'v1.0.0'
}, {
loadPath: '/locales/{{lng}}/{{ns}}.json'
}]
}
});9.3 预加载翻译
tsx
// 预加载其他语言
useEffect(() => {
// 预加载中文翻译
i18n.loadLanguages(['zh', 'ja']);
}, []);
// 预加载命名空间
useEffect(() => {
// 预加载表单翻译
i18n.loadNamespaces(['forms', 'validation']);
}, []);10. 测试
10.1 单元测试
tsx
// Component.test.tsx
import { render, screen } from '@testing-library/react';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18nForTests';
import Component from './Component';
describe('Component', () => {
it('should render translated text', () => {
render(
<I18nextProvider i18n={i18n}>
<Component />
</I18nextProvider>
);
expect(screen.getByText('Welcome')).toBeInTheDocument();
});
it('should change language', async () => {
const { rerender } = render(
<I18nextProvider i18n={i18n}>
<Component />
</I18nextProvider>
);
await i18n.changeLanguage('zh');
rerender(
<I18nextProvider i18n={i18n}>
<Component />
</I18nextProvider>
);
expect(screen.getByText('欢迎')).toBeInTheDocument();
});
});10.2 测试用i18n实例
typescript
// i18nForTests.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
// 测试资源
resources: {
en: {
common: {
welcome: 'Welcome',
greeting: 'Hello, {{name}}!'
}
},
zh: {
common: {
welcome: '欢迎',
greeting: '你好,{{name}}!'
}
}
},
interpolation: {
escapeValue: false
}
});
export default i18n;11. 常见问题
11.1 翻译不更新
typescript
// 问题: 翻译文件更新后页面不更新
// 解决1: 禁用缓存(开发环境)
i18n.init({
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
requestOptions: {
cache: 'no-cache'
}
}
});
// 解决2: 添加版本号
i18n.init({
backend: {
loadPath: `/locales/{{lng}}/{{ns}}.json?v=${Date.now()}`
}
});
// 解决3: 重新加载资源
function reloadTranslations() {
i18n.reloadResources();
}11.2 Suspense问题
tsx
// 问题: Suspense导致无限循环
// 解决: 禁用Suspense
function Component() {
const { t, ready } = useTranslation('common', {
useSuspense: false
});
if (!ready) {
return <div>Loading...</div>;
}
return <div>{t('welcome')}</div>;
}12. 最佳实践
typescript
const bestPractices = {
organization: [
'使用命名空间组织翻译',
'按功能模块分割翻译文件',
'保持翻译键简洁有意义',
'使用嵌套结构组织相关翻译'
],
performance: [
'启用延迟加载',
'使用缓存策略',
'预加载关键翻译',
'代码分割翻译资源'
],
development: [
'使用TypeScript获得类型安全',
'提供有意义的默认值',
'使用插值而非字符串拼接',
'正确处理复数和性别'
],
testing: [
'为翻译功能编写测试',
'使用模拟的i18n实例',
'测试所有支持的语言',
'验证插值和格式化'
]
};13. 总结
react-i18next的关键要点:
- 正确配置: 合理的初始化配置和插件使用
- 资源组织: 清晰的命名空间和翻译文件结构
- 类型安全: TypeScript支持提高开发体验
- 性能优化: 延迟加载、缓存和代码分割
- SSR支持: Next.js等框架的完整集成
- 测试覆盖: 全面的单元测试和集成测试
通过正确使用react-i18next,可以构建高质量的国际化React应用。