踩坑现场:升级 vue-i18n 后的"玄学"报错
前阵子给团队的一个老项目做依赖升级,顺手把 vue-i18n 从 8.x 干到了 9.x。本以为是个平滑过渡的常规操作,结果一跑起来,控制台直接糊了我一脸红:
text
Message compilation error: Unbalanced closing brace
一开始我还以为是自己哪里的 JSON 格式写错了,或者少打了个括号。排查了一圈,发现报错的源头居然是一句包含正则表达式的提示文案:
javascript
const messages = {
en: {
validate: 'Please enter a valid email, e.g., /^[a-zA-Z0-9]{4,10}$/'
}
}
看到这儿你可能反应过来了:问题就出在 {4,10} 和 | 这些字符上。
为什么 9.x 版本会和正则"八字不合"?
如果你看过 vue-i18n 9.x 的更新日志,就会知道它在底层重写了消息编译器(Message Compiler)。为了支持更强大的 pluralization(复数处理)和 linked messages(链接消息),9.x 版本把 {、}、@、| 这几个字符征用成了"保留字"。
{和}:用于变量插值,比如{count}。@:用于链接其他翻译 key,比如@:message.hello。|:用于复数形式的分隔符。
当编译器在文案里扫到 /^[a-zA-Z0-9]{4,10}$/ 时,它会把 {4,10} 当成一个变量插值,但发现里面没有合法的变量名,或者括号没闭合,直接就抛出了 Unbalanced closing brace 的异常。
说白了,不是你的正则写错了,而是 i18n 编译器"过度解读"了你的文案。
怎么破?两招搞定特殊字符转义
既然知道了病因,对症下药就不难了。这里给大家提供两种实操方案,按需取用。
方案一:字面量插值(Literal Interpolation)
这是官方文档里提到的一种轻量级解法。如果你只是偶尔在文案里用到几个特殊字符,可以直接用 {'...'} 的语法把它们包裹起来,告诉编译器:"这里面是原始字符串,别瞎解析"。
改造一下刚才的报错文案:
javascript
const messages = {
en: {
// 把 {4,10} 变成 {'{4,10}'}
validate: "Please enter a valid email, e.g., /^[a-zA-Z0-9]{'{4,10}'}$/"
}
}
- 优点:改动小,指哪打哪。
- 缺点 :如果文案里正则很长,或者有很多
|、@,满屏幕的{'...'}会让代码变得极其反人类,后期维护简直是视觉灾难。
方案二:Custom Message Format(自定义消息格式)
如果你的项目里大量使用了正则、代码片段,或者你实在不想在 JSON 里写那些丑陋的转义括号,强烈推荐使用 9.3+ 版本引入的 Custom Message Format。
这个特性的核心思想是:你可以接管特定前缀的文案解析逻辑,直接返回原始字符串。
具体怎么落地?我们可以在创建 i18n 实例时,自定义一个 messageCompiler:
javascript
import { createI18n } from 'vue-i18n'
// 自定义编译器
const messageCompiler = (message, { locale, key }) => {
// 如果文案以 "raw:" 开头,直接剥离前缀并返回纯文本函数
if (typeof message === 'string' && message.startsWith('raw:')) {
const rawText = message.slice(4)
return () => rawText
}
// 其他情况,走默认的编译逻辑
// 实际项目中建议结合 @intlify/message-compiler 使用,这里为演示做简化处理
return () => message
}
const i18n = createI18n({
locale: 'en',
messages: {
en: {
// 加上 raw: 前缀,告诉编译器走自定义逻辑
validate: 'raw:Please enter a valid email, e.g., /^[a-zA-Z0-9]{4,10}$/'
}
},
messageCompiler
})
注:上面为了演示核心逻辑做了简化。在真实的工程化项目中,建议通过 @intlify/message-compiler 提供的 compile 方法来处理 fallback 逻辑,确保普通的 {name} 插值依然生效。
- 优点 :一劳永逸。业务代码里只需要加个
raw:前缀,文案本身保持干净,对正则和代码片段极其友好。 - 缺点:需要稍微改一下 i18n 的初始化配置,对新手有一点点门槛。
老项目的升级建议
最后聊点工程实践上的建议。
如果你正在把一个庞大的 Vue 2 + vue-i18n 8.x 项目迁移到 Vue 3 + 9.x,千万不要盲目全局替换。
- 先跑一遍静态扫描 :写个简单的 Node 脚本,扫一遍所有的 locale JSON 文件,把包含
{、}、|、@且不是用于 i18n 插值的文案揪出来。 - 评估转义成本:如果特殊字符不多,直接用方案一(字面量插值)批量替换;如果涉及大量正则或技术文档类的文案,果断上方案二(Custom Message Format)。
- 补充单元测试:给 i18n 的配置和关键文案加上简单的快照测试,防止以后有人不小心改坏了编译器逻辑。
技术升级从来不是改个版本号那么简单,底层 API 的变动往往藏在这些不起眼的细节里。踩过的坑记录下来,希望大家下次升级时能少掉几根头发。