Cocos Creator 多語言組件實現(xiàn)
簡介
基于cocos creator 2.4.3 的一個手游項目模板, 提供一些自定義組件以及 Demo。
項目地址:
https://github.com/yanjifa/game-template
本次著重介紹多語言組件
Demo在線查看:
https://yanjifa.github.io/web-desktop/
目前的多語言組件如果想對已經(jīng)上線的項目進行多語言支持, 普遍都要對每個 Label組件都操作一遍, 掛上組件腳本, 這波操作說實話, 小項目還好, 大項目簡直讓人崩潰。
所以之前基于 1.10.3 版本搞了一個使用上更方便的多語言實現(xiàn), 現(xiàn)在適配到 2.4.3版本, 并添加了對 BMFONT 的支持。
繼承 cc.Label內(nèi)置組件實現(xiàn), 使用上完全兼容cc.Label。老項目方便接入, vscode 全局查找替換即可。
//?cocos?creator?開發(fā)者工具,?控制臺輸入
//?uuid?為?LocalizedLabel.ts?腳本的?uuid
Editor.Utils.UuidUtils.compressUuid("712432e0-72b6-4e45-90c5-42bf111e8964")
//?得到壓縮后的?uuid,?全局替換?prefab?&?fire?文件中的?cc.Label
"71243LgcrZORZDFQr8RHolk"
//?重新打開?prefab,?組件就以替換完畢
廢話不多說, 該上圖了:

支持編輯器預覽

修改語言設置立即生效

怎么跑起來
克隆完項目后初始化并更新子模塊, 子模塊使用了論壇 Next 大佬的 ccc-detools 我比較喜歡這個工具, 堪稱神器。
//?不更新無法使用瀏覽器預覽
git?submodule?update?--init?--recursive
安裝依賴, 項目根目錄執(zhí)行
//?必須
npm?install
全局安裝 ESlint
//?非必須
npm?install?-g?eslint?typescript?@typescript-eslint/parser?@typescript-eslint/eslint-plugin
//?nvm?可能還需指定需要?NODE?環(huán)境變量
export?NODE_PATH=$HOME/.nvm/versions/node/v12.19.0/lib/node_modules??//?根據(jù)自己使用的版本
什么是 ESlint ?
ESLint 是一個開源的 JavaScript代碼檢查工具,由 Nicholas C. Zakas于2013年6月創(chuàng)建。代碼檢查是一種靜態(tài)的分析,常用于尋找有問題的模式或者代碼,并且不依賴于具體的編碼風格。對大多數(shù)編程語言來說都會有代碼檢查,一般來說編譯程序會內(nèi)置檢查工具。
JavaScript 是一個動態(tài)的弱類型語言,在開發(fā)中比較容易出錯。因為沒有編譯程序,為了尋找 JavaScript 代碼錯誤通常需要在執(zhí)行過程中不斷調(diào)試。像 ESLint 這樣的可以讓程序員在編碼的過程中發(fā)現(xiàn)問題而不是在執(zhí)行的過程中。
ESLint 的初衷是為了讓程序員可以創(chuàng)建自己的檢測規(guī)則。ESLint 的所有規(guī)則都被設計成可插入的。ESLint 的默認規(guī)則與其他的插件并沒有什么區(qū)別,規(guī)則本身和測試可以依賴于同樣的模式。為了便于人們使用,ESLint 內(nèi)置了一些規(guī)則,當然,你可以在使用過程中自定義規(guī)則。
ESLint 使用 Node.js 編寫,這樣既可以有一個快速的運行環(huán)境的同時也便于安裝。
為什么不是 TSlint ?
TSLint已于2019年棄用。
Please see this issue for more details: Roadmap: TSLint → ESLint:
https://github.com/palantir/tslint/issues/4534
now typescript-eslint is your best option for linting TypeScript:
https://github.com/typescript-eslint/typescript-eslint
在我看來使用 ESlint 的意義
統(tǒng)一代碼風格, 項目組內(nèi)不同人員寫出風格基本一致的代碼。 提高代碼可讀性。
這是此項目使用到的規(guī)則
展開查看 .eslintrc.json
{
????"env":?{
????????"browser":?true,
????????"node":?true
????},
????"globals":?{
????????"Editor":?true,
????????"Vue":?true
????},
????"extends":?[
????????"eslint:recommended"
????],
????"parserOptions":?{
????????"ecmaVersion":?12,
????????"sourceType":?"module"
????},
????"rules":?{
????????//?尤達表達式
????????"yoda":?"warn",
????????//?parseInt
????????"radix":?"error",
????????//?禁止多個連續(xù)空格
????????"no-multi-spaces":?[
????????????"error",
????????????{
????????????????"ignoreEOLComments":?true,
????????????????"exceptions":?{
????????????????????"Property":?true,
????????????????????"VariableDeclarator":?true
????????????????}
????????????}
????????],
????????//?箭頭表達式空格
????????"arrow-spacing":?[
????????????"error",
????????????{
????????????????"before":?true,
????????????????"after":?true
????????????}
????????],
????????//?使用?===?or?!===
????????"eqeqeq":?"error",
????????//?for?in?循環(huán)必須包含?if?語句
????????"guard-for-in":?"error",
????????//?雙引號
????????"quotes":?[
????????????"error",
????????????"double"
????????],
????????//?行尾空格警告
????????"no-trailing-spaces":?"warn",
????????//?一行最大字符
????????"max-len":?[
????????????"warn",
????????????{
????????????????"code":?160
????????????}
????????],
????????//?未定義
????????"no-unused-vars":?"warn",
????????"no-undef":?"error",
????????//?分號
????????"semi":?[
????????????"error",
????????????"always",
????????????{
????????????????"omitLastInOneLineBlock":?true
????????????}
????????],
????????//?禁止分號前后空格
????????"semi-spacing":?"error",
????????//?禁止不必要的分號
????????"no-extra-semi":?"error",
????????//?注釋相關
????????"comma-spacing":?[
????????????"warn",
????????????{
????????????????"before":?false,
????????????????"after":?true
????????????}
????????],
????????"comma-dangle":?[
????????????"error",
????????????"always-multiline"
????????],
????????"no-multiple-empty-lines":?[
????????????"error",
????????????{
????????????????"max":?2,
????????????????"maxEOF":?1,
????????????????"maxBOF":?1
????????????}
????????],
????????"line-comment-position":?[
????????????"warn",
????????????{
????????????????"position":?"above"
????????????}
????????],
????????"spaced-comment":?[
????????????"error",
????????????"always",
????????????{
????????????????"line":?{
????????????????????"markers":?["/"],
????????????????????"exceptions":?["-",?"+"]
????????????????},
????????????????"block":?{
????????????????????"markers":?["!"],
????????????????????"exceptions":?["*"],
????????????????????"balanced":?true
????????????????}
????????????}
????????]
????},
????//?typescript?獨有規(guī)則
????"overrides":?[
????????{
????????????"files":?[
????????????????"*.ts"
????????????],
????????????"plugins":?[
????????????????"@typescript-eslint"
????????????],
????????????"parser":?"@typescript-eslint/parser",
????????????"extends":?[
????????????????"plugin:@typescript-eslint/recommended"
????????????],
????????????"rules":?{
????????????????"@typescript-eslint/no-duplicate-imports":?"error",
????????????????"@typescript-eslint/ban-ts-comment":?"off",
????????????????//?4?空格縮進
????????????????"@typescript-eslint/indent":?[
????????????????????"warn",
????????????????????4
????????????????],
????????????????"@typescript-eslint/explicit-module-boundary-types":?"off",
????????????????"@typescript-eslint/no-unused-vars":?"off",
????????????????"@typescript-eslint/space-before-function-paren":?[
????????????????????"error",
????????????????????{
????????????????????????"anonymous":?"never",
????????????????????????"named":?"never",
????????????????????????"asyncArrow":?"always"
????????????????????}
????????????????],
????????????????"@typescript-eslint/naming-convention":?[
????????????????????"warn",
????????????????????{
????????????????????????"selector":?"typeParameter",
????????????????????????"format":?[
????????????????????????????"PascalCase"
????????????????????????],
????????????????????????"prefix":?["T"]
????????????????????},
????????????????????{
????????????????????????"selector":?"variable",
????????????????????????"format":?[
????????????????????????????"camelCase",
????????????????????????????"UPPER_CASE"
????????????????????????]
????????????????????},
????????????????????{
????????????????????????"selector":?"interface",
????????????????????????"format":?[
????????????????????????????"PascalCase"
????????????????????????],
????????????????????????"custom":?{
????????????????????????????"regex":?"^I[A-Z]",
????????????????????????????"match":?true
????????????????????????}
????????????????????}
????????????????]
????????????}
????????},
????????{
????????????"files":?[
????????????????"assets/scripts/Enum.ts"
????????????],
????????????"rules":?{
????????????????"line-comment-position":?[
????????????????????"warn",
????????????????????{
????????????????????????"position":?"beside"
????????????????????}
????????????????]
????????????}
????????}
????]
}
目錄結構
.
├──?README.md
├──?assets
│???├──?resources
│???│???├──?language??????????????????//?多語言根目錄
│???│???│???├──?en????????????????????//?英文
│???│???│???│???├──?ArialUnicodeMs.fnt
│???│???│???│???├──?ArialUnicodeMs.png
│???│???│???│???└──?StringConfig.json???//?字符串配置表導出配置
│???│???│???└──?zh?????????????????????//?中文
│???│???│???????├──?ArialUnicodeMs.fnt
│???│???│???????├──?ArialUnicodeMs.png
│???│???│???????└──?StringConfig.json
│???│???├──?listview
│???│???│???└──?prefab
│???│???│???????├──?ListViewDemo.prefab
│???│???│???????└──?ListViewDemoItem.prefab
│???│???└──?setting
│???│???????└──?prefab
│???│???????????└──?Setting.prefab
│???├──?scene
│???│???└──?Main.fire
│???├──?scripts
│???│???├──?Enum.ts
│???│???├──?Game.ts
│???│???├──?Macro.ts
│???│???├──?Main.ts
│???│???├──?base
│???│???│???├──?BasePopView.ts????????//?彈窗基類
│???│???│???├──?BaseScene.ts??????????//?場景基類
│???│???│???└──?BaseSingeton.ts???????//?單例基類
│???│???├──?component?????????????????//?組件
│???│???│???├──?ListView.ts???????????//?支持復用,?和滾動條的?ListView?組件
│???│???│???├──?LocalizedLabel.ts?????//?多語言?label?組件
│???│???│???└──?LocalizedRichText.ts??//?多語言?RichText?組件
│???│???├──?manager
│???│???│???├──?AssetManager.ts???????//?資源管理器
│???│???│???├──?AudioManager.ts???????//?音頻管理器(未實現(xiàn))
│???│???│???├──?PopViewManager.ts?????//?彈窗管理器
│???│???│???└──?SceneManager.ts???????//?場景管理器
│???│???├──?scene
│???│???│???└──?Home.ts
│???│???├──?util
│???│???│???├──?GameUtil.ts??????????//?待實現(xiàn)
│???│???│???├──?LocalizedUtil.ts?????//?多語言工具,?負責加載語言目錄的資源,?獲取對應?Id?文本
│???│???│???├──?NotifyUtil.ts????????//?全局事件工具
│???│???│???└──?StorageUtil.ts???????//?存檔工具,?相比?cc.sys.localStorage?多了一層緩存機制
│???│???└──?view
│???│???????├──?listviewdemo
│???│???????│???├──?ListViewDemo.ts
│???│???????│???└──?ListViewDemoItem.ts
│???│???????└──?setting
│???│???????????└──?Setting.ts
│???└──?shader
│???????├──?effects
│???????│???└──?avatar-mask.effect
│???????└──?materials
│???????????└──?avatar-mask.mtl
├──?creator.d.ts
├──?game.d.ts????????????????????????//?為擴展的組件提供定義文件,?防止編輯器報錯
├──?package-lock.json
├──?package.json
├──?packages
│???└──?game-helper??????????????????//?項目插件
│???????├──?component????????????????//?插件提供的組件模板
│???????│???└──?prefab
│???????│???????├──?ListView.prefab
│???????│???????├──?LocalizedLabel.prefab
│???????│???????└──?LocalizedRichText.prefab
│???????├──?i18n
│???????│???├──?en.js
│???????│???└──?zh.js
│???????├──?inspectors???????????????//?組件都是繼承?creator?原生組件,?通過擴展?inspector?實現(xiàn)
│???????│???├──?listview.js
│???????│???├──?localizedlabel.js
│???????│???└──?localizedrichtext.js
│???????├──?main.js?????????????????//?編輯器模式下獲取多語言文本方法
│???????├──?package.json????????????//?里面設置了,?編輯器模式下,?返回的語言?zh?||?en
│???????└──?panel
│???????????└──?index.js
├──?project.json
實現(xiàn)方式
組件腳本
import?{?ENotifyType?}?from?"../Enum";
import?Game?from?"../Game";
const?{ccclass,?property,?executeInEditMode,?menu,?inspector}?=?cc._decorator;
@ccclass
@executeInEditMode()
@menu(`${CC_EDITOR?&&?Editor.T("game-helper.projectcomponent")}/LocalizedLabel`)
@inspector("packages://game-helper/inspectors/localizedlabel.js")
export?default?class?LocalizedLabel?extends?cc.Label?{
????@property()
????private?_tid?=?"";
????@property({
????????multiline:?true,
????????tooltip:?"多語言?text?id",
????})
????set?tid(value:?string)?{
????????this._tid?=?value;
????????this.updateString();
????}
????get?tid()?{
????????return?this._tid;
????}
????@property()
????private?_bmfontUrl?=?"";
????@property({
????????tooltip:?"動態(tài)加載?bmfonturl",
????})
????set?bmfontUrl(value:?string)?{
????????this._bmfontUrl?=?value;
????????this.updateString();
????}
????get?bmfontUrl()?{
????????return?this._bmfontUrl;
????}
????protected?onLoad()?{
????????super.onLoad();
????????Game.NotifyUtil.on(ENotifyType.LANGUAGE_CHANGED,?this.onLanguageChanged,?this);
????????this.updateString();
????}
????protected?onDestroy()?{
????????Game.NotifyUtil.off(ENotifyType.LANGUAGE_CHANGED,?this.onLanguageChanged,?this);
????????super.onDestroy();
????}
????/**
?????*?收到語言變更通知
?????*
?????*?@private
?????*?@memberof?LocalizedLabel
?????*/
????private?onLanguageChanged()?{
????????this.updateString();
????}
????/**
?????*?更新文本
?????*
?????*?@private
?????*?@returns?{*}
?????*?@memberof?LocalizedLabel
?????*/
????private?updateString():?void?{
????????if?(!this._tid)?{
????????????return;
????????}
????????if?(CC_EDITOR)?{
????????????//?編輯器模式下,?從插件中獲取文本
????????????Editor.Ipc.sendToMain("game-helper:getLangStr",?this._tid,?(e:?Error,?str:?string)?=>?{
????????????????if?(e)?{
????????????????????return;
????????????????}
????????????????this.string?=?""?+?str;
????????????});
????????}?else?{
????????????//?獲取多語言文本
????????????this.string?=?""?+?Game.LocalizeUtil.getLangStr(this._tid);
????????????//?如果使用了?bmfont,?切換對應語言的?bmfont
????????????//?_bmfontUrl?為自動生成
????????????if?(this._bmfontUrl)?{
????????????????const?lang?=?Game.LocalizeUtil.language;
????????????????this.font?=?cc.resources.get(this._bmfontUrl.replace("${lang}",?lang),?cc.BitmapFont);
????????????}
????????}
????}
}
插件腳本
"use?strict";
const?ipcMain?=?require("electron").ipcMain;
const?fs?=?require("fs");
module.exports?=?{
????localizeCfgs:?null,
????load()?{
????????ipcMain.on("editor:ready",?this.onEditorReady.bind(this));
????????//
????????this.profiles.load();
????????this.loadLangConfig();
????},
????unload()?{
????????//?execute?when?package?unloaded
????},
????onEditorReady()?{
????????//
????},
????//?加載多語言文本配置,?和項目中使用的是相同的
????loadLangConfig()?{
????????const?configPath?=?this.profiles.get("path");
????????const?lang?=?this.profiles.get("lang");
????????const?fileName?=?this.profiles.get("fileName");
????????try?{
????????????this.localizeCfgs?=?JSON.parse(fs.readFileSync(`${Editor.Project.path}/${configPath}/${lang}/${fileName}`,?"utf-8"));
????????????Editor.success("localized?config?load?success:",?lang);
????????}?catch?(e)?{
????????????Editor.warn("localized?config?load?fail:",?e);
????????}
????},
????messages:?{
????????open()?{
????????????Editor.Panel.open("game-helper");
????????},
????????//?reload?lang?config
????????reload()?{
????????????this.loadLangConfig();
????????},
????????//?獲取多語言配置字符串
????????getLangStr(event,?param)?{
????????????if?(this.localizeCfgs?===?null)?{
????????????????event.reply(new?Error("config?not?load"),?null);
????????????}
????????????const?[tid,?...args]?=?param.split(",");
????????????let?str?=?this.localizeCfgs[tid];
????????????if?(str)?{
????????????????args.forEach((arg,?index)?=>?{
????????????????????str?=?str.replace("${p"?+?(index?+?1)?+?"}",?arg);
????????????????});
????????????????event.reply(null,?str);
????????????}?else?{
????????????????event.reply(null,?tid);
????????????}
????????},
????},
????profiles:?{
????????config:?null,
????????path:?"",
????????load()?{
????????????this.path?=?Editor.url("packages://game-helper/package.json");
????????????this.config?=?JSON.parse(fs.readFileSync(this.path,?"utf8"));
????????},
????????get(key)?{
????????????return?this.config.profiles.local[key];
????????},
????},
};
結語
感興趣的開發(fā)者可以克隆下來查看學習喲!
感謝作者「alpha」的創(chuàng)作分享!本分享不定期維護中, 歡迎點擊「閱讀原文」進入社區(qū)原帖持續(xù)關注!
如果您在使用 Cocos 引擎的過程中,獲得了獨到的開發(fā)心得、見解或是方法,并且樂于分享出來,幫助更多開發(fā)者解決技術問題,加速游戲開發(fā)效率,期待您與我們聯(lián)系!
