做国际化一个很头疼的坑就是,你不知道项目里到底还有哪些中文词条没有国际化处理

纯靠人工去检查不现实,也不靠谱,而且浪费资源

所以还是得通过脚本工具来检查,思路是:

  1. 先保存好本地代码变更,准备好一个无文件变更的本地环境
  2. 再通过脚本把代码里的非展示性中文移除掉
    • 注释里的中文、console 里的中文,已经国际化处理过的中文
  3. 再用中文正则在 vscode 的全局搜索里匹配,捞出来的就是未国际化处理的中文词条
  4. 最后需要回退本地的更改,毕竟脚本是直接改动本地文件

脚本仅仅是检查用,用完记得回退代码

匹配中文词条的正则

  • 单个中文:

    • [\u4E00-\u9FFF]
  • 连续中文:
    • [\u4E00-\u9FFF]+
  • 掺杂了各种符号、字母的中文句子:
    • [a-zA-Z0-9、:]*[\u4E00-\u9FFF]+[\u4E00-\u9FFF\.\-\*。,,a-zA-Z0-9/()()::”“!?、%_【】《》>~~ ]*
    • (这里不建议把 : : - ' " 这几个特殊符号也列到正则里,因为这些符号比较特殊,有的语法层面也支持,列进来反而会引出新问题,所以宁愿这种场景的句子被截成多断)
  • 最好再加上文件的排除:
    • *.css,*.scss,*.less,*.json,*.bat,privacyProtocal.html,userProtocal.html,*.md,webpack**.js,*.txt,*.svg,*.properties,*.npmrc,vve-i18n-cli.config.js,baas,config,*.art,demo_index.html,*.sh,*.xml,*.java

脚本

移除非展示性中文的脚本

// index.js

#!/usr/bin/env node

/**
* 用来移除掉指定项目里的以下几类场景的中文:
* - 注释里的中文
* - 被国际化全局函数包裹的中文 $t
*
* 这样子方便借助 vs code 的全局正则搜索中文功能,来快速 review 未国际化的中文
* 正则: [\u4E00-\u9FA5]+
*/ "use strict";
const program = require("commander");
const { loadConfig } = require("../configuration");
const core = require("./core");
const vfs = require("vinyl-fs");
const map = require("map-stream");
const path = require("path");
const fs = require("fs"); function commaSeparatedList(value, split = ",") {
return value.split(split).filter((item) => item);
} program
.version(require("../../package.json").version)
.option("--cwd <path>", "工作目录")
.option("--root-dir <path>", "国际文本所在的根目录")
.option(
"--config <path>",
"配置文件的路径,没有配置,默认路径是在${cwd}/vve-i18n-cli.config.js"
)
.option("--no-config", "是否取配置文件")
.option(
"--i18n-file-rules <items>",
"匹配含有国际化文本的文件规则",
commaSeparatedList
)
.option(
"--ignore-i18n-file-rules <items>",
"不匹配含有国际化文本的文件规则",
commaSeparatedList
)
.parse(process.argv); const config = {
// 工作目录
cwd: ".",
// 根目录,国际文本所在的根目录
rootDir: "src",
// 配置文件的路径,没有配置,默认路径是在${cwd}/vve-i18n-cli.config.js
config: undefined,
// 是否取配置文件
noConfig: false,
// 匹配含有国际化文本的文件规则
i18nFileRules: ["**/*.+(vue|js|html|htm)"],
// 不匹配含有国际化文本的文件规则
ignoreI18nFileRules: ["**/node_modules/**"],
}; Object.assign(config, program); const CONFIG_JS_FILENAME = "vve-i18n-cli.config.js"; let absoluteCwd = path.resolve(config.cwd); // 优先判断是否需要读取文件
if (!config.noConfig) {
let configFilePath = path.join(absoluteCwd, CONFIG_JS_FILENAME);
if (config.config) {
configFilePath = path.resolve(config.config);
}
if (fs.existsSync(configFilePath)) {
const conf = loadConfig(configFilePath);
if (conf && conf.options && conf.options.zhCheck) {
Object.assign(config, conf.options.zhCheck, program);
}
}
} // 制定配置文件后,cwd在配置文件中定义,则cwd就需要重新获取
if (!program.cwd) {
absoluteCwd = path.resolve(config.cwd);
} const absoluteRootDir = path.resolve(absoluteCwd, config.rootDir); function run() {
console.log("================================>start");
vfs
.src(
config.i18nFileRules.map((item) => path.resolve(absoluteRootDir, item)),
{
ignore: config.ignoreI18nFileRules.map((item) =>
path.resolve(absoluteRootDir, item)
),
dot: false,
}
)
.pipe(
map((file, cb) => {
console.log("开始解析 =========================>", file.path);
const extname = path.extname(file.path);
let fileContent = file.contents.toString();
let newFileContent = fileContent;
if (extname.toLowerCase() === ".vue") {
newFileContent = core.removeUnusedZhInVue(fileContent);
} else if (extname.toLowerCase() === ".js") {
newFileContent = core.removeUnusedZhInJs(fileContent);
} else if ([".html", ".htm"].includes(extname.toLowerCase())) {
newFileContent = core.removeUnusedZhInHtml(fileContent);
}
if (newFileContent !== fileContent) {
console.log("发现无用的中文,正在移除中...");
fs.writeFileSync(file.path, newFileContent);
}
console.log("解析结束 =========================>", file.path);
cb();
})
)
.on("end", () => {
console.log("================================>end");
});
} run();
// core.js

// 包含中文
const zhReg = new RegExp("[\\u4E00-\\u9FFF]+", ""); // 处理 vue 文件
function removeUnusedZhInVue(fileContent) {
return removeUnusedZh(fileContent);
}
exports.removeUnusedZhInVue = removeUnusedZhInVue; // 处理 js 文件
function removeUnusedZhInJs(fileContent) {
return removeUnusedZh(fileContent);
}
exports.removeUnusedZhInJs = removeUnusedZhInJs; // 处理 html 文件
// 处理 js 文件
function removeUnusedZhInHtml(fileContent) {
return removeUnusedZh(fileContent);
}
exports.removeUnusedZhInHtml = removeUnusedZhInHtml; function removeUnusedZh(fileContent) {
const hasAnnotation = {
"/*": false,
"<!--": false,
}; // 逐行处理
fileContent = fileContent
.split("\n")
.map((line) => {
// 移除无用中文
if (line.match(zhReg)) {
const regs = [
new RegExp("//(.*[\\u4E00-\\u9FFF]+)", ""), // 移除 // xx
new RegExp("console.log\\(['\"](.*[\\u4E00-\\u9FFF]+)", ""), // 移除 console.log(xxx)
new RegExp("console.info\\(['\"](.*[\\u4E00-\\u9FFF]+)", ""), // 移除 console.info(xxx)
new RegExp(
"\\$t\\([ ]*['\"`](.*?[\\u4E00-\\u9FFF]+.*?)['\"`]\\)",
""
), // 移除 $t("xxx")
];
regs.forEach((reg) => {
let match = line.match(reg);
while (match && match[1]) {
line = line.replace(match[1], "");
match = line.match(reg);
}
});
}
if (!hasAnnotation["/*"] && line.indexOf("/*") > -1) {
hasAnnotation["/*"] = true;
}
if (!hasAnnotation["<!--"] && line.indexOf("<!--") > -1) {
hasAnnotation["<!--"] = true;
}
return line;
})
.join("\n"); if (hasAnnotation["/*"]) {
// 移除 /* xxx */
const reg = new RegExp("/\\*([\\s\\S]*?)\\*/", "g");
fileContent = fileContent.replace(reg, function (match, key, index) {
// console.log("[/**/] ==1 >", { match, key, index });
let newKey = key;
while (newKey.match(zhReg)) {
newKey = newKey.replace(zhReg, "");
}
return match.replace(key, newKey);
});
}
// 移除 <!-- xxx -->
if (hasAnnotation["<!--"]) {
const reg = new RegExp("<!--([\\s\\S]*?)-->", "g");
fileContent = fileContent.replace(reg, function (match, key, index) {
let newKey = key;
while (newKey.match(zhReg)) {
newKey = newKey.replace(zhReg, "");
}
return match.replace(key, newKey);
});
}
return fileContent;
}
// configuration.js
const buildDebug = require("debug");
const path = require("path"); const debug = buildDebug("files:configuration"); function loadConfig(filepath) {
try {
const conf = readConfig(filepath);
return conf;
} catch (e) {
debug("error", e);
return null;
}
} function readConfig(filepath) {
let options;
try {
const configModule = require(filepath);
options =
configModule && configModule.__esModule
? configModule.default || undefined
: configModule;
} catch (err) {
throw err;
} finally {
}
return {
filepath,
dirname: path.dirname(filepath),
options,
};
} module.exports = {
loadConfig,
readConfig,
};
{
"dependencies": {
"commander": "^3.0.2",
"debug": "^4.1.1",
"jsonfile": "^5.0.0",
"lodash.uniq": "^4.5.0",
"map-stream": "0.0.7",
"pinyin-pro": "^3.11.0",
"translation.js": "^0.7.9",
"vinyl-fs": "^3.0.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.2.1",
"nyc": "^14.1.1",
"shelljs": "^0.8.3",
"standard-version": "^7.0.0"
},
"version": "3.2.3"
}
// vve-i18n-cli.config.js
module.exports = {
// 工作目录
cwd: ".",
// 根目录,国际文本所在的根目录
rootDir: "demo",
// 默认所有模块,如果有传module参数,就只处理某个模块
// '**/module-**/**/index.js'
moduleIndexRules: ["*/pro.properties"],
// 匹配含有国际化文本的文件规则
i18nFileRules: ["**/*.+(vue|js)"],
// 国际化文本的正则表达式,正则中第一个捕获对象当做国际化文本
i18nTextRules: [/(?:[\$.])t\(['"](.+?)['"]/g],
// 模块的国际化的json文件需要被保留下的key,即使这些组件在项目中没有被引用
// key可以是一个字符串,正则,或者是函数
keepKeyRules: [
/^G\/+/, // G/开头的会被保留
],
ignoreKeyRules: [/^el/],
// 生成的国际化资源包的输出目录
outDir: "i18n",
// 生成的国际化的语言
i18nLanguages: [
"zh", // 中文
"en", // 英文
],
// 是否翻译
translate: false,
// 翻译的基础语言,默认是用中文翻译
translateFromLang: "zh",
// 是否强制翻译,即已翻译修改的内容,也重新用翻译生成
forceTranslate: false,
// 翻译的语言
translateLanguage: ["zh", "en"],
// 模块下${outDir}/index.js文件不存在才拷贝index.js
copyIndex: true,
// 是否强制拷贝最新index.js
forceCopyIndex: false,
// 国际化文本包裹相关
zhWrap: {
cwd: ".",
// 根目录,国际文本所在的根目录
rootDir: ".",
i18nFileRules: [
"!(node_modules|config)/**/*.+(vue)",
// "base/components/login.vue",
"base/common/js/httpHandle.js",
],
ignorePreReg: [
/t\s*\(\s*$/,
/tl\s*\(\s*$/,
/console\.(?:log|error|warn|info|debug)\s*\(\s*$/,
new RegExp("//.+"),
],
// js相关文件需要引入的国际化文件
i18nImportForJs: "import i18n from '@inap_base/i18n/core'",
// js相关文件需要使用国际化方法
jsI18nFuncName: "i18n.t",
// vue相关文件需要使用的国际化方法
vueI18nFuncName: "$t",
},
};

硬替换脚本

具体查看 zh-i18n.zip

如何用 vscode 捞出还未国际化的中文词条的更多相关文章

  1. 如何用css画出三角形

    看到有面试题里会有问到如何用css画出三角形 众所周知好多图形都可以拆分成三角形,所以说会了画三角形就可以画出很多有意思的形状 画出三角形的原理是调整border(边框)的四个方向的宽度,线条样式以及 ...

  2. jquery ajax 总是还未等到success回调就刷掉了,就进入了onError函数的错误案例分析

    jquery ajax 总是还未等到success回调就刷掉了,就进入了onError函数的错误案例分析: 同样的请求同时请求了2次,然后第二次的请求把第一次的给刷掉了! (比如:<div on ...

  3. 如何用VSCode手动编译Ace Editor

    对于习惯微软VS的用户,可能很不习惯开源社区兴起的前端开发流程.随着NodeJs的兴起,JavaScript已经成为Github上开源项目最多的语言.使用微软提供的VSCode可以很好地利用这些开源项 ...

  4. 缓存服务,还未创建完缓存时, 需要更改图层名称、服务名称、数据源位置、mxd名称等

    缓存服务,还未创建完缓存时, 需要更改图层名称.服务名称.数据源位置.mxd名称等.已经创建好的缓存还可以再用吗? 测试后可以, 注意:新服务相对旧服务,符号样式没有改变,切片方案没有变化. 测试步骤 ...

  5. 如何用MathType编辑出积分符号

    MathType由于能够编辑出众多的数学符号而备受理工科学生与老师的喜爱.利用它,你可以在文档中随意编写出你想要的公式.对于从来没有用过公式编辑器的人来说,在文档中看到那些复杂的数学公式时总是会为之惊 ...

  6. [错误记录_C] 还未给指针变量正确赋值的情况下,就使用它的值

    错误的代码: 错误的结果:  错误原因分析: 在使用(1) 将pB,pC的值赋给pA的lchild和rchild时: 还未给指针变量pB和pC赋值,现在pB和pC中存的是个垃圾值 Note: (2)- ...

  7. idea 高效找出全部未被使用的代码

    不得不说 idea 真的很强大,认真花一些时间,好好研究研究 idea 可以让你编写代码更加的高效,并且 idea 时不时会给你一些惊喜的,比如今天要分享的这个,就非常的惊喜: 背景 前几天,忽然又一 ...

  8. Java中过滤出字母、数字和中文的正则表达式

    1.Java中过滤出字母.数字和中文的正则表达式 (1)过滤出字母的正则表达式 [^(A-Za-z)] (2)过滤出数字的正则表达式 [^(0-9)] (3)过滤出中文的正则表达式 [^(\\u4e0 ...

  9. AJPFX总结关于Java中过滤出字母、数字和中文的正则表达式

    1.Java中过滤出字母.数字和中文的正则表达式 (1)过滤出字母的正则表达式      [^(A-Za-z)] (2) 过滤出 数字 的正则表达式   [^(0-9)] (3) 过滤出 中文 的正则 ...

  10. Java正则表达式过滤出字母、数字和中文

    原文:http://blog.csdn.net/k21325/article/details/54090066 1.Java中过滤出字母.数字和中文的正则表达式 (1)过滤出字母的正则表达式 [^(A ...

随机推荐

  1. C#开源且免费的Windows桌面快速预览神器 - QuickLook

    前言 今天给大家推荐一款由C#开源且免费的Windows桌面快速预览神器:QuickLook. 工具介绍 QuickLook是一款在Windows操作系统上的实用工具,它提供了一种快速预览文件内容的方 ...

  2. Jmeter连接数据库sql语句操作,查询后取值做变量

    第一步 :导入jar包 第二步 :创建JDBC Reques 第三步 :创建JDBC Connection Configuration  第四步:在request中输入数据进行操作 Query Typ ...

  3. 业务出海、高效传输、动态加速,尽在云栖大会「CDN与边缘计算」专场

    2023杭州·云栖大会,即将热力来袭. 一场云计算盛会,500+前沿话题,3000+科技展品,与阿里云一起,共赴72小时的Tech沉浸之旅. 今日,「CDN与边缘计算」Tech专场,重磅议题抢先知晓! ...

  4. 9.11 多校联测 Day1 总结

    我的想法是,你如果没读过题,看了这篇总结完全不能知道题面是啥,不存在泄题风险.所以不设密码力() 状态极差. 昨晚忘记定闹钟,好在早上突然想起今天考试惊醒了,rp--. 不过没有提前一个小时起来导致考 ...

  5. codeforces div1A

    A. Circular Local MiniMax 题目翻译:给我们一个数组(循环的也就是1和n是相邻的),我们可以对数组进行任意调序,对于每个数b[i]要求满足b[i] < b[i - 1] ...

  6. Python:基础&爬虫

    Python:基础&爬虫 Python爬虫学习(网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另 ...

  7. 工控机中部署Ubuntu 22.04 系统

    1.下载Ubuntu系统服务器版本 获取Ubuntu服务器版 | Ubuntu 2.下载启动盘制作工具 UltralSO(试用就可以) 文件 > 打开(Ubuntu.ISO) > 启动 & ...

  8. QGradient渐变填充

    QGradient渐变填充 QGradient (一)简介 (二)枚举类型 1.spread 2.CoordinateMode 3.type (三)常用函数 1.coordinateMode() 2. ...

  9. 用ps命令查看进程的内存

    http://blog.csdn.net/tigerscorpio/article/details/5960705 http://blog.csdn.net/licanhua/article/deta ...

  10. Windows下SVN服务端(Subversion)及客户端(TortoiseSVN)详细安装教程

    1. Subversion和TortoiseSVN 是什么? http://www.uml.org.cn/pzgl/200811146.asp Subversion 是什么,我想如果不知道的话就没有必 ...