vite 中可通过 rollup 插件实现中文字符串自动提取:利用 transform 钩子结合 ast 解析识别 $t() 或 i18n 前缀字符串,buildend 时合并去重并生成 locales/zh-cn.json,支持开发热更与生产构建,需 enforce: 'pre' 且兼容 vue sfc 和 esm/cjs。

在 Vite 中手写一个符合 Rollup 规范的自定义插件,用于在构建时自动提取代码中的中文字符串生成国际化 JSON 文件,核心是利用 Rollup 的 transform 钩子扫描源码、匹配中文(或带标记的字符串),再通过 buildEnd 或 generateBundle 写出 JSON。关键在于:精准识别目标字符串、避免误提、支持多文件合并、保证路径和格式正确。
识别中文字符串并标记提取点
不建议简单用正则匹配所有中文(易误伤注释、HTML、变量名等),推荐约定提取方式,比如统一用特定函数包裹:$t('按钮文字')、i18n('提交成功'),或使用带前缀的模板字面量:`i18n:确认删除?`。插件在 transform 阶段对每个 .ts/.js/.vue 文件做 AST 解析(可用 @babel/parser 或轻量 acorn),只提取符合约定的字符串字面量,并记录其来源文件路径、行号(可选)、原始内容。
- Vue 单文件组件需额外处理
<template></template>中的文本(可用@vue/compiler-sfc提取 template AST) - 跳过
node_modules和非源码目录(如dist、public) - 对重复字符串去重,但保留首次出现位置(便于后期定位)
收集结果并在构建结束时生成 JSON
用插件闭包或 this.meta(Rollup 插件上下文)维护一个全局 map,key 为字符串原文,value 包含文件列表、是否已翻译标记等。在 buildEnd 钩子中汇总所有提取项,按需整理成扁平结构或嵌套结构(如按模块分组),然后调用 fs.writeFileSync 输出到指定路径(例如 locales/zh-CN.json)。注意确保输出目录存在,可借助 fs.mkdirSync(..., { recursive: true })。
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
- JSON 内容推荐保留原始字符串作为 key,value 初始设为空字符串或占位符(如
"TODO"),方便后续人工补译 - 可选添加头部注释说明生成时间、来源文件数等信息
- 避免覆盖已有翻译——可先读取旧 JSON,仅更新新增 key,保留已有翻译值
兼容 Vite 开发与生产构建流程
Vite 默认开发服务器(vite dev)不触发 buildEnd,所以提取逻辑应同时响应 handleHotUpdate(热更时重新扫描单个文件)和 buildEnd(全量构建时汇总)。开发阶段可将提取结果暂存内存或临时 JSON,供 HMR 调试;生产构建(vite build)才真正落盘。另外,插件需声明 enforce: 'pre',确保在其他插件(如 TypeScript 编译)之前运行,避免代码已被转译而丢失原始字符串。
- Vue SFC 的 script setup 语法需兼容
defineProps、defineEmits等宏,提取时不破坏语法结构 - 支持 ESM / CJS 混合项目,插件本身导出为 ESM 格式,且不依赖 Node.js 特有 API(如需跨平台,避免
__dirname) - 可通过插件选项控制是否启用提取(
disabled: true)、输出路径、匹配模式等
简单示例插件骨架
以下是一个最小可行插件结构(省略 AST 解析细节,聚焦流程):
export function i18nExtractPlugin(options = {}) {
const { outDir = 'locales', lang = 'zh-CN', disabled = false } = options;
const messages = new Map();
return {
name: 'i18n-extract',
enforce: 'pre',
transform(code, id) {
if (disabled || /node_modules|\.d\.ts$/.test(id)) return;
// 此处用正则或 AST 提取 $t('xxx') 中的 'xxx'
const matches = code.match(/\$t\(['"`]([^'"`]+)['"`]\)/g);
if (matches) {
matches.forEach(match => {
const str = match.match(/\$t\(['"`]([^'"`]+)['"`]\)/)?.[1];
if (str && /^[\u4e00-\u9fa5\s,。!?;:“”‘’()【】《》、]+$/.test(str)) {
messages.set(str, messages.get(str) || []);
messages.get(str).push({ file: id });
}
});
}
},
buildEnd() {
const json = Object.fromEntries(messages.entries());
const fullPath = `${outDir}/${lang}.json`;
fs.writeFileSync(fullPath, JSON.stringify(json, null, 2), 'utf-8');
console.log(`[i18n] Extracted ${messages.size} strings to ${fullPath}`);
}
};
}
实际项目中建议用 @babel/parser + @babel/traverse 替代正则,提升健壮性。插件挂载到 vite.config.ts 的 plugins 数组即可生效。










