├── get -dirname.js ├── command.json ├── file-temp.txt ├── config.json ├── src ├── clone-temp.js ├── index.js ├── sprite-key.js ├── ParseI18nKey.js ├── copy-temp.js ├── uuid.js ├── label-key.js ├── i18nTool.js ├── utils.js └── change-prefab.js ├── package.json ├── bin └── main.js └── README.md /get -dirname.js: -------------------------------------------------------------------------------- 1 | module.exports.getDirname = function getDirname() { 2 | return __dirname; 3 | } -------------------------------------------------------------------------------- /command.json: -------------------------------------------------------------------------------- 1 | { 2 | "create": "create", 3 | "gen": "gen-key", 4 | "set": "set-key", 5 | "run": "gen-set-key" 6 | } -------------------------------------------------------------------------------- /file-temp.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "__type__": "", 4 | "_name": "", 5 | "_objFlags": 0, 6 | "node": { 7 | "__id__": 0 8 | }, 9 | "_enabled": true, 10 | "__prefab": { 11 | "__id__": 0 12 | }, 13 | "_string": "", 14 | "params": [], 15 | "_id": "" 16 | }, 17 | { 18 | "__type__": "cc.CompPrefabInfo", 19 | "fileId": "" 20 | } 21 | ] -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "win": { 3 | "_i18n_res_dir": "assets\\resources", 4 | "_i18n_src_dir": "assets", 5 | "_file_prefab_path": "assets\\resources", 6 | "_file_zh_json_path": "assets\\resources\\i18n\\text\\zh\\zh.json", 7 | "_file_zh_json_pic_path": "assets\\resources\\i18n\\sprite\\json\\zh.json" 8 | }, 9 | "mac": { 10 | "_i18n_res_dir": "assets/resources", 11 | "_i18n_src_dir": "assets", 12 | "_file_prefab_path": "assets/resources", 13 | "_file_zh_json_path": "assets/resources/i18n/text/zh/zh.json", 14 | "_file_zh_json_pic_path": "assets/resources/i18n/sprite/json/zh.json" 15 | } 16 | } -------------------------------------------------------------------------------- /src/clone-temp.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const download = require('download-git-repo'); 3 | const path = require('path'); 4 | const { utils } = require('./utils'); 5 | 6 | 7 | function clone() { 8 | return new Promise((resolve, reject) => { 9 | const url = utils.templateUrl(); 10 | if (!Fs.existsSync(url)) { 11 | Fs.mkdirSync(url); 12 | } 13 | download('github:hepeidong/i18n', url, function (err) { 14 | if (err) { 15 | reject(err); 16 | return; 17 | } 18 | resolve('成功...'); 19 | }); 20 | }); 21 | } 22 | 23 | module.exports.clone = clone; 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cc-i18n", 3 | "version": "2.0.12", 4 | "description": "基于cocos creator引擎的游戏开发i18n多语言模块", 5 | "main": "main.js", 6 | "bin": { 7 | "cc-i18n": "./bin/main.js" 8 | }, 9 | "dependencies": { 10 | "colors": "^1.4.0", 11 | "download-git-repo": "^3.0.2", 12 | "minimist": "^1.2.6" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^18.0.6" 16 | }, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/hepeidong/cc-i18n.git" 23 | }, 24 | "keywords": [ 25 | "i18n" 26 | ], 27 | "author": "何沛东", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/hepeidong/cc-i18n/issues" 31 | }, 32 | "homepage": "https://github.com/hepeidong/cc-i18n#readme" 33 | } 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const { clone } = require('./clone-temp'); 3 | const { createI18nConfig, copyTemp } = require('./copy-temp'); 4 | require('colors'); 5 | const { genI18nKey, setI18nKey, i18nTool, initPath } = require('./i18nTool'); 6 | const { utils } = require('./utils'); 7 | 8 | const configObj = utils.getPathConfig(); 9 | //项目动态资源目录 10 | const _file_prefab_path = configObj._file_prefab_path; 11 | //生成的i18n语言映射key存储json文件 12 | const _file_zh_json_path = configObj._file_zh_json_path; 13 | const _file_zh_json_pic_path = configObj._file_zh_json_pic_path; 14 | 15 | 16 | 17 | function path() { 18 | utils.log('正在初始化文件路径...'); 19 | const path1 = utils.cwd(_file_prefab_path); 20 | const path2 = utils.getI18nLabelPath(); 21 | const path3 = utils.cwd(_file_zh_json_path); 22 | const path4 = utils.getI18nSpritePath(); 23 | const path5 = utils.cwd(_file_zh_json_pic_path); 24 | initPath(path1, path2, path3, path4, path5, 'cc.Label', 'cc.Sprite'); 25 | utils.log('资源目录 =>', path1.path); 26 | utils.log('I18nLabel脚本meta文件 =>', path2.path); 27 | utils.log('label的json文件 =>', path3.path); 28 | utils.log('I18nSprite脚本meta文件 =>', path4.path); 29 | utils.log('sprite的json文件 =>', path5.path); 30 | utils.log('####################################################\n'.gray); 31 | } 32 | 33 | function genKey() { 34 | utils.log('正在生成i18n文字key...'); 35 | genI18nKey(); 36 | utils.log('####################################################\n'.gray); 37 | utils.log('成功...'.success); 38 | } 39 | 40 | function setKey() { 41 | utils.log('正在注入i18n文字key...'); 42 | setI18nKey(); 43 | utils.log('成功...'.success); 44 | } 45 | 46 | function run_gen_set_key() { 47 | path(); 48 | genKey(); 49 | setKey(); 50 | } 51 | 52 | function run_gen_set_key_g() { 53 | path(); 54 | utils.log('i18n工具运行...'); 55 | i18nTool(); 56 | utils.log('成功...'.success); 57 | } 58 | 59 | module.exports.path = path; 60 | module.exports.genKey = genKey; 61 | module.exports.setKey = setKey; 62 | module.exports.run_gen_set_key = run_gen_set_key; 63 | module.exports.run_gen_set_key_g = run_gen_set_key_g; 64 | module.exports.copyTemp = copyTemp; 65 | module.exports.createI18nConfig = createI18nConfig; 66 | module.exports.clone = clone; -------------------------------------------------------------------------------- /bin/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const Fs = require('fs'); 3 | const colors = require('colors'); 4 | const minimist = require('minimist'); 5 | const { utils } = require('../src/utils'); 6 | const { genKey, path, setKey, run_gen_set_key_g, run_gen_set_key, createI18nConfig, clone, copyTemp } = require('../src'); 7 | 8 | 9 | const commandStr = Fs.readFileSync(utils.nodeCwd('command.json')).toString(); 10 | const command = JSON.parse(commandStr); 11 | 12 | colors.setTheme({ 13 | error: 'red', 14 | warn: 'yellow', 15 | info: 'blue', 16 | path: () => { 17 | let color = ''; 18 | const platform = process.platform; 19 | if (platform === 'darwin') { 20 | color = 'magenta'; 21 | } 22 | else if (platform === 'win32') { 23 | color = 'white'; 24 | } 25 | return color; 26 | }, 27 | success: 'green' 28 | }); 29 | 30 | const argv = minimist(process.argv.splice(2), { 31 | 'g': false, 32 | 'create': false, 33 | 'gen-key': false, 34 | 'set-key': false, 35 | 'run-gen-set-key': false 36 | }); 37 | 38 | const cmd = argv._[0]; 39 | if (cmd) { 40 | if (cmd === command.create) { 41 | utils.log('###########################################'.gray); 42 | utils.log('正在拷贝资源...'.warn); 43 | clone().then((value) => { 44 | copyTemp(); 45 | createI18nConfig(); 46 | utils.log(value.success); 47 | utils.log('###########################################'.gray); 48 | process.exit(); 49 | }).catch((err) => { 50 | utils.log('克隆模板代码失败:', err); 51 | process.exit(); 52 | }); 53 | } 54 | else if (cmd === command.gen) { 55 | path(); 56 | genKey(); 57 | process.exit(); 58 | } 59 | else if (cmd === command.set) { 60 | path(); 61 | setKey(); 62 | process.exit(); 63 | } 64 | else if (cmd === command.run) { 65 | if (argv.g) { 66 | run_gen_set_key_g(); 67 | } 68 | else { 69 | run_gen_set_key(); 70 | } 71 | process.exit(); 72 | } 73 | else { 74 | utils.log(`无法搜索到命令: ${cmd}`.error); 75 | process.exit(); 76 | } 77 | } 78 | else if (argv.v || argv.version) { 79 | const str = Fs.readFileSync(utils.nodeCwd('package.json')).toString(); 80 | const obj = JSON.parse(str); 81 | console.log(obj.version); 82 | } 83 | else { 84 | utils.log(`无法搜索到命令: ${cmd}`.error); 85 | process.exit(); 86 | } 87 | 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 基于cocos creator引擎的游戏开发i18n多语言模块,是在nodejs环境下运行的i18n工具,所以需要确保安装nodejs环境。 3 | 该工具可以帮助你自动检测游戏预制体中的cc.Label组件,并生成相应的key存储在json文件中,并把相对应的key设置在 4 | I18nLabel组件中,同时也支持自动给预制体增加I18nLabel组件 5 | 6 | ## 介绍 7 | > + 安装 8 | > + 命令行 9 | > + 功能使用 10 | > + 如何增加新语言 11 | > + 如何使用标签多语言json文件 12 | > + 版本 13 | > + 使用说明链接 14 | 15 | ### 安装 16 | > 搭建nodejs环境,并查看是否有nodejs和npm版本 node --version npm --version,如果以确定安装了相应环境, 17 | > 则执行下面命令进行安装: 18 | > *npm install cc-i18n -g* 19 | 20 | ### 命令行 21 | > **工具通过五个命令可以启动运行:** 22 | 23 | > + cc-i18n create: 24 | > > 这个命令会在项目的scripts/i18n目录下生成i18n模块代码,同时在resources/i18n生成相应目录结构 25 | 26 | > + cc-i18n gen-key: 27 | 28 | > > + label多语言: 29 | > > > 这个命令是根据预制体Label节点中挂载的I18nLabel组件生成相应的key,并把key保存到一个Json文件中; 30 | > > > 注意:每一个需要自动生成多语言key的label节点,都需要手动挂载I8nLabel组件,如果想自动挂载,请使用 31 | > > > cc-i18n gen-set-key --g 这个命令,但这个命令会在所有label节点上挂载I18nLabel组件,故使用前 32 | > > > 需要考虑清楚是不是所有label节点都是静态文本。 33 | 34 | > > + sprite多语言: 35 | > > > 对于sprite多语言来说,会检测assets/resources/i18n/sprite目录下的所有图片资源,并获取图片资源的uuid, 36 | > > > 把uuid存储在json文件中。在使用时,必须先在i18n/sprite/路径下根据不同的语言增加相应的目录,比如,如果 37 | > > > 你只发布中文和英语两种语言,那么就在i18n/sprite路径下建zh和en两个文件夹,在这两个文件夹下各自语言的图片, 38 | > > > 必须要确保同一种图片,在不同语言文件夹下的图片文件名相同。 39 | 40 | > > > **注意:i18n/sprite/json路径下的文件内容不得更改,除非你完全了解更改后会产生什么后果** 41 | 42 | 43 | > + cc-i18n set-key: 44 | > > 需要开发者实现在须要进行多语言适配的挂有cc.Label组件或者cc.Sprite组件的节点挂载I18nLabel组件或者I18nSprite组件, 45 | > > 只有挂载了对应的I18nLabel组件和I18nSprite组件,才能对这两个组件设置key。 46 | 47 | > > + label多语言: 48 | > > > 这个命令会检查挂有cc.Label组件的这些节点是否有挂载I18nLabel组件,如果有,则把相应的key设置到组件上 49 | 50 | > > + sprite多语言: 51 | > > > 这个命令会检查挂有cc.Sprite组件的这些节点是否有挂载I18nSprite组件,如果有,则会根据cc.Sprite组件上挂载的 52 | > > > 相应图片,把这张图片的uuid设置到I18nSprite组件上。 53 | 54 | > + cc-i18n gen-set-key: 55 | > > 这个命令会同时执行 cc-i18n gen-key, cc-i18n set-key命令。 56 | 57 | > + cc-i18n gen-set-key -g: 58 | > > 这个命令是根据预制体节点中挂载的cc.Label组件生成相应的key,并把key保存到一个Json文件中,然后再在挂载了 59 | > > cc.Label组件的节点上增加I18nLabel组件,并把相应的key设置到组件上(这个功能在大项目中可能会存在问题) 60 | 61 | > > **注意:这个命令对sprite多语言不起作用,只有label多语言才有用,当使用这个命令时,sprite多语言不会生成key,** 62 | > > **以及设置key。** 63 | 64 | ### 功能使用 65 | 66 | > + 初始化 67 | > > 在项目根目录下运行命令 ***cc-i18n create*** 68 | 69 | > > + 静态文本 70 | > > > 在项目根目录运行 ***cc-i18n gen-set-key*** 可以生成并设置多语言key 71 | > > + 动态文本 72 | > > > 可以使用下面方法设置动态文本: 73 | 74 | node.getComponent(I18nLabel).setLabel("hello") 75 | 76 | 77 | ### 如何增加语言 78 | > 可以在LanguageEnum文件中增加语言枚举,例如: 79 | 80 | enum Language { 81 | 'zh', 82 | 'en', 83 | } 84 | 85 | > 同时在resources/i18n.text目录下增加与枚举同名的目录,再在这个目录下增加枚举同名的json文件即可 86 | 87 | ### 如何使用标签多语言 88 | > 只需要在项目完成后,把zh.json翻译成所需要的语言版本即可,注意key必须一样,不能改变,即把key对应的文本翻译成不同语言即可。 89 | 90 | 91 | ### cocos creator 版本 92 | 3.x以上 93 | 94 | ### 使用说明 95 | > [详细说明文档] https://blog.csdn.net/UchihaMadara_2022/article/details/126219095?spm=1001.2014.3001.5502 96 | -------------------------------------------------------------------------------- /src/sprite-key.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const { ParseI18nKey } = require("./ParseI18nKey"); 3 | const { utils } = require("./utils"); 4 | 5 | const parseI18nKey = new ParseI18nKey(); 6 | 7 | const _file_type = 'meta'; 8 | 9 | function traverseDir(dirPath) { 10 | const files = utils.getFiles(dirPath); 11 | for (const file of files) { 12 | if (file !== 'json') { 13 | const path = utils.rawUrl(dirPath, file); 14 | if (utils.isDir(path)) { 15 | const obj = {}; 16 | gennerateI18nKey(path, obj); 17 | //写入文件 18 | const url = utils.cwd('assets/resources/i18n/sprite/json/' + file + '.json'); 19 | Fs.writeFileSync(url, JSON.stringify(obj, null, 4)); 20 | } 21 | } 22 | } 23 | } 24 | 25 | function gennerateI18nKey(path, obj) { 26 | const files = utils.getFiles(path); 27 | for (const file of files) { 28 | const filePath = utils.rawUrl(path, file); 29 | if (utils.isFile(filePath)) { 30 | if (utils.juageFileType(file, _file_type)) { 31 | const str = Fs.readFileSync(filePath).toString(); 32 | const data = JSON.parse(str); 33 | const subMetas = data.subMetas; 34 | for (const key in subMetas) { 35 | if (subMetas[key].name === 'spriteFrame') { 36 | obj[subMetas[key].displayName] = subMetas[key].uuid; 37 | } 38 | } 39 | } 40 | } 41 | else if (utils.isDir(filePath)) { 42 | gennerateI18nKey(filePath); 43 | } 44 | } 45 | } 46 | 47 | 48 | function setKeyForI18nSprite(fileData, componentPlace, zhFilePath, component) { 49 | const zhObj = parseI18nKey.getZhObj(zhFilePath); 50 | const nodeObj = parseI18nKey.getNodeEle(fileData, componentPlace); 51 | const components = nodeObj._components; 52 | if (components) { 53 | for (const e of components) { 54 | const prefabObj = fileData[e.__id__]; 55 | if (prefabObj.__type__ === component) { 56 | const spriteFrame = prefabObj._spriteFrame; 57 | const uuid = spriteFrame.__uuid__; 58 | if (typeof uuid === 'string') { 59 | if (uuid !== 'null') { 60 | const key = parseI18nKey.contain(zhObj, uuid); 61 | utils.log('设置key =', key); 62 | if (key.length > 0) { 63 | const i18nSprite = parseI18nKey.getPrefabEle(fileData, componentPlace); 64 | i18nSprite._string = key; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | module.exports.genSpriteKey = traverseDir; 74 | module.exports.setKeyForI18nSprite = setKeyForI18nSprite; -------------------------------------------------------------------------------- /src/ParseI18nKey.js: -------------------------------------------------------------------------------- 1 | 2 | const Fs = require('fs'); 3 | 4 | function ParseI18nKey() { 5 | 6 | } 7 | 8 | ParseI18nKey.prototype.zhObj = null; 9 | 10 | ParseI18nKey.prototype.getZhObj = function(zhFilePath) { 11 | if (!this.zhObj) { 12 | if (Fs.existsSync(zhFilePath)) { 13 | zhFile = Fs.readFileSync(zhFilePath); 14 | this.zhObj = JSON.parse(zhFile.toString()); 15 | } 16 | else { 17 | this.zhObj = {}; 18 | } 19 | } 20 | return this.zhObj; 21 | } 22 | 23 | ParseI18nKey.prototype.contain = function(obj, value) { 24 | for (const key in obj) { 25 | if (obj[key] === value) { 26 | return key; 27 | } 28 | } 29 | return ''; 30 | } 31 | 32 | ParseI18nKey.prototype.getPrefabEle = function(fileData, componentPlace) { 33 | const prefabEle = fileData[componentPlace]; 34 | if (!prefabEle) { 35 | console.error('传入的componentPlace是非法的'); 36 | return null; 37 | } 38 | return prefabEle; 39 | } 40 | 41 | ParseI18nKey.prototype.getNodeEle = function(fileData, componentPlace) { 42 | const prefabEle = this.getPrefabEle(fileData, componentPlace); 43 | if (prefabEle) { 44 | const id = prefabEle.node.__id__; 45 | const nodeEle = fileData[id]; 46 | return nodeEle; 47 | } 48 | return null; 49 | } 50 | 51 | ParseI18nKey.prototype.genI18nKey = function(fileData, componentPlace) { 52 | const nodeEle = this.getNodeEle(fileData, componentPlace); 53 | if (nodeEle) { 54 | const keys = []; 55 | 56 | let p = nodeEle._parent; 57 | while (p) { 58 | const temp = fileData[p.__id__]; 59 | p = temp._parent; 60 | if (!p) { 61 | keys.push(temp._name); 62 | keys.push(componentPlace); 63 | } 64 | } 65 | 66 | return keys.join('_'); 67 | } 68 | return ''; 69 | } 70 | 71 | ParseI18nKey.prototype.saveZhFile = function(zhFilePath) { 72 | const keys = Object.keys(this.zhObj); 73 | let zhStr = JSON.stringify(this.zhObj); 74 | let resultStr = ''; 75 | const insertPlace = []; 76 | for (let i = 0; i < keys.length; ++i) { 77 | let index = zhStr.indexOf('"' + keys[i]); 78 | if (index > -1) { 79 | insertPlace.push(index); 80 | } 81 | } 82 | 83 | for (let k = 0; k < zhStr.length; ++k) { 84 | if (insertPlace.indexOf(k) === -1) { 85 | if (k === zhStr.length - 1) { 86 | resultStr += '\n}'; 87 | } 88 | else { 89 | resultStr += zhStr[k]; 90 | } 91 | } 92 | else { 93 | resultStr += '\n\t' + zhStr[k]; 94 | } 95 | } 96 | 97 | Fs.writeFileSync(zhFilePath, resultStr); 98 | } 99 | 100 | module.exports.ParseI18nKey = ParseI18nKey; -------------------------------------------------------------------------------- /src/copy-temp.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | require('colors'); 3 | const { utils } = require('./utils'); 4 | 5 | const configObj = utils.getPathConfig(); 6 | var _file_label_script = ""; 7 | var _file_sprite_script = ""; 8 | 9 | function copy(src, dest) { 10 | const files = utils.getFiles(src); 11 | for (const file of files) { 12 | const path = utils.rawUrl(dest, file); 13 | if (!Fs.existsSync(path)) { 14 | const srcPath = utils.rawUrl(src, file); 15 | if (utils.isDir(srcPath)) { 16 | Fs.mkdirSync(path); 17 | utils.log('创建目录 =>', path.path); 18 | copy(srcPath, path); 19 | } 20 | else if (utils.isFile(srcPath)) { 21 | Fs.copyFileSync(srcPath, path); 22 | utils.log('拷贝文件 =>', path.path); 23 | const filename = file.split('.')[0]; 24 | if (filename === 'I18nLabel') { 25 | _file_label_script = path; 26 | } 27 | else if (filename === 'I18nSprite') { 28 | _file_sprite_script = path; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | function copyCode() { 36 | const projPath = utils.cwd(configObj._i18n_src_dir); 37 | const src = utils.rawUrl(utils.templateUrl(), 'code'); 38 | let dest = ''; 39 | if (Fs.existsSync(utils.rawUrl(projPath,'script'))) { 40 | dest = utils.rawUrl(projPath, 'script'); 41 | } 42 | else if (Fs.existsSync(utils.rawUrl(projPath, 'scripts'))) { 43 | dest = utils.rawUrl(projPath, 'scripts'); 44 | } 45 | else if (Fs.existsSync(utils.rawUrl(projPath, 'Script'))) { 46 | dest = utils.rawUrl(projPath, 'Script'); 47 | } 48 | else if (Fs.existsSync(utils.rawUrl(projPath, 'Scripts'))) { 49 | dest = utils.rawUrl(projPath, 'Scripts'); 50 | } 51 | else { 52 | utils.log('缺少script目录'.error); 53 | process.exit(); 54 | } 55 | copy(src, dest); 56 | } 57 | 58 | function copyRes() { 59 | const projPath = utils.cwd(configObj._i18n_res_dir); 60 | const src = utils.rawUrl(utils.templateUrl(), 'res'); 61 | if (Fs.existsSync(projPath)) { 62 | copy(src, projPath); 63 | } 64 | else { 65 | utils.log('缺少resources目录'.error); 66 | process.exit(); 67 | } 68 | } 69 | 70 | function rmdirSync(path) { 71 | const files = utils.getFiles(path); 72 | for (const file of files) { 73 | const filePath = utils.rawUrl(path, file); 74 | if (utils.isFile(filePath)) { 75 | Fs.rmSync(filePath); 76 | } 77 | else if (utils.isDir(filePath)) { 78 | rmdirSync(filePath); 79 | } 80 | } 81 | Fs.rmdirSync(path); 82 | } 83 | 84 | function copyTemp() { 85 | copyRes(); 86 | copyCode(); 87 | rmdirSync(utils.templateUrl()); 88 | } 89 | 90 | 91 | function createI18nConfig() { 92 | _file_label_script = _file_label_script + '.meta'; 93 | _file_sprite_script = _file_sprite_script + '.meta'; 94 | utils.writeI18nconfig(_file_label_script, _file_sprite_script); 95 | } 96 | 97 | module.exports.copyTemp = copyTemp; 98 | module.exports.createI18nConfig = createI18nConfig; 99 | -------------------------------------------------------------------------------- /src/uuid.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | 3 | // const Uuid = require('node-uuid'); 4 | const Base64KeyChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 5 | 6 | const AsciiTo64 = new Array(128); 7 | for (var i = 0; i < 128; ++i) { AsciiTo64[i] = 0; } 8 | for (i = 0; i < 64; ++i) { AsciiTo64[Base64KeyChars.charCodeAt(i)] = i; } 9 | 10 | const Reg_Dash = /-/g; 11 | const Reg_Uuid = /^[0-9a-fA-F-]{36}$/; 12 | const Reg_NormalizedUuid = /^[0-9a-fA-F]{32}$/; 13 | const Reg_CompressedUuid = /^[0-9a-zA-Z+/]{22,23}$/; 14 | const Reg_CompressedSubAssetUuid = /^([0-9a-zA-Z+/]{22,23})(@.{5})$/; 15 | const Reg_subAssetUuid = /([^@]{32,36})(@.{5})$/; 16 | const Reg_UuidInLibPath = /.*[/\\][0-9a-fA-F]{2}[/\\]([0-9a-fA-F-@]{8,}).*/; 17 | 18 | // 加了这个标记后,字符串就不可能会是 uuid 了。 19 | // let = '.'; 20 | 21 | // 压缩后的 uuid 可以减小保存时的尺寸,但不能做为文件名(因为无法区分大小写并且包含非法字符)。 22 | // 默认将 uuid 的后面 27 位压缩成 18 位,前 5 位保留下来,方便调试。 23 | // fc991dd7-0033-4b80-9d41-c8a86a702e59 -> fc9913XADNLgJ1ByKhqcC5Z 24 | // 如果启用 min 则将 uuid 的后面 30 位压缩成 20 位,前 2 位保留不变。 25 | // fc991dd7-0033-4b80-9d41-c8a86a702e59 -> fcmR3XADNLgJ1ByKhqcC5Z 26 | /* 27 | * @param {Boolean} [min=false] 28 | */ 29 | function compressUuid(uuid, min) { 30 | const result = uuid.match(Reg_subAssetUuid); 31 | if (!result) { 32 | return compressNormalUuid(uuid, min); 33 | } 34 | uuid = result[1]; 35 | return compressNormalUuid(uuid, min) + result[2]; 36 | } 37 | 38 | function compressNormalUuid(uuid, min) { 39 | if (Reg_Uuid.test(uuid)) { 40 | uuid = uuid.replace(Reg_Dash, ''); 41 | } else if (!Reg_NormalizedUuid.test(uuid)) { 42 | return uuid; 43 | } 44 | var reserved = (min === true) ? 2 : 5; 45 | return compressHex(uuid, reserved); 46 | } 47 | 48 | function compressHex(hexString, reservedHeadLength) { 49 | var length = hexString.length; 50 | var i; 51 | if (typeof reservedHeadLength !== 'undefined') { 52 | i = reservedHeadLength; 53 | } else { 54 | i = length % 3; 55 | } 56 | var head = hexString.slice(0, i); 57 | var base64Chars = []; 58 | while (i < length) { 59 | var hexVal1 = parseInt(hexString[i], 16); 60 | var hexVal2 = parseInt(hexString[i + 1], 16); 61 | var hexVal3 = parseInt(hexString[i + 2], 16); 62 | base64Chars.push(Base64KeyChars[(hexVal1 << 2) | (hexVal2 >> 2)]); 63 | base64Chars.push(Base64KeyChars[((hexVal2 & 3) << 4) | hexVal3]); 64 | i += 3; 65 | } 66 | return head + base64Chars.join(''); 67 | } 68 | 69 | function decompressUuid(uuid) { 70 | const result = uuid.match(Reg_CompressedSubAssetUuid); 71 | if (!result) { 72 | return decompressNormalUuid(uuid); 73 | } 74 | 75 | uuid = result[1]; 76 | return decompressNormalUuid(uuid) + result[2]; 77 | } 78 | 79 | function decompressNormalUuid(str) { 80 | if (str.length === 23) { 81 | // decode base64 82 | let hexChars = []; 83 | for (let i = 5; i < 23; i += 2) { 84 | let lhs = AsciiTo64[str.charCodeAt(i)]; 85 | let rhs = AsciiTo64[str.charCodeAt(i + 1)]; 86 | hexChars.push((lhs >> 2).toString(16)); 87 | hexChars.push((((lhs & 3) << 2) | rhs >> 4).toString(16)); 88 | hexChars.push((rhs & 0xF).toString(16)); 89 | } 90 | // 91 | str = str.slice(0, 5) + hexChars.join(''); 92 | } else if (str.length === 22) { 93 | // decode base64 94 | let hexChars = []; 95 | for (let i = 2; i < 22; i += 2) { 96 | let lhs = AsciiTo64[str.charCodeAt(i)]; 97 | let rhs = AsciiTo64[str.charCodeAt(i + 1)]; 98 | hexChars.push((lhs >> 2).toString(16)); 99 | hexChars.push((((lhs & 3) << 2) | rhs >> 4).toString(16)); 100 | hexChars.push((rhs & 0xF).toString(16)); 101 | } 102 | // 103 | str = str.slice(0, 2) + hexChars.join(''); 104 | } else { 105 | return str; 106 | } 107 | return [str.slice(0, 8), str.slice(8, 12), str.slice(12, 16), str.slice(16, 20), str.slice(20)].join('-'); 108 | } 109 | 110 | function isUuid(str) { 111 | return Reg_CompressedUuid.test(str) || Reg_NormalizedUuid.test(str) || Reg_Uuid.test(str) || Reg_subAssetUuid.test(str); 112 | } 113 | 114 | // 从路径中提取 uuid 115 | // 支持类似 ".../5b/5b9cbc23-76b3-41ff-9953-4219fdbea72c/Fontin-SmallCaps.ttf" 这样的 116 | function getUuidFromLibPath(path) { 117 | var matches = path.match(Reg_UuidInLibPath); 118 | if (matches) { 119 | return encodeURI(matches[1]); 120 | } 121 | return ''; 122 | } 123 | 124 | function uuid() { 125 | var uuid = Uuid.v4(); 126 | return compressUuid(uuid, true); 127 | } 128 | 129 | module.exports.compressUuid = compressUuid; 130 | -------------------------------------------------------------------------------- /src/label-key.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const { ParseI18nKey } = require('./ParseI18nKey'); 3 | const { utils } = require('./utils'); 4 | 5 | const parseI18nKey = new ParseI18nKey(); 6 | 7 | 8 | 9 | function getLabelKey(fileData, componentPlace) { 10 | const nodeEle = parseI18nKey.getNodeEle(fileData, componentPlace); 11 | if (nodeEle) { 12 | const keys = [nodeEle._name]; 13 | let p = nodeEle._parent; 14 | while (p) { 15 | const temp = fileData[p.__id__]; 16 | utils.insertElement(keys, temp._name, 0); 17 | p = temp._parent; 18 | } 19 | return keys.join('_'); 20 | } 21 | return ''; 22 | } 23 | 24 | function getLabelKey1(fileData, componentPlace) { 25 | const nodeEle = parseI18nKey.getNodeEle(fileData, componentPlace); 26 | if (nodeEle) { 27 | const keys = []; 28 | 29 | let p = nodeEle._parent; 30 | while (p) { 31 | const temp = fileData[p.__id__]; 32 | p = temp._parent; 33 | if (!p) { 34 | keys.push(temp._name); 35 | keys.push(componentPlace); 36 | } 37 | } 38 | 39 | return keys.join('_'); 40 | } 41 | return ''; 42 | } 43 | 44 | function getLabelString(fileData, componentPlace, component) { 45 | const nodeEle = parseI18nKey.getNodeEle(fileData, componentPlace); 46 | if (nodeEle) { 47 | const components = nodeEle._components; 48 | for (const ele of components) { 49 | const id = ele.__id__; 50 | const prefabEle = fileData[id]; 51 | if (prefabEle.__type__ === component) { 52 | return prefabEle._string; 53 | } 54 | } 55 | } 56 | return null; 57 | } 58 | 59 | function contain(obj, value) { 60 | for (const key in obj) { 61 | if (obj[key] === value) { 62 | return key; 63 | } 64 | } 65 | return ''; 66 | } 67 | 68 | function juageSpaceChar(str) { 69 | for (let i = 0; i < str.length; ++i) { 70 | if (str[i] !== " ") { 71 | return false; 72 | } 73 | } 74 | if (str.length === 1 && str[0] !== '\n') { 75 | return false; 76 | } 77 | return true; 78 | } 79 | 80 | function juageI18nKey(key) { 81 | if (typeof key === 'string') { 82 | const flag = key in parseI18nKey.zhObj; 83 | if (!juageSpaceChar(key)) { 84 | return flag; 85 | } 86 | } 87 | return true; 88 | } 89 | 90 | 91 | /** 92 | * 给i18nLabel组件设置key值 93 | * @param {any[]} fileData 预制体文本数据 94 | * @param {Number} componentPlace 增加的组件在文本中的位置 95 | * @param {String} zhFilePath 放文字的json文件路径 96 | * @param {String} component 根据存在的这个组件增加I18nLabel脚本组件,在这里是cc.Label 97 | * @returns 98 | */ 99 | function setKeyAndAddI18nLabel(fileData, componentPlace, zhFilePath, component) { 100 | const zhObj = parseI18nKey.getZhObj(zhFilePath); 101 | const prefabEle = parseI18nKey.getPrefabEle(fileData, componentPlace); 102 | if (prefabEle && juageI18nKey(prefabEle._string)) { 103 | const labelString = getLabelString(fileData, componentPlace, component); 104 | if (labelString !== 'label' && labelString.length > 0 && !juageSpaceChar(labelString)) { 105 | let key = contain(zhObj, labelString); 106 | if (key.length === 0) { 107 | key = getLabelKey1(fileData, componentPlace); 108 | zhObj[key] = labelString; 109 | } 110 | prefabEle._string = key; 111 | } 112 | } 113 | } 114 | 115 | function gennerateI18nKey(fileData, componentPlace, zhFilePath, component) { 116 | const zhObj = parseI18nKey.getZhObj(zhFilePath); 117 | const labelEle = fileData[componentPlace]; 118 | if (labelEle.__type__ === component) { 119 | const labelString = labelEle._string; 120 | if (labelString !== 'label' && labelString.length > 0 && !juageSpaceChar(labelString)) { 121 | let key = contain(zhObj, labelString); 122 | if (key.length === 0) { 123 | key = getLabelKey1(fileData, componentPlace); 124 | zhObj[key] = labelString; 125 | } 126 | } 127 | } 128 | } 129 | 130 | function setKeyForI18nLabel(fileData, componentPlace, zhFilePath, component) { 131 | const zhObj = parseI18nKey.getZhObj(zhFilePath); 132 | const prefabEle = parseI18nKey.getPrefabEle(fileData, componentPlace); 133 | if (prefabEle && juageI18nKey(prefabEle._string)) { 134 | const labelString = getLabelString(fileData, componentPlace, component); 135 | if (labelString !== 'label' && labelString.length > 0 && !juageSpaceChar(labelString)) { 136 | let key = contain(zhObj, labelString); 137 | if (key.length > 0) { 138 | prefabEle._string = key; 139 | } 140 | } 141 | } 142 | } 143 | 144 | function saveZhFile(zhFilePath) { 145 | parseI18nKey.saveZhFile(zhFilePath); 146 | } 147 | 148 | module.exports = { 149 | setKeyAndAddI18nLabel(fileData, componentPlace, zhFilePath, component) { 150 | setKeyAndAddI18nLabel(fileData, componentPlace, zhFilePath, component); 151 | }, 152 | 153 | gennerateI18nKey(fileData, componentPlace, zhFilePath, component) { 154 | gennerateI18nKey(fileData, componentPlace, zhFilePath, component); 155 | }, 156 | 157 | setKeyForI18nLabel(fileData, componentPlace, zhFilePath, component) { 158 | setKeyForI18nLabel(fileData, componentPlace, zhFilePath, component); 159 | }, 160 | 161 | saveZhFile(zhFilePath) { 162 | saveZhFile(zhFilePath); 163 | } 164 | }; -------------------------------------------------------------------------------- /src/i18nTool.js: -------------------------------------------------------------------------------- 1 | var Fs = require('fs'); 2 | const { addComponent, removeComponent } = require('./change-prefab'); 3 | require('colors'); 4 | const { setKeyAndAddI18nLabel, saveZhFile, gennerateI18nKey, setKeyForI18nLabel } = require('./label-key'); 5 | const { genSpriteKey, setKeyForI18nSprite } = require('./sprite-key'); 6 | const { utils } = require('./utils'); 7 | 8 | 9 | //项目动态资源目录 10 | var _file_prefab_path = ""; 11 | //需要I18nLabel组件的meta文件路径 12 | var _label_meta_path = ""; 13 | //需要I18nSprite组件的meta文件路径 14 | var _sprite_meta_path = ""; 15 | //生成的Label的i18n语言映射key存储json文件的路径 16 | var _label_zh_json_path = ""; 17 | //生成的Sprite的i18n语言映射key存储json文件的路径 18 | var _sprite_zh_json_path = ""; 19 | 20 | //处理的文件类型后缀 21 | const _file_type = 'prefab'; 22 | /** 23 | * 如果是删除组件,则会删除这里设定的类型组件 24 | * 如果是增加组件,会以这个组件条件,当节点存在这个组件,则增加相应的脚本组件 25 | */ 26 | var _labelComponent = ""; 27 | var _spriteComponent = ""; 28 | 29 | 30 | function getI18nLabelUuid() { 31 | return utils.i18nLabelUuid(_label_meta_path); 32 | } 33 | 34 | function getI18nSpriteUuid() { 35 | return utils.i18nSpriteUuid(_sprite_meta_path); 36 | } 37 | 38 | 39 | function setLabelKey(fileData, component) { 40 | let flag = false; 41 | for (let i = 0; i < fileData.length; ++i) { 42 | if (fileData[i].__type__ === getI18nLabelUuid()) { 43 | flag = true; 44 | setKeyAndAddI18nLabel(fileData, i, _label_zh_json_path, component); 45 | } 46 | } 47 | if (flag) { 48 | saveZhFile(_label_zh_json_path); 49 | } 50 | return fileData; 51 | } 52 | 53 | //处理完的预制体数据文件缓存 54 | const _cache_prefab_file_list = []; 55 | //处理完的预制体数据文件路径缓存 56 | const _cache_prefab_path_list = []; 57 | 58 | /** 59 | * 遍历读取目录下的所有文件 60 | * @param {String} path 传入目录的路径 61 | * @param {String} component 要增加或者删除的组件 62 | * @param {Boolean} status 0:删除组件,1:增加组件,2:直接获取文件数据信息 63 | */ 64 | function readFileAllInDir(path, callback) { 65 | const files = utils.getFiles(path); 66 | for (const file of files) { 67 | let filePath = utils.rawUrl(path, file); 68 | if (utils.isFile(filePath)) { 69 | if (utils.juageFileType(file, _file_type)) { 70 | //是文件,则读取里面的数据 71 | const fileData = Fs.readFileSync(filePath); 72 | //预制体文件解析后的数据,数据格式是数组形式存储 73 | let data = JSON.parse(fileData.toString()); 74 | 75 | const result = callback && callback(data); 76 | if (result) { 77 | _cache_prefab_file_list.push(result.fileData); 78 | } 79 | else { 80 | _cache_prefab_file_list.push(data); 81 | } 82 | _cache_prefab_path_list.push(filePath); 83 | } 84 | } 85 | else if (utils.isDir(filePath)) { 86 | //是目录,则继续递归遍历改目录 87 | readFileAllInDir(filePath); 88 | } 89 | } 90 | } 91 | 92 | //删除cc.Label组件,暂时没用用,放在这里,用作技术储备 93 | function removeLabel() { 94 | readFileAllInDir(_file_prefab_path, (data) => { 95 | return removeComponent(data, _labelComponent); 96 | }); 97 | } 98 | 99 | function writeFile(component) { 100 | for (let i = 0; i < _cache_prefab_file_list.length; ++i) { 101 | Fs.writeFileSync(_cache_prefab_path_list[i], JSON.stringify(setLabelKey(_cache_prefab_file_list[i], component), null, 4)); 102 | utils.log('写入文件 =>', _cache_prefab_path_list[i].path); 103 | } 104 | } 105 | 106 | //给所有的预制体中有cc.Label组件的节点增加i18nLabel组件 107 | function i18nTool() { 108 | readFileAllInDir(_file_prefab_path, (data) => { 109 | return addComponent(data, _labelComponent, getI18nLabelUuid()); 110 | }); 111 | writeFile(_labelComponent); 112 | } 113 | 114 | 115 | //直接生成i18n的文字key 116 | function genI18nKey() { 117 | readFileAllInDir(_file_prefab_path); 118 | for (let i = 0; i < _cache_prefab_file_list.length; ++i) { 119 | const fileData = _cache_prefab_file_list[i]; 120 | let flag = false; 121 | for (let i = 0; i < fileData.length; ++i) { 122 | //这个是没有增加组件,直接生成key值 123 | if (fileData[i].__type__ === _labelComponent) { 124 | const nodeId = fileData[i].node.__id__; 125 | const components = fileData[nodeId]._components; 126 | for (const e of components) { 127 | const i18nLabel = fileData[e.__id__]; 128 | if (i18nLabel.__type__ === getI18nLabelUuid()) { 129 | //不是动态文本 130 | if (!i18nLabel.DTEXT) { 131 | flag = true; 132 | gennerateI18nKey(fileData, i, _label_zh_json_path, _labelComponent); 133 | break; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | if (flag) { 140 | saveZhFile(_label_zh_json_path); 141 | } 142 | } 143 | 144 | genSpriteKey(utils.cwd('assets/resources/i18n/sprite')); 145 | } 146 | 147 | function setI18nLabelKey() { 148 | for (let i = 0; i < _cache_prefab_file_list.length; ++i) { 149 | const fileData = _cache_prefab_file_list[i]; 150 | let flag = false; 151 | for (let i = 0; i < fileData.length; ++i) { 152 | //这个是没有增加组件,直接生成key值 153 | if (fileData[i].__type__ === getI18nLabelUuid()) { 154 | flag = true; 155 | setKeyForI18nLabel(fileData, i, _label_zh_json_path, _labelComponent); 156 | } 157 | } 158 | if (flag) { 159 | Fs.writeFileSync(_cache_prefab_path_list[i], JSON.stringify(fileData, null, 4)); 160 | utils.log('设置I18nLabel写入文件 =>', _cache_prefab_path_list[i].path); 161 | } 162 | } 163 | } 164 | 165 | function setI18nSpriteKey() { 166 | for (let i = 0; i < _cache_prefab_file_list.length; ++i) { 167 | const fileData = _cache_prefab_file_list[i]; 168 | let flag = false; 169 | for (let i = 0; i < fileData.length; ++i) { 170 | //这个是没有增加组件,直接生成key值 171 | if (fileData[i].__type__ === getI18nSpriteUuid()) { 172 | flag = true; 173 | setKeyForI18nSprite(fileData, i, _sprite_zh_json_path, _spriteComponent); 174 | } 175 | } 176 | if (flag) { 177 | Fs.writeFileSync(_cache_prefab_path_list[i], JSON.stringify(fileData, null, 4)); 178 | utils.log('设置I18nSprite写入文件 =>', _cache_prefab_path_list[i].path); 179 | } 180 | } 181 | } 182 | 183 | //把key放入到相应组件里 184 | function setI18nKey() { 185 | readFileAllInDir(_file_prefab_path); 186 | setI18nLabelKey(); 187 | setI18nSpriteKey(); 188 | } 189 | 190 | module.exports = { 191 | initPath(prefabPath, lanelMetaPath, labelZhJsonPath, spriteMetaPath, spriteZhJsonPath, component, spriteComponent) { 192 | _file_prefab_path = prefabPath; 193 | _label_meta_path = lanelMetaPath; 194 | _label_zh_json_path = labelZhJsonPath; 195 | _sprite_meta_path = spriteMetaPath; 196 | _sprite_zh_json_path = spriteZhJsonPath; 197 | _labelComponent = component; 198 | _spriteComponent = spriteComponent; 199 | }, 200 | 201 | genI18nKey() { 202 | genI18nKey(); 203 | }, 204 | 205 | setI18nKey() { 206 | setI18nKey(); 207 | }, 208 | 209 | i18nTool() { 210 | i18nTool(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | var Fs = require('fs'); 2 | const { getDirname } = require('../get -dirname'); 3 | const { compressUuid } = require('./uuid'); 4 | require('colors'); 5 | const { join } = require('path'); 6 | 7 | const hex = [ // 0-255 8 | "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", 9 | "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", 10 | "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", 11 | "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", 12 | "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", 13 | "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", 14 | "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", 15 | "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", 16 | "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", 17 | "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", 18 | "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", 19 | "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", 20 | "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", 21 | "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", 22 | "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", 23 | "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff" 24 | ]; 25 | 26 | function generateUUID() { 27 | const d0 = Math.random() * 0xffffffff | 0 28 | const d1 = Math.random() * 0xffffffff | 0 29 | const d2 = Math.random() * 0xffffffff | 0 30 | const d3 = Math.random() * 0xffffffff | 0 31 | return hex[d0 & 0xff] + hex[d0 >> 8 & 0xff] + hex[d0 >> 16 & 0xff] + hex[d0 >> 24 & 0xff] + '-' + 32 | hex[d1 & 0xff] + hex[d1 >> 8 & 0xff] + '-' + hex[d1 >> 16 & 0x0f | 0x40] + hex[d1 >> 24 & 0xff] + '-' + 33 | hex[d2 & 0x3f | 0x80] + hex[d2 >> 8 & 0xff] + '-' + hex[d2 >> 16 & 0xff] + hex[d2 >> 24 & 0xff] + 34 | hex[d3 & 0xff] + hex[d3 >> 8 & 0xff] + hex[d3 >> 16 & 0xff] + hex[d3 >> 24 & 0xff]; 35 | } 36 | 37 | //脚本的uuid 38 | var _label_script_uuid = ""; 39 | var _sprite_script_uuid = ""; 40 | var _path_config = null; 41 | var _i18nLabel_path = ""; 42 | var _i18nSprite_path = ""; 43 | var _i18nconfig = { 44 | i18nLabel: "", 45 | i18nSprite: "" 46 | }; 47 | const _i18nConfigFilename = "i18n.config.json"; 48 | 49 | module.exports.utils = { 50 | getFiles(path) { 51 | return Fs.readdirSync(path); 52 | }, 53 | 54 | isFile(path) { 55 | let stat = Fs.statSync(path); 56 | return stat.isFile(); 57 | }, 58 | 59 | isDir(path) { 60 | let stat = Fs.statSync(path); 61 | return stat.isDirectory(); 62 | }, 63 | 64 | /** 65 | * 文件是否为预制体文件 66 | * @param {String} filename 文件名 67 | * @param {String} type 文件类型后缀 68 | * @returns 返回是否为指定文件判断结果 69 | */ 70 | juageFileType(filename, type) { 71 | let suffix = filename.split('.').pop(); 72 | if (suffix.trim() === type.trim()) { 73 | return true; 74 | } 75 | return false; 76 | }, 77 | 78 | /** 79 | * 在数组中插入元素 80 | * @param {any[]} array 81 | * @param {any} ele 82 | * @param {Number} place 83 | */ 84 | insertElement(array, ele, place) { 85 | if (array.length < place) { 86 | return; 87 | } 88 | for (let i = array.length - 1; i >= place; --i) { 89 | array[i + 1] = array[i]; 90 | if (i === place) { 91 | array[i] = ele; 92 | return; 93 | } 94 | } 95 | }, 96 | 97 | generateUUID() { 98 | return generateUUID(); 99 | }, 100 | 101 | getPathConfig() { 102 | if (!_path_config) { 103 | const configStr = Fs.readFileSync(this.nodeCwd('config.json')).toString(); 104 | const config = JSON.parse(configStr); 105 | const platform = process.platform; 106 | if (platform === 'darwin') { 107 | this.log('运行环境', '"' + 'MacOS'.cyan + '"'); 108 | _path_config = config.mac; 109 | } 110 | else if (platform === 'win32') { 111 | this.log('运行环境', '"'+ 'Windows'.cyan + '"'); 112 | _path_config = config.win; 113 | } 114 | else { 115 | throw new Error('工具只能在windows平台或者mac平台下运行'); 116 | } 117 | } 118 | return _path_config; 119 | }, 120 | 121 | cwd(path) { 122 | var url = ''; 123 | url = join(process.cwd(), path); 124 | return url; 125 | }, 126 | 127 | rawUrl(path, filename) { 128 | return join(path, filename); 129 | }, 130 | 131 | nodeCwd(path) { 132 | return join(getDirname(), path); 133 | }, 134 | 135 | templateUrl() { 136 | return this.cwd('i18n_template'); 137 | }, 138 | 139 | setI18nLabelPath(path) { 140 | _i18nLabel_path = path; 141 | }, 142 | 143 | writeI18nconfig(labelScriptPath, spriteScriptPath) { 144 | let config; 145 | if (Fs.existsSync(this.cwd(_i18nConfigFilename))) { 146 | config = this.readI18nconfig(); 147 | config.i18nLabel = labelScriptPath; 148 | config.i18nSprite = spriteScriptPath; 149 | } 150 | else { 151 | _i18nconfig.i18nLabel = labelScriptPath; 152 | _i18nconfig.i18nSprite = spriteScriptPath; 153 | config = _i18nconfig; 154 | } 155 | Fs.writeFileSync(this.cwd(_i18nConfigFilename), JSON.stringify(config, null, 4)); 156 | }, 157 | 158 | readI18nconfig() { 159 | if (_i18nconfig.i18nLabel.length === 0) { 160 | const configStr = Fs.readFileSync(this.cwd(_i18nConfigFilename)).toString(); 161 | _i18nconfig = JSON.parse(configStr); 162 | } 163 | return _i18nconfig; 164 | }, 165 | 166 | getI18nLabelPath() { 167 | if (_i18nLabel_path.length === 0) { 168 | this.readI18nconfig(); 169 | _i18nLabel_path = _i18nconfig.i18nLabel; 170 | } 171 | return _i18nLabel_path; 172 | }, 173 | 174 | getI18nSpritePath() { 175 | if (_i18nSprite_path.length === 0) { 176 | this.readI18nconfig(); 177 | _i18nSprite_path = _i18nconfig.i18nSprite; 178 | } 179 | return _i18nSprite_path; 180 | }, 181 | 182 | log(...args) { 183 | console.log('[' + 'i18nTool'.cyan + ']', ...args); 184 | }, 185 | 186 | i18nLabelUuid(filePath) { 187 | if (_label_script_uuid.length === 0) { 188 | const fileData = Fs.readFileSync(filePath); 189 | const data = JSON.parse(fileData.toString()); 190 | _label_script_uuid = compressUuid(data.uuid, false); 191 | } 192 | return _label_script_uuid; 193 | }, 194 | 195 | i18nSpriteUuid(filePath) { 196 | if (_sprite_script_uuid.length === 0) { 197 | const fileData = Fs.readFileSync(filePath); 198 | const data = JSON.parse(fileData.toString()); 199 | _sprite_script_uuid = compressUuid(data.uuid, false); 200 | } 201 | return _sprite_script_uuid; 202 | }, 203 | 204 | getFileId() { 205 | return compressUuid(generateUUID(), false); 206 | } 207 | } -------------------------------------------------------------------------------- /src/change-prefab.js: -------------------------------------------------------------------------------- 1 | var Fs = require('fs'); 2 | const { utils } = require("./utils"); 3 | require('colors'); 4 | 5 | var _script_uuid = ""; 6 | const _file_temp_path = utils.nodeCwd('file-temp.txt'); 7 | 8 | function changeNodeId(nodeData, changeEleIndex) { 9 | const node = nodeData.node; 10 | if (node) { 11 | const id = node.__id__; 12 | if (id >= changeEleIndex) { 13 | nodeData.node.__id__ = id - 2; 14 | } 15 | } 16 | } 17 | 18 | function changePrefabId(nodeData, changeEleIndex, key) { 19 | const prefab = nodeData[key]; 20 | if (prefab) { 21 | const id = prefab.__id__; 22 | if (id >= changeEleIndex) { 23 | nodeData[key].__id__ = id - 2; 24 | } 25 | } 26 | } 27 | 28 | function changeComponentId(nodeData, changeEleIndex) { 29 | const components = nodeData._components; 30 | if (components) { 31 | for (const ele of components) { 32 | const id = ele.__id__; 33 | if (id >= changeEleIndex) { 34 | ele.__id__ = id - 2; 35 | } 36 | } 37 | } 38 | } 39 | 40 | function changeChildrenId(nodeData, changeEleIndex) { 41 | const children = nodeData._children; 42 | if (children) { 43 | for (const ele of children) { 44 | const id = ele.__id__; 45 | if (id >= changeEleIndex) { 46 | ele.__id__ = id - 2; 47 | } 48 | } 49 | } 50 | } 51 | 52 | function changeParentId(nodeData, changeEleIndex) { 53 | const parent = nodeData._parent 54 | if (parent) { 55 | const id = parent.__id__ 56 | if (id >= changeEleIndex) { 57 | nodeData._parent.__id__ = id - 2; 58 | } 59 | } 60 | } 61 | //改变脚本挂载的属性在文本中的Id数据 62 | function changeScriptPropId(obj, changeEleIndex) { 63 | const id = obj.__id__; 64 | if (typeof id === 'number') { 65 | if (id >= changeEleIndex) { 66 | obj.__id__ = id - 2; 67 | } 68 | } 69 | } 70 | 71 | function changeRoot(root, changeEleIndex) { 72 | if (root.__id__ >= changeEleIndex) { 73 | root.__id__ = root.__id__ - 2; 74 | } 75 | } 76 | 77 | function changeInstance(instance, changeEleIndex) { 78 | if (instance.__id__ >= changeEleIndex) { 79 | instance.__id__ = instance.__id__ - 2; 80 | } 81 | } 82 | 83 | function changePropertyOverrides(fileData, propertyOverrides, changeEleIndex) { 84 | for (const info of propertyOverrides) { 85 | if (info.__id__ >= changeEleIndex) { 86 | info.__id__ = info.__id__ - 2; 87 | } 88 | 89 | const propOverInfo = fileData[info.__id__]; 90 | const targetInfo = propOverInfo.targetInfo; 91 | if (targetInfo.__id__ >= changeEleIndex) { 92 | targetInfo.__id__ = targetInfo.__id__ - 2; 93 | } 94 | } 95 | } 96 | 97 | function changePrefabInfo(fileData, nodeData, changeEleIndex) { 98 | const root = nodeData.root; 99 | const instance = nodeData.instance; 100 | changeRoot(root, changeEleIndex); 101 | changeInstance(instance, changeEleIndex); 102 | 103 | const prefabRootNode = fileData[instance.__id__].prefabRootNode; 104 | if (prefabRootNode.__id__ >= changeEleIndex) { 105 | prefabRootNode.__id__ = prefabRootNode.__id__ - 2; 106 | } 107 | const propertyOverrides = fileData[instance.__id__].propertyOverrides; 108 | changePropertyOverrides(fileData, propertyOverrides, changeEleIndex); 109 | } 110 | 111 | /** 112 | * 增加i18nLabel组件 113 | * @param {any[]} fileData 114 | * @param {Number} place 115 | * @param {Number} nodeId 116 | * @returns 返回是否增加成功,如果当前节点已存在此组件,则不会重复增加,返回false 117 | */ 118 | function addComponentToPrefab(fileData, place, nodeId) { 119 | const uuid = _script_uuid 120 | const data = Fs.readFileSync(_file_temp_path).toString(); 121 | const temp = JSON.parse(data); 122 | temp[0].__type__ = uuid; 123 | temp[0].node.__id__ = nodeId; 124 | temp[0].__prefab.__id__ = place + 1; 125 | temp[1].fileId = utils.getFileId(); 126 | const node = fileData[nodeId]; 127 | 128 | const components = node['_components']; 129 | if (components) { 130 | let exis = false; 131 | for (const ele of components) { 132 | const id = ele['__id__']; 133 | if (fileData[id]['__type__'] === uuid) { 134 | exis = true; 135 | break; 136 | } 137 | } 138 | if (!exis) { 139 | utils.insertElement(fileData, temp[1], place); 140 | utils.insertElement(fileData, temp[0], place); 141 | components.push({__id__: place}); 142 | return true; 143 | } 144 | else { 145 | utils.log('节点 ' + node['_name'].warn + ' 已经有了' + uuid.warn + ' 组件'); 146 | } 147 | } 148 | else { 149 | utils.insertElement(fileData, temp[1], place); 150 | utils.insertElement(fileData, temp[0], place); 151 | components = [{__id__: place}]; 152 | return true; 153 | } 154 | return false; 155 | } 156 | 157 | /** 158 | * 是否有相应的组件 159 | * @param {any[]} fileData 160 | * @param {any} element 161 | * @param {String} component 组件名 162 | * @returns 163 | */ 164 | function hasComponent(fileData, element, component) { 165 | const components = element._components; 166 | if (components) { 167 | let index = -1; 168 | for (const ele of components) { 169 | index++; 170 | const id = ele.__id__; 171 | const data = fileData[id]; 172 | 173 | if (data.__type__ === component) { 174 | return {flag: true, id, componentArrIndex: index}; 175 | } 176 | } 177 | } 178 | return {flag: false, id: -1, componentArrIndex: -1}; 179 | } 180 | 181 | function juageObjType(obj) { 182 | if (obj !== null) { 183 | if (typeof obj === 'object') { 184 | return true; 185 | } 186 | } 187 | return false; 188 | } 189 | 190 | function juageKey(key) { 191 | if ( 192 | key !== 'node' && 193 | key !== '__prefab' && 194 | key !== '_components' && 195 | key !== 'data' && 196 | key !== '_children' && 197 | key !== 'root' && 198 | key !== 'asset' && 199 | key !== 'propertyOverrides' && 200 | key !== 'prefabRootNode' && 201 | key !== 'instance' && 202 | key !== 'targetInfo') { 203 | return true; 204 | } 205 | return false; 206 | } 207 | 208 | /** 209 | * 刷新预制体挂载的脚本绑定的属性的id信息 210 | * @param {any} node 211 | * @param {Number} changeEleIndex 212 | * @param {Boolean} addComponent 213 | */ 214 | function refreshScriptProp(node, changeEleIndex) { 215 | for (const key in node) { 216 | if (juageKey(key)) { 217 | const obj = node[key]; 218 | if (juageObjType(obj)) { 219 | if (Array.isArray(obj)) { 220 | for (const ele of obj) { 221 | if (juageObjType(ele)) { 222 | changeScriptPropId(ele, changeEleIndex); 223 | } 224 | } 225 | } 226 | else { 227 | changeScriptPropId(obj, changeEleIndex); 228 | } 229 | } 230 | } 231 | } 232 | } 233 | 234 | function refreshScript(node, changeEleIndex) { 235 | let tempIndex = node.__type__.indexOf('cc.'); 236 | if (tempIndex === -1) { 237 | refreshScriptProp(node, changeEleIndex) ; 238 | } 239 | else if ( 240 | node.__type__ !== 'cc.CompPrefabInfo' && 241 | node.__type__ !== 'cc.PrefabInfo' && 242 | node.__type__ !== 'cc.PrefabInstance' && 243 | node.__type__ !== 'CCPropertyOverrideInfo' && 244 | node.__type__ !== 'cc.TargetInfo' && 245 | node.__type__ !== 'cc.UITransform' && 246 | node.__type__ !== 'cc.Label' && 247 | node.__type__ !== 'cc.Sprite') { 248 | 249 | for (const key in node) { 250 | const obj = node[key]; 251 | if (juageObjType(obj)) { 252 | if (obj.__id__) { 253 | refreshScriptProp(node, changeEleIndex); 254 | } 255 | return; 256 | } 257 | } 258 | } 259 | } 260 | 261 | function refreshRefPrefab(fileData, node, changeEleIndex) { 262 | const id = node._prefab.__id__; 263 | const prefabInfo = fileData[id]; 264 | if (prefabInfo.root.__id__ > 1) { 265 | changePrefabInfo(fileData, prefabInfo, changeEleIndex) 266 | } 267 | } 268 | 269 | function refreshPrefab(fileData, changeEleIndex) { 270 | for (const node of fileData) { 271 | //刷新预制体各个节点组件在文本中的位置信息 272 | if (node['__type__'] === 'cc.Node') { 273 | changeComponentId(node, changeEleIndex); 274 | changePrefabId(node, changeEleIndex, '_prefab'); 275 | changeChildrenId(node, changeEleIndex); 276 | changeParentId(node, changeEleIndex); 277 | 278 | //处理预制体上关联的其他预制体,即把其他预制体直接挂到当前预制体下,成为当前预制体的一个子节点这种情况 279 | refreshRefPrefab(fileData, node, changeEleIndex); 280 | } 281 | else if (node['__type__'] !== 'cc.Node') { 282 | changeNodeId(node, changeEleIndex); 283 | changePrefabId(node, changeEleIndex, '__prefab'); 284 | 285 | //更新预制体挂载的脚本组件绑定的节点在文本中的位置信息 286 | refreshScript(node, changeEleIndex); 287 | } 288 | } 289 | } 290 | 291 | 292 | /** 293 | * 删除组件 294 | * @param {any[]} fileData 预制体数据,那个数组存储的数据 295 | * @param {String} component 所要删除的组件 296 | * @returns 返回删除了组件的预制体数据 297 | */ 298 | function removeComponent(fileData, component) { 299 | let flag = false; 300 | for (let index = 0; index < fileData.length; ++index) { 301 | const value = fileData[index]; 302 | const result = hasComponent(fileData, value, component); 303 | 304 | let changeEleIndex = -1; 305 | if (result.flag) { 306 | flag = result.flag; 307 | //移除组件的操作 308 | changeEleIndex = result.id; 309 | fileData.splice(result.id, 2); 310 | utils.log('在 '.warn + index + ' 位置的节点'.warn + '删除 '.warn + component.black + ' 组件'.warn); 311 | const components = value['_components']; 312 | components.splice(result.componentArrIndex, 1); 313 | refreshPrefab(fileData, changeEleIndex); 314 | } 315 | } 316 | return {fileData, flag}; 317 | } 318 | 319 | function addComponent(fileData, component, uuid) { 320 | _script_uuid = uuid; 321 | let flag = false; 322 | const len = fileData.length; 323 | for (let index = 0; index < len; ++index) { 324 | const value = fileData[index]; 325 | const result = hasComponent(fileData, value, component); 326 | if (result.flag) { 327 | flag = result.flag; 328 | const success = addComponentToPrefab(fileData, fileData.length - 1, index); 329 | if (success) { 330 | utils.log('在 ' + index + ' 位置的节点 ' + value['_name'].success + ' 成功增加 ' + _script_uuid.success + ' 组件'); 331 | fileData[1]._prefab.__id__ += 2; 332 | } 333 | } 334 | } 335 | return {fileData, flag}; 336 | } 337 | 338 | module.exports.removeComponent = removeComponent; 339 | module.exports.addComponent = addComponent; --------------------------------------------------------------------------------