Skip to content

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-i18next

2.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.json

4. 基础使用

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-i18next
javascript
// 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的关键要点:

  1. 正确配置: 合理的初始化配置和插件使用
  2. 资源组织: 清晰的命名空间和翻译文件结构
  3. 类型安全: TypeScript支持提高开发体验
  4. 性能优化: 延迟加载、缓存和代码分割
  5. SSR支持: Next.js等框架的完整集成
  6. 测试覆盖: 全面的单元测试和集成测试

通过正确使用react-i18next,可以构建高质量的国际化React应用。