当前位置: 首页 > Web前端 > HTML

前端国际化,从0开始

时间:2023-03-28 11:00:36 HTML

两周前接到了一个国际化前端项目的请求。为了书写方便,假设我们只需要中英文两个版本。手动替换一开始觉得很简单:把所有的文本替换成变量,按照配置读取即可。好在产品只需要一部分国际化项目,需要替换的文字不多。边钓鱼边复制粘贴花了三天时间,终于全部替换完成。遇到的问题然而,手动替换除了不需要动脑之外没有任何优点:需要给每个文本一个变量名。需要确定每段文本所属的域。对于一个大项目,文中必然会有大量的重复。当不需要国际化时,文本会在其位置进行硬编码。但是,当你需要提取它们的时候,你需要纠结一下,这两个相同的文本是整个系统中的同一个文本,还是只是恰好相同的两个不同的文本。确定这个也是一件很纠结的事情。如果不解决这个问题,以后修改文本的时候,可能会把两个不同的文本误提取为同一个变量,导致以后无法维护。需要不断的复制和粘贴。开发的时候需要把文本改成变量,以后开发起来会很麻烦。解决方案但是我已经更换了,只能祈祷产品不要再提要求了。但这是不可能的,终于在一周后,我收到了一个要求将整个项目国际化的请求。打开vscode并使用正则表达式全局搜索。我惊奇地发现,如果我继续手动替换,还有400个文件在等着我修改。所以只能想办法搞定自动国际化。AutomaticinternationalizationAutomaticinternationalizationpre-requirements如果要进行自动国际化,必须具备这两个能力:能够将Vue源代码编译成AST,并将修改后的AST转换成Vue源代码。能够编译TS源码,并将修改后的ConvertAST转为TS源码。Vue转AST可以使用vue-template-compiler将vue的模板部分转成AST。vue-template-compiler的compile方法的空格需要设置为condense。生成的AST中会删除冗余的空白文本节点,否则会因为这些冗余节点而导致布局混乱。constcompiler=require("vue-template-compiler");const{ast}=compiler.compile(source,{whitespace:"condense",});修改Vue模板AST树。只需修改node.text和node.attrsMap这两个属性即可。AST转Vue我没有找到成熟的AST转VUE包,只找到这个:vue-template-ast-to-template。这个包有很多错误。例如,将生成重复的属性,命名槽将丢失。幸运的是,相比于AST设计和编译的知识,从AST中获取Vue源码的逻辑还是很简单的。我们稍微改变了这个包来解决这些问题。解决了。importTemplateTransformfrom"./src/vue-template-transformer.mjs";consttransformer=newTemplateTransform();//传入vue-template-compiler创建的astconst{code}=transformer.generate(ast);搜索Vue模板AST树。首先,我们需要递归搜索Vue模板的AST树。在树结构中,一般的子节点都存储在名为children的属性中,但是vue模板的AST有些特殊。下面三个节点都保存子节点children:大部分子节点都保存在这里ifConditions:相邻的v-if,v-else-if,v-else节点都保存在这个属性中,在搜索整棵树的时候,需要找到这三个属性中保存的所有子节点,否则会漏掉一些节点。下面是我写的搜索Vue模板AST树的方法,供参考。其中,由于某个节点的ifConditions会包含自己,所以需要用变量来记录该节点是否处理完毕,防止死循环。函数vueAstLooper(node,cb){if(node.__scanned__)return;node.__scanned__=true;cb(节点);constchildren=[...(node.children||[]),...Object.values(node.scopedSlots||{}),...(node.ifConditions||[]).map((k)=>k.block).filter((k)=>!k.__scanned__),];if(children.length>0){children.forEach((item)=>vueAstLooper(item,cb));}};然后,需要在树中找到包含中文的节点和属性。对于文本节点,文本保存在node.text中的属性,文本保存在node.attrsMap{ref:"cp-table",class:"adjust-tab-margin","@change":"queryRuleList",":loading":"ruleListLoading",}遍历修改TS的AST使用Babel全家桶:constparser=require("@babel/parser");const_traverse=require("@babel/traverse");const_generate=require("@babel/generator");const_template=require("@babel/template");consttemplate=_template.default;consttraverse=_traverse.default;constgenerate=_ge生成默认值;constast=parser.parse(source,{sourceType:"unambiguous",plugins:["typescript","decorators-legacy"],});traverse(ast,{StringLiteral(path){//在这里处理普通字符串constoriginString=path.toString();path.replaceWith(template.ast(`"newString"`));path.skip()},TemplateLiteral(path){//在这里处理模板字符StringconstoriginString=path.toString();path.replaceWith(template.ast(`"newString"`));path.skip()},});TS的AST因为使用了decorator而还原为TS,所以需要将decoratorsBeforeExport设置为true,否则生成的代码中exportdefault的位置是错误的const{code}=generate(ast,{decoratorsBeforeExport:true,});自动国际化步骤的前置技能要求已经准备就绪,可以开始了。第一步是使用nodejs编写脚本来提取文本。将每个文件中的代码转换为AST,从AST中提取并保存所有中文文本。将所有扫描的中文文本保存在zh.json文件中。键是文本的哈希值,值是文本本身。注意必须是原文的哈希值。{"e9e8054f8b9b30a5bc0eab3aa4645f9c":"Mail",}得到zh.json文件后,复制一份,然后翻译得到en.json文件。{"e9e8054f8b9b30a5bc0eab3aa4645f9c":"Email",}在项目源码中,我们写了"Email"这个词,它的哈希值为e9e8054f8b9b30a5bc0eab3aa4645f9c。这个hash作为一个key,它的对应关系可以从中英文双语的zh.json和en.json文件中找到。第二步是写一个webpackloader。在loader中获取vue和ts文件中的文本,将文本的hash取hash,在之前保存的zh.json和en.json中找到需要的翻译,取得到的翻译,在代码中替换文本节点。这样,基本上,核心功能,就搞定了。但是不要沾沾自喜,你还需要解决这些问题:如何解决Vue的模板变量很多时候,模板变量会很简单,用正则表达式就可以轻松提取出someAppName变量

是吗确定要删除{{someAppName}}吗?
但是我自己动手写的太复杂了,那正则表达式就不行了。
您确定要删除{{booleanA?firstAppName:booleanB?secondAppName:thirdAppName}}吗?
因为在vue的template变量中可以写任何合法的js表达式,所以需要将{{}}之间的代码提取出来,交给babel解析,这里可以复用解析ts文件的代码.如何解决TS的模板字符串`Areyousureyouwanttoremove{{booleanA?firstAppName:booleanB?secondAppName:thirdAppName}}?`从表面上看,TS模板字符串和Vue模板变量是同一个问题。但事实并非如此。因为Vue的模板变量还是可以解析成TS代码的,TS本身已经是代码了,babel默认解析了。过于细化地解析段落中的变量会丢失上下文。如果不是整个段落:您确定要删除{{booleanA?firstAppName:booleanB?secondAppName:thirdAppName}}吗?保存,但进一步分析,保存为Areyousureyouwanttodeleteand?进入json文件。当你做翻译工作时,你会失去上下文,你不知道吗?干什么用的,也不知道你确定要删除什么。不是很好。因此,在国际化的时候,也需要规范代码,不要在模板变量和模板字符串中写过于复杂的表达式。总结因为两周前开始做国际化的时候,我对国际化一无所知,除了能想到准备翻译,运行时替换。如果我的方法中存在错误或漏洞,那是完全正常的。请帮我指出来。