一个由I18n引发的血案

Creator 版本 2.3.1

1. 起因

由于公司的项目需要上线苹果商店,其中有一些文字不能显示,但是安卓端需要显示,老大说用I18n吧,思来想去这个方案挺合适的呀, 于是就采用了多语言的解决方案[手动狗头]

2.过程

既然敲定了方案,那下一步就是找到解决方案,于是屁颠屁颠来到官网上找i18n的插件, 一看官方文档居然有介绍,当时感觉这个妙呀,素不知我正一步一步进入官方的深渊:2:


以上就是在新手教程中找的资源,然后我迫不及待的下载下来想试着跑一下,并幻想着明天就能升职加薪当上CEO迎娶白富美从此走上人生巅峰呢

3.探索

  1. 插件下载下来了,一看有些年头了,心中的信念瞬间灭了一半,但是万一好使呢[狗头],于是我新开了一个项目,将插件放在packages下面,想着接下来就能见证奇迹了呢:grinning:
  2. 然后经过不短的折腾最后证明,你越害怕什么,就越会发生什么。奇迹没有发生,插件跑步起来,甚至在我的编辑器中都显示不出来文字:9:
  3. 事情到了这一步感觉有点亏呀,作为一个羊毛党不能白干这半天,想着他既然跑不起来,那我学习一下原理也算是有收获了呀,于是开始查看源码,插件的源码就懒得看了,主要看了一下加载及初始化的过程及给label赋值的过程,大致明白了它的原理: 给label加一个组件,在onLoad中给他赋值(过去有点久了,快忘了)。
  4. 作为一个程序员,你告诉我要给每一个Label添加一个组件不如让我去死呀:3::4::6:,我必须找到一键式的替换方式,毕竟项目是中途要求做I18n,如果一个一个替换,可能我的职业生涯就到此结束了:10:

4.不断尝试

既然想要偷懒,那有一个可以偷懒的方案才可以,怎么办才好呢。。。。
一个危险的想法从我心中冒了出来,既然是改Label显示,那就直接一点,吧Label的实现改掉就好了,作为一个多人合作的项目,项目定制就显得太麻烦,作为一个程序猿最怕的就是麻烦了,定制引擎不仅要建仓库,还要让好几个人替换更改配置,而且之后引擎升级也会有困难。于是js的好处提现出来了,我直接覆盖你的方法就行了。于是好几种方法复现在我眼前

随便找个文件,将代码粘上就行,只要是全局运行一次就会覆盖之前定义好的方法
1.

import { i18n } from './I18n';
const CacheMode = cc.Enum({
    NONE: 0,
    BITMAP: 1,
    CHAR: 2,
});
cc.Label.prototype['onLoad'] = function(){
  // For compatibility with v2.0.x temporary reservation.
  if (this._batchAsBitmap && this.cacheMode === CacheMode.NONE) {
      this.cacheMode = CacheMode.BITMAP;
      this._batchAsBitmap = false;
  }

  if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS) {
      // CacheMode is not supported in Canvas.
      this.cacheMode = CacheMode.NONE;
  }
  this.string = i18n.zh[this._string] === undefined ? this._string : i18n.zh[this._string];
}

怎么说呢, 感觉有点累赘,并且以后扩展也会有问题 于是改版
2.

import { i18n } from './I18n';
cc.Label.prototype['onLoadOld'] = cc.Label.prototype['onLoad']
cc.Label.prototype['onLoad'] = function(){
  cc.Label.prototype['onLoadOld']();
  this.string = i18n.zh[this._string] === undefined ? this._string : i18n.zh[this._string];
}

嗯, 是比以前好点了, 但是吧,丑于是在改

import { i18n } from './I18n';
Object.defineProperty(cc.Label.prototype, 'string', {
    get : function(){
        if(this._string.indexOf("/i18n/") == 0){
            return i18n.zh[this._string] === undefined ? this._string : i18n.zh[this._string];
        }
        return this._string;
    }
})

嗯,我只改了get方法,官方不会打我吧
注意以上方法都会改变在编辑器中的显示,而且只需要全局执行一次,不需要再每个Label组件中再添加一个组件

5.不断优化

上面提到了,更改了set方法后会再编辑器中直接显示结果而不是I18n中的key的值,我们是一个追求完美的人呀,怎么能这么粗糙了事,我们追求的是在场景中显示i18n中的值在检查器中显示i18n中的key,怎么做呢。。。。
看来要扣引擎的源码了
于是找找找 找到了inspector: ‘packages://inspector/inspectors/comps/label.js’ 这样一句话,看我白嫖党的威力
这个inspector一般手段是找不到的, 不过官方的 开发者 工具给了我一线希望


格式化之后看了一下主要是 target.string 在作怪,而且内容是固定的,所以增加@property(cc.String)等方式扩展属性是不好使的
又想到官方可以通过Vue更定制inspector

于是在危险的边缘疯狂试探终于找打了方案,使用mixin给Vue组件扩展功能

if(CC_EDITOR && CC_DEV){
    Vue.mixin({
        created(){
            if(this.constructor.name == 'CcLabel'){
                this.target = new Proxy(this.target,{
                    get(t, p, r){
                        if(p == 'string'){
                            t[p].value =  t['_string'].value;
                            t[p].values =  t['_string'].values;
                        }
                        return t[p];
                    }
                })
            }
        }
    })
}

6. 总结

  1. 关于后期提取文本
    核心思路是遍历 prefab 因为 prefab 是一维数组,使用 JSON.parse 解析读取到的文件找到 “type” 为 “cc.Label"的对象,将其中的”_N$string"和"_string"都替换掉,然后通过JSON.stringify(prefabArr, null, 2) 覆盖原文件就可以了,最终的文件key的顺序不会乱,看来官方也是这么生成prefab文件的 [狗头护体]

  2. 其实我用到的I18n 就是一个 简单的 Object 可以在一个文件中处理好后再另一个地方导入

贴上代码

//  I18n.ts
export let i18n = {
	"zh": {
		"/i18n/name:931": "九九九"
         }
}
// manager.ts  随便一个文件,加载一次就会覆盖引擎的方法
import { i18n } from './I18n';
if(CC_EDITOR && CC_DEV){
    Vue.mixin({
        created(){
            if(this.constructor.name == 'CcLabel'){
                this.target = new Proxy(this.target,{
                    get(t, p, r){
                        if(p == 'string'){
                            t[p].value =  t['_string'].value;
                            t[p].values =  t['_string'].values;
                        }
                        return t[p];
                    }
                })
            }
        }
    })
}

Object.defineProperty(cc.Label.prototype, 'string', {
    get : function(){
        if(this._string.indexOf("/i18n/") == 0){
            return i18n.zh[this._string] === undefined ? this._string : i18n.zh[this._string];
        }
        return this._string;
    }
})

效果图

12赞

尴尬没人回帖

666, 一直在找比较好的方案, 这个最6

你不是来了吗:weary:

哈哈,有用就行:relieved:

直接继承 cc.Label 自己扩展一下 label 感觉比这方便呢

有些项目做一半了有这个需求,不可能把已经挂好的label全改一遍改成新的组件的,虽然可以直接批量替换prefab文件,但是毕竟不方便

mark在此

如果要加参数这么处理

如果一开始就设计好那当然是最好的了

那估计得自己扩展Label组件了,或者深入研究一下修改Vue组件的template [狗头]

非常有想法

仿佛一扇大门开启,哈哈哈

/i18n/name:931
这个不明显,不知道什么意思吧?

这是脚本跑出来的,怕重复

你的想法很危险:smiling_imp:

我项目就是 直接把 prefab 里 cc.label 全替换了, 用 vscode 一次性完事,代码都不用写, 还能扩展 inspector, 编辑器里面就能看效果

cc.Class({
extends: cc.Component,
editor: {
    requireComponent: cc.Label,
},
properties: {
    textKey: {
        default: 'TEXT_KEY',
        multiline: true,
        tooltip: 'Enter i18n key here',
    }
},

onLoad() {
    let label = this.node.getComponent(cc.Label);
    label.string = i18n.t(this.textKey);
}
});

弄了个依赖组件 在现有label的node上挂上

1赞

官方的坑真多

哦哦,那挺好的