npm package: @mico_fe/vite-plugin-i18n-vue-ast
Description: Vue i18n Vite plugin with AST-based transformation - More precise Chinese to $t(key) conversion
Author: mico
Latest version: 1.0.0

# @mico_fe/vite-plugin-i18n-vue-ast

基于 AST (抽象语法树) 的 Vue i18n 编译时转换插件。相比正则匹配方案，AST 方案更加精确，能够准确识别代码结构，避免误转换。

## ✨ 特性

- **🎯 精确转换** - 基于 AST 分析，精确识别需要转换的中文文本
- **🔍 语义理解** - 理解代码上下文，自动跳过注释、import、console 等
- **📦 完整支持** - 支持 Template、Script、Script Setup
- **⚡ 高性能** - 智能缓存，增量编译
- **🛡️ 类型安全** - 完整的 TypeScript 支持
- **🔧 可配置** - 丰富的配置选项

## 🔬 AST vs 正则方案对比

| 特性 | AST 方案 (本包) | 正则方案 |
|------|----------------|----------|
| 精确度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 跳过注释 | ✅ 自动 | ❌ 需特殊处理 |
| 跳过 import | ✅ 自动 | ❌ 需特殊处理 |
| 跳过 console | ✅ 自动 | ❌ 需特殊处理 |
| 嵌套结构 | ✅ 完美支持 | ⚠️ 可能误匹配 |
| 模板字符串 | ✅ 精确处理 | ⚠️ 复杂情况难处理 |
| 性能 | 中等 | 快 |
| 实现复杂度 | 高 | 低 |

## 📦 安装

```bash
# 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

```typescript
// 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. 准备语言包

```json
// src/locales/zh-CN.json
{
  "hello": "你好世界",
  "welcome": "欢迎使用",
  "button": {
    "submit": "提交",
    "cancel": "取消"
  }
}
```

```json
// src/locales/en-US.json
{
  "hello": "Hello World",
  "welcome": "Welcome",
  "button": {
    "submit": "Submit",
    "cancel": "Cancel"
  }
}
```

### 3. 编写 Vue 组件

```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>
```

### 编译后

```vue
<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>
```

## ⚙️ 配置选项

```typescript
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    ScriptSetup
```

### 2. 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` 根据收集的位置信息进行精确替换：

```typescript
// 从后往前替换，避免位置偏移
const sortedTransforms = transforms.sort((a, b) => b.start - a.start)

for (const { start, end, replacement } of sortedTransforms) {
  magicString.overwrite(start, end, replacement)
}
```

## 🎯 最佳实践

### 1. 语言包结构

推荐使用扁平化或浅层嵌套：

```json
{
  "common.confirm": "确认",
  "common.cancel": "取消",
  "user.login": "登录",
  "user.logout": "退出登录"
}
```

### 2. 参数化翻译

```json
// zh-CN.json
{
  "welcome.user": "欢迎，{name}！"
}
```

```vue
<template>
  <!-- 需要手动使用 $t -->
  <p>{{ $t('welcome.user', { name: userName }) }}</p>
</template>
```

### 3. 动态内容

对于动态生成的文本，AST 无法在编译时处理，需要手动使用 `t()` 或 `$t()`：

```vue
<script setup>
// 动态内容需要手动处理
const dynamicText = computed(() => t(props.messageKey))
</script>
```

### 4. 开启调试

开发时建议开启 `debug: true`，查看转换详情：

```typescript
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
```

## ⚠️ 注意事项

1. **性能考虑**：AST 解析比正则匹配慢，但更精确。对于大型项目，建议合理使用缓存。

2. **复杂表达式**：带变量的模板字符串无法自动转换：
   ```javascript
   // ❌ 无法自动转换
   const msg = `欢迎 ${name}！`
   
   // ✅ 需要手动处理
   const msg = t('welcome.user', { name })
   ```

3. **语言包同步**：确保所有语言包的 key 保持一致。

4. **与运行时配合**：此插件只负责编译时转换，运行时需要配合 `@mico_fe/i18n-vue` 使用。

## 🔗 相关包

- [@mico_fe/i18n-core](../core) - 核心运行时库
- [@mico_fe/i18n-vue](../vue) - Vue 运行时支持
- [@mico_fe/vite-plugin-i18n-vue](../vite-plugin-vue) - 正则方案（更快但精确度较低）

## 📄 License

MIT