前情:由于我们公司是做海外市场的,所以避免不了多语言,之前一直都是拿到运营的整理好的多语言,然后复制到对应的语言文件,但是想了想,不能做个粘贴复制的废物,于是写下了这个脚本工具(解释在代码里面),由浅入深, 但是这个功能针对的格式有限
基于Vue3+TS(不过框架语言影响不大哈,其他也都可以用)+Vue-i18n
1. Excel转json (无脑简单版)
我们要做的事,就是把excel里面的数据读取出来,变成一个对象
假设sample.xlsx的数据是这样:
在使用vue-i18n我们要得到的应该是,生成对应的语言文件,然后显示对应的语言
先上代码: package.json
"scripts": {
"jsonToExcel": "node src/i18n/jsonToExcel.js --bp",
"testToExcel": "node src/i18n/testToExcel.js --bp"
},
npm run xxx 就行啦~
const xlsx = require('node-xlsx') // 读取excel
const fs = require('fs')
const prettier = require('prettier') // json格式化
const colors = require('colors') // 颜色
// 定义最多所存在得语言
const allLanguages = ['zh-Hans', 'zh-Hant', 'en', 'ms'] // 假设最多有4种,简中/繁中/英文/马来
// 1.拿到excel数据
const workSheetsFromBuffer = xlsx.parse(fs.readFileSync(`${__dirname}/../language/sample.xlsx`));
console.log(colors.yellow(`mention1:由sample.xlsx生成多语言ts文件`))
workSheetsFromBuffer.forEach(item => {
// 2.可能有多个sheet表,选择有数据的那个
if(item.data.length) {
// 3. 获取excel中得语言
const currentLanguage = []
item.data[0].forEach(item => {
if (allLanguages.includes(item)) {
currentLanguage.push(item)
}
})
// 4. 获取各种语言所有得key
let eachLangIndex = 0
// 5. 去掉第一项(这里比较死, 必须按照key -> 语言得顺序)
const restData = item.data.slice(1)
currentLanguage.forEach(lang => {
eachLangIndex = item.data[0].indexOf(lang)
// 6. 获取到当前得语言,当前的index值, 以便知道获取那一栏的数据
createTs(lang, eachLangIndex, restData)
})
}
})
// 7.根据key创建出对应得ts文件
function createTs(tsFileName, eachLangIndex, data) {
const obj = {}
data.forEach(item => {
obj[item[0]] = item[eachLangIndex]
})
const filePath = `${__dirname}/../language/${tsFileName}.ts`
// 8.插入数据到文件里面
const content = `
const message =
${JSON.stringify(obj,null,"\t")}
export default message;
`
fs.exists(filePath, (exists) => {
if (!exists) {
fs.writeFile(filePath, beautifyJs(content), { 'flag': 'a' }, function(err) {
if (err) {
throw err;
}
// 写入成功后读取测试
fs.readFile(filePath, 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(colors.yellow(`mention3:${filePath}文件生成啦`))
});
});
} else {
console.log(colors.yellow(`mention2:${filePath}已经有相同的文件拉`))
}
})
}
// 格式化代码
function beautifyJs(txt) {
return prettier.format(txt, {
parser: "babel-ts",
printWidth: 80,
semi: true,
tabWidth: 2, // 缩进字节数
useTabs: false, // 缩进不使用tab,使用空格
singleQuote: false,
bracketSpacing: true,
trailingComma: "es5",
});
}
1.1 首先安装一些包
npm i node-xlsx prettier colors --save
node-xlsx 可以读取xlsx里面的内容,prettier用于将xlsx里面数据插入到ts中,colors在终端可以打印出一些提示等
1.2 读取excel中的数据
参考:node-xlsx, 文档里面都有
我们需要读取的就是sample.xlsx这个文件里面的数据,可以用
const workSheetsFromBuffer = xlsx.parse(fs.readFileSync(`${__dirname}/../language/sample.xlsx`))
进行读取,workSheetsFromBuffer读取出来的结果是什么呢?
也就是说拿到的是一个excel中所有的sheet里面的数据,一般来说我们也只有一个sheet里面有数据(这里比较死哈),所以我做了处理拿到有数据的那个表里面的data数据
1.3 处理成json数据
也许项目中有6种语言,但是针对的不同的活动,可能有的活动只需要做4种语言,因为活动也是针对国家来的,所以要遍历出目前excel中有多少中语言,allLanguages一定是包含了currentLanguage 上面的打印workSheetsFromBuffer可以看到,如果要拿到对应的语言,你得找到语言所在的index的值, ,通过
eachLangIndex = item.data[0].indexOf(lang)
能够拿到每个语言所在的index的值,这样下面的key对应的语言都能拿到
const restData = item.data.slice(1)
为什么这里我需要slice(1),是因为第一项,对我来创建这个ts(json数据)已经没有意义了,我唯一要做的就是把下面的数据,第一项的key和后面的value(借助eachLangIndex)对应起来,所以直接处理下面的,我选择手动删掉他
1.4写入ts文件
fs.writeFile(filePath, beautifyJs(content), { 'flag': 'a' }, function(err) {
if (err) {
throw err;
}
// 写入成功后读取测试
fs.readFile(filePath, 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(colors.yellow(`mention3:ts文件生成啦`))
});
});
以上我仅仅实现了最简单的value为字符串的情况, 对于value为数组和对象等第三个大标题去实现
2. json转excel
我们要做的事,就是把对象用在excel中表示
因为vue3直接引入ts用require或者import不太行,所以这里我直接用json数据,这样就可以require拉
假设sample.json的数据是这样,这是我们常用的格式, 如果超出这个格式,宝,你的层级是不是太多了,或者自己处理吧,小橘子已经不行了
{
"title": "抢红包活动",
"gameRule": ["超过18岁", "活动结束数据清零"],
"personalInfo": {
"age": 20,
"name": "小橘子"
},
"gameList": [
{
"gameTitle": "贪吃蛇",
"gameDesc": "儿时经典"
},
{
"gameTitle": "超级玛丽",
"gameDesc": "街机游戏"
}
]
}
下面是我们的目标
上代码吧
// npm run testToExcel
const fs = require('fs')
const xlsx = require('node-xlsx').default;
// 1.获取json的数据key以及结构
const sampleJson = require('../language/sample')
// 2.假设最多有4种,简中/繁中/英文/马来
const allLanguages = ['zh-Hans', 'zh-Hant', 'en', 'ms']
const save = `${__dirname}/../language/sampleJsonToExcel.xlsx`
const initArray = ['key', 'objSonKey', 'arrGrandSonKey', ...allLanguages]
const activeArray = []
//
for(key in sampleJson) {
// 只进行两层得判断,判断三种情况: 字符串/number, 数组, 对象
if (typeof(sampleJson[key]) === 'object') {
// 如果是数组
const isArray = sampleJson[key].constructor === Array
// 如果是对象(非数组)
const isObject = sampleJson[key].constructor !== Array
// 1.是数组
if (isArray) {
// 1.1 数组的子项是基础类型的值
if(typeof(sampleJson[key][0]) !== 'object') {
sampleJson[key].forEach(element => {
activeArray.push([key])
// activeArray.push([key, null, null, element])
});
}
// 1.2 数组的子项为对象(对象的子项为基础类型的值)
if(typeof(sampleJson[key][0]) === 'object' && sampleJson[key][0].constructor !== Array) {
sampleJson[key].forEach(element => {
for(grandSonKey in sampleJson[key][0]) {
activeArray.push([key, null, grandSonKey])
// activeArray.push([key, null, grandSonKey, element[grandSonKey]])
}
});
}
}
// 2.是对象(对象的子项为基础类型的值)
if(isObject) {
for(var sonKey in sampleJson[key]) {
activeArray.push([key, sonKey])
// activeArray.push([key, sonKey, null, sampleJson[key][sonKey]])
}
}
} else {
activeArray.push([key])
// activeArray.push([key, null, null, sampleJson[key]])
}
}
const data = [initArray].concat(activeArray)
var buffer = xlsx.build([ {name: "sheet1", data: data} ]); // Returns a buffer
fs.writeFileSync(save, buffer);
2.1 首先安装一些包
npm i node-xlsx prettier colors --save
node-xlsx 可以用来创建xlsx, 主要用到xlsx.build(),colors在终端可以打印出一些提示等
2.2 明确json的value各种情况在excel中如何体现
只进行三层得判断,判断三种情况: 字符串, 数组, 对象
{
"title": "抢红包活动",
"gameRule": ["超过18岁", "活动结束数据清零"],
"personalInfo": {
"age": 20,
"name": "小橘子"
},
"gameList": [
{
"gameTitle": "贪吃蛇",
"gameDesc": "儿时经典"
},
{
"gameTitle": "超级玛丽",
"gameDesc": "街机游戏"
}
]
}
if (typeof(sampleJson[key]) === 'object') {
// 如果是数组
const isArray = sampleJson[key].constructor === Array
// 如果是对象(非数组)
const isObject = sampleJson[key].constructor !== Array
// 1.是数组
if (isArray) {
// 1.1 数组的子项是基础类型的值
if(typeof(sampleJson[key][0]) !== 'object') {
sampleJson[key].forEach(element => {
activeArray.push([key])
// activeArray.push([key, null, null, element])
});
}
// 1.2 数组的子项为对象(对象的子项为基础类型的值)
if(typeof(sampleJson[key][0]) === 'object' && sampleJson[key][0].constructor !== Array) {
sampleJson[key].forEach(element => {
for(grandSonKey in sampleJson[key][0]) {
activeArray.push([key, null, grandSonKey])
// activeArray.push([key, null, grandSonKey, element[grandSonKey]])
}
});
}
}
// 2.是对象(对象的子项为基础类型的值)
if(isObject) {
for(var sonKey in sampleJson[key]) {
activeArray.push([key, sonKey])
// activeArray.push([key, sonKey, null, sampleJson[key][sonKey]])
}
}
} else {
activeArray.push([key])
// activeArray.push([key, null, null, sampleJson[key]])
}
1.如果value为数组,并且子项就是基础类型,那么直接在对应语言项进行写入值
2.如果value为数组,并且子项就是对象(对象的子项为基础类型的值),那么对象的key应该写在groundSonKey(因为已经是第三层的key)
3.如果value为对象(对象的子项为基础类型的值),那么key写在第二层sonKey
4.如果value是子项,直接写入值
这里发现,为啥第一个和第二个直接写入值呢,到时候excel转json怎么弄?这里我是采用length是不是大于1来判断,如果length为1,你还用啥数组呢?
2.3 写入
var buffer = xlsx.build([ {name: "sheet1", data: data} ]);
fs.writeFileSync(save, buffer);
2.4 value如何excel中体现出来?
请把这几行放出来,并且把上面的行注释
activeArray.push([key, null, null, element])
activeArray.push([key, null, grandSonKey, element[grandSonKey]])
activeArray.push([key, sonKey, null, sampleJson[key][sonKey]])
activeArray.push([key, null, null, sampleJson[key]])
json转excel只是为了生成最基础的excel的模板,所以这个value,应该是在excel中去粘贴实现.当然也许也用不上,直接在excel里面去修改key,objSonKey,arrGroundKey也是一样的,看什么样的多语言吧,这里最重要的是为复杂一点的excel文件的反推成json做铺垫吧
3. Excel转json (最终版本)
目标就是把下面的excel转成json
上代码吧
const xlsx = require('node-xlsx') // 读取excel
const fs = require('fs')
const prettier = require('prettier') // json格式化
// 定义最多所存在得语言
const allLanguages = ['zh-Hans', 'zh-Hant', 'en', 'ms'] // 假设最多有4种,简中/繁中/英文/马来
// 1.拿到excel数据
const workSheetsFromBuffer = xlsx.parse(fs.readFileSync(`${__dirname}/../language/sample.xlsx`));
console.log(colors.yellow(`mention1:由sample.xlsx生成多语言ts文件`))
workSheetsFromBuffer.forEach(item => {
// 2.可能有多个sheet表,选择有数据的那个
if(item.data.length) {
// 3. 获取excel中得语言
const currentLanguage = []
item.data[0].forEach(item => {
if (allLanguages.includes(item)) {
currentLanguage.push(item)
}
})
// 4. 获取各种语言所有得key
let eachLangIndex = 0
// 5. 去掉第一项(这里比较死, 必须按照key -> 语言得顺序)
const restData = item.data.slice(1)
currentLanguage.forEach(lang => {
eachLangIndex = item.data[0].indexOf(lang)
// 6. 获取到当前得语言,当前的index值, 以便知道获取那一栏的数据
createTs(lang, eachLangIndex, restData)
})
}
})
function handleData(arr, eachLangIndex) {
// 8.1 先将excel的数据,变成key与children带父子级的关系
let object = {}
arr.forEach((item) => {
let key = item[0]
if (!object[key]) {
object[key] = {
key,
children: []
}
}
object[key].children.push(item)
})
const initArr = Object.values(object)
let resObj = {}
initArr.forEach(item => {
const { children } = item
const firstChildren = children[0]
// 8.2对sonkey,grandSonKey项以及length进行判断,来确定是什么类型
// 8.2.1如果第二项(sonkey),第三项(grandSonKey)不存在,并且children只有一项,那么表示是字符串
if (!firstChildren[1] && !firstChildren[2] && item.children.length === 1) {
resObj[item.key] = beautifyText(firstChildren[eachLangIndex])
}
// 8.2.2 如果第二项(sonkey),第三项(grandSonKey)不存在,并且children多项,那么表示就是一个数组(子项为基础项)
if (!firstChildren[1] && !firstChildren[2] && item.children.length > 1) {
const arr = []
children.forEach(cSon => {
arr.push(beautifyText(cSon[eachLangIndex]))
})
resObj[item.key] = arr
}
// 8.2.3 如果第二项(sonkey)存在,第三项(grandSonKey)不存在,那么表示是一个对象
if (firstChildren[1]) {
const obj = {}
children.forEach(cSon => {
obj[cSon[1]] = beautifyText(cSon[eachLangIndex])
})
resObj[item.key] = obj
}
// 8.2.4 如果第三项(grandSonKey)存在,那么表示是一个数组(子项为对象)
if (firstChildren[2]) {
const items = []
let keyNumbers = 0
const arr = []
children.forEach(cSon => {
items.push(cSon[2])
})
keyNumbers = [...new Set(items)].length
let obj = {}
children.forEach((cSon, index) => {
obj[[cSon[2]]] = beautifyText(cSon[eachLangIndex])
if(index%keyNumbers) {
arr.push(obj)
obj = {}
}
})
resObj[item.key] = arr
}
})
return resObj
}
// 7.根据key创建出对应得ts文件
function createTs(tsFileName, eachLangIndex, data) {
// 8.处理数据
const obj = handleData(data, eachLangIndex)
const filePath = `${__dirname}/../language/${tsFileName}.ts`
// 9.插入数据到文件里面
const content = `
const message =
${JSON.stringify(obj,null,"\t")}
export default message;
`
fs.exists(filePath, (exists) => {
if (!exists) {
fs.writeFile(filePath, beautifyJs(content), { 'flag': 'a' }, function(err) {
if (err) {
throw err;
}
// 写入成功后读取测试
fs.readFile(filePath, 'utf-8', function(err, data) {
if (err) {
throw err;
}
console.log(colors.yellow(`mention3:${filePath}文件生成啦`))
});
});
} else {
console.log(colors.yellow(`mention2:${filePath}已经有相同的文件拉`))
}
})
}
// 格式化代码
function beautifyJs(txt) {
return prettier.format(txt, {
parser: "babel-ts",
printWidth: 80,
semi: true,
tabWidth: 2, // 缩进字节数
useTabs: false, // 缩进不使用tab,使用空格
singleQuote: false,
bracketSpacing: true,
trailingComma: "es5",
});
}
// 去掉首尾的换行和空格
function beautifyText(str) {
return String(str).replace(/^\s+|\s+$/g,'')
}
3.1 代码解析
与Excel转json (无脑简单版)相比,我只加上了一个beautifyText()以及handleData方法
beautifyText主要是excel里面得复制也许前后会有空格或者换行,可以去掉
handleData主要是excel中返回得数组,处理成json
因为excel获取有很多项,所以先进行key一致得一个合并
let object = {}
arr.forEach((item) => {
let key = item[0]
if (!object[key]) {
object[key] = {
key,
children: []
}
}
object[key].children.push(item)
})
const initArr = Object.values(object)
主要是将
const arr = [
[ 'title', null, null, '抢红包活动'],
[ 'gameRule', null, null, '超过18岁' ],
[ 'gameRule', null, null, '活动结束数据清零' ],
[ 'personalInfo', 'age', null, 20 ],
[ 'personalInfo', 'name', null, '小橘子' ],
[ 'gameList', null, 'gameTitle', '贪吃蛇' ],
[ 'gameList', null, 'gameDesc', '儿时经典' ],
[ 'gameList', null, 'gameTitle', '超级玛丽' ],
[ 'gameList', null, 'gameDesc', '街机游戏' ],
]
转化为
然后进行每个key对应得数组不同类型得一个处理,当然也许这里有更好得方式!
其中要注意得是8.2.4 如果第三项(grandSonKey)存在,那么表示是一个数组(子项为对象)的处理,说明在写excel的时候,数组里面的对象的key和value,应该按照固定的个数去写,如果乱了,会出问题的
4.效果
创建language/message.js
import zhHans from "./zh-Hans";
import en from "./en";
// 语言包
const messages = {
"zh-Hans": zhHans,
en,
};
export default messages;
创建language/index.ts
import { createI18n } from "vue-i18n";
import messages from "./message";
const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: "en",
globalInjection: true,
messages,
});
export default i18n;
main.js
import i18n from "./language";
createApp(App).use(i18n).mount('#app');
index.vue
<template>
test1
{{$t('personalInfo.name')}}
</template>
当然language底下得message.js和index.ts 都可以直接生成, 不用手动加入,大家自己去写吧,嘻嘻,后续有空我补上,不过这个不难的,重点是多语言这块,奥里给!感谢同事的excel的格式思路