Initial commit.

This commit is contained in:
genxium
2022-09-20 23:50:01 +08:00
commit e90a335c56
432 changed files with 101884 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
/typings
/node_modules
/.vscode

View File

@@ -0,0 +1,115 @@
# i18n for Cocos Creator
> 本仓库已暂停维护,仅供存档,建议开发者们在项目层面自行实现多语言切换。
Cocos Creator 编辑器扩展:实现 Label 和 Sprite 组件的多语言国际化i18n
注意,多语言国际化和本地化的区别是,国际化需要软件里包括多种语言的文本和图片数据,并根据用户所用设备的默认语言或菜单选择来进行实时切换。而本地化是在发布软件时针对某一特定语言的版本定制文本和图片内容。
本插件是多语言支持插件,因此不包括构建项目时去除一部分多语言数据的功能。
## 插件安装方法
请参考 [扩展编辑器:安装与分享](http://www.cocos.com/docs/creator/extension/install-and-share.html) 文档。
## 语言配置
首先从主菜单打开 i18n 面板: `插件->i18n`
然后需要创建包含多语言翻译数据的 JSON 文件(为了方便使用采用 .js 格式存储):
-`Manage Languages` 部分的 `New Language ID` 输入框里输入新增语言的 ID`zh`(代表中文),`en`(代表英文)等。
- 输入 ID 后点击 `Create` 按钮,会在相关的语言选择菜单里增加一种语言,并且会在项目的 `resources/i18n` 目录下创建对应语言的翻译数据模板,如 `resources/i18n/zh.js`
接下来在 i18n 面板的 `Preview Language` 部分的下拉菜单里就可以选择编辑器里预览时的语言了。
## 本地化 Label 文本
### 添加 Localize 组件
i18n 插件提供了两种组件分别用于配合 [Label](http://www.cocos.com/docs/creator/components/label.html) 和 [Sprite](http://www.cocos.com/docs/creator/components/sprite.html) 来显示多语言内容。
我们从 Label 开始,我们可以在场景或 prefab 中任何 Label 组件所在的节点上添加 `i18n/LocalizedLabel` 组件。这个组件只需要输入翻译数据索引的 dataID 就可以根据当前语言来更新 Label 的字符串显示。
下面我们来介绍如何配置 dataID。
### 翻译数据
插件创建的翻译数据模板是这样的:
```js
// zh.js
if (!window.i18n) window.i18n = {};
window.i18n.zh={
// write your key value pairs here
"label_text": {
"hello": "你好!",
"bye": "再见!"
}
};
```
其中 `window.i18n.zh` 全局变量的写法让我们可以在脚本中随时访问到这些数据,而不需要进行异步的加载。
在大括号里面的内容是用户需要添加的翻译键值对,我们使用了 AirBnb 公司开发的 [Polyglot](http://airbnb.io/polyglot.js/) 库来进行国际化的字符串查找,翻译键值对支持对象嵌套、参数传递和动态修改数据等功能,非常强大。更多用法请阅读上面链接里的文档。
如果像上面例子里一样设置我们的翻译数据,那么就会生成如下的键值对:
- "label_text.hello" : "你好!"
- "label_text.bye" : "再见!"
### 查看效果
接下来我们只要在 LocalizedLabel 组件的 `dataID` 属性里写入 `label_text.hello`,其所在节点上的 Label 组件就会显示 `你好!` 文字。
运行时如果需要修改 Label 渲染的文字,也请对 `LocalizedLabel.dataID` 进行赋值,而不要直接更新 `Label.string`
当需要预览其他语言的显示效果时,打开 i18n 面板,并切换 `Preview Language` 里的语言,场景中的 Label 显示就会自动更新。
### 运行时设置语言
游戏运行时可以根据用户操作系统语言或菜单选择来设置语言,在获取到需要使用的语言 ID 后,需要用以下的代码来进行初始化:
```js
const i18n = require('LanguageData');
i18n.init('zh'); // languageID should be equal to the one we input in New Language ID input field
```
需要在之后动态切换语言时也可以调用 `i18n.init()`
如果切换后需要马上更新当前场景,可以调用 `i18n.updateSceneRenderer()`
注意运行时必须保证 `i18n.init(language)` 在包含有 LocalizedLabel 组件的场景加载前执行,否则将会因为组件上无法加载到数据而报错。
### 脚本中使用翻译键值对获取字符串
除了和 LocalizedLabel 配合使用解决场景中静态 Label 的多语言问题,`LanguageData` 模块还可以单独在脚本中使用,提供运行时的翻译:
```js
const i18n = require('LanguageData');
i18n.init('en');
let myGreeting = i18n.t('label_text.hello');
cc.log(myGreeting); // Hello!
```
## 本地化 Sprite 图片
### 添加 LocalizedSprite 组件
首先在场景或 prefab 中任何 Sprite 组件所在的节点上添加 `i18n/LocalizedSprite` 组件。该组件需要我们手动添加一组语言 id 和 SpriteFrame 的映射,就可以在编辑器预览和运行时显示正确语言的图片了。
### 添加语言图片映射
负责承载语言到贴图映射的属性 `spriteFrameSet` 是一个数组,我们可以像操作其他数组属性一样来添加新的映射
- 首先设置数组的大小,要和语言种类相等
- 为每一项里的 `language` 属性填入对应语言的 id`en``zh`
- 将语言对应的贴图(或 SpriteFrame拖拽到 `spriteFrame` 属性里。
完成设置后,点击下面的 `Refresh` 按钮,就可以在场景中看到效果了。
`LocalizedLabel` 一样,当我们在 i18n 面板设更改了预览语言时,当前场景里所有的 `LocalizedSprite` 也会自动刷新,显示当前语言对应的图片。

View File

@@ -0,0 +1,9 @@
'use strict';
module.exports = {
current: 'Current language: ',
create_language: 'New language',
language: 'Language',
create: 'Create',
cancel: 'Cancel',
};

View File

@@ -0,0 +1,9 @@
'use strict';
module.exports = {
current: '当前语言:',
create_language: '创建新语言',
language: '语言名称',
create: '创建',
cancel: '取消',
};

View File

@@ -0,0 +1,29 @@
'use strict';
Vue.component('localized-sprite', {
template: `
<cc-array-prop :target.sync="target.spriteFrameSet"></cc-array-prop>
<ui-prop name="Update Scene">
<ui-button
class="green tiny"
@confirm="refresh"
>
Refresh
</ui-button>
</ui-prop>
`,
props: {
target: {
twoWay: true,
type: Object,
},
},
methods: {
refresh: function () {
let i18n = window.require('LanguageData');
i18n.updateSceneRenderers();
}
}
});

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2015",
"module": "es2015"
},
"exclude": [
"node_modules",
"library",
"local",
"settings",
"temp"
]
}

View File

@@ -0,0 +1,44 @@
'use strict';
const Package = require('./utils/package');
const Fs = require('fire-fs');
const Path = require('path');
module.exports = {
load () {
Package.mount();
},
unload () {
Package.unmount();
},
// register your ipc messages here
messages: {
'open' () {
// open entry panel registered in package.json
Editor.Panel.open('i18n');
Package.metrics();
},
'import-asset' (event, path) {
Editor.assetdb.refresh(path, (err, results) => {
if (err) {
Editor.assetdb.error('Failed to reimport asset %s, %s', path, err.stack);
return;
}
Editor.assetdb._handleRefreshResults(results);
let metaPath = path + '.meta';
if (Fs.existsSync(Editor.url(metaPath))) {
let meta = Fs.readJsonSync(Editor.url(metaPath));
meta.isPlugin = true;
Fs.outputJsonSync(Editor.url(metaPath), meta);
} else {
Editor.log('Failed to set language data file to plugin script');
return;
}
});
}
},
};

View File

@@ -0,0 +1,38 @@
{
"name": "i18n",
"version": "0.1.0",
"description": "The package template for getting started.",
"author": "Cocos Creator",
"main": "main.js",
"main-menu": {
"i18n:MAIN_MENU.package.title/i18n": {
"message": "i18n:open"
}
},
"runtime-resource": {
"path": "runtime-scripts",
"name": "plugin"
},
"reload": {
"ignore": [
"runtime-scripts/**/*"
]
},
"scene-script": "utils/scene.js",
"panel": {
"main": "panel/index.js",
"type": "dockable",
"title": "i18n",
"width": 400,
"height": 300,
"min-width": 300,
"min-height": 300
},
"profiles": {
"project": {
"languages": [
],
"default_language": ""
}
}
}

View File

@@ -0,0 +1,123 @@
'use strcit';
const Fs = require('fs');
const Path = require('path');
const Language = require('../../utils/language');
const Event = require('../../utils/event');
exports.template = Fs.readFileSync(Path.join(__dirname, '../template/home.html'), 'utf-8');
exports.props = [];
exports.data = function () {
return {
state: 'normal',
languages: [],
current: '',
_language: '',
}
};
exports.watch = {
current () {
Event.emit('language-changed', this.current);
}
};
exports.methods = {
_t (key) {
return Editor.T(`i18n.${key}`);
},
_getLanguagePath (language) {
return Path.join('resources/i18n/', `${language}.js`);
},
changeEdit () {
if (this.state === 'edit') {
this.state = 'normal';
} else {
this.state = 'edit';
}
},
changeCreate () {
if (this.state === 'create') {
this.state = 'normal';
this._language = '';
} else {
this.state = 'create';
this._language = '';
}
},
/**
* 创建一个新的语言包
* @param {string} name
*/
createLanguage (name) {
// 检查是否不存在
if (!name) {
return alert('创建语言失败 - 名称不能为空');
}
// 检查是否重名
if (this.languages.indexOf(name) !== -1) {
return alert('创建语言失败 - 该语言已经存在');
}
Language.create(name).then(() => {
this.languages.push(name);
if (!this.current) {
this.current = this.languages[0];
}
Event.emit('languages-changed', this.languages);
this._language = '';
this.state = 'normal';
}).catch(() => {
this._language = '';
this.state = 'normal';
// todo 错误提示
});
},
/**
* 删除一个已存在的语言包
* @param {string} name
*/
deleteLanguage (name) {
// 检查是否存在
if (this.languages.indexOf(name) === -1) {
return alert('删除语言失败 - 该语言不存在');
}
// 弹窗提示
let code = Editor.Dialog.messageBox({
type: 'warning',
buttons: ['Cancel', 'OK'],
title: 'Delete Language Data',
message: 'Delete i18n language data, this cannot be undone!',
detail: name,
noLink: true
});
if (code === 0) {
return;
}
// 删除 profile
Language.remove(name).then(() => {
let index = this.languages.indexOf(name);
this.languages.splice(index, 1);
Event.emit('languages-changed', this.languages);
if (name === this.current) {
this.current = this.languages[0] || '';
}
}).catch(() => {
// todo 错误提示
});
},
};
exports.ready = function () {};

View File

@@ -0,0 +1,60 @@
'use strict';
const Fs = require('fs');
const Event = Editor.require('packages://i18n/utils/event');
const Home = Editor.require('packages://i18n/panel/component/home');
Editor.Panel.extend({
style: Fs.readFileSync(Editor.url('packages://i18n/panel/style/home.css')),
template: Home.template,
$: {},
ready () {
if (!window.Vue) {
// todo 错误信息
return;
}
Home.el = this.shadowRoot;
Home.data = Home.data();
delete Home.props;
delete Home.template;
Home.components = {};
this._vm = new Vue(Home);
window.vm = this._vm
let profile = this.profiles.project;
if (profile.data.languages) {
profile.data.languages.forEach((name) => {
this._vm.languages.push(name)
});
}
let current = profile.data['default_language'];
if (this._vm.languages.indexOf(current) === -1) {
this._vm.current = this._vm.languages[0];
Editor.warn(`Language is not found - ${current}`);
} else {
this._vm.current = current;
}
Event.on('language-changed', (name) => {
profile.data['default_language'] = name;
profile.save();
Editor.Scene.callSceneScript('i18n', 'update-default-language', name, function (err, result) {
// console.log(result);
});
});
Event.on('languages-changed', (languages) => {
profile.data.languages = languages.map((name) => {
return name;
});
profile.save();
})
},
});

View File

@@ -0,0 +1,106 @@
@import url('app://bower_components/fontawesome/css/font-awesome.min.css');
:host {
display: flex;
}
.home {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
padding: 10px;
}
.home header {
border-bottom: 1px solid #666;
padding: 10px 5px;
}
.home .control {
padding: 8px 4px;
}
.home .control i {
padding: 4px 6px;
cursor: pointer;
transition: color 0.3s;
}
.home .control .fa-pencil-square-o[edit] {
color: #cc0000;
}
.home .list {
overflow: auto;
background: #333;
padding: 0;
flex: 1;
}
.home .list ul {
padding: 0;
margin: 0;
}
.home .list ul li {
padding: 5px 12px;
line-height: 20px;
display: flex;
list-style: none;
}
.home .list ul li:hover {
background: #282828;
}
.home .list ul li .edit {
width: 0;
opacity: 0;
font-size: 15px;
transition: opacity 0.3s, width 0.3s;
overflow: hidden;
margin-right: 5px;
}
.home .list ul li .edit i {
padding: 3px;
cursor: pointer;
color: #cc0000;
}
.home .list ul li .edit[edit] {
width: 18px;
opacity: 1;
}
.home .list ul li .name {
width: 110px;
overflow: hidden;
text-overflow: ellipsis;
}
.home .popup {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: none;
}
.home .popup .mask {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: #474747;
opacity: 0.9;
}
.home .popup .language {
position: absolute;
width: 260px;
left: 50%;
top: 45%;
margin: -50px 0 0 -130px;
}
.home .popup .language h3 {
margin: 0 0 12px 0;
text-align: center;
}
.home .popup .language .button {
text-align: right;
display: flex;
}
.home .popup .language .button ui-button {
margin: 10px;
flex: 1;
}
.home .popup[active] {
display: block;
}

View File

@@ -0,0 +1,136 @@
@import url('app://bower_components/fontawesome/css/font-awesome.min.css');
:host {
display: flex;
}
.home {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
padding: 10px;
header {
border-bottom: 1px solid #666;
padding: 10px 5px;
}
.control {
padding: 8px 4px;
i {
padding: 4px 6px;
cursor: pointer;
transition: color 0.3s;
}
.fa-pencil-square-o[edit] {
color: #cc0000;
}
}
.list {
overflow: auto;
background: #333;
padding: 0;
flex: 1;
ul {
padding: 0;
margin: 0;
li {
padding: 5px 12px;
line-height: 20px;
display: flex;
list-style: none;
&:hover {
background: #282828;
}
.edit {
width: 0;
opacity: 0;
font-size: 15px;
transition: opacity 0.3s, width 0.3s;
overflow: hidden;
margin-right: 5px;
i {
padding: 3px;
cursor: pointer;
color: #cc0000;
}
&[edit] {
width: 18px;
opacity: 1;
}
}
.name {
width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.path {
flex: 1;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.popup {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: none;
.mask {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: #474747;
opacity: 0.9;
}
.language {
position: absolute;
width: 260px;
left: 50%;
top: 45%;
margin: -50px 0 0 -130px;
h3 {
margin: 0 0 12px 0;
text-align: center;
}
.button {
text-align: right;
display: flex;
ui-button {
margin: 10px;
flex: 1;
}
}
}
&[active] {
display: block;
}
}
}

View File

@@ -0,0 +1,73 @@
<div class="home">
<header>
<span>{{_t('current')}}</span>
<span>
<ui-select
v-value="current"
>
<template v-for="item in languages">
<option>{{item}}</option>
</template>
</ui-select>
</span>
</header>
<section class="control">
<i class="fa fa-pencil-square-o" aria-hidden="true"
:edit="state === 'edit'"
@click="changeEdit"
></i>
<i class="fa fa-plus" aria-hidden="true"
@click="changeCreate"
></i>
</section>
<section class="list">
<ul>
<li
v-for="item in languages"
>
<div class="edit"
:edit="state === 'edit'"
>
<i class="fa fa-minus-circle" aria-hidden="true"
@click="deleteLanguage(item)"
></i>
</div>
<div class="name">
{{item}}
</div>
<div class="path">
{{_getLanguagePath(item)}}
</div>
</li>
</ul>
</section>
<div class="popup"
v-if="state === 'create'"
:active="state === 'create'"
>
<div class="mask"></div>
<div class="language">
<h3>{{_t('create_language')}}</h3>
<ui-prop
:name="_t('language')"
>
<ui-input
v-value="_language"
></ui-input>
</ui-prop>
<div class="button">
<ui-button
@click="createLanguage(_language)"
>{{_t('create')}}</ui-button>
<ui-button
@click="changeCreate"
>{{_t('cancel')}}</ui-button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,98 @@
const Polyglot = require('polyglot.min');
let polyInst = null;
if (!window.i18n) {
window.i18n = {
languages: {},
curLang:''
};
}
if (CC_EDITOR) {
Editor.Profile.load('profile://project/i18n.json', (err, profile) => {
window.i18n.curLang = profile.data['default_language'];
if (polyInst) {
let data = loadLanguageData(window.i18n.curLang) || {};
initPolyglot(data);
}
});
}
function loadLanguageData (language) {
return window.i18n.languages[language];
}
function initPolyglot (data) {
if (data) {
if (polyInst) {
polyInst.replace(data);
} else {
polyInst = new Polyglot({ phrases: data, allowMissing: true });
}
}
}
module.exports = {
/**
* This method allow you to switch language during runtime, language argument should be the same as your data file name
* such as when language is 'zh', it will load your 'zh.js' data source.
* @method init
* @param language - the language specific data file name, such as 'zh' to load 'zh.js'
*/
init (language) {
if (language === window.i18n.curLang) {
return;
}
let data = loadLanguageData(language) || {};
window.i18n.curLang = language;
initPolyglot(data);
this.inst = polyInst;
},
/**
* this method takes a text key as input, and return the localized string
* Please read https://github.com/airbnb/polyglot.js for details
* @method t
* @return {String} localized string
* @example
*
* var myText = i18n.t('MY_TEXT_KEY');
*
* // if your data source is defined as
* // {"hello_name": "Hello, %{name}"}
* // you can use the following to interpolate the text
* var greetingText = i18n.t('hello_name', {name: 'nantas'}); // Hello, nantas
*/
t (key, opt) {
if (polyInst) {
return polyInst.t(key, opt);
}
},
inst: polyInst,
updateSceneRenderers () { // very costly iterations
let rootNodes = cc.director.getScene().children;
// walk all nodes with localize label and update
let allLocalizedLabels = [];
for (let i = 0; i < rootNodes.length; ++i) {
let labels = rootNodes[i].getComponentsInChildren('LocalizedLabel');
Array.prototype.push.apply(allLocalizedLabels, labels);
}
for (let i = 0; i < allLocalizedLabels.length; ++i) {
let label = allLocalizedLabels[i];
if(!label.node.active)continue;
label.updateLabel();
}
// walk all nodes with localize sprite and update
let allLocalizedSprites = [];
for (let i = 0; i < rootNodes.length; ++i) {
let sprites = rootNodes[i].getComponentsInChildren('LocalizedSprite');
Array.prototype.push.apply(allLocalizedSprites, sprites);
}
for (let i = 0; i < allLocalizedSprites.length; ++i) {
let sprite = allLocalizedSprites[i];
if(!sprite.node.active)continue;
sprite.updateSprite(window.i18n.curLang);
}
}
};

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "61de0eb6-9f87-49ed-933d-fd776e9a8ce7",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,79 @@
const i18n = require('LanguageData');
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
cc.Class({
extends: cc.Component,
editor: {
executeInEditMode: true,
menu: 'i18n/LocalizedLabel'
},
properties: {
dataID: {
get () {
return this._dataID;
},
set (val) {
if (this._dataID !== val) {
this._dataID = val;
if (CC_EDITOR) {
this._debouncedUpdateLabel();
} else {
this.updateLabel();
}
}
}
},
_dataID: ''
},
onLoad () {
if(CC_EDITOR) {
this._debouncedUpdateLabel = debounce(this.updateLabel, 200);
}
if (!i18n.inst) {
i18n.init();
}
// cc.log('dataID: ' + this.dataID + ' value: ' + i18n.t(this.dataID));
this.fetchRender();
},
fetchRender () {
let label = this.getComponent(cc.Label);
if (label) {
this.label = label;
this.updateLabel();
return;
}
},
updateLabel () {
if (!this.label) {
cc.error('Failed to update localized label, label component is invalid!');
return;
}
let localizedString = i18n.t(this.dataID);
if (localizedString) {
this.label.string = i18n.t(this.dataID);
}
}
});

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "744dcb38-0c27-4da6-b361-1b4c70aba14a",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,54 @@
const SpriteFrameSet = require('SpriteFrameSet');
cc.Class({
extends: cc.Component,
editor: {
executeInEditMode: true,
inspector: 'packages://i18n/inspector/localized-sprite.js',
menu: 'i18n/LocalizedSprite'
},
properties: {
spriteFrameSet: {
default: [],
type: SpriteFrameSet
}
},
onLoad () {
this.fetchRender();
},
fetchRender () {
let sprite = this.getComponent(cc.Sprite);
if (sprite) {
this.sprite = sprite;
this.updateSprite(window.i18n.curLang);
return;
}
},
getSpriteFrameByLang (lang) {
for (let i = 0; i < this.spriteFrameSet.length; ++i) {
if (this.spriteFrameSet[i].language === lang) {
return this.spriteFrameSet[i].spriteFrame;
}
}
},
updateSprite (language) {
if (!this.sprite) {
cc.error('Failed to update localized sprite, sprite component is invalid!');
return;
}
let spriteFrame = this.getSpriteFrameByLang(language);
if (!spriteFrame && this.spriteFrameSet[0]) {
spriteFrame = this.spriteFrameSet[0].spriteFrame;
}
this.sprite.spriteFrame = spriteFrame;
}
});

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "f34acd86-1a25-4e05-b1ba-5e57ef8183f8",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,9 @@
const SpriteFrameSet = cc.Class({
name: 'SpriteFrameSet',
properties: {
language: '',
spriteFrame: cc.SpriteFrame
}
});
module.exports = SpriteFrameSet;

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "9701943c-d23a-44d9-87f3-e336ee09906a",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,17 @@
// (c) 2012 Airbnb, Inc.
//
// polyglot.js may be freely distributed under the terms of the BSD
// license. For all licensing information, details, and documention:
// http://airbnb.github.com/polyglot.js
//
//
// Polyglot.js is an I18n helper library written in JavaScript, made to
// work both in the browser and in Node. It provides a simple solution for
// interpolation and pluralization, based off of Airbnb's
// experience adding I18n functionality to its Backbone.js and Node apps.
//
// Polylglot is agnostic to your translation backend. It doesn't perform any
// translation; it simply gives you a way to manage translated phrases from
// your client- or server-side JavaScript application.
//
(function(e,t){typeof define=="function"&&define.amd?define([],function(){return t(e)}):typeof exports=="object"?module.exports=t(e):e.Polyglot=t(e)})(this,function(e){"use strict";function t(e){e=e||{},this.phrases={},this.extend(e.phrases||{}),this.currentLocale=e.locale||"en",this.allowMissing=!!e.allowMissing,this.warn=e.warn||c}function s(e){var t,n,r,i={};for(t in e)if(e.hasOwnProperty(t)){n=e[t];for(r in n)i[n[r]]=t}return i}function o(e){var t=/^\s+|\s+$/g;return e.replace(t,"")}function u(e,t,r){var i,s,u;return r!=null&&e?(s=e.split(n),u=s[f(t,r)]||s[0],i=o(u)):i=e,i}function a(e){var t=s(i);return t[e]||t.en}function f(e,t){return r[a(e)](t)}function l(e,t){for(var n in t)n!=="_"&&t.hasOwnProperty(n)&&(e=e.replace(new RegExp("%\\{"+n+"\\}","g"),t[n]));return e}function c(t){e.console&&e.console.warn&&e.console.warn("WARNING: "+t)}function h(e){var t={};for(var n in e)t[n]=e[n];return t}t.VERSION="0.4.3",t.prototype.locale=function(e){return e&&(this.currentLocale=e),this.currentLocale},t.prototype.extend=function(e,t){var n;for(var r in e)e.hasOwnProperty(r)&&(n=e[r],t&&(r=t+"."+r),typeof n=="object"?this.extend(n,r):this.phrases[r]=n)},t.prototype.clear=function(){this.phrases={}},t.prototype.replace=function(e){this.clear(),this.extend(e)},t.prototype.t=function(e,t){var n,r;return t=t==null?{}:t,typeof t=="number"&&(t={smart_count:t}),typeof this.phrases[e]=="string"?n=this.phrases[e]:typeof t._=="string"?n=t._:this.allowMissing?n=e:(this.warn('Missing translation for key: "'+e+'"'),r=e),typeof n=="string"&&(t=h(t),r=u(n,this.currentLocale,t.smart_count),r=l(r,t)),r},t.prototype.has=function(e){return e in this.phrases};var n="||||",r={chinese:function(e){return 0},german:function(e){return e!==1?1:0},french:function(e){return e>1?1:0},russian:function(e){return e%10===1&&e%100!==11?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2},czech:function(e){return e===1?0:e>=2&&e<=4?1:2},polish:function(e){return e===1?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2},icelandic:function(e){return e%10!==1||e%100===11?1:0}},i={chinese:["fa","id","ja","ko","lo","ms","th","tr","zh"],german:["da","de","en","es","fi","el","he","hu","it","nl","no","pt","sv"],french:["fr","tl","pt-br"],russian:["hr","ru"],czech:["cs"],polish:["pl"],icelandic:["is"]};return t});

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "e26fdf72-cbae-40e2-adff-264a559c5620",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,5 @@
'use strict';
const Events = require('events');
module.exports = new Events.EventEmitter;

View File

@@ -0,0 +1,48 @@
'use strict';
const Fs = require('fs');
const Path = require('path');
let template = Fs.readFileSync(Path.join(__dirname, './template.txt'), 'utf-8');
/**
* 创建新的语言包
* @param {string} name
* @return {Promise}
*/
let create = function (name) {
let js = template.replace('{{name}}', name);
let url = `db://assets/resources/i18n/${name}.js`;
return new Promise((resolve, reject) => {
Editor.assetdb.create(url, js, (error) => {
if (error) {
Editor.assetdb.error('Failed to create asset %s, %s', url, error.stack);
reject();
return;
}
resolve();
});
});
};
/**
* 删除语言包
* @param {string} name
*/
let remove = function (name) {
let url = `db://assets/resources/i18n/${name}.js`;
return new Promise((resolve, reject) => {
Editor.assetdb.delete([url], (error, results) => {
if (error) {
Editor.assetdb.error('Failed to delete asset %s, %s', path, error.stack);
reject();
return;
}
resolve();
});
});
};
exports.create = create;
exports.remove = remove;

View File

@@ -0,0 +1,39 @@
'use strict';
const Fs = require('fire-fs');
const Path = require('path');
// adapter project path
let projectPath = Editor.projectPath;
if (!projectPath) {
projectPath = Editor.Project.path;
}
let PATH = Path.join(projectPath, './assets/resources/i18n');
let mount = function () {
// 创建目录,保证目录存在
Fs.ensureDirSync(PATH);
};
let unmount = function () {
// 如果目录为空则删除目录
if (!Fs.existsSync(PATH)) {
return;
}
if (Fs.readdirSync(PATH).length === 0) {
Fs.unlink(PATH);
}
};
let metrics = function () {
Editor.Metrics.trackEvent({
category: 'Packages',
label: 'i18n',
action: 'Panel Open'
}, null);
};
exports.mount = mount;
exports.unmount = unmount;
exports.metrics = metrics;

View File

@@ -0,0 +1,20 @@
'use strict';
let message = {};
message['update-default-language'] = function (event, language) {
let i18n = cc.require('LanguageData');
i18n.init(language);
i18n.updateSceneRenderers();
if (!event.reply) {
return;
}
if (language) {
event.reply(null, 'successful');
} else {
event.reply(new Error('language not specified!'));
}
};
module.exports = message;

View File

@@ -0,0 +1,13 @@
'use strict';
if (!window.i18n) {
window.i18n = {};
}
if (!window.i18n.languages) {
window.i18n.languages = {};
}
window.i18n.languages['{{name}}'] = {
// write your key value pairs here
};