本文主要针对Egret开发的项目做详细介绍,对其它引擎开发的项目亦有参考价值。

需求背景:
1、对整个游戏项目进行多语言版本(国际化)开发,把游戏内的文字由中文换成英文;
2、尽量不要修改业务代码,至少不需要手动修改,减少工作量。
3、不要遗漏,保证准确性;

需求分析:
1、游戏内文本主要包括系统文字文本和图片文本,图片很好处理,每种语言一个文件夹,美术修改图片的文字替换即可,程序根据语言字段调用对应文件夹;
2、关键点是系统文字的文本处理,其中文本分布在 ts文件和exml文件中,得想办法批量处理这2部分文字;

方法介绍:

  • 针对ts文件里面的文本: 写个脚本把项目中所有ts文件里面的汉字文本读出来汇总到一个Language.ts文件中,定义在枚举enum对象里面,并且同时批量修改源码把该汉字字符替换为枚举引用;
    注意脚本需要很好的维护Language.ts文件,项目源码增加了文本或删除了文本,要保证Language.ts文件的准确和干净;
    有朋友就要问了为什么用枚举,不能封装一个方法吗?答案是因为枚举可以有代码提示!这非常重要,让你的代码可读性大大提高。

  • 针对exml文件里面的文本: 同样先写个脚本把项目中exml文件里面的汉字文本读出来汇总到一个Language2.ts文件中,定义在一个Object对象里面;
    然后再修改类代码中的partAdded方法,判断如果是Label或Button,则把其text或label属性设置为对应的语言文本。
    可是,这一步代码写好之后实测会发现问题,那就是一部分文本替换不超过,原因是egret引擎默认并不是所有Label都会调用partAdded方法,只有设置了id的对象才会进调用。因此还需要修改一下引擎代码。在eui解析EXML时判断如果Label没有设置id,就自动为其分配1个id,这样就能保证所有Label会触发partAdded方法了

通过以上方法,无需修改具体的业务代码,完美的实现了需求,剩下的就是把汇总的2个汉字文本提交给翻译了。

下面附代码:


public partAdded(partName: string,instance: any): void {super.partAdded(partName,instance);if(window["_LangExml"]){ //定义在主项目let key;if(instance instanceof eui.Label){key = instance.text;if(window["_LangExml"].hasOwnProperty(key))instance.text = window["_LangExml"][key];}else if(instance instanceof eui.Button){key = instance.label;if(window["_LangExml"].hasOwnProperty(key))instance.label = window["_LangExml"][key];} }
}
function changeEgretCodeForLange(){if(!window["_LangExml"]) return;console.error("启用了多语言版本,重写引擎 eui.sys.EXMLParser.prototype.addIds 方法");let fun = eui.sys.EXMLParser.prototype.addIds;eui.sys.EXMLParser.prototype.addIds = function(items){if (!items) {return;}var length = items.length;for (var i = 0; i < length; i++) {var node = items[i];if (node.nodeType === 1 && node.attributes["text"] && !node.attributes["id"]) {this.createIdForNode(node);}}fun.apply(this, [items]);}
}

脚本代码

/** 从项目文件夹 提取汉字字符串,生成汉字字典 -----------------* 原理:遍历项目里面的汉字字符串,生成ts字典,再用enum枚举方式引用,可以在代码处直接查看枚举值,保证开发效率
*/var fs = require('fs');
var path = require('path');var filterFils = ["Language","Language2"];
var filterDirs = ["xx"];
var root_Url = "D:/xxxx/src";
var root_ExmlUrl = "D:/xxxx";var outFilePath = "D:/xxxx/Language2.ts";
var outFilePath2 = "D:/xxxx/LanguageExml.ts";var map = {};
var total = 0;outLanguageTs(outFilePath);
outLanguageExml(outFilePath2);//先把已经生成的读取出来,避免重复或覆盖
function outLanguageTs(outFilePath){let langContent = fs.readFileSync(outFilePath, { encoding: "utf8" });langContent = langContent.split("\n");if(langContent > 2){langContent.shift();langContent.pop();let str = ", //";for(var i=0; i<langContent.length; i++){if(i == langContent.length-1)str = " //";var item = (langContent[i].split(str)[0]).split(" = ");map[ item[1] ] = Number(item[0].substr("    str".length));if(i == langContent.length-1)total = map[ item[1] ];}console.log("已有文案:", map);}//return;fileDisplay(path.resolve(root_Url));
}var timer;
//异步,不知道什么时候结束,用这个来判断
function reSetSave(reset){clearTimeout(timer);if(!reset){timer = setTimeout(()=>{let str = [];for(var key in map){console.log(key, map[key]);str.push( {v:"    str" + map[key] + " = " + key, key:key} );}let content = ["const enum Lang2 {"];for(var i=0; i<str.length - 1; i++){content.push(str[i].v + "," + " //" + str[i].key);}content.push(str[i].v + " //" + str[i].key); //最后1个不要逗号content.push("}")fs.writeFileSync(outFilePath, content.join("\n"), { encoding: "utf8" });console.log("保存文件:" + outFilePath);}, 1000);}
}/*** 文件遍历方法* @param filePath 需要遍历的文件路径*/
function fileDisplay(filePath){//根据文件路径读取文件,返回文件列表fs.readdir(filePath,function(err,files){if(err){console.warn(err)}else{//遍历读取到的文件列表files.forEach(function(filename){//获取当前文件的绝对路径var filedir = path.join(filePath,filename);//根据文件路径获取文件信息,返回一个fs.Stats对象fs.stat(filedir,function(eror,stats){if(eror){console.warn('获取文件stats失败');}else{var isFile = stats.isFile();//是文件var isDir = stats.isDirectory();//是文件夹if(isFile){//console.log(filename,filedir);let onlyName = filename.split(".")[0];if(filename.indexOf(".ts") != -1 && filterFils.indexOf(onlyName) == -1){changeDode(filedir);}}if(isDir){if(!filterDirs || filterDirs.indexOf(filename) == -1){fileDisplay(filedir);//递归,如果是文件夹,就继续遍历该文件夹下面的文件}}}})});}});
}function changeDode(phppath){//console.log("执行文件:" + phppath);let phpContent = fs.readFileSync(phppath, { encoding: "utf8" });//console.log(phpContent);//['"][\xff-\uffff 0-9$]+['"]let arr = phpContent.match(/['"][\xff-\uffff 0-9$]+['"]/g);//console.log(arr);if(arr){let change, nstr, regExp0;for(var i=0; i<arr.length; i++){let str = arr[i];if(str.match(/['"][\d$  ]+['"]/g)) continue; //纯数字、空格、$串,忽略regExp0 = new RegExp("console\\.warn\\(" + str, 'gi');if(phpContent.match(regExp0)) continue; //console打印的内容,忽略regExp0 = new RegExp("console\\.log\\(" + str, 'gi');if(phpContent.match(regExp0)) continue; //console打印的内容,忽略regExp0 = new RegExp("console\\.error\\(" + str, 'gi');if(phpContent.match(regExp0)) continue; //console打印的内容,忽略//console.log(str);if(!map[str]){total++;map[str] = total;nstr = "Lang2.str" + total;reSetSave(); //有改变,触发延时保存}else{nstr = "Lang2.str" + map[str];}change = true;regExp0 = new RegExp(str, 'gi');phpContent = phpContent.replace(regExp0, nstr);}//console.log(phpContent);if(change){fs.writeFileSync(phppath, phpContent, { encoding: "utf8" });//console.log("执行完成!--" + phppath);}}
}function Trim(str, is_global) {//console.log("==", str)str = String(str);var result;result = str.replace(/(^\s+)|(\s+$)/g,"");if(is_global && is_global.toLowerCase()=="g"){result = result.replace(/\s/g,"");}return result;
}
function lTrim(str) {str = String(str);var result = str.replace(/(^\s+)/g,"");return result;
}//先把已经生成的读取出来,避免重复或覆盖
function outLanguageExml(outFilePath){let langContent = fs.readFileSync(outFilePath, { encoding: "utf8" });try{langContent = langContent.substr('window["_LangExml"] = '.length);langContent = langContent.replace(/\\n/g, "");map = JSON.parse(langContent);}catch(e){}console.log("已有文案:", map);fileDisplay2(path.resolve(root_ExmlUrl));
}//异步,不知道什么时候结束,用这个来判断
function reSetSave2(reset){clearTimeout(timer);if(!reset){timer = setTimeout(()=>{let str = JSON.stringify(map);str = str.replace(/\\"/g, "");str = str.replace(/","/g, '",\n"');str = "{\n" + str.substr(1, str.length-2) + "\n}";fs.writeFileSync(outFilePath2, 'window["_LangExml"] = ' + str, { encoding: "utf8" });console.log("保存文件:" + outFilePath2);}, 1000);}
}/*** 文件遍历方法* @param filePath 需要遍历的文件路径*/
function fileDisplay2(filePath){//根据文件路径读取文件,返回文件列表fs.readdir(filePath,function(err,files){if(err){console.warn(err)}else{//遍历读取到的文件列表files.forEach(function(filename){//获取当前文件的绝对路径var filedir = path.join(filePath,filename);//根据文件路径获取文件信息,返回一个fs.Stats对象fs.stat(filedir,function(eror,stats){if(eror){console.warn('获取文件stats失败');}else{var isFile = stats.isFile();//是文件var isDir = stats.isDirectory();//是文件夹if(isFile){//console.log(filename,filedir);let onlyName = filename.split(".")[0];if(filename.indexOf(".exml") != -1 && filterFils.indexOf(onlyName) == -1){changeDode2(filedir);}}if(isDir){if(!filterDirs || filterDirs.indexOf(filename) == -1){fileDisplay2(filedir);//递归,如果是文件夹,就继续遍历该文件夹下面的文件}}}})});}});
}function changeDode2(phppath){//console.log("执行文件:" + phppath);let phpContent = fs.readFileSync(phppath, { encoding: "utf8" });//console.log(phpContent);//['"][\xff-\uffff 0-9]+['"]let arr = phpContent.match(/['"][\xff-\uffff 0-9]+['"]/g);//console.log(arr);if(arr){let change, nstr, regExp0;for(var i=0; i<arr.length; i++){let str = arr[i];if(str.match(/['"][\d$  ]+['"]/g)) continue; //纯数字、空格、$串,忽略str = str.substr(1, str.length-2);//console.log(str);if(!map.hasOwnProperty(str)){map[str] = "";reSetSave2(); //有改变,触发延时保存}}}
}

游戏项目如何快速实现多语言版本(国际化)--Egret篇相关推荐

  1. 游戏实现多语言版本(国际化)

    自从换了项目组之后一直都很忙,没什么时间写博客了,目前这个项目是自己挑的,希望多花点时间,把它做好. 因为项目还没有对外公开,关于项目就不透露信息,只能说一些实现方式. 到新项目之后第一个着手做的就是 ...

  2. 字母消消乐游戏(C语言版本_2023首篇新作)

    上一篇: 2022圣诞树(C语言摇钱树版本) 逐梦编程,让中华屹立世界之巅. 简单的事情重复做,重复的事情用心做,用心的事情坚持做: 文章目录 前言 一.图形库准备 1.EasyX绘图库下载 2.Ea ...

  3. 用C语言写国际化的俄罗斯方块,C++写面向对象思想俄罗斯方块版本

    因为最近在学习UNICODE编码和ANSI编码以及多字节编码在VS下是如何控制变化的,所以 用C语言写了个简单的俄罗斯方块版本,并让它兼容了UNICODE和ANSI字符集,即国际化可以在任何国家地区使 ...

  4. 快速排序的两种实现方法(c语言版本)

    经过调研发现,对任意无序整数数组,快速排序有两种实现方法,这里简单阐述下思路: 思路一:随意选择一个基准元,一般选择数组的起始元或末尾元,Weiss这本书上特意搞了个算法来选择基准元,--,总之就是基 ...

  5. 轻松搞定c++语言pdf_当年锤子的大爆炸,如今12个语言版本都可轻松搞定!

    第011期原创分享 作者:huber 大家好,我是Hub哥!又被Hub友们催更了. 这些天,刷抖音看到罗永浩老师的直播带货,发现了老罗的一些变化,头发少了几千根啊(瞎猜的),言行举止少了往日怼天怼地的 ...

  6. 一步一步实现网站的多语言版本

    网站在开发的过程中需要实现多语言版本,我们暂且认为有英语和汉语两个版本.网站结构包括,UI过程,rest服务,以及相应的js,各个部分我们都要实现多语言,不要求一键切换,但是在部署过程中要能实现多与语 ...

  7. JEECG Framework 3.5.2 (快速开发平台) ACE版本发布

    平台介绍: JEECG(J2EE Code Generation),一款基于代码生成器的JAVA快速开发平台,集成强大代码生成器和在线开发机制,在线报表配置机制. ------------------ ...

  8. MacOSX系统下HomeBrew安装指定版本的软件 IntelliJ IDEA 设置多个Go语言版本开发

    HomeBrew安装指定版本的软件 快速多版本切换 通过 brew install [formula]@[tag] 安装多个版本,然后把其他版本移动到[formula]默认目录中,然后通过brew s ...

  9. 傅里叶变换 c语言程序,(快速傅里叶变换)C语言程序汇编

    ( #include #include /********************************************************************* 快速傅立叶变换C函 ...

最新文章

  1. Smali源代码分析教程(转)
  2. 变压器绕组降低邻近效应_了解高频变压器设计基础(2)
  3. 分支语句语法格式小结 java
  4. 电子围栏判断_电子围栏大用处:进出罐箱指定区域自动通知amp;罐箱库存和使用率自动计算...
  5. infopath转换html,Microsoft Tools to Save InfoPath Forms as HTML
  6. 如何巧妙的运用好弹簧布局SpringLayout?
  7. 【NOI2014】魔法森林
  8. 嫌学校 App烂,极客父母做了开源版本
  9. python extension package_Python Extension Packages for Windows所有包下载
  10. 论文翻译——Skin Lesion Synthesis with Generative Adversarial Networks
  11. oracle自动化巡检报告
  12. 如何在Mysql中运行SQL文件
  13. 后端springboot、mybatisplus,前端vue-cli3、elementUI、axios,使用阿里巴巴提供的easyExcel导入导出excel表格
  14. 有用的.NET开发资料
  15. kubernetes进阶之路(十六)~~~Storage系列之StorageClass
  16. 计算机英语中文谐音,翻译成中文的英文歌 英文歌用中文谐音唱
  17. 【java笔记-006】【uni-app】当前运行的基座不包含原生插件[xxx],请在manifest中配置该插件,重新制作包括该原生插件的自定义运行基座
  18. 大学英语b和计算机三级,大学英语三级
  19. Mysql分区表为什么唯一键必须添加分区字段
  20. 甲基乙烯基硼酸 cas7547-97-9/异环己酰亚胺 cas4538-37-8

热门文章

  1. 如何计算网站或应用程序的带宽需求
  2. 12 个最佳的免费网络监控工具、免费网站监控工具超级好用的有那些
  3. 何为Deep Learning(深度学习)
  4. php漏洞检测修复,PHPStudy漏洞自查与修复指导
  5. 服务器自动关闭远程打印服务,服务器远程打印机设置
  6. css 半透明尖角上下调过来,不和背景色重叠
  7. JAVA模板引擎velocity语法讲解
  8. Windows 无法连接到SENS服务
  9. Spring中的@NumberFormat注解
  10. Ubuntu16.04+caffe+DIGITS的安装配置指南