使用 vve-i18n-cli 来一键式自动化实现国际化
【Github:vue-viewer-editor/vve-i18n-cli】
这是我同事开发的国际化自动处理脚本,我进行过一次扩展,让其也支持我们一个 jQuery 老项目的国际化日常维护
至此,我们团队内的国际化均是使用该脚本来进行日常维护
该自动化脚本极大的为我们提效,基本将国际化的词条相关工作降低到 0 了,这意味着我们基本上不用特意留出太多时间来处理国际化方面的工作
但是,国际化其实不只有词条相关的工作,至于还有哪些工作,我之前发表过一篇《项目国际化的难点痛点是什么》里面吐槽得很清晰了
不可否认的是,如果没有这个自动化脚本,根本就没法在领导期望的时间内完成国际化的工作
自从有了这个脚本后,从以前的跟领导评估说要 4 天的国际化工作量到现在只评估了 1 天工作量,实际上跑下脚本分分钟就解决了,我还可以愉快的滑一天水,领导开心,我也开心~
自动化脚本能力
检查项目里是否存在不合理的编程方式,如中文做 key 值等

上述示例命令运行结果呈现:
{"type":"script-pre","text":"这里也可能有中文,还用不了this上下文"}
{"type":"props","text":"这里的中文也用不了this上下文"}
{"type":"zh-key","text":"中文"}
script-pre 场景是说发现有中文存在于 script 标签内,这部分代码运行在 js 模块作用域内,this 指向不是 vue 组件,包裹 this.$t 的话会导致程序异常,所以要先手动处理下,能下沉就下沉,否则就先手动用全局函数包裹,然后忽略这个处理
props 场景是说发现有中文存在于 vue 的 props 字段里,这里也无法访问 this,会报异常,建议这块更改成 computed 用法
zh-key 场景是说发现中文做 key 值,需要用户确认是否能被翻译处理
这个命令可以提前主动的发现代码里的国际化处理问题,避免将问题遗留到测试或线上阶段
标记无需处理的词条
总有些场景,你希望这个中文词条不要被国际化处理,这时候可以类似 es-lint 的忽略配置一样,既可以忽略整个文件,也可以忽略文件中的某个词条
在 vve-i18n-cli.config.js 里增加下忽略配置规则:
// vve-i18n-cli.config.js
module.exports = {
// 国际化文本包裹相关
zhWrap: {
// 需要过滤的文件
ignoreI18nFileRules: [],
// 需要处理的文件
i18nFileRules: ["!(node_modules|config|statsvnTmp)/**/*.+(js|vue)"],
// 当词条前缀出现以下正则时,该词条过滤不处理
ignorePreReg: [
/t\s*\([\s\n]*$/, // 词条在 t( 方法内的不处理,$t() 符合该规则
/console\.(?:log|error|warn|info|debug)\s*\(\s*$/, // 词条在 console.xxx 方法内的不处理,过滤掉日志内的中文处理
new RegExp("//.+"), // 注释中的词条不处理
new RegExp("i18n-disabled.+"), // 词条前面出现 i18n-disabled 关键词的不处理
],
},
};
然后代码中这些场景就不被处理了:
const ZH_KEY = /*i18n-disabled*/ "出现忽略处理的关键词的词条也不会被处理";
// 注释里中文不会被处理
console.error("日志里的中文也不会被处理");
自动对各种场景的中文词条包裹翻译函数
当检查完代码基本没问题,也配置了需要忽略处理的词条文件后,就可以通过命令自动对中文词条进行包裹翻译函数处理了:

不管你项目文件有成百上千个,都是一个命令就自动完成国际化翻译函数包裹词条处理
包裹哪些文件、包含的翻译函数名,不同场景(js 里,vue 里)用什么函数包裹,js 里是否需要加入 import 引入包含函数的代码,哪些文件不处理,哪些词条不处理,哪些前缀标记的不处理等等
以上场景都是通过 vve-i18n-cli.config.js 配置文件处理,详情查看下面章节,有默认的配置,你也可以根据你项目需要进行自己诉求的配置
脚本不是写死的行为,通过不同配置,可以适应到各种项目里去使用,目前我们团队的老项目,新项目,各种项目就通过各自项目的配置来接入了这个国际化自动处理脚本
自动将所有词条提取到 json 文件中(按模块维护)
当项目完成的国际化包裹词条处理后,就可以接着下一步,把词条提取到 json 文件里了:

想机翻,可以,默认不翻译,只做提取
想按模块提取到不同 json 文件里,可以,配置下模块规则
想生成多份语言的 json,可以,默认只有 zh.json, en.json
国际化的词条工作无外乎就是词条包裹处理,词条提取,词条翻译
这些工作难度不大,但工作量大,借助这类国际化自动处理脚本,就可以极大的提高效率,开心的滑水了
如何使用
安装
npm install vve-i18n-cli -D
package 里添加脚本命令,简化命令使用
{
"scripts": {
"i18n": "vve-i18n-cli",
"i18n-wrap": "vve-i18n-zh-wrap-cli",
"i18n-check": "vve-i18n-zh-check-cli"
}
}
根目录下创建配置文件 vve-i18n-cli.config.js
// vve-i18n-cli.config.js
module.exports = {
// 工作目录
cwd: ".",
// 根目录,国际文本所在的根目录
rootDir: "src",
// 默认所有模块,如果有传module参数,就只处理某个模块
// '**/module-**/**/index.js'
moduleIndexRules: ["."],
// 忽略模块
ignoreModuleIndexRules: [],
// 匹配含有国际化文本的文件规则
i18nFileRules: ["**/*.+(vue|js)"],
// 不匹配含有国际化文本的文件规则
ignoreI18nFileRules: [],
// 国际化文本的正则表达式,正则中第一个捕获对象当做国际化文本
i18nTextRules: [/(?:[\$.])t\([\s\n]*['"](.+?)['"]/g],
// 模块的国际化的json文件需要被保留下的key,即使这些组件在项目中没有被引用
// 规则可以是一个字符串,正则,或者是函数
keepKeyRules: [
/^G\/+/, // G/开头的会被保留
],
// 忽略国际化KEY的规则
// 规则可以是一个字符串,正则,或者是函数
ignoreKeyRules: [],
// 生成的国际化资源包的输出目录
outDir: "lang",
// 生成的国际化的语言
i18nLanguages: [
"zh", // 中文
"en", // 英文
],
// 配置文件的路径,没有配置,默认路径是在${cwd}/vve-i18n-cli.config.js
config: undefined,
// 是否取配置文件
disableConfigFile: false,
// 是否翻译
translate: false,
// 翻译的基础语言,默认是用中文翻译
translateFromLang: "zh",
// 是否强制翻译,即已翻译修改的内容,也重新用翻译生成
forceTranslate: false,
// 翻译的语言
translateLanguage: ["zh", "en"],
// 非中文使用拼音来来翻译
translateUsePinYin: false,
// 模块下${outDir}/index.js文件不存在才拷贝index.js
copyIndex: false,
// 是否强制拷贝最新index.js
forceCopyIndex: false,
// 国际化文本包裹相关
zhWrap: {
cwd: ".",
// 根目录,国际文本所在的根目录
rootDir: ".",
ignoreI18nFileRules: [],
i18nFileRules: ["!(node_modules|config|statsvnTmp)/**/*.+(js|vue)"],
ignorePreReg: [
/t\s*\([\s\n]*$/,
/tl\s*\([\s\n]*$/,
/console\.(?:log|error|warn|info|debug)\s*\(\s*$/,
/\/\/\s*$/,
new RegExp("//.+"),
new RegExp("i18n-disabled.+"),
],
ignoreText: ["^[\\u4e00-\\u9fa5a-zA-Z0-9“._=,':;*#!”-]+$"],
// js相关文件需要引入的国际化文件
i18nImportForJs: "",
// js相关文件需要使用国际化方法
jsI18nFuncName: "$i18n.$t",
// vue相关文件需要使用的国际化方法
vueI18nFuncName: "$t",
},
zhCheck: {
cwd: ".",
// 根目录,国际文本所在的根目录
rootDir: ".",
ignoreI18nFileRules: [],
i18nFileRules: ["!(node_modules|config|statsvnTmp)/**/*.+(vue|js)"],
// 反引号中需要忽略的文本规则,可以是正则或者字符串
ignoreTextInQuoteRules: [/t\(/],
},
};
先检查是否存在不合理的代码实现
npm run i18n-check
再执行自动对词条包裹翻译函数的命令
npm run i18n-wrap
最后把这些被翻译函数包裹的词条提取到 json 文件里
npm run i18n
这份脚本很通用化,根据各自配置规则,可以适应到各种项目里面,实在不行,代码也是开源的,拉下来修修改改得了
扩展
我们团队的翻译不是机翻,而是有专门的翻译团队进行翻译,因此提取完 json 词条后,还需要用 excel 跟翻译团队打交道
所以可以来扩展下几个脚本
提取未翻译词条到 excel 文件中
/**
* 抽取未翻译的词条到excel文件中
*/
const map = require("map-stream");
const path = require("path");
const vfs = require("vinyl-fs");
const XLSX = require("xlsx");
const ROOT_DIR = path.resolve("./");
const fileRules = [
"**/*/i18n/en.json",
// "**/eweb-setting-planningDeployment/i18n/en.json",
];
const writeExcel = (arr, name = "未翻译词条") => {
const sheet_data = arr.map((v) => {
return {
中文: v,
English: "",
};
});
const new_sheet = XLSX.utils.json_to_sheet(sheet_data);
// // 创新一个新的excel对象,就是workbook
const new_workbook = XLSX.utils.book_new();
// // 将表的内容写入workbook
XLSX.utils.book_append_sheet(new_workbook, new_sheet, "sheet1");
XLSX.writeFile(new_workbook, `${name}.xlsx`);
};
function run() {
const zhList = [];
console.log("================================>start", ROOT_DIR);
vfs
.src(
fileRules.map((item) => path.resolve(ROOT_DIR, item)),
{
ignore: ["node_modules/**/*", "statsvnTmp/**/*"],
}
)
.pipe(
map((file, cb) => {
console.log("处理文件 =========================>", file.path);
let fileContent = file.contents.toString();
fileContent = JSON.parse(fileContent);
Object.keys(fileContent).map((zh) => {
if (zh.match(/[\u4E00-\u9FFF]/)) {
if (zh === fileContent[zh]) {
// 未翻译
zhList.push(zh);
}
}
});
cb();
})
)
.on("end", () => {
const uniZh = Array.from(new Set(zhList));
writeExcel(uniZh);
console.log("未翻译词条数量:", uniZh.length);
console.log(
"================================>end",
"根目录下生成 excle 文件"
);
});
}
run();
将 excel 中的词条回填到 json 文件中
/**
* 将翻译后的内容替换到en.json文件中
*/
const map = require("map-stream");
const path = require("path");
const vfs = require("vinyl-fs");
const fs = require("fs");
const XLSX = require("xlsx");
const ROOT_DIR = path.resolve("./");
const fileRules = ["**/*/i18n/en.json"];
// 文件名称 默认名称 resource.json
const fileName = "resource";
const fileJsonName = fileName + ".json";
const fileXlsName = fileName + ".xlsx";
const excelReader = (exlcePathArray = []) => {
if (!Array.isArray(exlcePathArray)) {
exlcePathArray = [exlcePathArray];
}
const obj = {};
for (const i in exlcePathArray) {
if (Object.hasOwnProperty.call(exlcePathArray, i)) {
const excleFilePath = exlcePathArray[i];
console.log("读取excle " + excleFilePath);
const workbook = XLSX.readFileSync(excleFilePath, {
type: "binary",
});
for (const sheet in workbook.Sheets) {
const dataArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]);
obj[sheet] = dataArray;
}
}
}
return obj;
};
// 如果未找到 resource.json 查找 excel文件
if (!fs.existsSync(path.resolve(ROOT_DIR, fileJsonName))) {
// 判断resource.excel是否存在
if (fs.existsSync(path.resolve(ROOT_DIR, fileXlsName))) {
let fileObj = excelReader(path.resolve(ROOT_DIR, fileXlsName));
let dataObj = {};
if (fileObj.sheet1) {
// 中文列存在 例子: {"中文":"下载中...","English":"down…"}
for (let i = 0; i < fileObj.sheet1.length; i++) {
let itemZhKey = fileObj.sheet1[i]["中文"];
let itemEnKey = fileObj.sheet1[i]["English"];
if (itemZhKey && itemEnKey) {
dataObj[itemZhKey] = itemEnKey;
}
}
}
// 获取sheet1的内容
const data = JSON.stringify(dataObj);
try {
fs.writeFileSync(path.resolve(ROOT_DIR, fileJsonName), data);
} catch (error) {
console.log("生成文件 resource.xlsx 异常");
throw error;
}
} else {
console.log("不存在文件 resource.xlsx");
throw new Error("不存在文件 resource.xlsx");
}
}
const originResource = require(path.resolve(ROOT_DIR, fileJsonName));
let resource = Object.assign({}, originResource);
Object.keys(originResource).map((key) => {
resource[`${key}:`] = `${originResource[key]}:`;
resource[`${key}:`] = `${originResource[key]}:`;
resource[`${key})`] = `${originResource[key]})`;
resource[`${key})`] = `${originResource[key]})`;
resource[`${key} `] = `${originResource[key]} `;
resource[` ${key}`] = ` ${originResource[key]}`;
resource[`(${key}`] = `(${originResource[key]}`;
resource[`(${key}`] = `(${originResource[key]}`;
resource[` ${key} `] = ` ${originResource[key]} `;
});
function run() {
console.log("================================>start", ROOT_DIR);
let failedCount = 0;
let successCount = 0;
let failedZhs = [];
vfs
.src(
fileRules.map((item) => path.resolve(ROOT_DIR, item)),
{
ignore: ["node_modules/**/*", "statsvnTmp/**/*"],
}
)
.pipe(
map((file, cb) => {
console.log("处理文件 =========================>", file.path);
let fileContent = file.contents.toString();
fileContent = JSON.parse(fileContent);
let hasChange = false;
Object.keys(fileContent).map((zh) => {
if (zh.match(/[\u4E00-\u9FFF]/)) {
if (zh === fileContent[zh]) {
// 未翻译
if (resource[zh] && resource[zh] !== zh) {
hasChange = true;
fileContent[zh] = resource[zh];
successCount++;
} else {
failedCount++;
failedZhs.push(zh);
}
}
}
});
if (hasChange) {
fs.writeFileSync(
file.path,
JSON.stringify(fileContent, " ", 2) + "\n"
);
}
cb();
})
)
.on("end", () => {
fs.writeFileSync(
"unHandle.json",
JSON.stringify(failedZhs, " ", 2) + "\n"
);
console.log("本次翻译成功词条数量:", successCount);
console.log("还剩余未翻译词条数量:", failedCount);
console.log("================================>end");
});
}
run();
使用 vve-i18n-cli 来一键式自动化实现国际化的更多相关文章
- 【EasyCi】持续集成交付,一键式自动化部署系统,开箱即用
前言 本人是一家互联网公司的java开发,由于公司初期公司未招运维人员,恰好我对linux比较熟悉,便在公司服务器搭建了一套Jenkins.Gitlab.Maven私服.Docker私服.Sonarq ...
- azure 云上MySQL最新版本 MySQL5.7.11 批量自动化一键式安装 (转)
--背景云端 以前都喜欢了源码安装mysql,总觉得源码是高大上的事情,不过源码也需要时间,特别是make的时候,如果磁盘和cpu差的话,时间很长很长,在虚拟机上安装mysql尤其甚慢了. 现在业务发 ...
- MySQL最新版本 MySQL5.7.11 批量自动化一键式安装(转)
--背景云端 以前都喜欢了源码安装MySQL,总觉得源码是高大上的事情,不过源码也需要时间,特别是make的时候,如果磁盘和cpu差的话,时间很长很长,在虚拟机上安装mysql尤其甚慢了. 现在业务发 ...
- 自动化运维(2)之一键式单实例安装MySQL
ZMySQLAutoTools文档 目标:自动化构建部署MySQL数据库,一键式单实例mysql安装,备份,监控,主从集群部署等.以及jdk,tomcat,nginx等基础中间件的自动化部署安装及运维 ...
- 一键式Spring集成工具 Spring Boot
最近公司使用Spring boot进行开发,稍微了解一下,不过自我感觉把集中式配置applicate.properties搞明白,注解用过Spring MVC的boot绝对没问题的 比如拦截器:@As ...
- Azure Site Recovery 通过一键式流程将虚拟机故障转移至 Azure虚拟机
Ruturaj Dhekane 云 + Enterprise 项目经理 现在,AzureSite Recovery可以通过其"灾难恢复至 Azure"功能保护您的工作负荷,并 ...
- 如何:使用 Visual Studio 中的一键式发布来部署 Web 应用程序项目
原文: 如何:使用 Visual Studio 中的一键式发布来部署 Web 应用程序项目 本主题介绍如何在以下产品中使用 一键式发布 发布(部署)Web 应用程序项目: Visual Studio ...
- Visual Studio 2012 Web一键式发布
按照保哥的介绍,尝试“ Web一键式发布”,但总是出错,主要就是404错误,不知道是什么原因.默认的 Web一键式发布是在C:\inetpub\wwwroot目录下,难道是权限问题?折腾N久无果.好吧 ...
- 无线网破解软件|一键式破解无线网|BT17软件包下载[笔记本+软件就行]
从新版BT17发布到现在已经有一段时间,谢谢大家的一直来的关注.现在给大家讲解一下无线网破解问题,告诉 大家如何一键式破解WPA,WPA2,AES.Tkip等加密方式以及新版BT17软件包的下载地址. ...
- C# 制作Java +Mysql+Tomcat 环境安装程序,一键式安装
原文:C# 制作Java +Mysql+Tomcat 环境安装程序,一键式安装 要求: JDK.Mysql.Tomcat三者制作成一个安装包, 不能单独安装,安装过程不显示三者的界面, 安装完成要配置 ...
随机推荐
- 文心一言 VS 讯飞星火 VS chatgpt (99)-- 算法导论9.3 5题
五.用go语言,假设你已经有了一个最坏情况下是线性时间的用于求解中位数的"黑箱"子程序.设计一个能在线性时间内解决任意顺序统计量的选择问题算法. 文心一言: 为了在线性时间内解决任 ...
- 解决Dependency 'fastdfs-client-java’not found
如何能把 fastdfs的jar包安装到本地的仓库中(因为中央仓库没有FASTDFS的jar包地址) 1.首先去github上下载下来fastdfs的压缩包 下载链接 然后直接解压出来 2.使用cmd ...
- 【Python进阶-PyQt5】00搭建PyQt5环境
1.创建独立开发虚拟环境 1.1虚拟环境简介 我们编写的程序,有时用到的Python库是不一样的,比如说开发桌面应用程序我们主要用到PyQt5相关的Python库.开发Web应用程序我们主要用到Dja ...
- Rethinking Point Cloud Registration as Masking and Reconstruction论文阅读
Rethinking Point Cloud Registration as Masking and Reconstruction 2023 ICCV *Guangyan Chen, Meiling ...
- isHex
public class Test { public static boolean isHex(String str) { boolean isHexFlg = true; int i = 0; ch ...
- Jmeter将响应数据的结果保存到本地的一个文件(xls和csv)
打印excel和csv文件的区别?? 第一种:打印excel 第二种:打印csv文件 创建beanshell后置处理器 import org.json.*;import java.io.*; Str ...
- Tinyalsa PCM API 实现深度剖析
高级 Linux 音频架构 (ALSA) 用于为 Linux 操作系统提供音频和 MIDI 功能.它可以高效地支持所有类型的音频接口,从消费者声卡到专业的多通道音频接口.它支持全模块化的音频驱动.它是 ...
- Chromium Mojo通信
Mojo 是一个跨平台 IPC 框架,它诞生于 chromium ,用来实现 chromium 进程内/进程间的通信.目前,它也被用于 ChromeOS. 在我们代码应用中,如何使用Mojo来作进程间 ...
- Spring Cloud OpenFeign系列:简介和使用
目录 一.简介 二.使用 1.创建父工程 2.创建order-service模块 3.创建order-client模块 三.效果 四.配置说明 1.超时配置 全局超时配置 局部超时配置 2.Gzip压 ...
- 一篇了解springboot3请求参数种类及接口测试
SpringBoot3数据请求: 原始数据请求: //原始方式 @RequestMapping("/simpleParam") public String simpleParam( ...