前端多语言检查脚本的核心原理

5 小时前(已编辑)
/
1

前端多语言检查脚本的核心原理

在多语言前端项目里,文案通常不会直接写死在页面中,而是通过类似下面的方式引用:

$t('Common.Button.Add')

这样做的好处是:同一套代码可以根据当前语言环境显示不同文案。

但项目变大之后,多语言也会带来一些维护问题:

  • 代码里用了一个不存在的多语言 key;
  • 同一段中文文案被重复添加了多个 key;
  • 开发时临时写了 $t('添加'),但项目里其实已经有对应 key;
  • 靠人工检查很容易遗漏。

多语言检查脚本的作用,就是把这些容易出错的地方自动找出来。

它的本质并不复杂:读取语言包,扫描源码,提取 $t(),然后判断里面的内容是否存在于语言包中。

一、整体流程

可以先用一张简单流程图理解:

读取语言包文件
      ↓
合并成一个语言字典
      ↓
扫描源码文件
      ↓
提取 $t('xxx')
      ↓
判断 xxx 是否存在
      ↓
输出缺失项,或自动替换为已有 key

这个脚本通常运行在 Node.js 环境里。

它不会启动前端页面,也不会真的渲染组件,而是直接读取源码文本,所以更像一个轻量级的“静态检查工具”。

二、准备语言字典

假设我们有一个中文语言包:

export default {
  Common: {
    Button: {
      Add: '添加',
      Delete: '删除'
    }
  },
  Product: {
    Name: '商品名称'
  }
}

脚本首先要把语言包加载进来,得到一个普通 JavaScript 对象:

const messages = {
  Common: {
    Button: {
      Add: '添加',
      Delete: '删除'
    }
  },
  Product: {
    Name: '商品名称'
  }
}

如果项目里有多个语言包文件,脚本会把它们逐个读取,再合并成一个大的语言字典。

有了这个字典之后,就可以检查某个 key 是否存在。

比如:

Common.Button.Add

对应的查找路径就是:

messages.Common.Button.Add

如果能取到值,说明这个 key 存在。

如果取不到,说明代码里使用了一个不存在的多语言 key。

三、根据路径查找 key

多语言 key 通常是点分格式:

Common.Button.Add

但对象访问需要一层一层取:

messages.Common.Button.Add

所以脚本里通常会有一个工具函数,把点分 key 拆开后逐层查找:

function getValueByPath(key, source) {
  const paths = key.split('.')
  let current = source

  for (const path of paths) {
    if (!current) return undefined
    current = current[path]
  }

  return current
}

使用时:

getValueByPath('Common.Button.Add', messages)

如果返回 '添加',说明 key 存在。

如果返回 undefined,说明 key 不存在。

这就是多语言检查最核心的一步。

四、扫描源码文件

语言字典准备好后,下一步就是扫描源码。

脚本通常会扫描这些文件:

.js
.ts
.vue
.jsx
.tsx

实际项目里一般不会扫描所有文件,而是只扫描业务源码目录。

可以使用 glob 找出目标文件:

const glob = require('glob')

const files = glob.sync('src/**/*.{js,ts,vue,jsx,tsx}')

然后逐个读取:

const fs = require('fs')

files.forEach(file => {
  const content = fs.readFileSync(file, 'utf-8')
})

此时我们拿到的是文件内容字符串。

接下来要做的事情,就是从字符串里找出所有 $t('xxx')

五、提取 $t() 中的内容

常见的多语言调用可能长这样:

$t('Common.Button.Add')
$t("Common.Button.Delete")
i18n.t('Product.Name')

脚本可以用正则表达式识别这些调用:

const i18nRe = /((?:i18n\.t|\$t)\(['"])([^'"]+)(['"]\))/g

这个正则最重要的是中间这一段:

([^'"]+)

它表示:匹配引号里面的内容。

比如:

$t('Common.Button.Add')

会被拆成:

$t('                 -> 调用开头
Common.Button.Add    -> 真正的 key
')                   -> 调用结尾

脚本真正关心的是中间的:

Common.Button.Add

提取之后,就可以拿它去语言字典里查。

六、检查缺失的多语言 key

把前面的步骤串起来,检查逻辑可以简化成这样:

const result = {}

files.forEach(file => {
  const content = fs.readFileSync(file, 'utf-8')
  const matches = content.match(i18nRe) || []

  const keys = matches.map(item => {
    return item.replace(i18nRe, '$2')
  })

  const missingKeys = keys.filter(key => {
    return !getValueByPath(key, messages)
  })

  if (missingKeys.length) {
    result[file] = missingKeys
  }
})

这段代码做了几件事:

  1. 读取文件内容;
  2. 找出所有多语言调用;
  3. 提取 $t() 里的 key;
  4. 去语言字典里查;
  5. 把不存在的 key 记录下来。

最后统一输出:

Object.keys(result).forEach(file => {
  console.log(file)
  console.log(result[file].join('\n'))
})

输出结果可能是:

src/pages/demo.vue
Common.Button.Unknown
Product.Detail.EmptyText

这就说明这些 key 在代码中被使用了,但在语言包里没有定义。

七、自动替换已有文案

除了检查缺失 key,还可以做一个更进一步的能力:把已有中文文案自动替换成标准 key。

比如代码里写了:

$t('添加')

但语言包里已经有:

{
  Common: {
    Button: {
      Add: '添加'
    }
  }
}

这时候脚本可以自动把代码改成:

$t('Common.Button.Add')

它的核心思路是:把语言字典反过来。

原来的语言字典是:

{
  Common: {
    Button: {
      Add: '添加'
    }
  }
}

反转后变成:

{
  '添加': 'Common.Button.Add'
}

这样当脚本看到:

$t('添加')

就可以通过中文文案直接查到对应 key:

textToKeyMap['添加']

得到:

Common.Button.Add

然后把源码中的 $t('添加') 替换成 $t('Common.Button.Add')

八、如何把嵌套语言包反转

语言包通常是嵌套对象,所以需要递归处理。

可以写一个扁平化函数:

function flattenMessages(source, prefix = '', map = {}) {
  Object.keys(source).forEach(key => {
    const value = source[key]
    const currentKey = prefix ? `${prefix}.${key}` : key

    if (typeof value === 'object' && value !== null) {
      flattenMessages(value, currentKey, map)
    } else {
      map[value] = currentKey
    }
  })

  return map
}

输入:

{
  Common: {
    Button: {
      Add: '添加'
    }
  }
}

输出:

{
  '添加': 'Common.Button.Add'
}

这样就完成了从“key 找文案”到“文案找 key”的转换。

九、为什么这种方案有效?

因为大多数多语言系统都有两个稳定特征:

第一,源码里会通过固定函数调用多语言,比如:

$t('xxx')

第二,语言包通常是结构化对象,比如:

{
  Common: {
    Button: {
      Add: '添加'
    }
  }
}

只要这两个条件成立,脚本就可以通过静态扫描完成检查。

它不需要理解整个前端框架,也不需要启动项目。

它只需要做三件事:

找文件
找 $t()
查字典

这也是它实现简单、运行速度快的原因。

十、这种方案的边界

正则扫描适合处理规范、静态的写法。

比如:

$t('Common.Button.Add')

但如果代码里大量使用动态 key:

$t(prefix + '.Add')
$t(`Common.${type}.Name`)

脚本就很难准确判断。

因为它在静态文本里无法知道运行时的 prefixtype 到底是什么。

所以,如果希望多语言检查脚本稳定工作,最好遵守一个约定:

多语言 key 尽量使用静态字符串,避免动态拼接。

如果确实需要动态 key,也可以在团队规范中单独约定白名单或注释跳过机制。

十一、可以继续扩展什么?

基础检查完成后,还可以继续扩展:

  • 检查重复文案;
  • 检查未被使用的多语言 key;
  • 检查不同语言之间 key 是否一致;
  • 输出 JSON 或 HTML 报告;
  • 接入 Git hooks,在提交前自动检查;
  • 接入 CI,在合并前自动拦截问题。

这些能力的底层原理仍然类似:扫描源码、读取语言包、建立映射关系、做差异比较。

十二、总结

前端多语言检查脚本的核心原理可以概括为一句话:

读取语言包形成字典,扫描源码提取 $t(),再判断 key 是否存在,或者根据已有文案反查并替换成标准 key。

它不是复杂的编译器,也不需要启动应用。

它只是把人工检查中最重复、最容易遗漏的部分自动化了。

对于中大型前端项目来说,这类脚本虽然小,但很有价值。它能帮助团队提前发现问题,减少重复翻译,并让多语言使用方式保持一致。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...