基于百度翻译API的node插件

背景

做过国际化的项目就明白要把每处的文案翻译成不同的语言版本,如果只是一点点,自己去百度上翻译成对应语言版本,copy过来就ok了,但是如果这个项目文案特别多的话,自己去翻译,可能会烦死

umijs如果构建国际化,会有一个locals的目录,里面存放前端项目中不需要存进数据库的各种语言版本文件,我就想实现只写中文的,其他版本通过nodejs+百度翻译api直接生成

一、百度API

需要注册开发者(使用时需要开发者的appid+密钥),并开通通用翻译API,每个月有200万个免费字符翻译,所以不用太担心会出现费用

我本來想使用google翻译api的,但是貌似需要翻墙,还是做个好公民,对比了有道和百度,百度翻译api更稳定和准确些,最终决定使用百度翻译API,下面分享一些坑:

  • 1、直接使用百度翻译的api很坑,首先传参很多,最终要的还是无法并发,如果你频繁调用的话就是返回 54003

  • 2、sign生成,需要md5加密多个字段的拼接

然后我写了这个插件,解决下这些问题,让使用更加方面!

二、该插件优点

  • 1、默认中转英
translate('密钥').then((res) => {
  console.log('res', res);	// res secret key
});
  • 2、能直接传入复杂对象值来进行翻译,例如:{ key: value },只翻译value,不翻译key
translate({ name: 'faker' }, { from: "en", to: 'zh' }).then((res) => {
  console.log('res', res);	// res { name: '冒牌货' }
});
  • 3、支持嵌套对象
translate({
  name: "小明",
  info: { father: "小明爸爸", mather: "小明妈妈" },
}).then((res) => {
  console.log("res", res);
  /*
  res {
    name: 'Xiao Ming',
    info: { father: "Xiao Ming's father", mather: "Xiao Ming's mother" }
  } 
  */
});

// 各种对象嵌套都行,数组嵌套对象,对象嵌套数组
translate([
  {
    name: "小明",
    info: { father: "小明爸爸", mather: "小明妈妈" },
  },
  {
    name: "小红",
    info: { father: "小红爸爸", mather: "小红妈妈" },
  },
]).then((res) => {
  console.log("res", res);
  /*
  res [
    {
      name: 'Xiao Ming',
      info: { father: "Xiao Ming's father", mather: "Xiao Ming's mother" }
    },
    {
      name: 'Xiaohong',
      info: { father: 'Little red Dad', mather: 'Little red mother' }
    }
  ]
  */
});
  • 4、解决了百度api的并发问题
translate('密钥', { from: "zh", to: 'en' }).then((res) => {
  console.log('res', res);
});

translate({ name: 'faker' }, { from: "en", to: 'zh' }).then((res) => {
  console.log('res', res);
});

直接用百度api,同时翻译,那么就会导致前面的请求失败,当然我不是vip,看说明文档上可能是没买vip,就不支持并发,但是我就是不想花钱,毕竟穷!

实现解决并发原理:其实就是上次请求没有完成,我就将这次请求排入队列

if (this.isRequesting) {
  return new Promise((resolve) => {
    setTimeout(() => {
      this.requestApi(value, parames).then((res) => {
        resolve(res);
      });
    }, 1000);
  });
}
  • 5、对于复杂对象直接传入翻译的优化

不用担心直接传入一个对象,有很多数据,因为我将对象的数据合并之后,只发送了一个请求到百度翻译,然后我再对应解析出来数据,其实传入对象,数组对翻译性能更高

translate([
  {
    name: "小明",
    info: { father: "小明爸爸", mather: "小明妈妈" },
  },
  {
    name: "小红",
    info: { father: "小红爸爸", mather: "小红妈妈" },
  },
]).then((res) => {
  console.log("res", res);
  /*
  res [
    {
      name: 'Xiao Ming',
      info: { father: "Xiao Ming's father", mather: "Xiao Ming's mother" }
    },
    {
      name: 'Xiaohong',
      info: { father: 'Little red Dad', mather: 'Little red mother' }
    }
  ]
  */
});

这个翻译流程我把图放出来,明显值请求了4次api,每个对象如果当前层级都是string,自然会被我合并,就能一次翻译完成:

三、插件源码

const md5 = require("md5-node");
const axios = require("axios");

function MysKeyTranslate(config) {
  this.requestNumber = 0;
  this.config = {
    showProgress: true,
    requestNumber: 1,
    agreement: 'http',
    ...config,
  };
  this.baiduApi = `${this.config.agreement}://api.fanyi.baidu.com/api/trans/vip/translate`

  this.createUrl = (domain, form) => {
    let result = domain + "?";
    for (let key in form) {
      result += `${key}=${form[key]}&`;
    }
    return result.slice(0, result.length - 1);
  };

  this.requestApi = (value, parames) => {
    if (this.requestNumber >= this.config.requestNumber) {
      return new Promise((resolve) => {
        setTimeout(() => {
          this.requestApi(value, parames).then((res) => {
            resolve(res);
          });
        }, 1000);
      });
    }
    this.requestNumber++;
    const { appid, secret } = this.config;
    const q = value;
    const salt = Math.random();
    const sign = md5(`${appid}${q}${salt}${secret}`);
    const fromData = {
      ...parames,
      q: encodeURIComponent(q),
      sign,
      appid,
      salt,
    };
    const fanyiApi = this.createUrl(this.baiduApi, fromData);
    // console.log("fanyiApi", fanyiApi);
    return new Promise((resolve) => {
      axios
        .get(fanyiApi)
        .then(({ data: res }) => {
          if (this.config.showProgress) console.log("翻译结果:", res);
          if (!res.error_code) {
            const resList = res.trans_result;
            resolve(resList);
          }
        })
        .finally(() => {
          setTimeout(() => {
            this.requestNumber--;
          }, 1000);
        });
    });
  };

  this.translate = async (value, parames = { from: "zh", to: "en" }) => {
    let result = "";
    if (typeof value === "string") {
      const res = await this.requestApi(value, parames);
      result = res[0]["dst"];
    }
    if (
      Array.isArray(value) ||
      Object.prototype.toString.call(value) === "[object Object]"
    ) {
      result = await this._createObjValue(value, parames);
    }
    return result;
  };

  this._createObjValue = async (value, parames) => {
    let index = 0;
    const obj = Array.isArray(value) ? [] : {};
    const strDatas = Array.isArray(value) ? value : Object.values(value);
    const reqData = strDatas
      .filter((item) => typeof item === "string")
      .join("\n");
    const res = reqData ? await this.requestApi(reqData, parames) : [];
    for (let key in value) {
      if (typeof value[key] === "string") {
        obj[key] = res[index]["dst"];
        index++;
      }
      if (
        Array.isArray(value[key]) ||
        Object.prototype.toString.call(value[key]) === "[object Object]"
      ) {
        obj[key] = await this.translate(value[key], parames);
      }
    }
    return obj;
  };

  return this.translate;
}

export default MysKeyTranslate;

四、使用方法

  • 1、安装 md5-node ,使用百度翻译api需要md5
npm i --save md5-node
  • 2、保存上面的源码到一个文件
  • 3、使用
const MysKeyTranslate = require("./MysKeyTranslate.js"); // 引入刚才保存的文件

const translate = new MysKeyTranslate({
  appid: "",  // 你的appid  去百度开发者平台查看 http://api.fanyi.baidu.com/doc/21
  secret: "", // 你的密钥
});

// 下面就可以直接使用了
translate('密钥', { from: "zh", to: 'en' }).then((res) => {
  console.log('res', res);
});
  • 4、如果想读取中文文件,然后生成其他版本内容,可以参考下面
const MysKeyTranslate = require("./MysKeyTranslate.js"); // 引入刚才保存的文件
const data = require("./ZH_CN.ts");		// 引入中文文件 

const translate = new MysKeyTranslate({
  appid: "",  // 你的appid  去百度开发者平台查看 http://api.fanyi.baidu.com/doc/21
  secret: "", // 你的密钥
});

["en", "jp"].forEach((item) => {
  translate(data, { from: "zh", to: item }).then((res) => {
    createFile(item, res);
  });
});

function createFile(fileName, fileContent) {
  fs.writeFileSync(
    `ZH_${fileName.toUpperCase()}.ts`,
    `export default ${JSON.stringify(fileContent)}`
  );
}

ZH_CN.ts文件内容如下:

module.exports = [
  {
    'home.title': '首页',
    'home.name': '首页名称',
    'book': {
      'name': '时间简史',
      'date': '公元前203年'
    }
  }
]

五、语言对应表

常用语言:

中文 	zh
英语 	en
韩语	kor
日语	jp
法语	fra
俄语	ru

完整版请参考百度翻译文档: http://api.fanyi.baidu.com/doc/21

六、后续

后面有时间的话我可以让这个不止在代码层面,还是上传到npm,完善一些功能,增加能命令直接生成其他语言版本的文件…

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐