【前端出海】多语言篇(二):实现一个多语言自动翻译脚本
众所周知,国内互联网行业已经进入存量市场,竞争激烈、利润微薄。一些公司为了摆脱困境,将目标转向了海外,海外市场相比国内,竞争小、利润高,潜力大。未来数年,出海会成为越来越多公司的重要战略。
先来看看自动翻译的实际效果:
从上面动图可以看到,我们只用编写zh-cn.json
文件中的语言配置,其他国家语言就会自动翻译生成。那这是如何实现的呢?其实仅仅通过执行一个js翻译脚本就会自动将中文翻译成他国语言。
下面我将介绍如何一步步编写这个脚本。
准备工作
翻译脚本采用的是阿里云SDK提供的机器翻译功能,所以拥有阿里云账号并且开通机器翻译功能是先决条件。
如果你没有阿里云账号,可以去 阿里云首页 注册一个,再通过支付宝账号进行实名认证。然后在 阿里云控制台首页 登录阿里云账号,在个人中心-开通助手里面勾选机器翻译,点击一键开通。
下一步就是申请AccessToken,同样是在 阿里云控制台首页 点击AccessKeys管理。
接着在AccessKey管理页面点击创建AccessKey,然后将创建的AccessKey ID和AccessKey Secret保存到本地。
核心代码
下面我将介绍自动翻译脚本的核心代码,整个代码其实是基于阿里云的机器翻译SDK改造的。
阿里云的机器翻译提供了多种开发语言的SDK,包括Java、Python、.NET等常用语言,我们采用node.js运行脚本,使用TypeScript版本比较合适。
打开 TypeScript SDK示例文档 页面,可以看到需要下载的依赖包以及SDK示例代码。
然后点击SDK示例代码的链接,打开 机器翻译_API调试 页面,就能看到完整的示例代码以及可以根据修改代码和参数进行调试。
按照示例代码在项目中下载依赖
npm install --save @alicloud/alimt20181012 @alicloud/openapi-client @alicloud/tea-util
注:如果你需要使用TypeScript,还需要下载这个依赖@alicloud/tea-typescript
新建一个文件aliyun-translator.js
,放在 /scripts 目录下,然后复制整块代码,稍作改动,整个翻译脚本的核心功能就完成了,代码如下:
const {default: alimt20181012, ...$alimt20181012} = require('@alicloud/alimt20181012');
const $OpenApi = require('@alicloud/openapi-client');
const {default: Util, ...$Util} = require('@alicloud/tea-util');
function createClient(accessKeyId, accessKeySecret) {
let config = new $OpenApi.Config({
// 您的 AccessKey ID
accessKeyId: accessKeyId,
// 您的 AccessKey Secret
accessKeySecret: accessKeySecret,
});
// 访问的域名
config.endpoint = `mt.cn-hangzhou.aliyuncs.com`;
return new alimt20181012(config);
}
async function main(sourceLanguage, targetLanguage, sourceText) {
// 传入 AccessKey ID 和 AccessKey Secret,示例代码,敏感数据打码
let client = createClient("******Dy9gLx2Lzwqs******", "******OcI0H88SC64ce4DizT******");
let translateGeneralRequest = new $alimt20181012.TranslateGeneralRequest({
formatType: "text",
sourceLanguage: sourceLanguage,
targetLanguage: targetLanguage,
sourceText: sourceText,
scene: "general",
});
let runtime = new $Util.RuntimeOptions();
try {
// 复制代码运行请自行打印 API 的返回值
const result = await client.translateGeneralWithOptions(translateGeneralRequest, runtime);
return result.body.data?.translated;
} catch (error) {
if (error.code === 'ECONNRESET') {
return main(sourceLanguage, targetLanguage, sourceText);
}
throw error;
}
}
module.exports = main;
目录结构如下:
- scripts
- aliyun-translator.js
- build.js
- start.js
- test.js
- src
- package.json
其实整块代码最核心的就是TranslateGeneralRequest
方法的调用
let translateGeneralRequest = new $alimt20181012.TranslateGeneralRequest({
formatType: "text",
sourceLanguage: sourceLanguage,
targetLanguage: targetLanguage,
sourceText: sourceText,
scene: "general"
});
我们来看看阿里云开发文档上,对这个方法的配置项传参说明
文档里也描述了返回参数,也是我们整个核心代码的返回参数
示例如下,
Translated
就是我们需要的翻译结果
{
"Code": 200,
"Message": "success",
"RequestId": "86D18195-D89C-4C8C-9DC4-5FCE789CE6D5",
"Data": {
"Translated": "Hello",
"WordCount": "10",
"DetectedLanguage": "zh"
}
}
自动翻译的脚本
核心代码写好后,我们还需要编写一个用node.js运行的脚本,在aliyun-translator.js
文件同级目录下,新建一个translate-to-other.js
文件,整体代码如下:
const fs = require('fs');
const aliyunTranslator = require('./aliyun-translator');
// 需要使用阿里云翻译的语言
const langMap = [
['de', 'de'], // 德语
['en', 'en-us'], // 英语
['es', 'es'], // 西班牙语
['fr', 'fr'], // 法语
['ja', 'ja'], // 日语
['ru', 'ru'], // 俄语
];
const forEachObject = function(obj, cb) {
for (let i in obj) {
const v = obj[i];
if (typeof v === 'object') {
forEachObject(v, (keys, v) => cb([i, ...keys], v));
} else {
cb([i], v);
}
}
}
const objectGet = function(obj, keys) {
const [firstKey, ...otherKeys] = keys;
if (typeof obj[firstKey] === 'object' && otherKeys.length) {
return objectGet(obj[firstKey], keys.slice(1));
}
return obj[firstKey];
}
const objectSet = function(obj, keys, value) {
const [firstKey, ...otherKeys] = keys;
if (typeof obj[firstKey] === 'object' && otherKeys.length) {
objectSet(obj[firstKey], otherKeys, value);
} else if (obj[firstKey] === undefined && otherKeys.length) {
obj[firstKey] = {};
objectSet(obj[firstKey], otherKeys, value);
} else if (otherKeys.length === 0) {
obj[firstKey] = value;
}
}
let i = 0;
// 翻译脚本主方法
doTrans('../src/i18n', './src/i18n');
function doTrans(relativeDir, localBaseDir) {
// 读取中文基准文件
const baseZhCN = require(`${relativeDir}/zh-cn.json`);
// 然后遍历langMap数组找到需要翻译的目标语言文件
langMap.forEach(([lang, fileName]) => {
const promiseArray = [];
let target;
try{
target = require(`${relativeDir}/${fileName}.json`);
}catch(e){
try{
target = require(`${relativeDir}/${fileName.toLowerCase()}.json`)
}catch(e){
process.stdout.write(`找不到文件${relativeDir}/${fileName}.json,跳过\n`);
return;
}
}
forEachObject(baseZhCN, (keys, v) => {
if (objectGet(target, keys) === undefined) {
// 没有对应翻译结果就调用阿里云的翻译功能
promiseArray.push(
new Promise((resolve, reject) => {
setTimeout(() => {
aliyunTranslator('zh', lang, v).then((r) => {
objectSet(target, keys, r);
process.stdout.write('中间结果 ' + v + '\t' + r + '\n');
process.stdout.write(
keys.join('.') + '\t' + v + '\t' + r + '\n'
);
resolve(target);
}).catch((error) => {
reject(error);
});
}, i * 200);
i++;
})
);
}
});
process.stdout.write(`开始翻译 ${localBaseDir}/${fileName}.json`);
process.stdout.write('\n');
Promise.all(promiseArray).then((results) => {
const r = results[results.length - 1];
if (r) {
const file = `${localBaseDir}/${fileName}.json`;
fs.writeFile(file, JSON.stringify(r, null, 2), (error) => {
if (!error) {
process.stdout.write(`写文件${file}成功`);
} else {
process.stderr.write(error.message);
}
process.stdout.write('\n');
});
}
process.stdout.write('翻译结束 ' + fileName + '\n');
process.stdout.write('-------------------------------------------\n');
})
});
}
这段代码很长,但并不复杂,我来对代码一一拆解。
首先整个脚本是从doTrans
这个方法开始运行的。
doTrans('../src/i18n', './src/i18n');
它有两个参数:第一个是i18n文件夹相对当前文件的相对路径,第二个参数是i18n文件夹相对于整个项目的路径。
这个函数通过第一个参数读取中文基准文件zh-cn.json
,然后遍历langMap
数组找到需要翻译的目标语言的json文件。
...
// 需要使用阿里云翻译的语言
const langMap = [
['de', 'de'], // 德语
['en', 'en-us'], // 英语
['es', 'es'], // 西班牙语
['fr', 'fr'], // 法语
['ja', 'ja'], // 日语
['ru', 'ru'], // 俄语
];
...
function doTrans(relativeDir, localBaseDir) {
// 读取中文基准文件
const baseZhCN = require(`${relativeDir}/zh-cn.json`);
// 然后遍历langMap数组找到需要翻译的目标语言文件
langMap.forEach(([lang, fileName]) => {
const promiseArray = [];
let target;
try{
target = require(`${relativeDir}/${fileName}.json`);
}catch(e){
try{
target = require(`${relativeDir}/${fileName.toLowerCase()}.json`)
}catch(e){
process.stdout.write(`找不到文件${relativeDir}/${fileName}.json,跳过\n`);
return;
}
}
...
});
}
然后用forEachObject
方法递归遍历基准文件zh-cn.json
中所有的key,如果目标语言文件中没有对应的翻译结果,则调用上文提到的aliyunTranslator
方法,请求阿里云的翻译SDK,生成对应语言的翻译结果。
...
const forEachObject = function(obj, cb) {
for (let i in obj) {
const v = obj[i];
if (typeof v === 'object') {
forEachObject(v, (keys, v) => cb([i, ...keys], v));
} else {
cb([i], v);
}
}
}
...
function doTrans(relativeDir, localBaseDir) {
// 读取中文基准文件
const baseZhCN = require(`${relativeDir}/zh-cn.json`);
// 然后遍历langMap数组找到需要翻译的目标语言文件
langMap.forEach(([lang, fileName]) => {
...
forEachObject(baseZhCN, (keys, v) => {
if (objectGet(target, keys) === undefined) {
// 没有对应翻译结果就调用阿里云的翻译功能
promiseArray.push(
new Promise((resolve, reject) => {
setTimeout(() => {
aliyunTranslator('zh', lang, v).then((r) => {
objectSet(target, keys, r);
process.stdout.write('中间结果 ' + v + '\t' + r + '\n');
process.stdout.write(
keys.join('.') + '\t' + v + '\t' + r + '\n'
);
resolve(target);
}).catch((error) => {
reject(error);
});
}, i * 200);
i++;
})
);
}
});
...
});
}
最后使用fs的writeFile
方法,将所有翻译结果写入目标文件,然后要用到doTrans
方法的第二个参数localBaseDir
const fs = require('fs');
...
function doTrans(relativeDir, localBaseDir) {
const baseZhCN = require(`${relativeDir}/zh-cn.json`);
langMap.forEach(([lang, fileName]) => {
...
process.stdout.write(`开始翻译 ${localBaseDir}/${fileName}.json`);
process.stdout.write('\n');
Promise.all(promiseArray).then((results) => {
const r = results[results.length - 1];
if (r) {
const file = `${localBaseDir}/${fileName}.json`;
fs.writeFile(file, JSON.stringify(r, null, 2), (error) => {
if (!error) {
process.stdout.write(`写文件${file}成功`);
} else {
process.stderr.write(error.message);
}
process.stdout.write('\n');
});
}
process.stdout.write('翻译结束 ' + fileName + '\n');
process.stdout.write('-------------------------------------------\n');
})
});
}
注:localBaseDir
这个参数一定要传入i18n文件夹相对于整个项目的路径,不然文件内容会写入不成功。
脚本执行命令
在项目package.json
文件中,添加一个脚本执行命令trans-from-zh
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js",
"trans-from-zh": "node scripts/translate-to-other.js"
},
这样运行如下命令时,就会用node执行translate-to-other.js
yarn trans-from-zh
最终效果
最后再来看一遍自动翻译的实际效果
注意事项
1、每个需要翻译的json文件,要手动输入一对大括号,不然翻译结果会写入失败。
2、如果需要翻译的中文有成千上万条,一次性翻译多国语言很容易导致翻译结果写入不成功,如果遇到这种情况,可以每次跑脚本时只翻译一种语言。
在translate-to-other.js
文件中,每次只启用一种语言进行翻译。
const fs = require('fs');
const aliyunTranslator = require('./aliyun-translator');
// 每次跑脚本只启用一种语言,跑一次换一种语言
const langMap = [
['de', 'de'], // 德语
// ['en', 'en-us'], // 英语
// ['es', 'es'], // 西班牙语
// ['fr', 'fr'], // 法语
// ['ja', 'ja'], // 日语
// ['ru', 'ru'], // 俄语
];
结语
本文讲解了如何实现一个多语言翻译脚本,将中文自动翻译成指定的多个语言。
在下一篇中,我将介绍多语言的规范、常见问题以及一些注意事项,干货满满,敬请期待。
转载自:https://juejin.cn/post/7352079441525047307