最新要闻
- 因高度计算出错 导致日本“白兔号”撞上月球摔个稀碎
- 著名演员罗京民老师因病去世 曾出演高分电视剧《士兵突击》
- 舅舅党爆料 微软Xbox将在未来推出《星空》主题限定手柄与无线耳机
- 育碧天猫旗舰店将于6月7号停止运营 不再经营国内的衍生品销售业务
- 五月天演唱会于今日晚间在鸟巢举行 不少歌迷在场外听起演唱会
- 湖北多地遭遇暴雨 一高校学生宿舍内居然有鱼儿出没
- 特斯拉或向其他制造商开放部分汽车操作系统代码 与谷歌和苹果展开竞争
- 米哈游《崩坏:星穹铁道》手机版全球总营收已超1亿美元 超过《原神》
- 印度一新郎临阵脱逃 新娘狂追到20多公里外找到新郎
- 路过的小学生顺手把火灭了 网友:点赞机智勇敢的小少年
- 环球热门:骁龙影像旗舰“百花齐放”:哪一款是你的菜?
- 热消息:索尼Xperia 1 V为何不用一英寸主摄?背后原因揭开
- 环球热消息:桌面版RTX 4060 Ti啥水平?实测表现差强人意
- 《英雄联盟手游今日更新4.2版本 无限火力模式正式上线
- 《魔戒咕噜》Steam多半差评 首日仍有800人在线受苦
- 理想L系列车型推送更新:“小主人模式”上线 通讯
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
世界焦点!Node翻译i18n多语言文件,1分钟生成100种语言包
前言
在需要国际化的项目中经常会遇到写完代码后需要将文案翻译到其他很多国家语言,人工翻译再复制文案到对应 json 或 js / ts 文件,这样将会浪费大量时间且非常枯燥,所以很有必要开发一款 node 插件,将这些重复乏味的工作交给机器完成。话不多说,先展示成品再讲原理
(资料图)
插件链接
https://github.com/hymhub/language-translatelanguage-translate 是一款基于 Google 翻译在线转换 ts/js/json 多语言文件并批量生成或插入指定文件的插件,支持增量更新,可使用 bash 翻译单个文件,也能集成在项目中持续批量翻译,支持单文件转单文件,单文件转多文件,多文件转多文件,多文件转单文件
效果演示
正常翻译效果:
压力测试(1分钟内生成100种语言包):
原理
插件原理比较简单,核心是使用 node 读取文案文件,再利用 google 翻译 API 翻译文案后最终生成或写入结果,其中需要注意的是翻译前文案合并和翻译后文案拆分细节,此操作是翻译速度提升的关键
下面写一个简易版方便理解原理
安装依赖
首先安装以下 2 个依赖:
@vitalets/google-translate-api
:用于翻译文案tunnel
:用于网络代理(大陆无法直接使用 Google API)
注意版本,es6 和 conmonjs 模块化下载的版本不同,方便演示,以 conmonjs 为例
npm i @vitalets/google-translate-api@8.0.0npm i tunnel@0.0.6
编写翻译脚本
const fs = require("fs")const tunnel = require("tunnel")const google = require("@vitalets/google-translate-api")const googleTranslator = (text) => google( text, { from: "en", to: "zh-CN" }, { agent: tunnel.httpsOverHttp({ proxy: { host: "127.0.0.1",// 代理 ip port: 7890, // 代理 port headers: { "User-Agent": "Node" } } }) })// 读取 json 文案文件const sourceJson = require("./en.json")// 定义翻译方法const translateRun = async (inputJson) => { const sourceKeyValues = Object.entries(inputJson) const resultJson = {} for (let i = 0; i < sourceKeyValues.length; i++) { const [key, value] = sourceKeyValues[i] const { text } = await googleTranslator(value) resultJson[key] = text } return resultJson}// 将翻译结果写入硬盘translateRun(sourceJson).then(resultJson => { fs.writeFileSync("./zh.json", JSON.stringify(resultJson))})
将文案放入 en.json 例如:
{ "hello": "hello"}
执行此脚本就会发现目录下生成了 zh.json 的翻译结果:
{"hello":"你好"}
但是现在还不能递归翻译 json 内容,并且每一个 key 都调用了一次接口
先完成递归功能,递归翻译的实现方案有多种,考虑到后期文案合并/拆分减少 API 调用频率,这里采用 json 扁平化的方法
编写扁平化和反扁平化方法
const flattenObject = (obj, prefix = "") => { let result = {} for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const nestedKey = prefix.length > 0 ? `${prefix}/${key}` : key if (typeof obj[key] === "object" && obj[key] !== null) { const nestedObj = flattenObject(obj[key], nestedKey) result = { ...result, ...nestedObj } } else { result[nestedKey] = obj[key] } } } return result}const unflattenObject = (obj) => { const result = {} for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const nestedKeys = key.split("/") let nestedObj = result for (let i = 0; i < nestedKeys.length; i++) { const nestedKey = nestedKeys[i] if (!Object.prototype.hasOwnProperty.call(nestedObj, nestedKey)) { nestedObj[nestedKey] = {} } if (i === nestedKeys.length - 1) { nestedObj[nestedKey] = obj[key] } nestedObj = nestedObj[nestedKey] } } } return result}
扁平化方法的如何实现递归翻译?例如从传入:
const inputJson = { "hello": "hello", "colors": { "red": "red" }}flattenObject(inputJson) // { hello: "hello", "colors/red": "red" }// 此时进行翻译,例如结果是 { hello: "你好", "colors/red": "红色的" }// 再进行反扁平化unflattenObject(resultJson) // {"hello":"你好","colors":{"red":"红色的"}}
搞懂扁平化原理后接着改造 translateRun
方法:
const translateRun = async (inputJson) => { inputJson = flattenObject(inputJson) const sourceKeyValues = Object.entries(inputJson) const resultJson = {} for (let i = 0; i < sourceKeyValues.length; i++) { const [key, value] = sourceKeyValues[i] const { text } = await googleTranslator(value) resultJson[key] = text } return unflattenObject(resultJson)}
现在已经能进行递归翻译了,接下来进行翻译前文案合并和翻译后文案拆分,目的是为了减少 API 调用频率,也是大大提高翻译速度的核心
翻译提速
继续改造 translateRun
方法:
const translateRun = async (inputJson) => { inputJson = flattenObject(inputJson) let chunkValuesLength = 0 let chunk = [] const chunks = [] const sourceKeyValues = Object.entries(inputJson) sourceKeyValues.forEach(([key, value]) => { // Google 翻译单次最大字符长度 5000 字, 5 为占位分隔符长度 if (chunkValuesLength + value.length + 5 >= 5000) { chunks.push(chunk) chunkValuesLength = 0 chunk = [] } else { chunk.push({ key, value }) chunkValuesLength += (value.length + 5) } }) if (chunk.length > 0) {// 遍历完后检查不满 5000 字符的遗留 chunks.push(chunk) chunkValuesLength = 0 chunk = [] } const resultJson = {} for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i] const mergeText = chunk.map(v => v.value).join("\n###\n")// 合并文案 const { text } = await googleTranslator(mergeText) const resultValues = text.split(/\n *# *# *# *\n/).map((v) => v.trim())// 拆分文案 if (chunk.length !== resultValues.length) { throw new Error("翻译前文案碎片长度和翻译后的不一致") } chunk.forEach(({ key }, index) => { resultJson[key] = resultValues[index] }) } return unflattenObject(resultJson)}
现在放入大量文案在 en.json 文件,执行翻译脚本,假如文案有 1000 个 key 原本需要调用 1000 次接口,现在不到 10 次甚至不到 5 次即可翻译完成。
完整 demo:
const fs = require("fs")const tunnel = require("tunnel")const google = require("@vitalets/google-translate-api")const flattenObject = (obj, prefix = "") => { let result = {} for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const nestedKey = prefix.length > 0 ? `${prefix}/${key}` : key if (typeof obj[key] === "object" && obj[key] !== null) { const nestedObj = flattenObject(obj[key], nestedKey) result = { ...result, ...nestedObj } } else { result[nestedKey] = obj[key] } } } return result}const unflattenObject = (obj) => { const result = {} for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const nestedKeys = key.split("/") let nestedObj = result for (let i = 0; i < nestedKeys.length; i++) { const nestedKey = nestedKeys[i] if (!Object.prototype.hasOwnProperty.call(nestedObj, nestedKey)) { nestedObj[nestedKey] = {} } if (i === nestedKeys.length - 1) { nestedObj[nestedKey] = obj[key] } nestedObj = nestedObj[nestedKey] } } } return result}const googleTranslator = (text) => google( text, { from: "en", to: "zh-CN" }, { agent: tunnel.httpsOverHttp({ proxy: { host: "127.0.0.1",// 代理 ip port: 7890, // 代理 port headers: { "User-Agent": "Node" } } }) })// 读取 json 文案文件const sourceJson = require("./en.json")// 定义翻译方法const translateRun = async (inputJson) => { inputJson = flattenObject(inputJson) let chunkValuesLength = 0 let chunk = [] const chunks = [] const sourceKeyValues = Object.entries(inputJson) sourceKeyValues.forEach(([key, value]) => { // Google 翻译单次最大字符长度 5000 字, 5 为占位分隔符长度 if (chunkValuesLength + value.length + 5 >= 5000) { chunks.push(chunk) chunkValuesLength = 0 chunk = [] } else { chunk.push({ key, value }) chunkValuesLength += (value.length + 5) } }) if (chunk.length > 0) {// 遍历完后检查不满 5000 字符的遗留 chunks.push(chunk) chunkValuesLength = 0 chunk = [] } const resultJson = {} for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i] const mergeText = chunk.map(v => v.value).join("\n###\n")// 合并文案 const { text } = await googleTranslator(mergeText) const resultValues = text.split(/\n *# *# *# *\n/).map((v) => v.trim())// 拆分文案 if (chunk.length !== resultValues.length) { throw new Error("翻译前文案碎片长度和翻译后的不一致") } chunk.forEach(({ key }, index) => { resultJson[key] = resultValues[index] }) } return unflattenObject(resultJson)}// 将翻译结果写入硬盘translateRun(sourceJson).then(resultJson => { fs.writeFileSync("./zh.json", JSON.stringify(resultJson))})
结语
有了核心思路,其他的就是细节完善以及不断排坑了,例如合并拆分文案的特殊字符在不同语言会有异常,所以需要测试出不同语言所支持的特殊字符拆分方法,在翻译时根据不同语言使用不同的特殊字符进行分割,以及断点再续(文案太长,翻译中断导致浪费已翻译的文案)、增量更新等等,希望这篇文章对你有所帮助哦~
关键词:
世界焦点!Node翻译i18n多语言文件,1分钟生成100种语言包
因高度计算出错 导致日本“白兔号”撞上月球摔个稀碎
著名演员罗京民老师因病去世 曾出演高分电视剧《士兵突击》
舅舅党爆料 微软Xbox将在未来推出《星空》主题限定手柄与无线耳机
育碧天猫旗舰店将于6月7号停止运营 不再经营国内的衍生品销售业务
五月天演唱会于今日晚间在鸟巢举行 不少歌迷在场外听起演唱会
湖北多地遭遇暴雨 一高校学生宿舍内居然有鱼儿出没
特斯拉或向其他制造商开放部分汽车操作系统代码 与谷歌和苹果展开竞争
米哈游《崩坏:星穹铁道》手机版全球总营收已超1亿美元 超过《原神》
印度一新郎临阵脱逃 新娘狂追到20多公里外找到新郎
路过的小学生顺手把火灭了 网友:点赞机智勇敢的小少年
环球热门:骁龙影像旗舰“百花齐放”:哪一款是你的菜?
热消息:索尼Xperia 1 V为何不用一英寸主摄?背后原因揭开
环球热消息:桌面版RTX 4060 Ti啥水平?实测表现差强人意
《英雄联盟手游今日更新4.2版本 无限火力模式正式上线
《魔戒咕噜》Steam多半差评 首日仍有800人在线受苦
高精度加法(含代码)|天天快播报
《国家水网建设规划纲要》要点速览_全球微资讯
理想L系列车型推送更新:“小主人模式”上线 通讯
【聚看点】满意率超99%!小米13 Ultra站稳高端:雷军摆庆功宴
瑞松科技因信息披露违规等违规行为被上海证券交易所采取监管措施|新动态
PC、手机生态融合!Intel、腾讯一起找到了最好的路子
AMD RX 7600公版卡小翻车:6+2针电源线插不上 全球热点
99元 联想拯救者M5鼠标上架:8000 DPI、5档调节
00后折叠男孩首次手术成功:矫正脊柱至少90度
【新要闻】真人《芭比》曝美女足部特写:暴雪高管不淡定了
神马股份: 神马股份关于向不特定对象发行可转换公司债券2023年跟踪评级结果的公告
机械硬盘可以淘汰了 梵想4TB SSD硬盘1099元(满血性能+国产闪存)|每日视点
央视曝光李鬼搬家公司:说好1700元路上疯狂加价到9000! 天天热头条
刚出生就要上绞肉机 公鸡连生存的权利都没了
全球通讯!福特CEO:超长续航电动车很难赚钱 大电池成本太高了
今日报丨7)where子句
每日消息!记录--前端小票打印、网页打印
[ESP] ESP-IDF WiFi配网(SoftAP+HTTPD)代码备注_环球看点
用好Prompt 可以让AI更智能
宕昌县南阳镇综合养老服务中心改建项目中标公示|天天观热点
日本白兔航天器月球着陆撞个稀碎 原因公布:一个错误引发惨案
资讯推荐:消除汉字“数字鸿沟”!蚂蚁“汉字拾光计划”解决生僻字输入难题
环球观速讯丨退出手机市场已有两年!LG SmartWorld服务即将停止运营
男生炫酷“海胆头”参加毕业典礼:嗨翻全场
不必过分担忧大米产需缺口 |当前速讯
获取门禁记录方式-主动获取
软件开发全部文档下载(超过三百份)
天天观天下!一文看懂GPT风口,都有哪些创业机会?
今日要闻!如何把数据从 TDengine 2.x 迁移到 3.x ?
总结Vue3 的一些知识点:Vue3 计算属性
首次10nm以下!三星研发全新4F2内存芯片:面积缩减30% 每日讯息
3199元 铭凡UM790 Pro迷你主机上架:锐龙9 7940HS_每日速讯
OneUI 5.1确认存在锁屏Bug!三星:已进行修复
成都刮起剩菜盲盒风!便宜的临期食品供不应求 焦点播报
葫芦娃救爷爷 理想ONE陷车:拖车、飞度、一串越野车来救均动弹不得|环球快播报
如何改变孩子的坏脾气教育笔记-儿童教育
海通证券联合深交所走进广西开展金融帮扶专项活动-天天头条
最全iOS 上架指南_天天视讯
天天热头条丨Spring Boot + URule 规则引擎,可视化配置太爽了!
亚马逊平台API接口是什么?-全球头条
2 分钟,搞懂 SLO 最佳实践
全网最全Kubernetes(k8s)知识点,看着一篇就够了-当前热讯
【财经分析】推动“1到100”的跨越式发展 长三角崛起生物医药产业“新基建”高地
天天快讯:缩减5G基站招标规模 大幅减少5G投资?中移动回应:外界误读
天天关注:又一个满血14GB/s!PCIe 5.0 SSD用上巨型风扇 太过分了
抗原检测盒优惠了!50人份到手19.9元
白玉兰奖入围名单揭晓 网友:正午阳光赢麻了
抛弃反人类半幅方向盘 特斯拉Cybertruck电动皮卡实车图:今年必交付 当前热门
环球要闻:渗透测试之Payload
7个工程应用中数据库性能优化经验分享
焦点热议:Prometheus笔记-告警规则配置
北京市2023年新增地方政府债务限额1117亿元
收评:沪指午后反弹涨0.35% AI赛道股发力 新能源行业低迷_天天信息
武磊:客场也能感受球迷的热爱和支持 超越一切对立_天天最资讯
手机性价比被吐槽 HTC对元宇宙是真爱:不认同降温说、非常乐观
Redmi性能王者!K60 Ultra工业设计图曝光 前沿资讯
《王者荣耀》体验服爆料:中单法师狂喜 斩杀史诗级优化
全球热消息:导航出错驶入紧急停车带 驶出时被撞 科普:紧急停车带该怎么用
全球视点!内存连续三个季度暴跌 三大厂疯狂减产!想涨价?没门儿
快报:做数据分析的常用方法有哪些?
尚硅谷Hadoop的WordCount案例实操练习出现的bug-环球新视野
JS 里如何实现异步?
如何在上架App之前设置证书并上传应用|全球今头条
海贼王:“七武海”原型揭秘!居然来自30年前游戏《浪漫沙加2》
举报比亚迪排放不达标!长城汽车晒业绩:1-4月同比增长99.1% 买它还是比亚迪?_每日精选
焦点播报:16核R9 7945HX加持!联想公布新版拯救者R9000P参数
世界百事通!法拉第未来官宣:FF 91第一阶段交付5月31日开始 车主先培训
环球视讯!促进跨区域产业链、供应链、创新链、资金链、人才链深度融合,一大批长三角G60科创走廊跨区域合作重点项目签约
广州市花都区秀全中学:720分以上学生可以考虑秀中清北班-世界微头条
易基因:MeRIP-seq等揭示m6A reader YTHDF1在结直肠癌PD-1免疫治疗中的作用|Gut 全球实时
手把手教你在昇腾平台上搭建PyTorch训练环境_天天新要闻
今日上映!《小美人鱼》豆瓣评论:难以接受黑人鱼、强凑CP、毁童年-焦点热门
即时:宝德暴芯x86处理器现身GeekBench 5:坐实就是Intel i3-10105
知名演员罗京民去世 曾饰演许三多的爹:张译等人发文悼念 焦点速递
ST浩源:截至2023年5月20日,公司股东14658户,谢谢对公司的关注!|天天最新
每个.NET开发都应该知道的10个.NET库
关于AWS中VPC下的IGW-internet gateway的创建与说明
升级天玑8200处理器:vivo S17 Pro现身Geekbench
曝小米13 Ultra欧洲售价超1.1万元:比iPhone 14 Pro、华为P60 Pro都贵_世界今日讯
全球球精选!破首发仅7499元!华硕天选4游戏本配锐龙9与RTX4060:高性价比真香
热点!散了吧!特斯拉车顶维权女车主败诉:刹车失灵观点站不住脚 没任何证据证明
《变形金刚7:超能勇士崛起》超燃特辑出炉:保时捷964街头飞车_热资讯
Netty实战(三)
上市公司实控人离婚140亿归女方,盘点彤程新材投资版图