使用 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三者制作成一个安装包, 不能单独安装,安装过程不显示三者的界面, 安装完成要配置 ...
随机推荐
- Redis系列24:Redis使用规范
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...
- c语言代码练习2(2)
//利用for循环,输出1-10阶乘的和#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main( ) { int i = ...
- 关于tiptop gp5.2采购模块,价格变更的随笔
采购价格变更要看具体环节,你可以把他当作是三张表,采购价格表.收货价格表.入库价格表,这些还好处理,如果已抛砖到财务端生成账款再要求改价格就更复杂,会产生更多张表了,改起来也就更复杂. 用apmt91 ...
- 基于 Angular和Material autocomplete组件再封装的可双向绑定key-value的可输入下拉框
GitHub: https://github.com/Xinzheng-Li/AngularCustomerComponent 效果图:为了方便使用,把许多比如ADD的功能去了,可以在使用后自行实现. ...
- ApiPost发送请求报错UT000036: Connection terminated parsing multipart data
发送请求报错Caused by: java.io.IOException: UT000036: Connection terminated parsing multipart data 这个报错是因为 ...
- 使用 GitHub Action 自动更新 Sealos 集群的应用镜像
在 IT 领域,自动化无疑已成为提高工作效率和减少人为错误的关键.Sealos 作为一个强大的云操作系统,已经为许多企业和开发者提供了稳定可靠的服务.与此同时,随着技术不断发展,集成更多的功能和服务变 ...
- HarmonyOS原生分析能力,即开即用助力精细化运营
数据分析产品对开发者的价值呈现在两个层面,第一个是产品的层面,可以通过数据去洞察用户的行为,从而找到产品的优化点.另外一个就是运营层面,可以基于数据去驱动,来实现私域和公域的精细化运营. 在鸿蒙生态上 ...
- 在Finalshell中一直弹出弹窗要求输入密码的解决方案
在finalshell中即使输入正确了密码,仍然要求输入(密码错误), 对CentOS的防火墙也已经关闭,但是仍然提示 这个原因是因为自创的用户名在finalshell中不被认可,需要将用户名更改为r ...
- yum仓库及NFS共享
yum仓库及NFS共享 1.yum简介 yum是一个基于RPM包(是Red-Hat Package Manager红帽软件包管理器的缩写)构建的软件更新机制,能够自动解决软件包之间的依赖关系.解决了日 ...
- [C++]P5024 树形DP 保卫王国
树形DP 保卫王国P5024 前置知识 1.邻接表 + Dfs(深度优先搜索) 2.基础DP(如 01背包 ) 3.最小公共祖先(LCA) LCA我有写过Blog 首先解读一下题意 城市即为节点 每个 ...