外观
第50章—国际化资源包如何通过Excel和GoogleSheet分享给产品经理
2865字约10分钟
2024-09-19
上节我们学了用 react-intl 做国际化。
我们会把文案抽离出来,放在不同的资源包里维护。
比如 zh-CN.json:
en-US.json:
而这个文案的翻译一般是产品经理做的。
那怎么把这个资源包给产品经理编辑呢?
直接给他 json 文件么?
这样并不好。
一般我们都是导出 excel。
来写一下:
mkdir excel-export
cd excel-export
npm init -y
进入项目,安装 exceljs:
npm install --save exceljs
写下 index.js
const { Workbook } = require('exceljs');
async function main(){
const workbook = new Workbook();
const worksheet = workbook.addWorksheet('guang111');
worksheet.columns = [
{ header: 'ID', key: 'id', width: 20 },
{ header: '姓名', key: 'name', width: 30 },
{ header: '出生日期', key: 'birthday', width: 30},
{ header: '手机号', key: 'phone', width: 50 }
];
const data = [
{ id: 1, name: '光光', birthday: new Date('1994-07-07'), phone: '13255555555' },
{ id: 2, name: '东东', birthday: new Date('1994-04-14'), phone: '13222222222' },
{ id: 3, name: '小刚', birthday: new Date('1995-08-08'), phone: '13211111111' }
]
worksheet.addRows(data);
workbook.xlsx.writeFile('./data.xlsx');
}
main();
就是按照 workbook(工作簿) > worksheet(工作表)> row (行)的层次来添加数据。
跑一下:
生成了 excel 文件。
打开看下:
可以看到 worksheet 的名字,还有每行的数据都是对的。
这样,就完成了 excel 的生成。
那我们就可以把 zh-CN.json、en-US.json 等的内容整合到 excel 文件里:
把 zh-CN.json 和 en-US.json 复制过来:
zh-CN.json
{
"username": "用户名 <bbb>{name}</bbb>",
"password": "密码",
"rememberMe": "记住我",
"submit": "提交",
"inputYourUsername": "请输入你的用户名!",
"inputYourPassword": "请输入你的密码!"
}
en-US.json
{
"username": "Username <bbb>{name}</bbb>",
"password": "Password",
"rememberMe": "Remember Me",
"submit": "Submit",
"inputYourUsername": "Please input your username!",
"inputYourPassword": "Please input your password!"
}
然后写下 index2.js
const { Workbook } = require('exceljs');
const fs = require('fs');
const languages = ['zh-CN', 'en-US'];
async function main(){
const workbook = new Workbook();
const worksheet = workbook.addWorksheet('test');
const bundleData = languages.map(item => {
return JSON.parse(fs.readFileSync(`./${item}.json`));
})
const data = [];
bundleData.forEach((item, index) => {
for(let key in item) {
const foundItem = data.find(item => item.id === key);
if(foundItem) {
foundItem[languages[index]] = item[key]
} else {
data.push({
id: key,
[languages[index]]: item[key]
})
}
}
})
console.log(data);
worksheet.columns = [
{ header: 'ID', key: 'id', width: 30 },
...languages.map(item => {
return {
header: item,
key: item,
width: 30
}
})
];
worksheet.addRows(data);
workbook.xlsx.writeFile('./bundle.xlsx');
}
main();
这里我们读取了 en-US.json 和 zh-CN.json 的内容,然后按照 id、en-US、zh-CN 的 column 来写入 excel。
跑一下:
看下生成的 excel:
现在这个 excel 已经可以交给产品经理去编辑了,但是还少了一些描述。
可能产品经理看到某个 key 并不知道这个文案是在哪里用的,干啥的。
所以我们最好加一些描述。
打开上节的项目,再次执行 extract 命令:
npx formatjs extract "src/**/*.tsx" --out-file messages.json
现在有 defaultMessage,没有 description,我们在 defineMessages 的时候加一下:
const messsages = defineMessages({
username: {
id: "username",
defaultMessage: '用户名',
description: '这是登录的用户名'
},
password: {
id: "password",
defaultMessage: '密码',
description: '这是登录的密码'
},
rememberMe: {
id: 'rememberMe',
defaultMessage: '记住我',
description: '登录页的记住我复选框'
},
submit: {
id: 'submit',
defaultMessage: '提交',
description: '登录页的提交按钮'
},
inputYourUsername: {
id: 'inputYourUsername',
defaultMessage: '请输入用户名!',
description: '登录页的用户名为空的提示'
},
inputYourPassword: {
id: 'inputYourPassword',
defaultMessage: '请输入密码!',
description: '登录页的密码为空的提示'
}
})
重新 extract 生成 messages.json
npx formatjs extract "src/**/*.tsx" --out-file messages.json
上节我们把这个文件删掉了,其实没必要删掉,可以用它来生成 excel。
把 messages.json 复制过去,我们改下 index2.js
const { Workbook } = require('exceljs');
const fs = require('fs');
const languages = ['zh-CN', 'en-US'];
async function main(){
const workbook = new Workbook();
const worksheet = workbook.addWorksheet('test');
const bundleData = languages.map(item => {
return JSON.parse(fs.readFileSync(`./${item}.json`));
})
const data = [];
const messages = JSON.parse(fs.readFileSync('./messages.json'));
bundleData.forEach((item, index) => {
for(let key in messages) {
const foundItem = data.find(item => item.id === key);
if(foundItem) {
foundItem[languages[index]] = item[key]
} else {
data.push({
id: key,
defaultMessage: messages[key].defaultMessage,
description: messages[key].description,
[languages[index]]: item[key]
})
}
}
})
console.log(data);
worksheet.columns = [
{ header: 'ID', key: 'id', width: 30 },
{ header: 'defaultMessage', key: 'defaultMessage', width: 30 },
{ header: 'description', key: 'description', width: 50 },
...languages.map(item => {
return {
header: item,
key: item,
width: 30
}
})
];
worksheet.addRows(data);
workbook.xlsx.writeFile('./bundle.xlsx');
}
main();
现在生成的是这样的:
这样产品经理就知道每个 key 是哪里的文案,什么意思,就知道怎么翻译了。
改一下:
然后改完之后要用这个生成 en-US.json 和 zh-CN.json 在项目里引入用。
写一下这个脚本:
index3.js
const { Workbook } = require('exceljs');
async function main(){
const workbook = new Workbook();
const workbook2 = await workbook.xlsx.readFile('./bundle.xlsx');
workbook2.eachSheet((sheet, index1) => {
console.log('工作表' + index1);
sheet.eachRow((row, index2) => {
const rowData = [];
row.eachCell((cell, index3) => {
rowData.push(cell.value);
});
console.log('行' + index2, rowData);
})
})
}
main();
解析也是按照 workbook(工作簿) > worksheet(工作表)> row (行)的层次,调用 eachSheet、eachRow、eachCell 就好了。
然后生成 json:
const { Workbook } = require('exceljs');
const fs = require('fs');
async function main(){
const workbook = new Workbook();
const workbook2 = await workbook.xlsx.readFile('./bundle.xlsx');
const zhCNBundle = {};
const enUSBundle = {};
workbook2.eachSheet((sheet) => {
sheet.eachRow((row, index) => {
if(index === 1) {
return;
}
const key = row.getCell(1).value;
const zhCNValue = row.getCell(4).value;
const enUSValue = row.getCell(5).value;
zhCNBundle[key] = zhCNValue;
enUSBundle[key] = enUSValue;
})
});
console.log(zhCNBundle);
console.log(enUSBundle);
fs.writeFileSync('zh-CN.json', JSON.stringify(zhCNBundle, null, 2));
fs.writeFileSync('en-US.json', JSON.stringify(enUSBundle, null, 2));
}
main();
跑一下:
这样就把产品经理编辑后的 excel 生成了国际化资源包:
项目里直接用这个资源包就好了。
现在这样的工作流是可以的,但是不能协同编辑。
如果能够像在线文档一样协同编辑这个 excel 就好了。
可以的,用 google sheets.
打开 google sheets: https://docs.google.com/spreadsheets/
登录之后创建一个新的 sheet:
它可以导入 csv 格式的文件:
选择 replace 替换当前工作表:
这样,就导入了 csv 的数据:
可以在线编辑了。
把这个 url 分享出去就行。
比如这个 url:
https://docs.google.com/spreadsheets/d/1FgCNmoTz9FWuR6Jv1SJ9ioWd2bBfrtRAeoi5CYpmXBA/edit?usp=sharing
接下来的问题就变成了如何用 node 生成和解析 csv 文件。
这个可以用 csv-parse 和 csv-stringify 来做。
安装 csv-stringify:
npm install --save-dev csv-stringify
然后写下 index4.js
const { stringify } = require("csv-stringify");
const fs = require('fs');
const languages = ['zh-CN', 'en-US'];
async function main(){
const bundleData = languages.map(item => {
return JSON.parse(fs.readFileSync(`./${item}.json`));
})
const data = [];
const messages = JSON.parse(fs.readFileSync('./messages.json'));
bundleData.forEach((item, index) => {
for(let key in messages) {
const foundItem = data.find(item => item.id === key);
if(foundItem) {
foundItem[languages[index]] = item[key]
} else {
data.push({
id: key,
defaultMessage: messages[key].defaultMessage,
description: messages[key].description,
[languages[index]]: item[key]
})
}
}
})
console.log(data);
const columns = {
id: "Message ID",
defaultMessage: "Default Message",
description: "Description",
'zh-CN': "zh-CN",
'en-US': "en-US"
};
stringify(data, { header: true, columns }, function (err, output) {
fs.writeFileSync("./messages.csv", output);
});
}
main();
也是定义 columns 和 column 对应的 data,调用 stringify 来转成 csv 文件。
跑一下:
可以看到,生成了 message.csv 文件。
然后在 google sheet 里导入:
你可以点开这个链接看一下:
https://docs.google.com/spreadsheets/d/1FgCNmoTz9FWuR6Jv1SJ9ioWd2bBfrtRAeoi5CYpmXBA/edit?usp=sharing
改一下这个文案:
然后导出到本地再转成 json 就好了。
怎么导出呢?
在现在的 url 后加一个 export?format=csv 就好了:
比如这个链接: https://docs.google.com/spreadsheets/d/1FgCNmoTz9FWuR6Jv1SJ9ioWd2bBfrtRAeoi5CYpmXBA/export?format=csv
然后在代码里下载下导出的 csv:
index5.js
const { execSync } = require('child_process');
const { parse } = require("csv-parse/sync");
const fs = require('fs');
const sheetUrl = "https://docs.google.com/spreadsheets/d/1FgCNmoTz9FWuR6Jv1SJ9ioWd2bBfrtRAeoi5CYpmXBA";
execSync(`curl -L ${sheetUrl}/export?format=csv -o ./message2.csv`, {
stdio: 'ignore'
});
const input = fs.readFileSync("./message2.csv");
const records = parse(input, { columns: true });
console.log(records);
这里用 curl 命令来下载,-L 是自动跳转的意思,因为访问这个 url 会跳转一个新的地址。
安装用到的包:
npm install --save-dev csv-parse
跑一下:
可以看到,message2.csv 下载了下来,并且还解析出了其中的数据。
接下来用这个生成 zh-CN.json 和 en-US.json,然后在项目里用就好了。
const { execSync } = require('child_process');
const { parse } = require("csv-parse/sync");
const fs = require('fs');
const sheetUrl = "https://docs.google.com/spreadsheets/d/1FgCNmoTz9FWuR6Jv1SJ9ioWd2bBfrtRAeoi5CYpmXBA";
execSync(`curl -L ${sheetUrl}/export?format=csv -o ./message2.csv`, {
stdio: 'ignore'
});
const input = fs.readFileSync("./message2.csv");
const data = parse(input, { columns: true });
const zhCNBundle = {};
const enUSBundle = {};
data.forEach(item => {
const keys = Object.keys(item);
const key = item[keys[0]];
const valueZhCN = item[keys[3]];
const valueEnUS = item[keys[4]];
zhCNBundle[key] = valueZhCN;
enUSBundle[key] = valueEnUS;
})
console.log(zhCNBundle);
console.log(enUSBundle);
fs.writeFileSync('zh-CN.json', JSON.stringify(zhCNBundle, null, 2));
fs.writeFileSync('en-US.json', JSON.stringify(enUSBundle, null, 2));
跑一下:
这样,就完成了资源包在 google sheet 的在线编辑,以及编辑完以后下载并解析生成资源包的功能。
相比用 exceljs 生成 excel 文件的方式,google sheet 可以把 url 分享出去,可以协同编辑,更方便一点。
案例代码上传了小册仓库
总结
国际化资源包需要交给产品经理去翻译,我们会把 json 转成 excel 交给他。
我们先用 exceljs 实现了 excel 的解析和生成,编辑完之后再转成 en-US.json、zh-CN.json 的资源包。
然后用 google sheet 实现了在线编辑和分享,编辑完之后下载并解析 csv,然后转成 en-US.json、zh-CN.json 的资源包。
用到了 csv-parse、csv-stingify。
这两种方案都可以,确定好方案之后把这些脚本内置到项目里就可以了。