@mico_fe/vite-plugin-i18n-vue-ast
v1.0.0
Published
Vue i18n Vite plugin with AST-based transformation - More precise Chinese to $t(key) conversion
Readme
@mico_fe/vite-plugin-i18n-vue-ast
基于 AST (抽象语法树) 的 Vue i18n 编译时转换插件。相比正则匹配方案,AST 方案更加精确,能够准确识别代码结构,避免误转换。
✨ 特性
- 🎯 精确转换 - 基于 AST 分析,精确识别需要转换的中文文本
- 🔍 语义理解 - 理解代码上下文,自动跳过注释、import、console 等
- 📦 完整支持 - 支持 Template、Script、Script Setup
- ⚡ 高性能 - 智能缓存,增量编译
- 🛡️ 类型安全 - 完整的 TypeScript 支持
- 🔧 可配置 - 丰富的配置选项
🔬 AST vs 正则方案对比
| 特性 | AST 方案 (本包) | 正则方案 | |------|----------------|----------| | 精确度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | | 跳过注释 | ✅ 自动 | ❌ 需特殊处理 | | 跳过 import | ✅ 自动 | ❌ 需特殊处理 | | 跳过 console | ✅ 自动 | ❌ 需特殊处理 | | 嵌套结构 | ✅ 完美支持 | ⚠️ 可能误匹配 | | 模板字符串 | ✅ 精确处理 | ⚠️ 复杂情况难处理 | | 性能 | 中等 | 快 | | 实现复杂度 | 高 | 低 |
📦 安装
# npm
npm install @mico_fe/vite-plugin-i18n-vue-ast -D
# pnpm
pnpm add @mico_fe/vite-plugin-i18n-vue-ast -D
# yarn
yarn add @mico_fe/vite-plugin-i18n-vue-ast -D🚀 快速开始
1. 配置 Vite
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueI18nAst from '@mico_fe/vite-plugin-i18n-vue-ast'
export default defineConfig({
plugins: [
vueI18nAst({
localesDir: 'locales', // 语言包目录名(向上查找)或绝对路径
sourceLocale: 'zh-CN',
debug: true, // 开发时开启,查看转换日志
}),
vue(),
],
})2. 准备语言包
// src/locales/zh-CN.json
{
"hello": "你好世界",
"welcome": "欢迎使用",
"button": {
"submit": "提交",
"cancel": "取消"
}
}// src/locales/en-US.json
{
"hello": "Hello World",
"welcome": "Welcome",
"button": {
"submit": "Submit",
"cancel": "Cancel"
}
}3. 编写 Vue 组件
<template>
<div>
<!-- 自动转换为 {{ $t('hello') }} -->
<h1>你好世界</h1>
<!-- 属性自动转换为 :title="$t('welcome')" -->
<div title="欢迎使用">
<button>提交</button>
<button>取消</button>
</div>
</div>
</template>
<script setup lang="ts">
// 自动转换为 t('welcome')
const message = '欢迎使用'
console.log('这不会被转换') // console 自动跳过
</script>编译后
<template>
<div>
<h1>{{ $t('hello') }}</h1>
<div :title="$t('welcome')">
<button>{{ $t('button.submit') }}</button>
<button>{{ $t('button.cancel') }}</button>
</div>
</div>
</template>
<script setup lang="ts">
const message = t('welcome')
console.log('这不会被转换')
</script>⚙️ 配置选项
interface LocalesRule {
/** 文件路径匹配模式(glob 或正则) */
match: string | RegExp
/** 该匹配使用的语言包目录 */
localesDir: string
/** 可选的命名空间前缀 */
namespace?: string
}
interface VueI18nAstPluginConfig {
/** 语言包目录名(向上查找)或绝对路径(直接使用)
*
* - 字符串:单一目录名,如 'locales'
* - 数组:多个目录名,按优先级排序,如 ['locales', 'i18n', 'langs']
* - 绝对路径:直接使用,不进行向上查找
*
* @default 'locales'
*/
localesDir?: string | string[]
/** 路径匹配规则(优先级高于 localesDir)
* 用于特殊项目的精确控制
*/
localesRules?: LocalesRule[]
/** 源语言代码(用于构建反向映射)
* @default 'zh-CN'
*/
sourceLocale?: string
/** 模板中的翻译函数名
* @default '$t'
*/
translateFn?: string
/** 脚本中的翻译函数名
* @default 't'
*/
scriptTranslateFn?: string
/** 是否转换 <script> 中的中文
* @default true
*/
transformScript?: boolean
/** 是否转换 <script setup> 中的中文
* @default true
*/
transformScriptSetup?: boolean
/** 开发模式是否启用
* @default true
*/
enableInDev?: boolean
/** 排除的文件模式
* @default [/node_modules/, /\.d\.ts$/, /\/locales\//, /\/langs\//]
*/
exclude?: (string | RegExp)[]
/** 调试模式 - 输出详细转换日志
* @default false
*/
debug?: boolean
/** 缺少翻译时的处理方式
* - 'warn': 控制台警告(默认)
* - 'error': 抛出错误,中断构建
* - 'ignore': 静默忽略
* @default 'warn'
*/
onMissing?: 'warn' | 'error' | 'ignore'
/** 是否自动导入 t 函数
* @default false
*/
autoImport?: boolean
/** 自动导入来源
* @default '@mico_fe/i18n-vue'
*/
importFrom?: string
}🔍 AST 转换原理
1. SFC 解析
使用 @vue/compiler-sfc 将 .vue 文件解析为三个部分:
┌─────────────────────────────────────────┐
│ Vue SFC (.vue) │
├─────────────────────────────────────────┤
│ <template>...</template> │
│ <script>...</script> │
│ <script setup>...</script> │
│ <style>...</style> │
└─────────────────────────────────────────┘
│
▼
@vue/compiler-sfc.parse()
│
┌──────────┼──────────┐
▼ ▼ ▼
Template Script ScriptSetup2. Template AST 处理
使用 @vue/compiler-dom 解析模板:
Template Content
│
▼
@vue/compiler-dom.parse()
│
▼
┌─────────────────────────────────────────┐
│ Template AST │
├─────────────────────────────────────────┤
│ ROOT │
│ └── ELEMENT (div) │
│ ├── props: [ATTRIBUTE, DIRECTIVE] │
│ └── children: │
│ ├── TEXT "你好" │
│ ├── INTERPOLATION {{ msg }} │
│ └── ELEMENT (span) │
└─────────────────────────────────────────┘节点类型处理:
| 节点类型 | 示例 | 转换结果 |
|---------|------|---------|
| TEXT | >你好< | >{{ $t('key') }}< |
| ATTRIBUTE | title="提示" | :title="$t('key')" |
| INTERPOLATION | {{ '中文' }} | {{ $t('key') }} |
3. Script AST 处理
使用 @babel/parser + @babel/traverse:
Script Content
│
▼
@babel/parser.parse()
│
▼
┌─────────────────────────────────────────┐
│ Script AST │
├─────────────────────────────────────────┤
│ Program │
│ └── statements: │
│ ├── ImportDeclaration │
│ ├── VariableDeclaration │
│ │ └── StringLiteral "中文" │
│ └── ExpressionStatement │
│ └── CallExpression (console) │
│ └── StringLiteral "日志" │
└─────────────────────────────────────────┘
│
▼
@babel/traverse (遍历并收集需要转换的节点)自动跳过的上下文:
- ✅
import声明 - ✅
export声明的 source - ✅
console.xxx()调用 - ✅ 已经是
t()/$t()的参数 - ✅ 对象的 key(非计算属性)
- ✅ TypeScript 类型注解
- ✅
require()调用
4. 代码生成
使用 magic-string 根据收集的位置信息进行精确替换:
// 从后往前替换,避免位置偏移
const sortedTransforms = transforms.sort((a, b) => b.start - a.start)
for (const { start, end, replacement } of sortedTransforms) {
magicString.overwrite(start, end, replacement)
}🎯 最佳实践
1. 语言包结构
推荐使用扁平化或浅层嵌套:
{
"common.confirm": "确认",
"common.cancel": "取消",
"user.login": "登录",
"user.logout": "退出登录"
}2. 参数化翻译
// zh-CN.json
{
"welcome.user": "欢迎,{name}!"
}<template>
<!-- 需要手动使用 $t -->
<p>{{ $t('welcome.user', { name: userName }) }}</p>
</template>3. 动态内容
对于动态生成的文本,AST 无法在编译时处理,需要手动使用 t() 或 $t():
<script setup>
// 动态内容需要手动处理
const dynamicText = computed(() => t(props.messageKey))
</script>4. 开启调试
开发时建议开启 debug: true,查看转换详情:
vueI18nAst({
debug: process.env.NODE_ENV === 'development',
})控制台输出示例:
[vue-i18n-ast] Processing (AST): /src/components/Header.vue
[vue-i18n-ast] Text: "你好世界" → {{ $t('hello') }}
[vue-i18n-ast] Attr: title="欢迎使用" → :title="$t('welcome')"
[vue-i18n-ast] Script: "提交" → t('button.submit')📊 构建统计
构建完成后会输出统计信息:
[vue-i18n-ast] Build complete: 156 transformations in 23/45 files
- Template: 89, Script: 45, Attribute: 22
- Cache hits: 12⚠️ 注意事项
性能考虑:AST 解析比正则匹配慢,但更精确。对于大型项目,建议合理使用缓存。
复杂表达式:带变量的模板字符串无法自动转换:
// ❌ 无法自动转换 const msg = `欢迎 ${name}!` // ✅ 需要手动处理 const msg = t('welcome.user', { name })语言包同步:确保所有语言包的 key 保持一致。
与运行时配合:此插件只负责编译时转换,运行时需要配合
@mico_fe/i18n-vue使用。
🔗 相关包
- @mico_fe/i18n-core - 核心运行时库
- @mico_fe/i18n-vue - Vue 运行时支持
- @mico_fe/vite-plugin-i18n-vue - 正则方案(更快但精确度较低)
📄 License
MIT
