从手动到自动,React一站式前端国际化解决方案

前端国际化自动化方案实践与原理

一、前端国际化的痛点与挑战

1.1 业务背景

随着互联网产品的全球化,前端国际化(i18n)已成为大型项目的标配。无论是SaaS平台、B端管理系统,还是C端应用,支持多语言切换、动态翻译、内容本地化,都是提升用户体验和市场竞争力的关键。

1.2 传统国际化的常见痛点

  • 人工替换繁琐:早期做法通常是手动查找、替换代码中的中文文本,包裹国际化函数,极易遗漏且效率低。
  • key维护混乱:国际化key往往需要人工命名、维护,容易重复、冲突或丢失。
  • 语言包同步难:新增/修改文案后,语言包与代码难以同步,漏翻、错翻频发。
  • 历史遗留代码改造成本高:老项目动辄成千上万行代码,手动国际化几乎不现实。
  • 多语言协作低效:开发、测试、翻译、产品多角色协作时,缺乏自动化工具支撑,流程割裂。

二、react-translate-cli:一站式前端国际化自动化工具

2.1 项目定位

translate-cli 是一款专为 React/TypeScript 项目设计的国际化自动化命令行工具,支持批量扫描、自动替换、key生成、语言包同步、diff预览等全流程,极大提升国际化开发效率。

2.2 主要特性

  • 支持递归扫描指定目录下所有 .js.jsx.ts.tsx 文件
  • 自动识别并替换代码中的中文为国际化函数调用(如 getI18nText({ key, zh })
  • 自动生成唯一且无重复的 key,格式为 相对路径_4位随机码 + Set去重
  • 支持自定义国际化函数名、导入路径、输出语言包目录等
  • 支持自定义翻译API、缓存等高级功能
  • 支持命令行一键处理和自动化脚本集成
  • 详细的单元测试,保证核心功能稳定
  • diff 预览,安全可控

2.3 典型使用流程

2.3.1 初始化配置
bash 复制代码
# 生成默认配置文件
t-cli init
2.3.2 生成国际化代码和语言包
bash 复制代码
# 自动扫描并替换纯中文为国际化函数,生成语言包
t-cli gen
2.3.3 替换未带key的国际化调用
bash 复制代码
# 自动为 getI18nText({ zh: 'xxx' }) 生成 key 并替换源码
t-cli replace
2.3.4 diff 预览与保存
bash 复制代码
# 预览所有变更,确认无误后批量保存
t-cli preview

2.4 代码转换前后对比

转换前
js 复制代码
const label = '你好,世界';
const title = $t('table.pagination.total_number', { values: { count: total } });  // 旧的国际化语法中,源码中只保留了table.pagination.total_number这种形式,难以理解其含义,

//旧项目中存在的国际化调用,徐改改写为新国际化调用规范
<FormattedTableHeaderMessage text="标题" />    // Antd Table 中的header组件,中文提取为国际化
<FormattedTableHeaderMessage id="table.title.status" /> // 旧的国际化语法升级为国际化调用规范,id属性将被移除,

FormattedTableHeaderMessage 为Antd Table CustomHeader组件,业务代码中针对语种长度做了特殊处理

转换后
js 复制代码
import { getI18nText } from '@/i8n/intl';
const label = getI18nText({ key: 'src/pages/xxx_abcd', zh: '你好,世界' });
const title = getI18nText({ key: 'table.pagination.total_number', zh: '共{count}条数据', values: { count: total } }); //中文提取到zh中


// 新国际化调用规范
<FormattedTableHeaderMessage text={getI18nText({ key: 'src/pages/zzz_ijkl', zh: '标题' })} />
<FormattedTableHeaderMessage text={getI18nText({ key: 'table.title.status', zh: '状态' })} />  // 中文提取到zh中,id字段填充到key字段中

配置文件说明

translate-cli 通过根目录下的 translate-config.js 文件进行高度自定义配置。主要配置项如下:

js 复制代码
module.exports = {
  // 扫描目录配置
  scanDirs: [
    './test/entry/**/*.{js,jsx,ts,tsx}',  // 支持 glob pattern
  ],

  // 翻译配置
  translate: {
    // 翻译API配置
    translateApi: {
      /**
       * 自定义翻译函数
       * 
       * @param {Object} payload - 翻译请求参数
       * @param {Object} payload.texts - 待翻译的文本对象,key为文本标识,value为待翻译文本
       * @param {string|string[]} payload.targetLang - 目标语言,可以是单个语言代码或语言代码数组
       * @param {string} [payload.sourceLang='zh'] - 源语言,默认为中文
       * 
       * @returns {Promise<Object>} 翻译结果对象
       * @returns {Object.<string, Object>} 返回对象的key为文本标识,value为各语言的翻译结果
       * 
       * @example
       * // 单目标语言翻译
       * const result = await customFn({
       *   texts: {
       *     'welcome': '欢迎使用',
       *     'hello': '你好'
       *   },
       *   targetLang: 'en',
       *   sourceLang: 'zh'
       * });
       * // 返回结果:
       * // {
       * //   'welcome': { en: 'Welcome to use' },
       * //   'hello': { en: 'Hello' }
       * // }
       * 
       * @example
       * // 多目标语言翻译
       * const result = await customFn({
       *   texts: {
       *     'welcome': '欢迎使用',
       *     'hello': '你好'
       *   },
       *   targetLang: ['en', 'th'],
       *   sourceLang: 'zh'
       * });
       * // 返回结果:
       * // {
       * //   'welcome': { 
       * //     en: 'Welcome to use',
       * //     th: 'ยินดีต้อนรับ'
       * //   },
       * //   'hello': { 
       * //     en: 'Hello',
       * //     th: 'สวัสดี'
       * //   }
       * // }
       */
      customFn: async function (payload) {
        const axios = require('axios');
        const { texts, targetLang, sourceLang } = payload;
        // 构造批量翻译请求数据
        const dataArr = Object.entries(texts).map(([key, text]) => ({
          key,
          text,
          source: sourceLang || 'zh',
          targetLang
        }));
        // 调用翻译服务
        const res = await axios.post('https://test-fe.aiadfly.com/tool/ai/translate', { data: dataArr });
        const resultArr = res.data.data;
        // 处理翻译结果
        const langs = Array.isArray(targetLang) ? targetLang : [targetLang];
        const results = {};
        for (const item of resultArr) {
          if (item && item.text && item.results) {
            const key = Object.keys(texts).find(k => texts[k] === item.text);
            if (key) {
              results[key] = {};
              for (const lang of langs) {
                results[key][lang] = item.results[lang] || '';
              }
            }
          }
        }
        // 处理未翻译的文本
        for (const key of Object.keys(texts)) {
          if (!(key in results)) {
            results[key] = {};
            for (const lang of langs) {
              results[key][lang] = texts[key];
            }
          }
        }
        return results;
      }
    }
  },

  // 输出配置
  output: {
    dir: './src/i18nData',  // 语言包输出目录
    langs: ['zh', 'en', 'th'],  // 支持的语言列表
    translateFn: 'getI18nText',  // 翻译函数名
    textKey: 'zh',  // 默认文本的key名
    libPath: '@/i8n/intl',  // 导入路径配置
    replaceOldFn: '$t'  // 需要替换的旧函数名
  },

  // Babel 生成代码的配置
  babel: {
    semicolons: false,  // 是否使用分号,默认为 false
    quotes: 'single'  // 字符串引号类型,可选值:'single' | 'double',默认为 'single'
  }
}

主要配置项说明:

  1. scanDirs:指定需要国际化的目录和文件类型,支持 glob pattern。

  2. translate

    • cache :翻译缓存配置
      • enabled:是否启用缓存
      • file:缓存文件路径
      • expireTime:缓存过期时间
    • translateApi :翻译服务配置
      • customFn:自定义翻译函数,支持批量翻译和多目标语言
  3. output

    • dir:语言包输出目录
    • langs:支持的语言列表
    • translateFn:翻译函数名
    • textKey:默认文本的key名
    • libPath:导入路径配置
    • replaceOldFn:需要替换的旧函数名
  4. babel:代码生成配置

    • semicolons:是否使用分号
    • quotes:字符串引号类型

主要命令与使用场景

gen 指令

  • 作用:扫描源码中的纯中文字符串,并将其替换为 getI18nText({ key, zh }) 形式,同时生成国际化 key。
  • 典型使用场景
    • 新项目或老项目首次批量国际化改造。
    • 需要将所有硬编码中文批量替换为国际化函数调用。
  • 支持的转换类型
    1. 纯中文字符串(如 '你好')
  • 流程
    1. 扫描指定目录下的所有代码文件。
    2. 仅提取和替换纯中文字符串。
    3. 生成国际化 key,并输出到资源文件。
  • 示例
    • 转换前:

      js 复制代码
      const label = '你好,世界';
    • 转换后:

      js 复制代码
      const label = getI18nText({ key: 'src/pages/xxx_abcd', zh: '你好,世界' });
    • 生成的资源文件(如 zh.json):

      json 复制代码
      {
        "src/pages/xxx_abcd": "你好,世界"
      }
    • 注意:gen 指令不会处理组件调用(如 FormattedTableHeaderMessage)和 $t 旧调用形式。

replace 指令

  • 作用:自动为源码中未带 key 的国际化内容生成 key,并替换源码。

  • 典型使用场景

    • 已经部分国际化的项目,需补全 key 或升级国际化调用规范。
    • 需要将旧的 $t 调用、未带 key 的 getI18nText、组件属性等批量升级。
  • 支持的转换类型

    1. 纯中文字符串 → getI18nText({ key, zh })
    2. 旧的 $t('xxx') 调用 → getI18nText({ key, zh })
    3. getI18nText({ zh: 'xxx' }) → getI18nText({ key: 'xxx', zh: 'xxx' })
    4. → <FormattedTableHeaderMessage text={getI18nText({ key: 'xxx', zh: 'xxx' })} />
  • 流程

    1. 扫描源码,查找所有未国际化的中文、未带 key 的国际化函数/组件、旧的 $t 调用。
    2. 为每个文案生成唯一 key(规则见 help.js 的 generateKey)。
    3. 替换为带 key 的国际化调用或组件。
    4. 结果体现在 diff 中,便于代码 review。
  • 示例

    • 纯中文字符串

      • 转换前:

        js 复制代码
        const label = '你好,世界';
      • 转换后:

        js 复制代码
        const label = getI18nText({ key: 'src/pages/xxx_abcd', zh: '你好,世界' });
    • $t 调用

      • 转换前:

        js 复制代码
        const title = $t('table.pagination.total_number', { values: { count: total } });
      • 转换后:

        js 复制代码
        const title = getI18nText({ key: 'table.pagination.total_number', zh: '共{count}条数据', values: { count: total } });
    • FormattedTableHeaderMessage 组件 (文本转换)

      • 转换前:

        js 复制代码
        <FormattedTableHeaderMessage text="标题" />
      • 转换后:

        js 复制代码
        <FormattedTableHeaderMessage text={getI18nText({ key: 'src/pages/zzz_ijkl', zh: '标题' })} />
    • FormattedTableHeaderMessage 组件 (id转换)

      • 转换前:

        js 复制代码
        <FormattedTableHeaderMessage id="table.title.status" />
      • 转换后:

        js 复制代码
        <FormattedTableHeaderMessage text={getI18nText({ key: 'table.title.status', zh: '状态' })} />

注:FormattedTableHeaderMessage 为 Antd Table CustomHeader 组件,业务代码中针对语种长度做了特殊处理

三、技术原理与实现细节

3.1 AST 解析与代码转换

translate-cli 基于 Babel 的 parser/traverse/generator 实现源码的自动化转换。

3.1.1 解析源码为 AST
  • 使用 @babel/parser 支持 TypeScript/JSX
  • 递归遍历所有文件,parse 为 AST
3.1.2 AST 遍历与节点识别
  • 识别所有字符串字面量(纯中文)
  • 识别 getI18nText({ zh: ... })、 <math xmlns="http://www.w3.org/1998/Math/MathML"> t ( ′ k e y ′ ) 、 t('key')、 </math>t(′key′)、t('key', { values })
  • 识别 FormattedTableHeaderMessage 组件的 id/text 属性
3.1.3 自动 key 生成与替换
  • 对于纯中文,调用 generateKey 生成唯一 key
  • 对于 getI18nText({ zh: ... }),如无 key 自动补全
  • 对于 $t('key'),查找语言包自动补全 zh
  • 对于 FormattedTableHeaderMessage,优先 id,否则 text
  • 所有 key/zh 自动收集,便于生成语言包
3.1.4 代码生成与格式化
  • 使用 @babel/generator 生成新代码
  • 保证 import 语句自动插入且不重复
  • 支持自定义引号、分号、缩进等格式
3.1.5 语言包同步
  • 自动收集所有 key-zh 映射,生成/更新 zh.json、en.json 等
  • 支持自定义翻译API,自动批量翻译

3.2 关键流程图

graph TD; A[递归扫描目录] --> B[AST 解析源码] B --> C[遍历AST节点] C --> D{是否为国际化目标} D -- 纯中文 --> E[生成key,替换为getI18nText] D -- $t调用 --> F[查找key,补全zh,替换为新函数] D -- FormattedTableHeaderMessage --> G[提取id/text,生成key,替换为text属性] E & F & G --> H[收集key-zh,生成语言包] H --> I[生成新代码,写入diff]

3.3 代码核心片段

3.3.1 自动 key 化 getI18nText({ zh: ... })
js 复制代码
traverse(ast, {
  CallExpression(path) {
    if (t.isIdentifier(path.node.callee, { name: fnMain })) {
      const arg = path.node.arguments[0];
      if (t.isObjectExpression(arg)) {
        let hasZh = false, hasKey = false, zhValue = '';
        arg.properties.forEach(prop => {
          if (t.isObjectProperty(prop) && prop.key.name === 'zh' && t.isStringLiteral(prop.value)) {
            hasZh = true;
            zhValue = prop.value.value;
          }
          if (t.isObjectProperty(prop) && prop.key.name === 'key') {
            hasKey = true;
          }
        });
        if (hasZh && !hasKey) {
          const key = generateKey(zhValue, filePath);
          arg.properties.unshift(
            t.objectProperty(t.identifier('key'), t.stringLiteral(key, { quote }))
          );
        }
      }
    }
  }
});
3.3.2 FormattedTableHeaderMessage 自动替换
js 复制代码
traverse(ast, {
  JSXElement(path) {
    const opening = path.node.openingElement;
    if (!t.isJSXIdentifier(opening.name, { name: 'FormattedTableHeaderMessage' })) return;
    // ...省略,见 convert.js
  }
});

3.4 配置与扩展性

  • 支持 translateFn、libPath、scanDirs、output.dir、langs 等高度自定义
  • 支持自定义翻译API、缓存、key生成规则
  • 支持多种国际化场景,兼容历史代码

3.5 典型应用场景

  • 新项目一键国际化
  • 老项目批量改造
  • 语言包同步与翻译自动化
  • 代码review与diff可视化

3.6 语言包生成示例

translate-cli 会自动生成结构化的语言包文件,支持多语言同步。以下是生成的语言包示例:

语言包采用 JSON 格式,包含以下特点:

  • 按模块/页面组织 key 结构
  • 支持嵌套对象,便于管理大量文案
  • 自动同步所有配置的语言版本
  • 支持增量更新,保留已有翻译

3.7 翻译服务集成

translate-cli 支持灵活配置外部翻译服务,通过自定义翻译接口实现与各种翻译服务的集成。目前主要集成了基于 Qwen 大模型的批量翻译服务。

3.7.1 翻译接口设计

translate-cli 提供了统一的翻译接口抽象,支持自定义翻译实现。通过配置文件中的 translateApi.customFn 可以灵活配置外部翻译服务:

js 复制代码
// translate-config.js 配置示例
module.exports = {
  translate: {
    translateApi: {
      /**
       * 自定义翻译函数
       * 
       * @param {Object} payload - 翻译请求参数
       * @param {Object} payload.texts - 待翻译的文本对象,key为文本标识,value为待翻译文本
       * @param {string|string[]} payload.targetLang - 目标语言,可以是单个语言代码或语言代码数组
       * @param {string} [payload.sourceLang='zh'] - 源语言,默认为中文
       * 
       * @returns {Promise<Object>} 翻译结果对象
       * @returns {Object.<string, Object>} 返回对象的key为文本标识,value为各语言的翻译结果
       * 
       * @example
       * // 单目标语言翻译
       * const result = await customFn({
       *   texts: {
       *     'welcome': '欢迎使用',
       *     'hello': '你好'
       *   },
       *   targetLang: 'en',
       *   sourceLang: 'zh'
       * });
       * // 返回结果:
       * // {
       * //   'welcome': { en: 'Welcome to use' },
       * //   'hello': { en: 'Hello' }
       * // }
       * 
       * @example
       * // 多目标语言翻译
       * const result = await customFn({
       *   texts: {
       *     'welcome': '欢迎使用',
       *     'hello': '你好'
       *   },
       *   targetLang: ['en', 'th'],
       *   sourceLang: 'zh'
       * });
       * // 返回结果:
       * // {
       * //   'welcome': { 
       * //     en: 'Welcome to use',
       * //     th: 'ยินดีต้อนรับ'
       * //   },
       * //   'hello': { 
       * //     en: 'Hello',
       * //     th: 'สวัสดี'
       * //   }
       * // }
       */
      customFn: async function (payload) {
        const axios = require('axios');
        const { texts, targetLang, sourceLang } = payload;
        // 构造批量翻译请求数据
        const dataArr = Object.entries(texts).map(([key, text]) => ({
          key,
          text,
          source: sourceLang || 'zh',
          targetLang
        }));
        // 调用翻译服务
        const res = await axios.post('https://test-fe.aiadfly.com/tool/ai/translate', { data: dataArr });
        const resultArr = res.data.data;
        // 处理翻译结果
        const langs = Array.isArray(targetLang) ? targetLang : [targetLang];
        const results = {};
        for (const item of resultArr) {
          if (item && item.text && item.results) {
            const key = Object.keys(texts).find(k => texts[k] === item.text);
            if (key) {
              results[key] = {};
              for (const lang of langs) {
                results[key][lang] = item.results[lang] || '';
              }
            }
          }
        }
        // 处理未翻译的文本
        for (const key of Object.keys(texts)) {
          if (!(key in results)) {
            results[key] = {};
            for (const lang of langs) {
              results[key][lang] = texts[key];
            }
          }
        }
        return results;
      }
    }
  }
}
3.7.2 Qwen 大模型翻译服务

translate-cli 集成了基于 Qwen 大模型的翻译服务,通过封装 Qwen API 实现高质量的批量翻译。以下是核心实现:

js 复制代码
// 服务端翻译接口实现
import OpenAI from 'openai'

const client = new OpenAI({
  apiKey: 'your-api-key',
  baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1'
})

// 支持的语言列表
const ALLOWED_LANGS = ['zh', 'en', 'th', 'jp']

// 批量翻译接口
router.post('/translate', async function (ctx) {
  const { data } = ctx.request.body
  
  // 构建批量翻译的提示词
  const translationPrompts = data.map(item => {
    const { text, source, targetLang } = item
    const targetLangs = Array.isArray(targetLang) ? targetLang : [targetLang]
    return {
      text,
      source,
      targetLangs,
      prompt: `将文本"${text}"从${source}翻译成${targetLangs.join(',')}`
    }
  })

  // 调用 Qwen 模型进行翻译
  const response = await client.chat.completions.create({
    model: 'qwen-max',
    messages: [
      {
        role: 'system',
        content: `你是一个专业的翻译助手。请严格只返回JSON,格式为:
        {"translations": [{"text": "原文", "source": "源语言", "targetLangs": ["目标语言1", "目标语言2"], 
        "results": {"语言代码1": "翻译结果1", "语言代码2": "翻译结果2"}}]}`
      },
      {
        role: 'user',
        content: JSON.stringify(translationPrompts)
      }
    ]
  })

  // 处理翻译结果
  const result = JSON.parse(response.choices[0].message.content)
  return result.translations
})

翻译服务的主要特点:

  1. 批量翻译能力

    • 支持一次性翻译多个文本
    • 自动处理多目标语言翻译
    • 保持上下文连贯性
  2. 多语言支持

    • 支持中文、英文、泰语、日语等
    • 自动识别源语言
    • 支持多语言互译

3.9 diff 预览与人工审核平台

translate-cli 不仅在命令行自动生成 diff 数据,还配套了前端可视化 diff 审核平台,如图所示:

平台主要功能:

  • 左侧为文件树,支持快速定位和筛选变更文件
  • 中间为代码对比视图,左为原始代码,右为转换后代码,所有国际化替换一目了然
  • 右上角支持"保存所有/当前文件变更"、"生成语言包"、"预览语言包"等操作
  • 支持逐条/批量审核、回滚,极大提升团队协作效率和安全性

典型流程:

  1. CLI 工具扫描并生成 diff-data.json
  2. 前端 diff 平台读取 diff-data.json,展示所有变更
  3. 团队成员可逐条/批量审核、确认、保存或回滚变更
  4. 审核通过后,变更写回源码并同步语言包

优势:

  • 让国际化替换过程"可见、可控、可回溯"
  • 降低误操作风险,提升团队协作效率
  • 支持大规模项目的安全国际化改造

四、总结与展望

translate-cli 通过 AST 自动化、配置化、流程可视化,极大降低了前端国际化的门槛和成本。

  • 效率提升:批量处理,极大减少人工操作
  • 质量保障:key唯一、语言包同步、diff可视化
  • 易用性强:命令行一键集成,支持自定义
  • 可扩展性好:支持多语言、API扩展、key规则自定义

未来可进一步支持:

  • 智能检测未国际化文本
  • 多人协作与翻译平台对接
  • 更丰富的组件/场景自动化支持

欢迎 Star

如果这个工具对您有帮助,欢迎给个 Star 支持一下!您的支持是我持续改进的动力。

GitHub: github.com/huqc2513/tr...

相关推荐
90后的晨仔9 分钟前
RxSwift 中的 DisposeBag解析
前端·ios
蓝胖子的多啦A梦44 分钟前
搭建前端项目 Vue+element UI引入 步骤 (超详细)
前端·vue.js·ui
TE-茶叶蛋1 小时前
WebSocket 前端断连原因与检测方法
前端·websocket·网络协议
骆驼Lara1 小时前
前端跨域解决方案(1):什么是跨域?
前端·javascript
离岸听风1 小时前
学生端前端用户操作手册
前端
onebyte8bits1 小时前
CSS Houdini 解锁前端动画的下一个时代!
前端·javascript·css·html·houdini
yxc_inspire1 小时前
基于Qt的app开发第十四天
前端·c++·qt·app·面向对象·qss
一_个前端1 小时前
Konva 获取鼠标在画布中的位置通用方法
前端
[email protected]2 小时前
Asp.Net Core SignalR导入数据
前端·后端·asp.net·.netcore
小满zs7 小时前
Zustand 第五章(订阅)
前端·react.js