node - 实现多语言脚本快速生成
前言
多个项目做了多语言,需要多个多语言表(也就是.xlsx文件)来维护。前端需要从一个文件中根据key 来 读取值(值可能对应中/英两种情况),怎么将多个.xlsx 文件,合并成一个前端可以根据key 来读取的文件,这就是今天我要写的内容
配置文件
做一个 lang-config.json ,里面做input / outout 的配置文件
- input 配置表名/工作博
- output 配置 中/英 的key / value
egg:
{
"input": [
{
"fileName": "project1.xlsx",
"sheetNames": ["Sheet1"]
},
{
"fileName": "project2.xlsx",
"sheetNames": ["Sheet1"]
},
{
"fileName": "error-code.xlsx",
"sheetNames": ["Sheet1"]
}
],
"output": [
{ "key": "zh-CN", "value": "中文" },
{ "key": "en-US", "value": "英文" }
]
}
实现流程
- 读取配置文件,根据配置文件中的input,获取配置文件所有的fileName
[ 'project1.xlsx', 'project2.xlsx', 'error-code.xlsx']
- 循环fileName,为之拼接相对应的文件路径,使用xlsx.utils.sheet_to_json,根据工作簿,做文件内容读取
- 文件路径
/Users/Desktop/vue-project/tools/lang/project1.xlsx
/Users/Desktop/vue-project/tools/lang/project2.xlsx
/Users/Desktop/vue-project/tools/lang/error-code.xlsx
- 文件内容读取
主要代码
xlsx.utils.sheet_to_json(workbook.Sheets[sheetName])
输入产物
: sheet_to_json的产物
[
{
key: 'error_code_502',
'含义': '服务器无响应,请稍后重试',
'中文': '服务器无响应,请稍后重试',
'英文': 'The server has no response, please try it later'
},
{
key: 'error_code_503',
'含义': '服务器未就绪,请稍后重试',
'中文': '服务器未就绪,请稍后重试',
'英文': 'The server is not ready, please try again later'
}
]
现在已经根据配置文件(input配置),将所有的xlsx 读取完毕
。接下来就是怎样将这些数据,输入到en-us.ts/zh-us.ts 文件中
,那自然是根据output配置了
- 输出文件数据配置,需要哪些字段 ?
key
(en-us/zh-us): (因为要用key,做输出文件路径的后缀
)value
(中文/英文): 来和sheet_to_json的产物 做匹配
。如果是中文文件,匹配sheet_to_json里的中文,英文文件,匹配sheet_to_json里的英文文,- path:
输出的文件,要存放的位置
- object:
输出文件的内容
输出产物
: 根据output 文件输出的产物
[
{
key: 'zh-CN',
value: '中文',
path: '/Users/Desktop/vue-project/src/generated/lang/zh-CN.ts',
object: {}
},
{
key: 'en-US',
value: '英文',
path: '/Users/Desktop/vue-project/src/generated/lang/en-US.ts',
object: {}
}
]
- 如何将输入产物,放到指定的输出产物里呢?
- 输入产物和输出产物,都是两个数组对象,最粗暴的方式,就是
对二者进行双重for 循环
。 将输入产物的key 作为输出产物的key
,根据输出产物的value 去和输入产物的中文/英文做匹配
主要代码
jsonData.forEach((json)=>{
result.forEach((item)=>{
item.object[json['key']] = json[item.value] // ⚠️⚠️⚠️⚠️
})
})
产物
[
{
key: 'zh-CN',
value: '中文',
path: '/Users/Desktop/vue-project/src/generated/lang/zh-CN.ts',
object: {
error_code_502: '服务器无响应,请稍后重试',
error_code_502: '服务器未就绪,请稍后重试',
},
{
key: 'en-US',
value: '英文',
path: '/Users/Desktop/vue-project/src/generated/lang/zh-CN.ts',
object: {
error_code_502: 'The server has no response, please try it later',
error_code_503: 'The server is not ready, please try again later',
},
]
- 如何将这个输出产物,
导出为 en-US.ts / zh-US.ts 文件
呢?
主要代码
result.forEach((config) => {
const { key, value, path, object } = config;
//const content = `export default\n{ \n${toSingleQuotedValues(object)}};`;
const content = `export default ${JSON.stringify(object, null, 2)};`;
fs.writeFileSync(path, content);
});
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
边界/细节处理
如何导出en-US.ts/zh-US.ts 文件格式怎么处理
我们要读取en-US.ts/zh-US.ts ,里面需要导出一个对象:export default {},对象里面是key:value
。
说到底,.ts 也是一个文件
,里面的文件内容,自己做拼接就好
,
可以使用 JSON.stringify
export default ${JSON.stringify(object, null, 2)};
也可以自己做一层数据格式转换
`export default\n{ \n${toSingleQuotedValues(object)}};`
const entries = Object.entries(obj).map(([key, value]) => {
const quotedValue = `'${value}'`;
return ` ${key}: ${quotedValue}`;
});
return entries.join(',\n');
}
多语言如何传参数
动态传参,在多语言中是必不可少的。比如还有xx天过期
。没必要还有
天过期
分开做多语言,直接使用 还有${params}天过期
,参数按照项目定义即可,比如我们用的是 %1$@
代表参数
在赋值的时候,只需要将item.value 根据正则匹配,做一个替换即可
jsonData.forEach((json)=>{
let newValue = json[item.value] // +++
newValue = newValue.replace( // +++
/%(\d+)\$@/g,
(match, index) => `{param${index}}`
);
result.forEach((item)=>{
item.object[json['key']] = json[newValue] // +++
})
})
vue-i18n中 动态传值,采用{}
即可
egg:
{ "welcome": "Welcome, {username}!" }
如果是多个
变量呢,
{ "welcome": "Welcome, {username1} {username1} {username3}!" }
我们可以利用 replace
的第二
个参数,对每一个特殊参数进行匹配替换,为保证参数唯一性,可以拼接 index
重复key 问题
在excel 文件中,由于多人协作,可能会出现重复key的情况
,那就会出现覆盖,导致key 设置的value 不生效
,我们在脚本中对这种情况也要做处理
// 存储已处理的 key
const processedKeys = new Set();
// 在循环json 数据的时候,要一个判断,如果已经存在了key,则chalk抛异常
if (processedKeys.has(json['key'])) {
handleError(
chalk.red.bold(
`文件 ${fileName} 的工作表 ${sheetName} 存在重复的 key: ${json['key']}`
)
);
}
// 否则就将key 添加到map 结构中
processedKeys.add(json['key']);
异常终中断
出现异常,终断process.exit(1);
当前进程 退出
/**
* 异常处理
* @param errorMessage
*/
function handleError(errorMessage) {
spinner.fail(chalk.red.bold(errorMessage));
process.exit(1);
}
文件读取异常处理
let directoryPath = path.dirname(outputPath)
/**
* 辅助函数,确保输出目录存在,不存在则创建
* @param directoryPath 路径
*/
function ensureDirectoryExists(directoryPath) {
try {
fs.mkdirSync(directoryPath, { recursive: true });
} catch (error) {
handleError(chalk.red.bold(`创建目录失败: ${directoryPath}: ${error}`));
}
}
显示脚本进度条和任务状态
import ora from 'ora';
const spinner = ora()
spinner.start();
spinner.text = 'language脚本生成中...';
// ora('language脚本生成中...').start()
给脚本增加彩色样式(成功/ 失败 )特殊提示
import chalk from 'chalk';
import ora from 'ora';
const spinner = ora()
spinner.succeed(chalk.green.bold(`文件已保存:${path}`))
spinner.succeed(chalk.red(`文件已保存:${path}`))
相关三方插件
- 操作表格 xlsx: https://www.npmjs.com/package/xlsx
- 脚本样式 chalk: https://www.npmjs.com/package/chalk
- 脚本执行进度/sucess/error ora https://www.npmjs.com/package/ora
更多详细内容,可根据插件详细配置
项目地址
欢迎 Star ⭐️⭐️⭐️⭐️