vue-i18n 升级 9.x 踩坑:正则表达式引发的“花括号”惨案

踩坑现场:升级 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,千万不要盲目全局替换

  1. 先跑一遍静态扫描 :写个简单的 Node 脚本,扫一遍所有的 locale JSON 文件,把包含 {}|@ 且不是用于 i18n 插值的文案揪出来。
  2. 评估转义成本:如果特殊字符不多,直接用方案一(字面量插值)批量替换;如果涉及大量正则或技术文档类的文案,果断上方案二(Custom Message Format)。
  3. 补充单元测试:给 i18n 的配置和关键文案加上简单的快照测试,防止以后有人不小心改坏了编译器逻辑。

技术升级从来不是改个版本号那么简单,底层 API 的变动往往藏在这些不起眼的细节里。踩过的坑记录下来,希望大家下次升级时能少掉几根头发。

相关推荐
天天喝旺仔1 小时前
Vue 3 组合式 API:从 Options 迁移到 script setup
前端·javascript·vue.js
Cry丶3 小时前
Vue 3 业务管理页面实战:组件拆分、父子通信与弹窗复用
前端·javascript·vue.js·父子通信·组件拆分·弹窗复用
雪芽蓝域zzs3 小时前
Vue前端配置路由通配捕获 404
前端·javascript·vue.js
木公子7 小时前
Vue3源码精读04:Scheduler 调度器深度解析|异步批量更新与任务队列源码全解
前端·vue.js
nicole bai8 小时前
dialog封装
前端·javascript·vue.js
索西引擎9 小时前
【Vue】Vue.js 3 声明式渲染架构的设计原理与模块协同机制研究
前端·javascript·vue.js
mayaairi11 小时前
Vue2 事件绑定完全指南:从入门到原理
前端·javascript·vue.js
ℋᙚᵐⁱᒻᵉ鲸落17 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
用户50093768390391 天前
热敏小票 Web 打印:我在 58/80mm 上折腾的两周
前端·vue.js