您现在的位置是:首页 > 技术教程 正文

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

admin 阅读: 2024-04-01
后台-插件-广告管理-内容页头部广告(手机)

背景

有一个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; 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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

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); });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

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));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

5、代码国际化处理

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

首先要分析程序需要国际化的代码规则,因为这个替换不是简单的去就把中文替换,可能代码都由变化,我们分析项目代码中目前的规则如下:

场 景**代码示例******查找内容******替换内容****
作为组件元素内容的取消 如调整了颜色尺码,保存后请务必核对检查数量和配色数据! 尺码信息>取消<>{{$lang(‘10000’, ‘取消’)}}<
作为组件元素属性值的 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’, ‘选择日期’)”
组件模板代码中三元运算结果{{onlyShow?'查看':'修改'}}‘查看’:‘查看’ ::‘修改’: ‘修改’ 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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

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

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

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

建议

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

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

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

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索