找回密码
 会员注册
查看: 15|回复: 0

国际化翻译平台文档解析2.0技术原理解析_UTF_8

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
74108
发表于 2024-9-30 18:57:35 | 显示全部楼层 |阅读模式
字节跳动国际化翻译平台为业务提供高效专业的【平台+服务】本地化一站式解决方案,简化本地化管理流程,提高多语言内容管理效率,助力产品出海。除文案管理外,国际化翻译平台 也提供文案、文档、视频等多模态翻译服务。目前不仅为公司内部大部分业务线提供服务,还通过火山引擎为外部客户提供服务,详见 https://www.volcengine.com/product/i18ntranslate。一、背景随着业务快速发展,国际化翻译平台文档翻译从最初仅支持 word 文档,到目前支持 8 种文档类型,其中核心 Node.js SDK(以下称为“文档解析 1.0”)变得越来越难以维护,究其根本,文档解析 1.0 对于每种文档类型的解析、还原、机器翻译、字数计算等能力,都单独维护一套逻辑,其优势是在文档解析开发的 0-1 阶段可以快速完成每种格式的能力建设,但随着后期优化需求以及新文档格式的增加,这种独立式架构的弊端会愈发凸显——改动某个逻辑需要同时修改 N 套代码、新接入一种文档格式需要重新编写几乎完全重复的业务逻辑。为了更好的支持文档翻译业务的高效维护与未来更多文档格式扩展的需求,有必要对现有架构进行一次彻底的重构升级。二、重构收益SDK 核心逻辑代码量减少 70%+(6000+ 行 -> 2000 行)受益于新架构的高可维护性与可扩展性:a. lark 文档 2.0 还原效率提升 10 倍以上b. 使用 SDK 的 Node BFF 项目文档解析相关逻辑代码量降低 80%+c. 实现了基于 TS Decorator 与 FaaS 的 SDK 数据可视化我们可以看到重构收益还是比较明显的,那么文档解析 2.0 的新架构具体是怎么样,以及是如何实现的呢?接下来的技术原理部分会进行详细解析。三、技术原理3.1 架构设计让我们回到文档解析与还原的本质,解析的本质是将文档对应格式的文件转化为一套目标 DSL,而还原的本质则是将目标 DSL 转化为文件 DSL 然后生成文件。也就是说,所有类型文档的解析与还原流程都可以抽象为如下过程:因此,可以将解析与还原抽象为文档的底层能力。在国际化翻译平台的业务场景下,文档经过解析之后需要转化为句段(Segments,如果有样式信息,则存储在每个 segment 的标签中),对于 segments 化之后的数据,有分句(根据标点符号进行句段划分)、机器翻译、统计字数、合并句段等需求。对于这些功能层面的需求,参考 TCP/IP 四层模型的话,可以类比为最上层的应用层。这样我们便有了底层解析与还原能力层,以及上层的应用层。但光有这两层架构还无法达到我们的最终目标,为什么呢?因为对于每种不同的文档类型,其解析后的数据结构都是各不相同的,而不同的数据结构就需要应用层的每个功能针对不同的数据结构做适配,随着不同文档数据结构的增多,在应用层做适配的复杂度也会呈指数级上升。如何解决这个问题呢?一个经典设计模式——Adapter(适配器)模式可以给我们答案。我们可以在底层与应用层中间,加入一个 Adapter 层,将每种文档的解析结果统一为一致的 DSL。如此一来,国际化翻译平台文档解析 2.0 的三层架构便呼之欲出:其中底层 parser 层负责所有文档的解析与还原,最上层 feature 应用层负责统一的能力暴露,中间层 adapter 则负责将 parser 层的解析与还原结果适配为统一 DSL。3.2 分层实现解析完总体架构之后,我们可以继续往下深入,看看各层的具体实现。其中 Parser 层设计如下:classSomeTypeParser{constructor(config){}@type2bridgeparse(){}@bridge2typerestore(){}}细心的读者可能发现了 parser 的实现借助了 ts decorator,这部分会在 3.3 SDK 数据可视化部分详解。这里可以先略过这部分。Feature这里需要前置说明一下 CAT 的含义,CAT 的意思是计算机辅助翻译(Computer aided translation,CAT),也是国际化翻译平台文档解析的目标,国际化翻译平台经过文档解析生成 segments,翻译人员在由 segments 所构成的 CAT 编辑器中进行翻译操作。国际化翻译平台 CAT 编辑器目前如下图所示:Feature 层设计如下:classCAT{//文档解析,返回结果就是segmentsadaptCAT(type,config){}//机器翻译adaptCATWithMT(){}//字数统计countWords(){}//生成文档genDoc(){}//自定义解析器apply(type,parser){}}AdapterAdapter 层主要是以下两个方法,分别是文档数据转中间层数据,与中间层数据转文档数据。exportconsttype2bridge=()=>{}exportconstbridge2type=()=>{}统一 DSL{blockId:stringelements:{type:string//文本:text, 其他:other,对应着单、双标签textRun:{style:Object//样式content:string//文本}location:{start:numberend:number}}[]}以上便是国际化翻译平台文档解析 2.0 架构的设计过程与分层实现。接下来对 SDK 中 decorator 的使用做更详细的解析,有了它的帮助,SDK 底层的解析、还原与数据可视化的具体实现变得更加简洁。3.3 Decorator 的使用与 SDK 数据可视化Decorator 可以用来增强类中的方法,使其具备额外的功能,提供增强后的接口。以国际化翻译平台 TXT 文档 parser 层的具体实现为例,exportdefaultclassTxtParser{@txt2bridgeasyncparse(token:string|Buffer){constbuffer=path2buffer(token)return[buffer.toString()]}@bridge2txtasyncrestore(blocks:string[],_raw:string){return{buffer:Buffer.from(blocks.join())}}}我们可以看到两个装饰器方法txt2bridge与bridge2txt,实现如下:exportconsttxt2bridge=proxyForReturnValue(function(blocks){constbridgeBlocks=blocks.map((block,index)=>{constbridgeBlock:Bridge.Block={style:{},blockId:index+'',elements:[{type:'text',textRun:{content:block,style:{}},location:{start:0,end:block.length}}]}returnbridgeBlock})return{raw:JSON.stringify(blocks),blocks:bridgeBlocks}})exportconstbridge2txt=proxyForParam(function(blocks,_raw){returnblocks.map(block=>{returnblock.elements.map(ele=>ele.textRun.content).join()})})我们可以观察到这两个装饰器并不是直接实现的,是经过proxyForParam与proxyForReturnValue而间接实现,这里又涉及到了 proxy 设计模式的使用,用来对返回值与函数参数做一次转化。这两个代理方法的具体实现如下:Proxy 被用于做访问控制(access control),对本身想要访问的对象做转化,并且对外提供相同的接口。exportfunctionproxyForReturnValue(proxythis:THIS,data:T,config:C)=>Bridge.Data|Promise ){returnfunction(_target:any,_propertyName:string,descriptor:TypedPropertyDescriptor){constmethod=descriptor.valuedescriptor.value=asyncfunction(token:string,config:C){constdata=awaitmethod.call(this,token,config)returnawaitproxy.call(this,data,config)}}}exportfunctionproxyForParam(proxythis:THIS,blocks:Bridge.Block[],raw:string|Buffer,config:C)=>T|Promise){returnfunction(_target:any,_propertyName:string,descriptor:TypedPropertyDescriptor){constmethod=descriptor.valuedescriptor.value=asyncfunction(blocks:Bridge.Block[],raw:string|Buffer,config:C){returnawaitmethod.call(this,awaitproxy.call(this,blocks,raw,config),raw,config)}}}通过上述实现,我们可以看到 Decorator 真正起作用的语句(descriptor.value = () => {})在这两个 proxy 方法内实现。通过 proxy 设计模式的使用,我们可以将 Decorator 的实现与业务逻辑进行解耦,更加便于后续维护。通过 Decorator,我们可以对方法做增强处理,使之具备更多的能力,因此,解析与还原过程中的数据收集也可以通过 Decorator 来实现。在 TxtParser 中,我们只需要增加一个trace装饰器即可:exportdefaultclassTxtParser{@trace('txt','parse')@txt2bridgeasyncparse(token:string|Buffer){ constbuffer=path2buffer(token) return[buffer.toString()]}@trace('txt','restore')@bridge2txt asyncrestore(blocks:string[],_raw:string){return{buffer:Buffer.from(blocks.join())}}}trace装饰器实现如下:exportfunctiontrace(docTypeocType,operateType:'parse'|'restore'){returnfunction(_target:any,_propertyName:string,descriptor:TypedPropertyDescriptor){//prepareconstmethod=descriptor.valuedescriptor.value=asyncfunction(...args){try{//...constres=awaitmethod.apply(this,args)//信息收集,例如操作时间、CPU、内存等消耗情况//通过FaaS上报文档操作过程中的统计信息returnres}catch(error){//错误处理,通过FaaS上报错误信息}}}}我们在装饰器中进行信息收集,并把收集到的数据上报到 FaaS 平台之后,我们就可以开发一个运营后台来将这些数据进行可视化展现,这也是国际化翻译平台的文档分析后台的由来。3.4 Larkdocx 批量更新关键实现最后可以再分享一下国际化翻译平台文档解析 2.0 中 Larkdocx 批量更新实现的一个关键。起初,国际化翻译平台对飞书文档 2.0 的还原速度非常慢,对于大型文档来说,平台几乎无法正常导出,且飞书开放平台有同篇文档 QPS iteratorFn(item));result.push(p);executing.add(p);constclean=()=>executing.delete(p);p.then((r)=>rretry.push(Promise.resolve().then(()=>iteratorFn(r))):clean()).catch(clean);if(executing.size>=poolLimit){awaitPromise.race(executing);}}returnPromise.all(result).then(()=>romise.all(retry));}//UsageasyncbatchUpdateBlock(blocksark.Block[],documentId:string){constblockUpdates=blocks.map(block=>this.getUpdateForBlock(block)).filter(Boolean)constchunkedBlockUpdates=chunk(blockUpdates,MAX_BATCH_SIZE)constbatchUpdateWithErrorHandle=asyncchunk=>{returnawaitthis.lark.batchUpdateBlockForDocx(documentId,{requests:chunk}).then(resp=>{//如果遇到无权限报错,需要进行降级处理if(resp.data.code===LARK_BLOCK_UPDATE_ERROR_CODE.ForBidden){returnthis.downgradeUpdatesForBlocks(chunk)}//...其余兜底处理操作returnnull})}//3的QPS限制,用batchUpdateWithErrorHandle中的then条件判断决定是否降级处理与重试awaitconcurrentFetch(3,chunkedBlockUpdates,batchUpdateWithErrorHandle)}四、总结通过对国际化翻译平台文档解析当前所面临的问题分析,将 SDK 中解析与还原、各种翻译能力还原到本质,最终抽象出一套适配当前需求与未来发展的三层架构——文档解析 2.0。其中的具体实现使用了 Decorator 装饰器、Adapter 与 Proxy 设计模式、限流调度器等等,从中我们也可以看出平时似乎不太常用的语法、设计模式与热门面试题等等其实都有它们各自发挥作用的场合,这也是为什么我们要更加注重平时基础知识积累的原因——所有强大的上层实现,都离不开底层更强大的基础知识。加入我们点击“阅读原文”马上加入我们!和优秀的人一起做有挑战的事,你的技术与创意将影响亿级用户,激发创意、丰富生活!参考资料[1]updateBlock: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document-block/patch[2]批量更新块接口: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document-block/batch_update点击上方关注追更不迷路
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-14 04:08 , Processed in 1.178184 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表