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

news/2024/12/26 10:00:43/

背景

有一个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 = /[一-龥]+/g;  function extractChineseFromFile(filePath) {  const content = fs.readFileSync(filePath, 'utf-8');  const chineseWords = content.match(chineseRegex);  return chineseWords || [];  
}  function processDirectory(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));  } else if (stats.isFile() && ['.js', '.vue'].indexOf(path.extname(filePath)) > -1) {const chineseWords = extractChineseFromFile(filePath);  chineseSentences.push(...chineseWords);  }  });  return chineseSentences;  
}  function main() {  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(new Set(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";function md5Hash(input) {  // 创建一个哈希对象  const hash = crypto.createHash('md5');  // 更新哈希对象的内容  hash.update(input);  // 获取哈希值的二进制表示  const hashBuffer = hash.digest();  // 将二进制转换为十六进制表示  const hashHex = hashBuffer.toString('hex');  // 返回小写的哈希值  return hashHex.toLowerCase();  
}  // 使用百度翻译API进行翻译  
async function translateToEnglish(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;  
}  function sleep(ms) {  return new Promise(resolve => setTimeout(resolve, ms));  
}  async function mysleep() {  console.log('休息1秒......................');  await sleep(1000); // 暂停 1 秒  console.log('休息完成...');  
} async function process() {  // 读取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 = await translateToEnglish(chineseString);  // 将原中文字符串和英文字符串形成键值对存储到translationData对象中  translationData[chineseString] = englishString;if (execNumber >= 120) {   // 如果不想全部执行,则执行多少场退出break;} else if (i == execNumber*20) { // 每执行20次接口调用,就休息1秒execNumber++;await mysleep()}}// 将翻译结果写入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 数据  
function loadTranslations() {const cnJsonPath = path.join(__dirname, 'src', 'lang', 'cn.json');const content = fs.readFileSync(cnJsonPath, 'utf-8');return JSON.parse(content);
}// 判断字符串是否以指定前缀开头  
function startsWith(str, prefix) {return str.startsWith(prefix);
}/*** 每个键值对的场景匹配* @param {String} fileContent 文件内容* @param {String} key 国际化变量名* @param {String} value 中文字符串*/
function replaceAllScene(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;
}// 在给定文件中替换指定的字符串  
function replaceStringsInFile(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}`);}
}// 在指定目录下处理所有文件  
function processDirectory(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);} else if (stats.isFile()) {replaceStringsInFile(filePath, replacements);}});
}function main() {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(‘国际化编码’,‘中文’) ,这样我们在第二次再提取时,就不会重复提取已经国际化处理后的代码中文了。


http://www.ppmy.cn/news/1558235.html

相关文章

pycharm断点调试pydevd_comm.py“, line 306, in _on_run r = r.decode(‘utf-8‘)

最近在远程调试pycharm代码时发现会报错&#xff1a; pydevd_comm.py", line 306, in _on_run r r.decode(utf-8) 网上搜索说是有中文路径&#xff0c;但我的调试的文件就没有中文&#xff0c;这是什么原因呢&#xff1f; &#xff08;已解决&#xff09;pycharm…

基于深度学习(HyperLPR3框架)的中文车牌识别系统-搭建开发环境

本篇内容为搭建开发环境。包括&#xff1a;python开发环境&#xff0c;Qt/C开发环境&#xff0c;以及用到的各个库的安装和配置。 一、Python开发环境搭建与配置 1、下载并安装Anaconda 我没有用最新的版本&#xff0c;安装的是 Anaconda3-2021.05-Windows-x86_64.exe&#…

3GPP R18 MT-SDT

Rel-17 指定MO-SDT允许针对UL方向的数据包进行小数据包传输。对于DL,MT-SDT(即DL触发的小数据)可带来类似的好处,即 通过不转换到 RRC_CONNECTED来减少信令开销和UE功耗,并通过允许快速传输(小而少见的)数据包(例如用于定位)来减少延迟。 在R17中,NR_SmallData_INACTIVE的工…

打造高效租赁小程序让交易更便捷

内容概要 在如今节奏飞快的商业世界里&#xff0c;租赁小程序如同一只聪明的小狐狸&#xff0c;迅速突围而出&#xff0c;成为商家与消费者之间的桥梁。它不仅简化了交易流程&#xff0c;还在某种程度上将传统租赁模式带入了互联网时代。越来越多的企业意识到&#xff0c;这种…

“鞋履数据库”:运动鞋店产品信息管理系统

2.1 SSM框架介绍 本课题程序开发使用到的框架技术&#xff0c;英文名称缩写是SSM&#xff0c;在JavaWeb开发中使用的流行框架有SSH、SSM、SpringMVC等&#xff0c;作为一个课题程序采用SSH框架也可以&#xff0c;SSM框架也可以&#xff0c;SpringMVC也可以。SSH框架是属于重量级…

函数式编程Lambda表达式

函数式编程&Lambda表达式 定义 Lambda 表达式是一个匿名函数&#xff0c;用来实现函数式接口。它可以作为参数传递&#xff0c;也可以作为返回值。 能够使用Lambda表达式的一个重要依据是必须有相应的函数式接口&#xff0c;所谓的函数式接口&#xff0c;指的就是“一个接…

Spring Boot 项目中 Maven 剔除无用 Jar 引用的最佳实践

目录 引言Maven 依赖管理的基础概念 2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机制 无用依赖的常见问题与影响剔除无用 Jar 引用的常见方法 4.1 识别无用依赖4.2 使用 Maven 的 dependency:analyze 插件4.3 配置 scope 以优化依赖范围4.4 使用 exclude 排除传递依赖4.5 分析…

微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁

Senparc.Weixin SDK 更新 Sample,批处理发布单个模块的 Sample 更新 Sample&#xff0c;引用最新版本 SDK NeuCharFramework 文档更新&#xff0c;提供全文检索功能 模板更新&#xff0c;提供菜单设置页面的父层节点置顶&#xff0c;解决菜单项过长&#xff0c;不容易编辑…