什么是国际化
国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式。它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素。换言之,应用程序的功能和代码设计考虑在不同地区运行的需要,其代码简化了不同本地版本的生产。开发这样的程序的过程,就称为国际化。
背景前期未使用国际化,只采用中文的项目,突然来个需求需要完成国际化开发,那就意味着有大量的文字需要进行替换。首先就得需要提取出中文,翻译中文,然后再进行vue-i18n国际化替换。这无疑是个庞大的项目。经过前期一个一个替换,眼睛盯着屏幕,连续加班加点替换搬砖,直接累成马。不经的怀疑程序员不该这样,于是乘着周末时间研究了下更简单快捷的方式来完成这个任务。
前期涉及到读文件,写文件,作为前端肯定是想到使用node来实现。首先需要将整个项目中的中文提取出来,交给翻译官翻译。这就需要遍历整个文件夹,说到这是否有想到什么?没错,webpack中require.context这个API就有类似的功能,但是这终究是webpack的功能,在node是不可使用的,于是前期工作就是模仿require.context实现一个能在node上使用的类似的功能。
模仿实现require.context功能//context.jsconstpath=require(path)constfs=require(fs)//获取文件的具体信息constgetPathInfo=p=path.parse(p)/***
description://递归读取文件,类似于webpack的require.context()*param{String}directory文件目录*param{Boolean}useSubdirectories是否查询子目录,默认false*param{array}extList查询文件后缀,默认[.js]*return{array}fileList文件的路径数组*author:syx*/functioncontext(directory,useSubdirectories=false,extList=[.js]){constfilesList=[]//递归读取文件functionreadFileList(directory,useSubdirectories,extList){constfiles=fs.readdirSync(directory)files.forEach(item={constfullPath=path.join(directory,item)conststat=fs.statSync(fullPath)if(stat.isDirectory()useSubdirectories){readFileList(path.join(directory,item),useSubdirectories,extList)}else{constinfo=getPathInfo(fullPath)//将文件路径存放到filesList中extList.includes(info.ext)filesList.push(fullPath)}})}readFileList(directory,useSubdirectories,extList)returnfilesList}module.exports=context//app.jsconstpath=require(path)constcontext=require("./context")console.log(context(path.join(__dirname,./test),true))
新建一个test目录用于测试
test
testChild
child-test1.js
child-test2.js
test1.js
test2.js
app.js
context.js
执行node命令nodeapp.js就会得到如下结果
如图所示就使用context方法拿到了test文件夹下的所有路径。接下来就是尝试获取测试目录文件下的中文集合
通过路径获取中文内容集合在测试文件大概输入测试内容如下
test1.vuetemplatedivh1placeholder="测试test1"测试test1/h1/div/templatescriptexportdefault{data(){return{name:"测试一",data:测试二,data1:《测试二恶趣味群二》data2:发顺丰三发《测试二》发大厦的}}}/scriptchild-test/child-test1.vuetemplatedivh1placeholder="子测试test1"test1子测试/h1h1placeholder=子测试test2子测试test2/h1h1placeholder=子2测试子1测试/h1h1placeholder=子测试子测试/h1h1placeholder=子测试a子测试a/h1/div/templatescriptexportdefault{data(){return{name:"子测试一",data:子测试二}}}/script
由于项目组大多是在vue中使用,因此上述的测试文件统统改为.vue文件
新建一个getChineseArr.js文件暴露一个根据数组文件路径读取文件中引号或者包着的中文字符串。大致内容如下
constfs=require("fs")functiongetChineseArr(fileList){letarr=[]fileList.map(item={constcontent=fs.readFileSync(item,utf8)//匹配单引号包含中文或者双引号包含中文的或者XXX数据letresult=content.match(/("\s*((\w
\(
《
》
(
)
\))*[\u4E00-\u9FA5]{1,}(\w
\(
《
》
(
)
\))*)+\s*")
(\s*((\w
\(
(
)
《
》
\))*[\u4E00-\u9FA5]{1,}(\w
\(
(
)
《
》
\))*)+\s*)
(\s*((\w
\(
(
)
《
》
\))*[\u4E00-\u9FA5]{1,}(\w
\(
(
)
《
》
\))*)+\s*)/g)if(resultresult.length0){result=result.map(item={//去除首尾的单双引号、、还有首尾连在一起的空格returnitem.replace(/(\"\s*)
(\s*\")
(\\s*)
(\s*\)
(^\s*)
(\s*)/g,"")})arr=arr.concat(result)}returnitem})returnArray.from(newSet(arr))}module.exports=getChineseArr
在app.js文件中调用这个方法
constpath=require(path)constcontext=require("./context")constfilesList=context(path.join(__dirname,./test),true,[".vue"])constgetChineseArr=require("./getChineseArr")console.log(getChineseArr(filesList))
这样就实现了我们获取中文的方法,就可以将获取到数据,交给翻译人员翻译。如果翻译人员已经翻译了一些数据的话,那就要排除未翻译的中文。如何将翻译官翻译的一些数据弄成js文件?接下来就讲讲excel的小操作。
将excel的数据复制成我们想要的数据翻译官一般会将文件弄成excel数据,样子大概如下所示
如果我们要凑成个数据,就是已翻译的中文弄成数组,类似["基本信息","姓名","证件类型","证件号码"]
我们就需要将每个中文的前后加上引号,且加个逗号。
于是在D3写个“在E3写个”,在F3写=D3B3E3这串数字的意思字符用来分隔,
D3代表下拉时都是取D3的值同理E3也一样B3则下拉时会变
此时就会如图所示拿到了”基本信息“,然后在F3悬停右下角的下拉下拉住即可拿到中文所有此格式的数据。最后选择后复制文本,我们在凑个中括号,就能实现我们想要的数组。
constwasTranslate=["基本信息","姓名","证件类型","证件号码","联系方式","定点医疗机构登记信息","定点医疗机构名称","请选择定点医药机构名称"]module.exports=wasTranslate
如果要去除已翻译的,只要引入这个已翻译的再取差集就可以
//一般是先去重获取到的中文,再过滤掉已翻译的中文即可letunTranslate=[...newSet(arr)].filter(item=!newSet(wasTranslate).has(item))
这样就可以拿到未翻译的中文
接下来就是将这些数据写入到文件中去。
将读取的中文写入文件将获取到的unTranslate写到文件中,就是我们未翻译的文本。
//换行是为了等会复制到excel的时候会换行unTranslate=unTranslate.map(item={returnitem+"\n"})fs.writeFile("unTranslate.txt",unTranslate.join(""),function(err){if(err){returnconsole.error(err);}console.log("数据写入成功!");console.log("--------我是分割线-------------")console.log("读取写入的数据!");fs.readFile(unTranslate.txt,function(err,data){if(err){returnconsole.error(err);}console.log("异步读取文件数据:"+data.toString());});})
打开unTranslate.txt,将内容复制到excel即可。
新建一个文件夹assets/js/wasTranslate.js
constwasTranslate=[]module.exports=wasTranslate
app.js内容如下
constpath=require(path)constfs=require("fs")constwasTranslate=require("./assets/js/wasTranslate")constcontext=require("./context")constgetChineseArr=require("./getChineseArr")constfilesList=context(path.join(__dirname,./test),true,[".vue"])constchineseArr=getChineseArr(filesList)letunTranslate=chineseArr.filter(item=!newSet(wasTranslate).has(item))unTranslate=unTranslate.map(item={returnitem+"\n"})fs.writeFile("unTranslate.txt",unTranslate.join(""),function(err){if(err){returnconsole.error(err);}console.log("数据写入成功!");console.log("--------我是分割线-------------")console.log("读取写入的数据!");fs.readFile(unTranslate.txt,function(err,data){if(err){returnconsole.error(err);}console.log("异步读取文件数据:"+data.toString());});})
运行app.js结果如下
获取未翻译文本目录结构大致如下
getUntranslate获取未翻译文本目录
assets
js
wasTranslate.js//已翻译中文数组
getChineseArr.js//获取中文方法
app.js//运行文件
这样国际化切换实现了基本的获取要翻译中文的目的。当然,由于正则可能匹配的不是很全,一些包含其他特殊字符的中文可能匹配不到,还是会漏掉少部分,不过这样已经足矣。接下来就是实现替换文本了。讲替换文本前,先讲下vue如何实现国际化吧,就需要用到vue-i18n。
vue-i18n的使用安装依赖:npmivue-i18n-S
引入、调用
importVueI18nfromvue-i18n;Vue.use(VueI18n);
生成实例
consti18n=newVueI18n({locale:localStorage.getItem(locale)
zh,//语言标识messages:{zh:require(
/assets/lang/zh.js),en:require(/assets/lang/en.js)}});locale一般先从缓存获取,如果没有则设置默认语言。在系统中切换语言后一般会进行设置语言缓存操作。
js里面内容:en.js:
exportconstlang={login:login,register:register,rember:rember,home:home,about:about,aboutDesc:Thisisanaboutpage}
zh.js:
exportconstlang={login:登录,register:注册,rember:记住密码,home:首页,about:关于,aboutDesc:这是关于页面}
跟store一样,把实例传给vue:
newVue({router,store,i18n,render:h=h(App)}).mount(#app)
内部会把i18n通过mixin合并到vue实例上,新加属性i18n、t等属性方法。
直接使用:{{t(lang.login)}}
js中使用:this.t(lang.login)
切换语言:this.i18n.locale=type;
替换中文上面讲到了vue-i18n的使用,所有显然我们需要将原先中文的代码替换成t("XXX")类似如此这样。
接下来就是考虑如何把文件的内容替换成国际化的方法。
不说了,直接上代码。基本上符合要求,不过还是有匹配不到的地方
constpath=require(path)constfs=require("fs")constcontext=require("./context")constfilesList=context(path.join(__dirname,./test),true,[".vue"])//改变规则constchangeRules={//匹配第一种模式template/template中的label=""label=attrRule:{//增加少部分中英文掺杂文本判断,以及句尾(元)等单位判定type:/\b[\w-]+\s*=\s*("([\s\w\/()\(\)]*[\u4E00-\u9FA5]{1,}[\s\w\/()\(\)]*)+"
([\s\w\/()\(\)]*[\u4E00-\u9FA5]{1,}[\s\w\/()\(\)]*)+)/g,reg1:/("
)([\s\w\/()\(\)]*[\u4E00-\u9FA5]{1,}[\s\w\/()\(\)]*)+("
)/,reg2:/"
/g,sliceFlag:"=",changeContent:function(keyContent,elementFlag,prefix){if(elementFlag===script){return`{prefix}=this.t({keyContent})`}elseif(elementFlag===template){return`:{prefix}="t({keyContent})"`}}},//匹配第二种模式template/templateXXXXXXcontentRule:{type:/([\s\w\/()\(\),!?]*[\u4E00-\u9FA5]{1,}[\s\w\/()\(\),!?]*)+/g,reg2:/^[\s]*
[\s]*/g,changeContent:function(keyContent,elementFlag,prefix){return`{{t("{keyContent}")}}`}},//匹配第三种模式。script/script模块中的"中文"或中文scriptRule:{type:/(?!(!=
==)=?\s*)(("([\s\w\/()\(\),!?]*[\u4E00-\u9FA5]{1,}[\s\w\/()\(\),!?]*)+")
(([\s\w\/()\(\),!?]*[\u4E00-\u9FA5]{1,}[\s\w\/()\(\),!?]*)+))(?!\s*(!=
==)=?)/g,reg2:/"
/g,changeContent:function(keyContent){return`this.t({keyContent})`}}}letmodifyLog=[]constlangObj={"子测试test1":"lang1","test1子测试":"lang2","子测试test2":"lang3","子2测试":"lang4","子1测试":"lang5","子测试":"lang6","a子测试a":"lang7","子测试一":"lang8","子测试二":"lang9","测试test1":"lang10","测试一":"lang11","测试二":"lang12","《测试二恶趣味群二》":"lang13","发顺丰三发《测试二》发大厦的":"lang14"}filesList.map(item={//获取文件内容letcontent=fs.readFileSync(item,utf8)//对文本进行切割,分为template/template模块,以及script/script模块consttempRge=newRegExp(/(?=template)(.
\n
\r)*(?=\/template)/g)constscriptRge=newRegExp(/(?=script)(.
\n
\r)*(?=\/script)/g)lettempContets=content.match(tempRge)letscriptContets=content.match(scriptRge)tempContets=tempContetstempContets.length0?tempContets[0]:""scriptContets=scriptContetsscriptContets.length0?scriptContets[0]:""//针对不模块内容,调用不同模块规则替换tempContets=changFileContent(tempContets,template,changeRules.attrRule)//替换template中类似:label="姓名":title="标题"的中文tempContets=changFileContent(tempContets,template,changeRules.contentRule)//替换template中类似姓名的中文scriptContets=changFileContent(scriptContets,script,changeRules.scriptRule)//替换script中类似"哈哈哈"的中文//将template部分的文本替换掉content=content.replace(tempRge,tempContets)//将script部分的文本替换掉content=content.replace(scriptRge,scriptContets)//重新写入文件fs.writeFile(item,content,function(err){if(err){returnconsole.error(err);}console.log(item+"文件替换完成!");})})createChageLog()/***
description:*param{*}content文本*param{*}elementFlag文本标志是template还是script*param{*}type*param{*}reg1*param{*}reg2*param{*}changeContent*param{*}sliceFlag*return{*}*author:syx*/functionchangFileContent(content,elementFlag,{type,reg1,reg2,changeContent,sliceFlag}){//搜索出所有符合规格的stringletcontentType=content.match(type)if(contentTypecontentType.length0){//去重contentType=Array.from(newSet(contentType))for(leti=0;icontentType.length;i++){//获取主要中文内容,用于替换letoriginContent=contentType[i]//如果有切割标志,则切割字符串,获取前缀(label,title,placeholder等)letprefix=""if(sliceFlag){prefix=originContent.split(sliceFlag)[0].trim()}letchineseContent=""if(!reg1){chineseContent=originContent.replace(reg2,"").trim()}else{chineseContent=originContent.match(reg1)[0].trim().replace(reg2,"").trim()}//替换文本匹配内容letoriginContentFormatter=originContent.replace(/\(/g,"\\(").replace(/\)/g,"\\)").replace(/\//g,"\\/")//防止一些不符合标准的中文被全局替换if(elementFlag===script){originContentFormatter=(?!(!===)=?\\s*)+originContentFormatter+(?!\\s*(!=
==)=?)}constreplaceReg=newRegExp(originContentFormatter,g)if(Object.keys(langObj).includes(chineseContent)){content=content.replace(replaceReg,(target)={constnewText=changeContent(langObj[chineseContent],elementFlag,prefix)modifyLog.push(target+=+newText+\n)returnnewText})}}}returncontent}/***
description根据modifyLog生成日志*/functioncreateChageLog(){consttime=newDate()constyear=time.getFullYear()constmonth=time.getMonth()+1constday=time.getDate()consthour=time.getHours()constminute=time.getMinutes()constseconds=time.getSeconds()//不能使用英文的":"fs报错找不到文件constlogName=`{year}-{month}-{day}{hour}:{minute}:{seconds}_log`constlogPath=./log/+logName+.txtletlogContent=""modifyLog.forEach(item={logContent+=item})fs.writeFile(path.join(__dirname,logPath),logContent,utf8,(err)={if(err){returnconsole.error(err);}console.log("--------日志写入成功-----------")})}以上就是国际化经验分享。正则真的太晕了。大概可以匹配到95%的中文。一些特殊的匹配替换不到的只能再去手动修改了。
沈小布
转载请注明:http://www.chongqinghg.com/fygsz/8948.html