最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 基于百度翻译API的node插件

    正文概述 掘金(mySkey)   2020-12-16   561

    背景

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

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

    一、百度API

    • 文档地址

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

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

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

    基于百度翻译API的node插件

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

    基于百度翻译API的node插件

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

    二、该插件优点

    • 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,自然会被我合并,就能一次翻译完成:

    基于百度翻译API的node插件

    三、插件源码

    const md5 = require("md5-node");
    const http = require("http");
    
    const baiduApi = "http://api.fanyi.baidu.com/api/trans/vip/translate";
    
    function request(fanyiApi) {
      return new Promise((resolve, reject) => {
        http
          .get(fanyiApi, (res) => {
            const { statusCode } = res;
            const contentType = res.headers["content-type"];
    
            let error;
            if (statusCode !== 200) {
              error = new Error(`状态码: ${statusCode}`);
            } else if (!/^application\/json/.test(contentType)) {
              error = new Error(
                "无效的 content-type.\n" +
                  `期望 application/json 但获取的是 ${contentType}`
              );
            }
            if (error) {
              console.error(error.message);
              // 消耗响应数据以释放内存
              res.resume();
              return;
            }
    
            res.setEncoding("utf8");
            let rawData = "";
            res.on("data", (chunk) => {
              rawData += chunk;
            });
            res.on("end", () => {
              try {
                const parsedData = JSON.parse(rawData);
                resolve(parsedData);
              } catch (e) {
                console.error(e.message);
              }
            });
          })
          .on("error", (e) => {
            reject(e);
          });
      });
    }
    
    function MysKeyTranslate(config) {
      this.config = config;
    
      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.isRequesting) {
          return new Promise((resolve) => {
            setTimeout(() => {
              this.requestApi(value, parames).then((res) => {
                resolve(res);
              });
            }, 1000);
          });
        }
        this.isRequesting = true;
        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(baiduApi, fromData);
        // console.log("fanyiApi", fanyiApi);
        return new Promise((resolve) => {
          request(fanyiApi)
            .then((res) => {
              console.log("翻译结果:", res);
              if (!res.error_code) {
                const resList = res.trans_result;
                resolve(resList);
              }
            })
            .finally(() => {
              setTimeout(() => {
                this.isRequesting = false;
              }, 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
    }
    
    module.exports = 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
    

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

    六、后续

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


    下载网 » 基于百度翻译API的node插件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元