@mico_fe/vite-plugin-i18n-react-ast
v1.0.0
Published
React i18n Vite plugin with AST-based transformation - More precise Chinese to t(key) conversion
Downloads
94
Readme
@mico_fe/vite-plugin-i18n-react-ast
基于 AST (抽象语法树) 的 React/JSX i18n 编译时转换插件。相比正则匹配方案,AST 方案更加精确,能够准确识别代码结构,避免误转换。
✨ 特性
- 🎯 精确转换 - 基于 AST 分析,精确识别 JSX 文本、属性和脚本中的中文
- 🔍 语义理解 - 理解代码上下文,自动跳过注释、import、console 等
- 📦 完整支持 - 支持 JSX/TSX、普通 JS/TS 文件
- ⚡ 高性能 - 智能缓存,增量编译
- 🛡️ 类型安全 - 完整的 TypeScript 支持
- 🔧 可配置 - 丰富的配置选项
🔬 AST vs 正则方案对比
| 特性 | AST 方案 (本包) | 正则方案 | |------|----------------|----------| | 精确度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | | 跳过注释 | ✅ 自动 | ❌ 需特殊处理 | | 跳过 import | ✅ 自动 | ❌ 需特殊处理 | | 跳过 console | ✅ 自动 | ❌ 需特殊处理 | | JSX 嵌套结构 | ✅ 完美支持 | ⚠️ 可能误匹配 | | 模板字符串 | ✅ 精确处理 | ⚠️ 复杂情况难处理 | | 性能 | 中等 | 快 | | 实现复杂度 | 高 | 低 |
📦 安装
# npm
npm install @mico_fe/vite-plugin-i18n-react-ast -D
# pnpm
pnpm add @mico_fe/vite-plugin-i18n-react-ast -D
# yarn
yarn add @mico_fe/vite-plugin-i18n-react-ast -D🚀 快速开始
1. 配置 Vite
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import reactI18nAst from '@mico_fe/vite-plugin-i18n-react-ast'
export default defineConfig({
plugins: [
reactI18nAst({
localesDir: 'locales', // 语言包目录名(向上查找)或绝对路径
sourceLocale: 'zh-CN',
debug: true, // 开发时开启,查看转换日志
}),
react(),
],
})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. 编写 React 组件
// App.tsx
import React from 'react'
function App() {
// 脚本中的中文自动转换
const message = '欢迎使用'
console.log('这不会被转换') // console 自动跳过
return (
<div>
{/* JSX 文本自动转换 */}
<h1>你好世界</h1>
{/* 属性自动转换 */}
<div title="欢迎使用">
<button>提交</button>
<button>取消</button>
</div>
</div>
)
}编译后
import React from 'react'
function App() {
const message = t('welcome')
console.log('这不会被转换')
return (
<div>
<h1>{t('hello')}</h1>
<div title={t('welcome')}>
<button>{t('button.submit')}</button>
<button>{t('button.cancel')}</button>
</div>
</div>
)
}⚙️ 配置选项
interface LocalesRule {
/** 文件路径匹配模式(glob 或正则) */
match: string | RegExp
/** 该匹配使用的语言包目录 */
localesDir: string
/** 可选的命名空间前缀 */
namespace?: string
}
interface ReactI18nAstPluginConfig {
/** 语言包目录名(向上查找)或绝对路径(直接使用)
*
* - 字符串:单一目录名,如 'locales'
* - 数组:多个目录名,按优先级排序,如 ['locales', 'i18n', 'langs']
* - 绝对路径:直接使用,不进行向上查找
*
* @default 'locales'
*/
localesDir?: string | string[]
/** 路径匹配规则(优先级高于 localesDir)
* 用于特殊项目的精确控制
*/
localesRules?: LocalesRule[]
/** 源语言代码(用于构建反向映射)
* @default 'zh-CN'
*/
sourceLocale?: string
/** 翻译函数名
* @default 't'
*/
translateFn?: string
/** 开发模式是否启用
* @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-react'
*/
importFrom?: string
}🔍 AST 转换原理
1. 代码解析
使用 @babel/parser 解析 JSX/TSX 代码:
React Component (.tsx/.jsx)
│
▼
@babel/parser.parse()
│
▼
┌─────────────────────────────────────────┐
│ React AST │
├─────────────────────────────────────────┤
│ Program │
│ └── FunctionDeclaration │
│ └── ReturnStatement │
│ └── JSXElement │
│ ├── JSXOpeningElement │
│ │ └── JSXAttribute │
│ └── JSXText "你好" │
└─────────────────────────────────────────┘2. AST 遍历
使用 @babel/traverse 遍历并识别需要转换的节点:
traverse(ast, {
// JSX 文本: <div>你好</div>
JSXText(path) { /* ... */ },
// JSX 属性: <div title="标题">
JSXAttribute(path) { /* ... */ },
// JSX 表达式: <div>{'中文'}</div>
JSXExpressionContainer(path) { /* ... */ },
// 普通字符串: const msg = '中文'
StringLiteral(path) { /* ... */ },
// 模板字符串: const msg = `中文`
TemplateLiteral(path) { /* ... */ },
})3. 节点类型转换
| 原始代码 | AST 节点类型 | 转换结果 |
|---------|-------------|---------|
| <h1>你好</h1> | JSXText | <h1>{t('key')}</h1> |
| title="中文" | JSXAttribute | title={t('key')} |
| {'中文'} | JSXExpressionContainer | {t('key')} |
| const x = '中文' | StringLiteral | const x = t('key') |
| `中文` | TemplateLiteral | t('key') |
4. 自动跳过的上下文
基于 AST 的语义分析,自动跳过以下上下文:
- ✅
import声明 - ✅
export声明的 source - ✅
console.xxx()调用 - ✅ 已经是
t()的参数 - ✅ 对象的 key(非计算属性)
- ✅ TypeScript 类型注解
- ✅
require()调用 - ✅ Tagged template literals (如
css\...``)
🎯 最佳实践
1. 配合 useTranslation Hook
import { useTranslation } from '@mico_fe/i18n-react'
function MyComponent() {
const { t } = useTranslation()
// 自动转换后会使用这个 t 函数
return <div>你好世界</div>
}2. 动态内容处理
对于动态内容,需要手动使用 t():
// 动态 key - 需要手动处理
const statusMap = {
success: t('status.success'),
error: t('status.error'),
}
// 动态参数
const message = t('welcome.user', { name: userName })3. 条件渲染
// ✅ 正常转换
{isLoggedIn ? <span>已登录</span> : <span>未登录</span>}
// ✅ 转换后
{isLoggedIn ? <span>{t('logged_in')}</span> : <span>{t('not_logged_in')}</span>}4. 开启调试
开发时建议开启 debug: true:
reactI18nAst({
debug: process.env.NODE_ENV === 'development',
})控制台输出示例:
[react-i18n-ast] Processing (AST): /src/components/Header.tsx
[react-i18n-ast] JSX Text: "你好世界" → {t('hello')}
[react-i18n-ast] JSX Attr: "欢迎使用" → {t('welcome')}
[react-i18n-ast] Script: "提交" → t('button.submit')📊 构建统计
构建完成后会输出统计信息:
[react-i18n-ast] Build complete: 156 transformations in 23/45 files
- JSX Text: 89, Script: 45, Attribute: 22
- Cache hits: 12⚠️ 注意事项
性能考虑:AST 解析比正则匹配慢,但更精确。对于大型项目,建议合理使用缓存。
复杂表达式:带变量的模板字符串无法自动转换:
// ❌ 无法自动转换 const msg = `欢迎 ${name}!` // ✅ 需要手动处理 const msg = t('welcome.user', { name })Fragment 中的文本:正常支持
// ✅ 正常转换 <>你好世界</>与运行时配合:此插件只负责编译时转换,运行时需要配合
@mico_fe/i18n-react使用。
🔗 相关包
- @mico_fe/i18n-core - 核心运行时库
- @mico_fe/i18n-react - React 运行时支持
- @mico_fe/vite-plugin-i18n-react - 正则方案(更快但精确度较低)
📄 License
MIT
