从手动到自动,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: ... })、 t ( ′ k e y ′ ) 、 t('key')、 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...

相关推荐
乘风gg30 分钟前
还在养虾吗?虾王已诞生:微信龙虾 ClawBot
前端·ai编程·claude
小小小小宇1 小时前
LLM 长期记忆构建
前端
lichenyang4531 小时前
从 Express 老项目到 NestJS + Docker:一次车辆管理系统的渐进式重构
前端
Momo__2 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富2 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇2 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇2 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆2 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马2 小时前
Verilog开发常见问题汇总解析
前端
子兮曰3 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端