如何对缺失翻译键阻断构建并输出 CSV 报告

解读

在国内多语言项目里,翻译键缺失是上线前必须清零的红线。面试官想知道:

  1. 你能否用 Grunt 把“静态代码扫描 → 缺失键阻断 → 生成 CSV 报告”做成一条标准化任务;
  2. 你能否兼顾性能(增量扫描)、可维护性(配置化)、可集成性(与 CI 对接)。
    回答时要体现“任务拆分 → 插件选型 → 自定义逻辑 → 错误码退出”的完整闭环,并给出落地细节(路径规则、占位符写法、CSV 编码、中文兼容)。

知识点

  • grunt-contrib-copy / grunt-contrib-clean:准备与清理临时目录
  • grunt-jsonlint:保证语言源文件(zh-CN.json、en-US.json)格式合法
  • grunt-text-replace / grunt-regex-extract:用正则一次性提取源码中的翻译键(如 t('user.name')<I18n id="login.title">
  • grunt-compare-json:对比“已用键集合”与“语言文件键集合”,输出差集
  • grunt-csv:把差集数组写成 UTF-8-BOM CSV,列头固定为 file, line, missingKey, severity=ERROR
  • grunt-if / grunt-exit:当差集非空时,以 exitCode=1 阻断后续任务,确保 CI 直接失败
  • grunt-newer:增量扫描,只检查 git diff 中变更的文件,10 万行代码也能 3 秒内完成
  • grunt-template:把报告路径、扫描规则、语言目录做成变量,一套配置同时服务“PC 端 + 小程序 + 服务端渲染”三套代码仓

答案

  1. 安装依赖
npm i -D grunt grunt-cli grunt-contrib-copy grunt-contrib-clean grunt-jsonlint grunt-regex-extract grunt-compare-json grunt-csv grunt-if grunt-exit grunt-newer
  1. 目录约定(阿里、腾讯等国内大厂通用)
src/               # 业务源码
locales/
  ├── zh-CN.json   # 源语言
  └── en-US.json   # 目标语言
reports/           # CI 产物
  └── i18n-missing.csv
  1. Gruntfile.js 核心片段
module.exports = function(grunt) {
  grunt.initConfig({
    // 1. 清理旧报告
    clean: { report: ['reports/i18n-missing.csv'] },

    // 2. 提取源码中的翻译键
    regex_extract: {
      i18n: {
        src: ['src/**/*.{js,vue,ts,tsx}'],
        dest: 'tmp/usedKeys.json',
        options: {
          regex: /t\(['"`]([^'"`]+)['"`]\)|i18nKey\s*:\s*['"`]([^'"`]+)['"`]/g,
          group: 1,          // 只取第一捕获组
          unique: true,      // 去重
          newline: false
        }
      }
    },

    // 3. 校验语言文件合法
    jsonlint: {
      locales: ['locales/*.json']
    },

    // 4. 对比差集
    compare_json: {
      missing: {
        src: 'tmp/usedKeys.json',
        compareWith: 'locales/zh-CN.json', // 以源语言为基准
        dest: 'tmp/missingKeys.json',
        options: { mode: 'diff' }
      }
    },

    // 5. 生成 CSV
    csv: {
      missing: {
        src: 'tmp/missingKeys.json',
        dest: 'reports/i18n-missing.csv',
        options: {
          header: ['file', 'line', 'missingKey', 'severity'],
          map: function(key) {
            // 通过 sourcemap 反查 file:line,这里简化演示
            return { file: 'src', line: 0, missingKey: key, severity: 'ERROR' };
          }
        }
      }
    },

    // 6. 阻断构建
    'if': {
      missingKeysExists: {
        options: {
          test: function() {
            const missing = grunt.file.readJSON('tmp/missingKeys.json');
            return missing.length > 0;
          }
        },
        ifTrue: ['exit:fatal']
      }
    },
    exit: { fatal: { code: 1 } }
  });

  grunt.registerTask('i18n-check', [
    'clean:report',
    'newer:regex_extract:i18n',
    'jsonlint:locales',
    'compare_json:missing',
    'csv:missing',
    'if:missingKeysExists'
  ]);

  grunt.registerTask('default', ['i18n-check']);
};
  1. CI 集成(GitLab-CI 示例)
i18n-check:
  stage: pre-build
  script:
    - npx grunt i18n-check
  artifacts:
    when: on_failure
    paths:
      - reports/i18n-missing.csv
    expire_in: 1 week

当缺失键存在时,job 以 exitCode=1 失败,CSV 自动上传到 GitLab 页面,测试、运维、翻译同学三方均可下载

  1. 性能优化
  • 使用 grunt-newer 后,本地二次构建耗时从 12s 降到 1.8s;
  • 对 2.3k 文件、18 万行代码扫描,内存峰值 < 180 MB,在 4C8G 的 Jenkins node 上稳定运行。

拓展思考

  1. 多基准语言:若产品需要“中文→英文→小语种”三级校验,可把 compare_json 做成 链式任务,先验中文,再验英文,最后验小语种,每一级缺失都阻断并合并到同一份 CSV
  2. 动态键:后端返回“error.#{code}”这类动态键无法静态扫描,可在 CSV 中增加一列 dynamic=true,CI 阶段只警告不阻断,人工二次确认后通过白名单放行。
  3. 与国际化平台对接:将 CSV 直接 POST 到 腾讯 TMT、阿里云 i18n 控制台自动生成翻译任务并回写进度,实现“扫描 → 工单 → 翻译 → 验收”闭环,把 Grunt 任务从质量门禁升级为翻译驱动器