vue-i18n-generator
v2.0.2
Published
Vue-i18n 国际化生成器,提取项目中的”中文“替换为多语言`$t(key)`,并导出翻译文件。
Downloads
1,564
Readme
vue-i18n-generator
介绍
Vue-i18n 国际化生成器,提取项目中的”中文“替换为多语言$t(key),并导出翻译文件。
安装
# 全局安装
npm install vue-i18n-generator -g
# 或本地安装
npm install vue-i18n-generator --save-dev
命令行参数
translator <env> options
Arguments:
env 指定环境:dev、test、prod
Options:
-d, --dir <dir> 提取的文件夹路径,默认 src/
-o, --output <dir> 提取结果输出路径,默认 src/locales
-e, --excludes <value...> 需要排除的文件或文件夹,可指定多个
-l, --locales <value...> 需要生成的语言代码(符合BCP47标准),如 zh-CN
-t, --translate <value...> 需要翻译的语言代码,对应--locales参数
-p, --prefix <value> 多语言Key前缀,如 L1 L2 L3 custom
-c, --channel <value> 翻译渠道 baidu(百度) youdao(有道) google(谷歌)
-m, --key-mode <value> Key生成模式,pinyin(拼音) | pinyin-hash(拼音+哈希) | hash(哈希)
--appKey <value> 翻译api应用key
--secretKey <value> 翻译api密钥
--v2 <value> 是否为Vue2.x版本, 默认为Vue3.x版本env参数详解:- dev 仅输出翻译文件。
- test 输出翻译文件并生成
.lang结尾的对照文件(可通过执行dev删除)。 - prod 不输出翻译文件,仅将翻译
key替换原文件中文,用于生产打包部署前命令。
备注:
- 谷歌翻译需提供
--appKey(Google Cloud Translation API v2)。百度和有道翻译需同时提供--appKey和--secretKey - 只对新增
key翻译,需重新翻译可删除locale.json中的key后重新执行命令 prefix为多语言前缀,L1为路径层级文件夹名,路径中pages|views不计算层级- 生成的翻译
key为[前缀]_文件名.[Key生成策略],需注意中文修改后key将会被改变 - 可添加自定义
公共多语言(在根目录创建translator.json),适用于高频率出现的中文
- 谷歌翻译需提供
注意事项:
defineProps和defineEmits的选项中不能使用t方法,故在它们选项中不能使用中文块,我们可引入全局i18n实例,然后使用实例的i18n.global.t方法来解决。因为它们会从setup中提升到模块的作用域,因此传入的选项不能引用在setup作用域中声明的局部变量,这样会引起编译报错,具体可查看官方文档。
语种编码对照
本项目使用的语言编码遵循 BCP 47 标准。不同翻译渠道的 API 对语言代码的格式要求不同,本工具已内置自动修正,无需手动转换,如无需使用翻译功能则可忽略。
| 语种 | 项目多语言编码 | 百度翻译 | 有道翻译 | 谷歌翻译 |
|------|:---:|:---:|:---:|:---:|
| 简体中文 | zh-CN | zh | zh-CHS | zh-CN |
| 繁体中文(台湾) | zh-TW | cht | zh-CHT | zh-TW |
| 繁体中文(香港) | zh-HK | cht | zh-CHT | zh-HK |
| 英语(美国) | en-US/en | en | en | en |
| 英语(英国) | en-GB/en | en | en | en |
| 日语 | ja-JP/ja | jp | ja | ja |
说明:百度翻译使用自定义语种代码(繁体中文统一映射为
cht);有道翻译使用zh-CHS/zh-CHT后缀区分简繁体;谷歌翻译支持 BCP 47 标准,与项目编码完全一致。传入小写非标准代码(如ja)也可被解析。
key-mode 参数详解
控制多语言 Key 的生成策略,解决不同中文字符串可能生成相同 Key 的碰撞问题。
| 模式 | 参数值 | Key 示例 (确认删除?) | 说明 |
|------|--------|----------------------|------|
| 拼音 | pinyin | que_ren_shan_chu | 全拼 ≤20 字符直接使用,超长用首字母或截取。可能碰撞 |
| 拼音+哈希 | pinyin-hash | que_ren_shan_chu | 首次出现用可读拼音,同一 Key 再次出现或过长时追加 4~8 位 MD5 哈希 |
| 哈希 | hash | fc98faab450a | 始终用原文的 12 位 MD5 哈希作为 Key,绝对唯一但不可读 |
碰撞示例:
| 中文原文 | pinyin(默认) | pinyin-hash | hash |
|----------|:--:|:--:|:--:|
| 确认删除? | que_ren_shan_chu | que_ren_shan_chu | fc98faab450a |
| 确认删除! | que_ren_shan_chu ❌ 覆盖 | que_ren_shan_chu_450a ✅ | 450a828b3f1e |
| 确认删除。 | que_ren_shan_chu ❌ 覆盖 | que_ren_shan_chu_04ad ✅ | 04ad497b2c8d |
| 攻击 | gong_ji | gong_ji | 33781e80a9f2 |
| 公鸡 | gong_ji ❌ 覆盖 | gong_ji_2daa ✅ | 2daa2de31b7c |
| 一段很长的中文... | zsydfccdzw... | zsydfccdzw_5830a3f0 | 5830a3f012ef |
命令行使用
# 帮助文档
translator --help
# 开发环境:提取中文并生成翻译文件(百度翻译)
translator dev -d src/ -o src/locales -l zh-CN en-US -t en-US -c baidu --appKey YOUR_KEY --secretKey YOUR_SECRET
# 开发环境:使用谷歌翻译(需 API 密钥)
translator dev -d src/ -o src/locales -l zh-CN en-US -t en-US -c google --appKey YOUR_API_KEY
# 指定 Key 生成模式(推荐 pinyin-hash,防碰撞)
translator dev -d src/ -o src/locales -l zh-CN en-US -m pinyin-hash
# 测试环境:额外生成对照文件(.lang后缀)
translator test -l zh-CN zh-TW en-US --v2
# 生产环境:直接替换原文件
translator prod -l zh-CN zh-TW en-US --v2
项目中导入
- 安装
vue-i18n依赖包:npm install vue-i18n --save - 在Vue项目中的
main.js文件中添加如下代码:
Vue2.0
// main.js
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import VueRouter from 'vue-router';
import ElementUI from 'element-ui';
import elementZhCN from 'element-ui/lib/locale/lang/zh-CN';
import elementEnUS from 'element-ui/lib/locale/lang/en';
import router from '@/router';
import { i18n } from '@/locales';
import App from './App.vue';
Vue.use(VueRouter);
// 合并 ElementUI 语言包
i18n.mergeLocaleMessage('zh-CN', elementZhCN);
i18n.mergeLocaleMessage('en-US', elementEnUS);
Vue.use(ElementUI, {
i18n: (key, value) => i18n.t(key, value),
});
new Vue({
router,
i18n,
render: (h) => h(App),
}).$mount('#app');Vue3.0
// main.js
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import router from '@/router';
import i18n from '@/locales';
import App from './App.vue';
const app = createApp(App);
app.use(i18n);
app.use(router);
app.use(ElementPlus);
app.mount('#app');<!-- App.vue -->
<template>
<el-config-provider :locale="message">
<RouterView />
</el-config-provider>
</template>
<script setup lang="ts">
import elementPlusZhCN from 'element-plus/es/locale/lang/zh-cn';
import elementPlusEnUS from 'element-plus/es/locale/lang/en';
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
// 语言编码与生成的地区语言代码对应
const messages: any = {
'zh-CN': elementPlusZhCN,
'en-US': elementPlusEnUS,
};
const message = computed(() => messages[locale.value]);
</script>示例
test.vue
<template title="用户设置">
<div class="user-form">
<!-- 纯文本节点 -->
请填写以下用户信息
<!-- 元素内中文(短文本 / 长文本→哈希Key / 中英混合) -->
<p>基本信息</p>
<p>名称长度需在7~13个字符之间</p>
<p>机身号、项目名称 or 设备名称</p>
<!-- 中文与模板语法混合(前缀 + 三元内嵌中文 + 后缀) -->
<p>开始{{ isAutoMode ? '篮球' : '乒乓球' }}比赛</p>
<!-- 静态属性(多属性 + Common短语匹配) -->
<el-form-item label="持卡人" placeholder="请输入"></el-form-item>
<el-date-picker start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
<!-- 动态属性(三元 / 字符串拼接 / 逻辑或+三元) -->
<el-form-item :label="isAutoMode ? '地球' : '月球'"></el-form-item>
<el-form-item :label="'检查项' + ' (' + userRole.length + ')'"></el-form-item>
<el-form-item :label="isAutoMode || useContactPerson ? '设备号' : '机身号'"></el-form-item>
<!-- 模板字符串(单变量 / 三元插槽 / 多插槽+嵌套三元) -->
<p>{{ `测试共${charLimit}个字符 ` }}</p>
<p>{{ `${isAutoMode ? '自动' : '手动'}:` }}</p>
<p>{{ `您确定要删除${isAutoMode ? '自动清洗' : '手动清洗'}路线${isAutoMode ? '图片' : '定位'}吗?` }}</p>
<!-- 动态属性中的模板字符串(单变量 / 三元插槽) -->
<el-form-item :label="`${userRole}姓名`"></el-form-item>
<el-form-item :label="`${isPhoneMode ? '手机号码' : '电话号码'}:`"></el-form-item>
<!-- 深层嵌套 -->
<div class="wrapper">
<div class="inner">
<span>嵌套层级中的提示信息</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
charLimit: 10,
userRole: '管理员',
isAutoMode: true,
isPhoneMode: true,
useContactPerson: true,
currentDay: '星期一',
};
},
computed: {
// 计算属性中的模板字符串
roleDisplayName() {
return `当前${this.userRole}用户`;
},
},
mounted() {
// 生命周期钩子:debug 日志 + Common 短语匹配
console.log('组件加载完成');
this.$message('保存成功');
},
methods: {
// 单行注释 + debug 日志 + 字符串替换(中文 / 纯英文 / 中英混合)
handleStringReplacement() {
console.log('这是一条调试日志');
console.error('数据拉取失败');
const accountInfo = '账户信息';
const englishOnly = 'welcome';
const mixedText = '聚会时间';
},
/*
* 多行注释中的中文不影响替换
* 含中文的业务说明文字
*/
handleTemplateLiteral() {
const contactText = `联系${this.useContactPerson ? '张三' : '李四'}`;
const accountText = `这是(${this.userRole})的账户`;
const greetingText = `${this.currentDay},${this.userRole}先生`;
const confirmMsg = `统计:排行榜总计 ${Object.keys(obj).length} 条,本次新增 ${addCount} 条,删除 ${delCount} 条。`;
this.$confirm(confirmMsg, () => {});
},
// 条件分支中的中文
handleConditionalMessage() {
if (this.isAutoMode) {
this.$message('自动保存成功');
} else {
this.$message('手动保存成功');
}
},
},
};
</script>执行结果
<template>
<div class="user-form">
<!-- 纯文本节点 -->
{{ $t('test.qtxyxyhxx') }}
<!-- 元素内中文(短文本 / 长文本→哈希Key / 中英混合) -->
<p>{{ $t('test.ji_ben_xin_xi') }}</p>
<p>{{ $t('test.mccdxz713gzfzj') }}</p>
<p>{{ $t('test.jshxmmcorsbmc') }}</p>
<!-- 中文与模板语法混合(前缀 + 三元内嵌中文 + 后缀) -->
<p>{{ $t('test.kai_shi') }}{{ isAutoMode ? $t('test.lan_qiu') : $t('test.ping_pang_qiu') }}{{ $t('test.bi_sai') }}</p>
<!-- 静态属性(多属性 + Common短语匹配) -->
<el-form-item :label="$t('test.chi_ka_ren')" :placeholder="$t('test.qing_shu_ru')"></el-form-item>
<el-date-picker :start-placeholder="$t('test.kai_shi_ri_qi')" :end-placeholder="$t('test.jie_shu_ri_qi')"></el-date-picker>
<!-- 动态属性(三元 / 字符串拼接 / 逻辑或+三元) -->
<el-form-item :label="isAutoMode ? $t('test.di_qiu') : $t('test.yue_qiu')"></el-form-item>
<el-form-item :label="$t('test.jian_cha_xiang') + ' (' + userRole.length + ')'"></el-form-item>
<el-form-item :label="isAutoMode || useContactPerson ? $t('test.she_bei_hao') : $t('test.ji_shen_hao')"></el-form-item>
<!-- 模板字符串(单变量 / 三元插槽 / 多插槽+嵌套三元) -->
<p>{{ $t('test.csg$0gzf', [charLimit]) }}</p>
<p>{{ $t('test.$0', [isAutoMode ? $t('test.zi_dong') : $t('test.shou_dong')]) }}</p>
<p>{{ $t('test.nqdysc$0lx$1m', [isAutoMode ? $t('test.zi_dong_qing_xi') : $t('test.shou_dong_qing_xi'),isAutoMode ? $t('test.tu_pian') : $t('test.ding_wei')]) }}</p>
<!-- 动态属性中的模板字符串(单变量 / 三元插槽) -->
<el-form-item :label="$t('test.$0xing_ming', [userRole])"></el-form-item>
<el-form-item :label="$t('test.$0', [isPhoneMode ? $t('test.shou_ji_hao_ma') : $t('test.dian_hua_hao_ma')])"></el-form-item>
<!-- 深层嵌套 -->
<div class="wrapper">
<div class="inner">
<span>{{ $t('test.qtcjzdtsxx') }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
charLimit: 10,
userRole: this.$t('test.guan_li_yuan'),
isAutoMode: true,
isPhoneMode: true,
useContactPerson: true,
currentDay: this.$t('test.xing_qi_yi'),
};
},
computed: {
// 计算属性中的模板字符串
roleDisplayName() {
return this.$t('test.dang_qian$0yong_hu', [this.userRole]);
},
},
mounted() {
// 生命周期钩子:debug 日志 + Common 短语匹配
console.log('组件加载完成');
this.$message(this.$t('Common.com_save_success'));
},
methods: {
// 单行注释 + debug 日志 + 字符串替换(中文 / 纯英文 / 中英混合)
handleStringReplacement() {
console.log('这是一条调试日志');
console.error('数据拉取失败');
const accountInfo = this.$t('test.zhang_hu_xin_xi');
const englishOnly = 'welcome';
const mixedText = this.$t('test.ju_hui_shi_jian');
},
/*
* 多行注释中的中文不影响替换
* 含中文的业务说明文字
*/
handleTemplateLiteral() {
const contactText = this.$t('test.lian_xi$0', [this.useContactPerson ? this.$t('test.zhang_san') : this.$t('test.li_si')]);
const accountText = this.$t('test.zhe_shi$0de_zhang_hu', [this.userRole]);
const greetingText = this.$t('test.$0$1xian_sheng', [this.currentDay,this.userRole]);
const confirmMsg = this.$t('test.tjpxbzj$0t_a194f468', [Object.keys(obj).length,addCount,delCount]);
this.$confirm(confirmMsg, () => {});
},
// 条件分支中的中文
handleConditionalMessage() {
if (this.isAutoMode) {
this.$message(this.$t('test.zdbccg'));
} else {
this.$message(this.$t('test.sdbccg'));
}
},
},
};
</script>
翻译文件 (locales/locale.json)
{
"Common": {
"com_yes": "是",
"com_no": "否",
"com_save_success": "保存成功",
"com_copy_success": "复制成功"
},
"test": {
"__filePath": "/test.vue",
"__title": "用户设置",
"dang_qian$0yong_hu": "当前{0}用户",
"zhang_san": "张三",
"li_si": "李四",
"lian_xi$0": "联系{0}",
"zhe_shi$0de_zhang_hu": "这是({0})的账户",
"$0$1xian_sheng": "{0},{1}先生",
"tjpxbzj$0t_a194f468": "统计:排行榜总计 {0} 条,本次新增 {1} 条,删除 {2} 条。",
"guan_li_yuan": "管理员",
"xing_qi_yi": "星期一",
"zhang_hu_xin_xi": "账户信息",
"ju_hui_shi_jian": "聚会时间",
"zdbccg": "自动保存成功",
"sdbccg": "手动保存成功",
"csg$0gzf": "测试共{0}个字符 ",
"zi_dong": "自动",
"shou_dong": "手动",
"$0": "{0}:",
"zi_dong_qing_xi": "自动清洗",
"shou_dong_qing_xi": "手动清洗",
"tu_pian": "图片",
"ding_wei": "定位",
"nqdysc$0lx$1m": "您确定要删除{0}路线{1}吗?",
"$0xing_ming": "{0}姓名",
"shou_ji_hao_ma": "手机号码",
"dian_hua_hao_ma": "电话号码",
"qtxyxyhxx": "请填写以下用户信息",
"ji_ben_xin_xi": "基本信息",
"mccdxz713gzfzj": "名称长度需在7~13个字符之间",
"jshxmmcorsbmc": "机身号、项目名称 or 设备名称",
"kai_shi": "开始",
"bi_sai": "比赛",
"lan_qiu": "篮球",
"ping_pang_qiu": "乒乓球",
"chi_ka_ren": "持卡人",
"qing_shu_ru": "请输入",
"kai_shi_ri_qi": "开始日期",
"jie_shu_ri_qi": "结束日期",
"di_qiu": "地球",
"yue_qiu": "月球",
"jian_cha_xiang": "检查项",
"she_bei_hao": "设备号",
"ji_shen_hao": "机身号",
"qtcjzdtsxx": "嵌套层级中的提示信息"
}
}CHANGELOG.md
## v2.0.2
- feat: 新增 `-m, --key-mode` 参数,支持 pinyin / pinyin-hash / hash 三种 Key 生成模式
- feat: 自动检测 `<script setup>`,Vue 3 Options API 模板自动使用 `$t` 前缀
- change: 模板占位符 `{0}` `{1}` 保留为 `$0` `$1`,不再替换为固定字符 `o`
- change: Key 生成保留 `$` 符号,并自动去除开头的数字(Key 不应以数字开头)
- change: 移除生成的通用多语言 `Common` 中时间戳 `__timestamp` 字段
- fix: 修复 `console.log/error/warn` 正则无法匹配嵌套括号的问题
- fix: 修复行首单行注释未被保护的 bug
- fix: 修复不同中文生成相同 Key 的碰撞问题(需使用 `--key-mode pinyin-hash`)
- fix: 修复部分 bug
## v1.0.0
- change: 新增`index.js`文件,并将语言代码文件移至`lang`文件夹中
- change: 移除内置的公共多语言,不再提供默认公共多语言
- fix: 修复部分 bug
## v0.5.1
- fix: 提升百度翻译每次请求效率,批量请求翻译
- fix: 新增有道翻译,谷歌翻译(需要科学上网)
- fix: 修复对新增数据翻译时旧翻译被覆盖问题
- fix: 修复部分 bug