1.1.1 • Published 6 months ago
jdy-intl-babel v1.1.1
babel 国际化插件
通用前端国际化转译工具,基于 babel v7
它能做什么?
它能识别泛 js 代码(js、ts、jsx、tsx、vue...)中的硬编码中文,参照一定的格式进行转换并保存识别到的中文。
- 可以指定路径范围,支持 glob 模式解析路径。
- 自定义国际化转译函数。
- 指定中文提取包保存路径。
- 性能优化,提供批量编译配置。
它没有做什么...
该库仅对 babel 插件做了两层封装,只做转换工作,对于其他工程化工具不做限制。在使用该工具的同时,项目中应该还要存在:
- 代码格式化工具(prettier 等):babel 在转换过程中并不会刻意保留源代码的格式,在转换后的代码格式会出现较大改动,请参考下面的示例对转换后的代码进行格式化后保存。
- (可选)提交流检查工具(lint-staged 等):该工具可集成到各种提交流工具中,方便提交时自动对修改处国际化。
安装
该插件目前位于公开npm源中,可直接执行任意安装命令
yarn add @jdy/intl-babel
随后安装依赖:(推荐 yarn or pnpm)
yarn install
使用
场景 1 - 项目整体国际化
// 这里以node的commonJs为例,使用esm请自行进行转换。
const fs = require("fs");
const path = require("path");
const prettier = require("prettier");
const { default: I18nPlugin, writeFileAsync } = require("@jdy/intl-babel");
// 常量
const projectPath = path.resolve(__dirname, "./"),
prettierConfPath = path.join(projectPath, ".prettierrc"),
saveJsonPath = path.join(projectPath, "lang", "zh_CN.json");
// prettier 配置项
const prettierConf = JSON.parse(
fs.readFileSync(prettierConfPath, { encoding: "utf-8" })
);
I18nPlugin({
//这里是解析路径的规则
pathOption: {
// 解析匹配到下面路径的文件
pattern: ["src/**/*.{js,ts,tsx,jsx}"],
// 传递给Glob的参数
options: {
// 忽略配置文件、文档文件、测试文件(默认不以./开头)
ignore: ["src/**/*.{config,stories,test}.{js,ts,jsx,tsx}"],
},
},
// 此次转换收集到的所有国际化字典的回调,需要自行执行保存
dictCallback:(Intldict)=>saveJson(IntlDict).then(....)
// 国际化转译相关配置
i18nOptions: {
// 插件配置
pluginOptions: {
/**
* 翻译函数的导入形式,请使用alias形式的导入。以下是判断注入方式:
* 1. 当前文件内不存在需要翻译的地方,不注入
* 2. 当前文件内存在需要翻译的地方
* 2.1 若已存在相同导入,则不再注入
* 2.2 未导入(该文件第一次被翻译)则在文件首行注入
*/
injectIntlImport: 'import {getLangMsg} from "@/utils/intl";',
/**
* 项目内中文的替换形式,请注意必须满足翻译函数(hash).默认值(code)的形式;
* 其中hash是中文的md5值,code则是被替换的中文;
* t是翻译库的翻译函数,d则是当t函数传参hash得到空字符串时的默认值(也用于帮助开发了解该文本内容)
*/
getIntlFunc: (hash, sourceMsg) => `getLangMsg(${hash}).d(${sourceMsg})`,
},
// 默认为["jsx", "typescript"],当项目中用到dva等高阶HOC时使用下面的配置
parserPlugins: ["jsx", "typescript", "decorators-legacy"],
},
callback: (dict) => {
// 插件不会默认替换掉目标文件
const { code, filePath } = dict;
// 这里假设你使用prettier v3进行格式化代码
return prettier
.format(code, { ...prettierConf, filepath: filePath })
.then((formatCode) => writeFileAsync(filePath, formatCode));
},
}).then((dict) => {
/** 在这里dict表示此次转换拿到的对应映射
* dict 是一个对象,里面类似:
* {
* "02ddbbf19885bc7": "已还款",
* "02ec202bc579881": "请输入",
* "041b02ca833756e": "事由",
* "049ca0b50228c4c": "预计还款日期:{arg0}",
* }
* 这里需要对一些情况进行说明:
* 1. 当一份文件第一次被翻译时,可以拿到该文件内所有被翻译的映射
* 2. 当该文件没有有关中文的改动再次被翻译时,返回的是空的对象
* 3. 当该文件内有部分中文被改动,执行翻译后,拿到的是改动过后的映射对象。
*/
});
场景 2 - 接入提交流
首先新建一个文件 intl-plugin.js
// scripts/intl-plugin.js
const path = require("path");
const fs = require("fs");
const prettier = require("prettier");
const { default: I18nPlugin } = require("@jdy/intl-babel");
// 常量
const projectPath = path.resolve(__dirname, "../"),
prettierConfPath = path.join(projectPath, ".prettierrc"),
srcPath = path.join(projectPath, "src"),
saveJsonPath = path.join(projectPath, "lang", "zh_CN.json");
// prettier 配置项
const prettierConf = JSON.parse(
fs.readFileSync(prettierConfPath, { encoding: "utf-8" })
);
//获取命令行传递过来的参数
const args = process.argv.slice(2);
const fileList = args
.map((filePath) => path.resolve(filePath))
// 确保是在src内的文件,甚至可以更上一步,确保是tsx等类型文件
.filter((filePath) => filePath.startsWith(srcPath));
I18nPlugin({
pathOption: {
pattern: fileList,
options: {
// 忽略配置文件、文档文件、测试文件
ignore: ["src/**/*.{config,stories,test}.{js,ts,jsx,tsx}"],
},
},
i18nOptions: {
pluginOptions: {
injectIntlImport: 'import {t} from "@/utils/intl"',
getIntlFunc: (hash, code) => `t(${hash}).d(${code})`,
},
},
callback: (dict) => {
const { code, filePath } = dict;
// 这里prettier可格式化可不格式化,如果你配置了提交自动格式化的话
return prettier
.format(code, { ...prettierConf, filepath: filePath })
.then((formatCode) =>
fs.writeFileSync(filePath, formatCode, { encoding: "utf-8" })
);
},
}).then(() => {
// 这里格式化一下json文件
const jsonData = fs.readFileSync(saveJsonPath, { encoding: "utf-8" });
prettier
.format(jsonData, { ...prettierConf, filepath: saveJsonPath })
.then((formatCode) =>
fs.writeFileSync(saveJsonPath, formatCode, { encoding: "utf-8" })
);
});
// 这里假设你使用lint-staged作为提交流检查工具
//lintstagedrc.js
const handler18n = (filenames) =>
`node ./scripts/intl-plugin.js ${filenames.join(" ")}`;
module.exports = {
// 这里会按照顺序执行
"*.{js,ts,tsx,md,json}": [
handler18n,
"git add ./lang/zh_CN.json",
"prettier --write",
],
};
提交流注意事项
- 提交流不会删除 json 内不再使用的字段,只能新增字段。
- 插件本身会对 json 内字段进行排序(需确保在 es3 以上),尽量避免合并冲突。
- 若需要发布时对 json 进行 shaking,可参考以下方案:
- 以 zh_CN 为准,删除 en_US 内多余的字段(en_US 只在 release 时更新)。
- 以 en_US 为准,找出 zh_CN 内多余的字段,与上面提取的多余字段做差集。
编译提速
插件内实现了线程池,由于线程之间数据交换需要序列化和解析,请酌情使用。
I18nPlugin({
threadOptions: {
// 必须显示开启
enable: true,
// 请务必提供一个大于0的数
num: 6,
},
//...其他配置项
});
或者可以使用 bun 替代 node,具体示例如下:
# 之前是这样执行的
node your-script.js
# 改成这样即可
bun your-script.js
参考案例1:销售助手(全面扫描 800+文件,提取 3700+词条)
配置 m1 pro 16gb
编译模式 | 编译速度 |
---|---|
普通编译 (node 16.20.0) | 13~15s |
普通编译(node 20.11.0) | 10~11s |
thread-6 (node 16.20.0) | 9.247s |
thread-6(node 20.11.0) | 8.5s |
bun 1.0.29 | 10.9s |
thread-6 & bun | 7.3s |
参考案例2:pos-h5(全面扫描1000+文件,提取词条6600+词条)
配置 m1 pro 16gb
编译模式 | 编译速度 |
---|---|
普通编译 (node 16.20.0) | 21~25s |
普通编译(node 20.11.0) | 19~21s |
thread-6 (node 16.20.0) | 16~18.5s |
thread-6(node 20.11.0) | 15~16s |
bun 1.1.30 | 18~21s |
thread-6 & bun | 14~15s |
调试
I18nPlugin({
_debugger: {
// 测速
timer: true,
// json相关操作结果打印
console: true,
},
//...其他配置项
});
接入其他框架
- 目前已支持vue2