前端多语言检查脚本的核心原理
在多语言前端项目里,文案通常不会直接写死在页面中,而是通过类似下面的方式引用:
$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
}
})这段代码做了几件事:
- 读取文件内容;
- 找出所有多语言调用;
- 提取
$t()里的 key; - 去语言字典里查;
- 把不存在的 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`)脚本就很难准确判断。
因为它在静态文本里无法知道运行时的 prefix 或 type 到底是什么。
所以,如果希望多语言检查脚本稳定工作,最好遵守一个约定:
多语言 key 尽量使用静态字符串,避免动态拼接。
如果确实需要动态 key,也可以在团队规范中单独约定白名单或注释跳过机制。
十一、可以继续扩展什么?
基础检查完成后,还可以继续扩展:
- 检查重复文案;
- 检查未被使用的多语言 key;
- 检查不同语言之间 key 是否一致;
- 输出 JSON 或 HTML 报告;
- 接入 Git hooks,在提交前自动检查;
- 接入 CI,在合并前自动拦截问题。
这些能力的底层原理仍然类似:扫描源码、读取语言包、建立映射关系、做差异比较。
十二、总结
前端多语言检查脚本的核心原理可以概括为一句话:
读取语言包形成字典,扫描源码提取
$t(),再判断 key 是否存在,或者根据已有文案反查并替换成标准 key。
它不是复杂的编译器,也不需要启动应用。
它只是把人工检查中最重复、最容易遗漏的部分自动化了。
对于中大型前端项目来说,这类脚本虽然小,但很有价值。它能帮助团队提前发现问题,减少重复翻译,并让多语言使用方式保持一致。