0


记一次前端Vue项目国际化解决方案

背景

有一个vue项目,要实现国际化功能,能够切换中英文显示,因为该项目系统的用户包括了国内和国外用户。

需求

1、页面表单上的所有中文标签要国际化,包括表单属性标签、表格列头标签等, title=“数量”;

2、输入框的提示内容需要国际化,如 placeholder=“选择日期”

3、js代码中的提示信息需要国际化,如 message(“请勾选批量设置”)、confirm(‘您确定要设置业务损耗吗?’)、title: ‘删除错误’ 等;

解决方案

1、开发流程,一开始开发过程中,我们不考虑国际化,等代码基本完成后,最后再进行国际化;

2、考虑日后还可能由其他语种,所以这里我们做国际化词语库时,国际化编码使用5位数字,对应多种语言值,即一对多;

3、前端我们重新封装一个全局方法 $lang(param1, param2) 来支持国际化,param1是国际化编码,param2是默认值(如果国际化编码没找到对应的语言单词,则默认用param2,且去掉左右两边的 ‘~’符号);

其实后来又分析了下,如果一开始前端开发人员把所有需要国际化的中文词语,都写成 $lang(‘中文词语’) , $lang方法逻辑再修改下,如果没有第二个参数并且第一个参数对应的国际化词语也没有,则直接显示第一个参数字符串,而且这样的话,到后面再提取代码中的需要国际化的内容时就会很精确了。

4、国际化流程:

  • 从前端代码文件中将所有的中文提取出来,形成一个数组放到一个json文件中,并且数组需要去重一下;
  • 使用第三方的翻译接口,来对导出的中文进行翻译,生成一个中英文对照键值对json文件;
  • 校对中英文对照表,因为有的翻译不一定准确;
  • 根据校对后的中英文对照表,生成国际化编码库,并创建两个国际化文件;
  • 根据校对后的中英文对照表,并分析代码规则,将程序代码中的中文进行国际化处理;

国际化流程实施

在国际化流程实施中,我使用编写js脚本代码来实现相关的处理,使用node环境来执行脚本;

1、提取中文

从前端代码文件中将所有的中文提取出来,形成一个数组放到一个json文件中,并且数组需要去重一下;

下面的代码,是用来提取文件代码中的中文的,我们可以将代码文件命名为extractChinese.js,使用node来执行该脚本;

代码中要国际化的路径设置的是当前目录下的src下的 components和pages文件夹

const fs =require('fs');const path =require('path');const chineseRegex =/[\u4e00-\u9fa5]+/g;functionextractChineseFromFile(filePath){const content = fs.readFileSync(filePath,'utf-8');const chineseWords = content.match(chineseRegex);return chineseWords ||[];}functionprocessDirectory(directoryPath){const files = fs.readdirSync(directoryPath);const chineseSentences =[];  
  
  files.forEach((fileName)=>{const filePath = path.join(directoryPath, fileName);const stats = fs.statSync(filePath);if(stats.isDirectory()){  
      chineseSentences.push(...processDirectory(filePath));}elseif(stats.isFile()&&['.js','.vue'].indexOf(path.extname(filePath))>-1){const chineseWords =extractChineseFromFile(filePath);  
      chineseSentences.push(...chineseWords);}});return chineseSentences;}functionmain(){const srcDirectory = path.join(__dirname,'src');const componentsDirectory = path.join(srcDirectory,'components');const pagesDirectory = path.join(srcDirectory,'pages');const componentsChineseSentences =processDirectory(componentsDirectory);const pagesChineseSentences =processDirectory(pagesDirectory);const allChineseSentences =[...componentsChineseSentences,...pagesChineseSentences];//const allChineseSentences = componentsChineseSentences;  const outputPath = path.join(__dirname,'output.json');// 使用 Set 对象来去重  let backString = Array.from(newSet(allChineseSentences));// 对去重后的数组进行排序  
  backString.sort();
  fs.writeFileSync(outputPath,JSON.stringify(backString,null,2),'utf-8');  
  
  console.log('提取到的中文单词或语句已保存到output.json文件中。');}main();

2、翻译中文

使用第三方的翻译接口,来对导出的中文进行翻译,生成一个中英文对照键值对json文件;

翻译接口,这里我们用的是百度翻译,至于如何去使用百度翻译,这里就不再说了,自己去百度看吧;

该步骤需要用到第一步生成的 output.json 文件,然后翻译结果是存在 translated_zh_en.json 中。

const fs =require('fs');const axios =require('axios');const appId ='123456789';// 替换成你的百度翻译的APP ID  const secretKey ='999999999';// 替换成你的百度翻译的密钥  const crypto =require('crypto');  
axios.defaults.headers.post["Content-Type"]="application/x-www-form-urlencoded;charset=UTF-8";functionmd5Hash(input){// 创建一个哈希对象  const hash = crypto.createHash('md5');// 更新哈希对象的内容  
  hash.update(input);// 获取哈希值的二进制表示  const hashBuffer = hash.digest();// 将二进制转换为十六进制表示  const hashHex = hashBuffer.toString('hex');// 返回小写的哈希值  return hashHex.toLowerCase();}// 使用百度翻译API进行翻译  asyncfunctiontranslateToEnglish(text){const params ={q: text,appid: appId,salt: Date.now(),from:'zh',to:'en',sign:''};// 计算签名  
    params.sign =md5Hash(params.appid + params.q + params.salt + secretKey);// 请求翻译  const url =`http://api.fanyi.baidu.com/api/trans/vip/translate?q=${encodeURIComponent(params.q)}&from=zh&to=en&appid=${params.appid}&salt=${params.salt}&sign=${params.sign}`;const response =await axios.get(url);//console.log(url);//console.log(response.data)// 返回翻译结果  return response.data.trans_result[0].dst;}functionsleep(ms){returnnewPromise(resolve=>setTimeout(resolve, ms));}asyncfunctionmysleep(){  
  console.log('休息1秒......................');awaitsleep(1000);// 暂停 1 秒  
  console.log('休息完成...');}asyncfunctionprocess(){// 读取json文件  const data =JSON.parse(fs.readFileSync('output.json','utf8'));// 存储翻译结果的对象  let translationData ={};let execNumber =1;// 遍历中文字符串数组,进行翻译for(let i =0; i < data.length; i++){const chineseString = data[i];const englishString =awaittranslateToEnglish(chineseString);// 将原中文字符串和英文字符串形成键值对存储到translationData对象中  
        translationData[chineseString]= englishString;if(execNumber >=120){// 如果不想全部执行,则执行多少场退出break;}elseif(i == execNumber*20){// 每执行20次接口调用,就休息1秒
            execNumber++;awaitmysleep()}}// 将翻译结果写入translate.json文件中  
    fs.writeFileSync('translated_zh_en.json',JSON.stringify(translationData,null,2));}process().catch(error=>{  
    console.error(error);});

3、校对翻译

校对中英文对照表,因为有的翻译不一定准确;(找个行业英语水平高点的人,自己去校对吧)

4、创建国际化库

根据校对后的中英文对照表,生成国际化编码库,并创建两个国际化文件;

const fs =require('fs');// 读取原始 JSON 文件  const data =JSON.parse(fs.readFileSync('translated_zh_en.json','utf8'));// 中文和英文的 JSON 文件  const chineseData ={};const englishData ={};let serialNumber =00001;// 遍历原始数据,生成新的键值对  for(let chinese in data){const english = data[chinese];// 生成新的键值对,序号为 5 位数字  const key =`N${String(serialNumber).padStart(5,'0')}`;  
    
  chineseData[key]= chinese;  
  englishData[key]= english;  
  
  serialNumber++;}// 将中文和英文的 JSON 数据写入文件  
fs.writeFileSync('cn.json',JSON.stringify(chineseData,null,2));  
fs.writeFileSync('en.json',JSON.stringify(englishData,null,2));

5、代码国际化处理

根据第4步生成的中文国际化文件 cn.json ,并分析代码规则,将程序代码中的中文进行国际化处理;

首先要分析程序需要国际化的代码规则,因为这个替换不是简单的去就把中文替换,可能代码都由变化,我们分析项目代码中目前的规则如下:
场 景代码示例查找内容替换内容作为组件元素内容的

<vxe-button @click="closeModel">取消</vxe-button> <span style="color: red;">如调整了颜色尺码,保存后请务必核对检查数量和配色数据!</span> <div class="title">尺码信息</div>

取消<>{{$lang(‘10000’, ‘取消’)}}<作为组件元素属性值的

<vxe-table-column field="odgc_pcs" title="数量" width="100" header-align="center" align="right"> <el-date-picker v-if="row.type == 'date'" type="date" placeholder="选择日期" v-model="row.value">

title="数量"placeholder=“选择日期”:title=“

        l 
       
      
        a 
       
      
        n 
       
      
        g 
       
       
       
         ( 
        
       
         ′ 
        
       
      
        1000 
       
       
       
         1 
        
       
         ′ 
        
       
       
       
         , 
        
       
         ′ 
        
       
      
         数量 
       
       
       
           
        
       
         ′ 
        
       
      
        ) 
       
      
        " 
       
      
        : 
       
      
        p 
       
      
        l 
       
      
        a 
       
      
        c 
       
      
        e 
       
      
        h 
       
      
        o 
       
      
        l 
       
      
        d 
       
      
        e 
       
      
        r 
       
      
        = 
       
      
        " 
       
      
     
       lang('10001', '~数量~')":placeholder=" 
      
     
   lang(′10001′,′ 数量 ′)":placeholder="lang(‘Ph_select_data’, ‘选择日期’)”组件模板代码中三元运算结果
<el-button size="mini" @click="alterConsumption(row)">{{onlyShow?'查看':'修改'}}</el-button>

‘查看’:‘查看’ ::‘修改’: ‘修改’

        l 
       
      
        a 
       
      
        n 
       
      
        g 
       
       
       
         ( 
        
       
         ′ 
        
       
      
        1000 
       
       
       
         2 
        
       
         ′ 
        
       
       
       
         , 
        
       
         ′ 
        
       
      
         查看 
       
       
       
           
        
       
         ′ 
        
       
      
        ) 
       
      
        同上 
       
      
     
       lang('10002', '~查看~')同上 
      
     
   lang(′10002′,′ 查看 ′)同上lang(‘10003’, ‘修改’)同上js 中方法参数值
this.$XModal.message("请勾选批量设置", "error"); this.$XModal.confirm('您确定要设置吗?') this.$confirm("确定要删除此记录吗 ?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", })

message("请勾选批量设置"message('请勾选要批量设置’confirm("您确定要设置吗?"confirm(‘您确定要设置吗?’“提示”,confirmButtonText: “确定”,cancelButtonText: “取消”,message(this.

        l 
       
      
        a 
       
      
        n 
       
      
        g 
       
       
       
         ( 
        
       
         ′ 
        
       
      
        1000 
       
       
       
         4 
        
       
         ′ 
        
       
       
       
         , 
        
       
         ′ 
        
       
      
         请勾选批量设置 
       
       
       
           
        
       
         ′ 
        
       
      
        ) 
       
      
        同上 
       
      
        c 
       
      
        o 
       
      
        n 
       
      
        f 
       
      
        i 
       
      
        r 
       
      
        m 
       
      
        ( 
       
      
        t 
       
      
        h 
       
      
        i 
       
      
        s 
       
      
        . 
       
      
     
       lang('10004', '~请勾选批量设置~')同上confirm(this. 
      
     
   lang(′10004′,′ 请勾选批量设置 ′)同上confirm(this.lang(‘10005’, ‘您确定要设置吗?’)同上this. 
    
     
      
      
        l 
       
      
        a 
       
      
        n 
       
      
        g 
       
       
       
         ( 
        
       
         ′ 
        
       
      
        1000 
       
       
       
         6 
        
       
         ′ 
        
       
       
       
         , 
        
       
         ′ 
        
       
      
         提示 
       
       
       
           
        
       
         ′ 
        
       
      
        ) 
       
      
        , 
       
      
        c 
       
      
        o 
       
      
        n 
       
      
        f 
       
      
        i 
       
      
        r 
       
      
        m 
       
      
        B 
       
      
        u 
       
      
        t 
       
      
        t 
       
      
        o 
       
      
        n 
       
      
        T 
       
      
        e 
       
      
        x 
       
      
        t 
       
      
        : 
       
      
        t 
       
      
        h 
       
      
        i 
       
      
        s 
       
      
        . 
       
      
     
       lang('10006', '~提示~'),confirmButtonText: this. 
      
     
   lang(′10006′,′ 提示 ′),confirmButtonText:this.lang(‘10007’, ‘确定’)cancelButtonText: this.$lang(‘10008’, ‘取消’)js 中对象属性赋值`this. 
    
     
      
      
        X 
       
      
        M 
       
      
        o 
       
      
        d 
       
      
        a 
       
      
        l 
       
      
        . 
       
      
        m 
       
      
        e 
       
      
        s 
       
      
        s 
       
      
        a 
       
      
        g 
       
      
        e 
       
      
        ( 
       
       
       
         m 
        
       
         e 
        
       
         s 
        
       
         s 
        
       
         a 
        
       
         g 
        
       
         e 
        
       
         : 
        
       
         " 
        
       
         保存失败 
        
       
         " 
        
       
         , 
        
       
         s 
        
       
         t 
        
       
         a 
        
       
         t 
        
       
         u 
        
       
         s 
        
       
         : 
        
       
         " 
        
       
         e 
        
       
         r 
        
       
         r 
        
       
         o 
        
       
         r 
        
       
         " 
        
       
      
        ) 
       
      
        ; 
       
      
        t 
       
      
        h 
       
      
        i 
       
      
        s 
       
      
        . 
       
      
     
       XModal.message({ message: "保存失败", status: "error" }); this. 
      
     
   XModal.message(message:"保存失败",status:"error");this.message({ message: ‘请选择要设置的物料!’, type: ‘warning’ }); this. 
    
     
      
      
        X 
       
      
        M 
       
      
        o 
       
      
        d 
       
      
        a 
       
      
        l 
       
      
        . 
       
      
        a 
       
      
        l 
       
      
        e 
       
      
        r 
       
      
        t 
       
      
        ( 
       
       
       
         m 
        
       
         e 
        
       
         s 
        
       
         s 
        
       
         a 
        
       
         g 
        
       
         e 
        
       
         : 
        
       
         " 
        
       
         请选择附件分类 
        
       
         " 
        
       
         , 
        
       
         s 
        
       
         t 
        
       
         a 
        
       
         t 
        
       
         u 
        
       
         s 
        
       
         : 
        
       
         " 
        
       
         w 
        
       
         a 
        
       
         r 
        
       
         n 
        
       
         i 
        
       
         n 
        
       
         g 
        
       
         " 
        
       
         , 
        
       
      
        ) 
       
      
        ; 
       
      
        t 
       
      
        h 
       
      
        i 
       
      
        s 
       
      
        . 
       
      
     
       XModal.alert({ message: "请选择附件分类", status: "warning", }); this. 
      
     
   XModal.alert(message:"请选择附件分类",status:"warning",);this.XModal.alert({ status: “error”, title: “删除错误”, message: response.msg“服务器删除发生错误”, });`js 中 || 赋值`this.$XModal.alert({ status: “error”, title: “删除错误”, message: response.msg“服务器删除发生错误”, });`

替换的脚本代码如下:

const fs =require('fs');const path =require('path');// 读取 cn.json 文件并解析 JSON 数据  functionloadTranslations(){const cnJsonPath = path.join(__dirname,'src','lang','cn.json');const content = fs.readFileSync(cnJsonPath,'utf-8');returnJSON.parse(content);}// 判断字符串是否以指定前缀开头  functionstartsWith(str, prefix){return str.startsWith(prefix);}/**
 * 每个键值对的场景匹配
 * @param {String} fileContent 文件内容
 * @param {String} key 国际化变量名
 * @param {String} value 中文字符串
 */functionreplaceAllScene(fileContent, key, value){// 场景:>取消<let searchValue =`>${value}<`;let replaceValue =`>{{$lang('${key}', '~${value}~')}}<`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// 场景:title="数量"
    searchValue =`title="${value}"`;
    replaceValue =`:title="$lang('${key}', '~${value}~')"`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// 场景:placeholder="选择日期"
    searchValue =`placeholder="${value}"`;
    replaceValue =`:placeholder="$lang('${key}', '~${value}~')"`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// 场景:message("请勾选批量设置"
    searchValue =`message("${value}"`;
    replaceValue =`message(this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// 场景:message('请勾选批量设置'
    searchValue =`message('${value}'`;
    replaceValue =`message(this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// 场景:confirm("您确定要设置业务损耗吗?"
    searchValue =`confirm("${value}"`;
    replaceValue =`confirm(this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// 场景:confirm('您确定要设置业务损耗吗?'
    searchValue =`confirm('${value}'`;
    replaceValue =`confirm(this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// confirmButtonText: "确定",
    searchValue =`confirmButtonText: "${value}",`;
    replaceValue =`confirmButtonText: this.$lang('${key}', '~${value}~'),`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// cancelButtonText: "取消",
    searchValue =`cancelButtonText: "${value}",`;
    replaceValue =`cancelButtonText: this.$lang('${key}', '~${value}~'),`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// message: "保存失败"
    searchValue =`message: "${value}"`;
    replaceValue =`message: this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// message: '保存失败''
    searchValue =`message: '${value}'`;
    replaceValue =`message: this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// title: "删除错误"
    searchValue =`title: "${value}"`;
    replaceValue =`title: this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);// title: '删除错误'
    searchValue =`title: '${value}'`;
    replaceValue =`title: this.$lang('${key}', '~${value}~')`;
    fileContent = fileContent.split(searchValue).join(replaceValue);return fileContent;}// 在给定文件中替换指定的字符串  functionreplaceStringsInFile(filePath, replacements){const content = fs.readFileSync(filePath,'utf-8');let newContent = content;for(const[key, value]of Object.entries(replacements)){// 如果匹配到的字符串前面存在 "message(",则去掉左右两边的双引号  //const searchValue = startsWith(value, 'message("') ? value.slice(8, -1) : value;

        newContent =replaceAllScene(newContent, key, value);//newContent = newContent.split(searchValue).join("$lang('" + key + "',')" + searchValue + "'");}if(newContent !== content){
        fs.writeFileSync(filePath, newContent,'utf-8');
        console.log(`Replaced strings in ${filePath}`);}}// 在指定目录下处理所有文件  functionprocessDirectory(directoryPath, replacements){const files = fs.readdirSync(directoryPath);

    files.forEach((fileName)=>{const filePath = path.join(directoryPath, fileName);const stats = fs.statSync(filePath);if(stats.isDirectory()){processDirectory(filePath, replacements);}elseif(stats.isFile()){replaceStringsInFile(filePath, replacements);}});}functionmain(){const translations =loadTranslations();const componentsDirectory = path.join(__dirname,'src','components');const pagesDirPath = path.join(__dirname,'src','pages');processDirectory(componentsDirectory, translations);processDirectory(pagesDirPath, translations);}main();

到此,我们就完成了前端代码的国际化实现;

我们为什么要把原中文作为 国际化方法 $lang 的第二个参数呢?

因为,如果代码文件中看不到中文,修改代码的时候太难找了,你只能看到国际化数字编码。

建议

建议是在前端一开始开发的时候,就把需要国际化的地方都写成 $lang(‘中文’),包括模板代码和js代码中,

这样后期替换更精确,而且一开始开发人员也不用去管国际化,

并且,我们在提取代码中文时,就可以按 $lang(‘中文’) 这个格式精确提取了,国际化处理后就变成 $lang(‘国际化编码’,‘中文’) ,这样我们在第二次再提取时,就不会重复提取已经国际化处理后的代码中文了。


本文转载自: https://blog.csdn.net/qzonemen/article/details/135977520
版权归原作者 蜘蛛上网 所有, 如有侵权,请联系我们删除。

“记一次前端Vue项目国际化解决方案”的评论:

还没有评论