npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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

⚠️ 注意事项

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

  2. 复杂表达式:带变量的模板字符串无法自动转换:

    // ❌ 无法自动转换
    const msg = `欢迎 ${name}!`
       
    // ✅ 需要手动处理
    const msg = t('welcome.user', { name })
  3. Fragment 中的文本:正常支持

    // ✅ 正常转换
    <>你好世界</>
  4. 与运行时配合:此插件只负责编译时转换,运行时需要配合 @mico_fe/i18n-react 使用。

🔗 相关包

📄 License

MIT