前端国际化 (i18n) 最佳实践:从硬编码到自动化工作流

By | 2026年10月12日

为什么你的国际化方案总是「差点意思」?

很多团队在项目初期为了快速上线,直接把中文硬编码在组件里,等到需要支持多语言时,才发现要改的地方遍布整个代码库。更麻烦的是,翻译文案散落在各个文件,产品经理改个文案,开发要全局搜索替换,一不小心就漏掉或改错。

我们需要的不仅仅是一个 t('key') 函数,而是一套可维护、可扩展、自动化的国际化工作流。本文将带你从零搭建一套生产级的前端国际化方案,基于 React + i18next,但核心思想适用于任何框架。

第一步:选择合适的技术栈

前端国际化库有很多,比如 react-intl、vue-i18n、i18next 等。我们选择 i18next 因为它:

  • 框架无关,支持 React、Vue、原生 JS
  • 功能全面:插值、复数、上下文、嵌套
  • 生态丰富:语言检测、后端加载、缓存
  • 社区活跃,文档详尽

安装核心依赖:


npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend

第二步:设计翻译文件的组织结构

不要把所有翻译放在一个巨大的 JSON 里,那样难以维护。推荐按「命名空间 (namespace)」拆分,通常按页面或功能模块划分:


public/locales/
  en/
    common.json
    home.json
    user.json
  zh/
    common.json
    home.json
    user.json

每个文件内部使用嵌套结构,便于管理:


// public/locales/zh/home.json
{
  "title": "欢迎回来,{{name}}",
  "description": "今天是个好日子",
  "actions": {
    "save": "保存",
    "cancel": "取消"
  }
}

💡 注意:key 的命名要有意义,避免 text1、text2 这种。推荐使用语义化的路径,如 home.actions.save。

第三步:初始化 i18next 实例

创建一个 i18n.js 配置文件:


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
  .init({
    fallbackLng: 'zh', // 默认语言
    supportedLngs: ['zh', 'en'],
    ns: ['common', 'home', 'user'], // 命名空间列表
    defaultNS: 'common',
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json', // 加载路径
    },
    detection: {
      order: ['querystring', 'cookie', 'localStorage', 'navigator'],
      caches: ['localStorage', 'cookie'], // 缓存用户选择
    },
    interpolation: {
      escapeValue: false, // React 已经处理了 XSS
    },
    react: {
      useSuspense: true, // 配合 Suspense 使用
    },
  });

export default i18n;

在入口文件引入:


// index.js
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import './i18n';
import App from './App';

ReactDOM.render(
  <Suspense fallback={<div>Loading...</div>}>
    <App />
  </Suspense>,
  document.getElementById('root')
);

第四步:在组件中使用翻译

使用 useTranslation Hook:


import React from 'react';
import { useTranslation } from 'react-i18next';

function HomePage() {
  const { t, i18n } = useTranslation('home'); // 指定命名空间

  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng);
  };

  return (
    <div>
      <h1>{t('title', { name: '张三' })}</h1>
      <p>{t('description')}</p>
      <button>{t('actions.save', { ns: 'common' })}</button>
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('zh')}>中文</button>
    </div>
  );
}

💡 注意:当使用多个命名空间时,可以在 useTranslation(['home', 'common']) 中传入数组,然后通过 t('actions.save', { ns: 'common' }) 或 t('common:actions.save') 来指定。

第五步:处理复数与动态变量

i18next 内置了强大的复数处理,只需在翻译文件中使用 one、other 等后缀:


// public/locales/en/home.json
{
  "itemCount": "You have {{count}} item",
  "itemCount_plural": "You have {{count}} items"
}

// public/locales/zh/home.json
{
  "itemCount": "你有 {{count}} 个物品"
}

使用:


<p>{t('itemCount', { count: 5 })}</p>

i18next 会根据语言自动选择正确的复数形式(中文没有复数变化,所以只写一个即可)。

第六步:自动化提取与翻译工作流

手动维护翻译文件容易遗漏。推荐使用 i18next-parser 自动扫描代码中的 t() 调用,提取 key 并生成 JSON 文件。

安装:


npm install i18next-parser --save-dev

创建配置文件 i18next-parser.config.js:


module.exports = {
  locales: ['zh', 'en'],
  output: 'public/locales/$LOCALE/$NAMESPACE.json',
  input: ['src/**/*.{js,jsx,ts,tsx}'],
  sort: true,
  createOldCatalogs: false,
  keepRemoved: false,
  lexers: {
    js: ['JavascriptLexer'],
    jsx: ['JsxLexer'],
    default: ['JavascriptLexer'],
  },
  defaultValue: (locale, namespace, key) => {
    // 中文环境使用 key 作为默认值,英文环境留空
    return locale === 'zh' ? key : '';
  },
};

在 package.json 中添加脚本:


{
  "scripts": {
    "i18n:extract": "i18next-parser"
  }
}

运行 npm run i18n:extract,它会扫描代码并更新 JSON 文件。

💡 注意:提取的 key 默认使用 t('key') 中的字符串。如果使用动态 key,如 t(variable),解析器无法识别,需要手动维护或使用 t('key', { defaultValue: '...' }) 并提供注释。

第七步:与翻译平台集成

对于大型项目,推荐将翻译文件同步到专业平台(如 Crowdin、Lokalise)。以 Crowdin 为例:

  1. 在 Crowdin 创建项目,上传源语言文件(如 zh)
  2. 配置 GitHub 集成,自动同步
  3. 翻译完成后,Crowdin 会生成 PR 合并到仓库

这样,产品经理可以直接在平台上修改文案,翻译团队协作翻译,开发只需合并 PR。

第八步:优化加载性能

默认情况下,i18next 会一次性加载所有命名空间。对于大型应用,可以按需加载:


import { useTranslation } from 'react-i18next';

function UserPage() {
  const { t } = useTranslation('user', { useSuspense: false });
  // 当组件挂载时,i18next 会自动加载 user 命名空间
  return <div>{t('profile')}</div>;
}

也可以预加载关键命名空间:


i18n.loadNamespaces(['common', 'home']);

常见坑与解决方案

  1. XSS 风险:i18next 默认转义 HTML,如果必须渲染 HTML,使用 组件:

“jsx import { Trans } from 'react-i18next'; Welcome User “

  1. 语言切换后组件不更新:确保使用 useTranslation Hook 或 withTranslation HOC,它们会订阅语言变化。
  1. 日期、货币格式化:使用 Intl API 或 i18next 的格式化插件:

“javascript import i18next from 'i18next'; i18next.services.formatter.add('currency', (value, lng) => { return new Intl.NumberFormat(lng, { style: 'currency', currency: 'USD' }).format(value); }); “

  1. SEO 与 SSR:如果使用 Next.js,需要在服务端初始化 i18n 并传递语言。参考 next-i18next 库。

总结

一套好的国际化方案应该具备:

  • 清晰的资源组织:按命名空间拆分,语义化 key
  • 自动化工具链:提取、翻译、同步一体化
  • 性能优化:按需加载,避免阻塞渲染
  • 团队协作:产品、翻译、开发各司其职

按照本文的步骤,你可以快速搭建起生产级的国际化工作流。下一步,可以探索更高级的主题,如:

  • 使用 TypeScript 为翻译 key 提供类型提示
  • 结合 CI/CD 自动检查未翻译的 key
  • 实现伪翻译(pseudo-localization)测试 UI 布局

希望这篇文章能帮你少走弯路,让国际化不再是难题。