Skip to content

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 和按需加载。