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

React项目国际化:实现自动组装解决方案

时间:2023-03-28 00:38:07 HTML

本方案提供插件式前端项目国际化实现方案,由于某些原因可以在一开始就支持国际化,几乎不需要修改原有业务后续代码中的国际化都无一例外的支持了。使用构建工具实现不涉及业务开发的国际化解决方案。国际开发过程中的流程一般是这样的:前端开发工程师遇到中文,需要先设计一段代码。通常,为了避免代码重复,他也需要遵守一定的规则,随着业务迭代变得越来越冗长;然后import国际化多语言工具函数调用国际化多语言函数;然后翻译和维护国际化的配置数据;如果国际化数据放在数据库中,支持在线动态配置,也需要将数据发送到后台,在系统中统一维护。整个过程冗长,需要不同人员的配合,容易出现问题。例如使用react-intl-universal支持国际化:importintlfrom'react-intl-universal';//当初始化代码在整个系统的入口文件中时。intl.get('简单').d('简单');假设开发了一个前端翻译工具,当代码中遇到中文时,会自动导入国际化的工具函数,按照一定的规则自动生成代码,将原来的中文代码替换为国际化的调用函数,然后整个工程编译完成后,收集所有的国际化语言数据,可以直接生成国际化语言的配置文件,也可以生成一定的结构化数据插入数据库。根据这个思路,可以实现一个自动组装项目国际化的解决方案。在该方案中,前端开发工程师在开发时无需关注国际化,可以获得与不需要国际化支持的项目相同的开发体验,可以更专注于业务开发。同样,这个方案是一个基础支持,以挂载的形式,可以快速支持一个一开始不支持国际化,后来因为开发需要面向国际化的项目。同样,本方案着重于如何自动生成国际化多语言功能的调用代码,不限制使用某种国际化框架。您可以根据实际需要选择任意一种国际化框架,然后进行代码转换以供使用。该解决方案仅针对简单的国际化需求。对于一些复杂的需求,比如金额、日期等,还是需要手动使用一些国际化框架的API。但是在一个项目中,最多的应该是对一些简单的展示文字的国际化支持。从程序设计的角度来看,主要分为两部分:分析代码:遇到中文时,翻译成国际化的函数调用语句。收集信息:收集分析代码过程中转换语句的信息,用于生成配置数据。这两部分分别用两种刀具加工。代码分析工具分析代码实现??一个babel插件在翻译js代码时进行中文国际化处理。中文文本主要是字符串或者是模板字符串,所以只需要对这两条语句进行解析和转换,即babel插件需要处理StringLiteral和TemplateLiteral语句。那么插件的主要结构是:module.exports=(babel)=>{visitor:{StringLiteral(path,state){},TemplateLiteral:{enter(_path,state){},},},};TemplateLiteral处理起来比较复杂,所以以StringLiteral为例来说明一下关键逻辑。在StringLiteral语句中,分析字符串是否包含中文,使用正则判断:StringLiteral(path,state){const{node}=path;consttext=node.value;如果(str.search(/[^\x00-\xff]/)===-1){返回;}},如果不包含中文,不做处理直接返回。如果包含中文,则转换为国际化导入函数(使用react-intl-universal库):constintlMember=t.memberExpression(t.identifier('intl'),t.identifier('get'),false,false,);//代码生成,这里直接使用中文作为代码。如果怕乱码等问题,//可以使用md5编码或者根据实际规则和文件路径生成编码constcodeText=text;constcodeTextNode=t.stringLiteral(codeText);//求解codeTextNode.extra={rawValue:codeText,raw:`'${codeText.split("'").join('\\\'').split('\n').join('\\\n')}'`,};constintlCall=t.callExpression(intlMember,[codeTextNode]);constmemberExpression=t.memberExpression(intlCall,t.identifier('d'),false,false);让fnNode=t.callExpression(memberExpression,[node]);constparentNode=_.get(path,'parentPath.node');if(t.isJSXAttribute(parentNode)){fnNode=t.jsxExpressionContainer(fnNode);}path.replaceWith(fnNode);所以对于textconsttext='ChineseChinese';会转化为:consttest=intl.get('Chinese').d('Chinese');上面intl是硬编码直接使用的,需要依赖入口文件把intl函数放到全局对象中中:importintlfrom'react-intl-universal';window.intl=国际;但为了更高的可扩展性,可以使用代码自动导入,在转换代码之前,先导入国际化的多语言函数:constnode=addDefault(path,'react-intl-universal',{nameHint:'intl'});constintlLibName=节点名称;constintlMember=t.memberExpression(我ntlLibName,t.identifier('get'),false,false,);babel工具库@babel/helper-module-imports中的addDefault函数可以生成默认导入组件库的语句,不会跟其他变量命名冲突,如果使用其他库,可以修改生成导入语句的相应方式。这样,上面的文字就会转化为:importintlfrom'react-intl-universal';consttest=intl.get('中文').d('中文');如果当前文件已经手动导入,如:importintlfrom'react-intl-universal';consttest=intl.get('代码').d('现有文本');consttext='中文';将被转换为:importintlfrom'react-intl-universal';consttest=intl.get('代码').d('现有文本');consttext=intl.get('中文').d('中文');这样就实现了核心代码,保存处理后的信息供后续采集:module.exports=(babel)=>{constrecords=newMap();访问者:{程序:{enter(_1,state){records.clear();},exit(_1,state){const{文件名:filePath}=state;const_records=Array.from(记录);//保存数据records.clear();},},StringLiteral(path,state){//转换代码records.set(codeText,text);},},};保存数据需要特殊处理,因为在webpack4或者5中,一般都是以多进程的方式构建的,所以不能简单的放在内存中,可以放在文件系统中。而且还要解决多进程操作文件的锁问题。将每个可以解析的js文件的信息放在一个单独的文件中,或者将同一个进程的信息放在一个文件中,避免锁竞争。对于TemplateLiteral的处理,由于模板可能极其复杂,有多个文本变量区间,还可能嵌入其他字符串或模板语言,需要特殊处理,比如对于模板语句:consthasChineseTemplate=`Chinese${someVars}ChineseChinese${1+1+'你好'}哈哈哈`;推荐两种处理方式:用国际化函数替换完整模板:consthasChineseTemplate=intl.get('code').d(`Chinese${someVars}ChineseChinese${1+1+'Hello'}哈哈哈`);模板中的单个项目作为国际化函数处理:consthasChineseTemplate=`${intl.get('code1').d('Chinese')${someVars}}${intl.get('code2').d('ChineseChinese')${1+1+intl.get('code3').d('Hello')}${intl.get('code4').d('哈哈哈')}`在实际执行过程中,还需要处理重复解析的问题。由于babel的架构和插件基本上都会先执行,后续插件转换时,可能会触发插件重新启用,会重复解析同一个语句在实际意义上,这需要一种标记后处理的机制。我实现了一个工具库babel-plugin-i18n-chinese。这个工具已经在线运行了一年,解决了一些常见的问题,并提供了尽可能多的可扩展性。信息收集工具收集代码的工具,可以使用编译后生效的webpack插件。信息采集工具需要处理的功能比较简单。根据代码分析工具存储国际化数据的方式,获取数据,然后根据实际需要生成数据文件。唯一需要注意的是,webpack插件需要编译后才能生效,即插件需要这样注册:compiler.hooks.done.tapPromise(this.constructor.name,async()=>{//获取分析工具生成的数据//输出数据文件});},}我实现的对应插件webpack-plugin-i18n-chinese。

猜你喜欢