基于Modern工具包的本地化方式(上)

Posted 上将军

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Modern工具包的本地化方式(上)相关的知识,希望对你有一定的参考价值。

新项目需要从服务器下载本地化资源,如果继续使用快速模板的本地化策略就很尴尬了,绝不可能等待本地化资源全部下载后再去加载项目,得另想办法。在研究过了《Internationalization & Localization with Sencha Ext JS》一文后,终于有思路了。
文章的思路是通过重写Ext.Component来导入本地化数据,但前提还是要先加载好本地化资源,要解决这个问题不难,在本地化资源加载完成后,使用Ext.fireEvent出发一个本地化已准备好的事件就行了,而在Ext.Component的构造函数或初始化函数内,判断本地化是否已经准备好,如果准备好,直接执行本地化操作,如果还没准备好,就监听事件等待本地化资源加载。
思路有了就可以实现了,先完成本地化资源服务类,代码如下:

Ext.define('CommonShared.service.Localized', 
    alternateClassName: 'LocalizedService',
    singleton: true,

    config:
        currentLanguage: null
    ,

    requires:[
        'CommonShared.util.Url',
        'CommonShared.service.OAuth',
    ],
  
    isReady: false,

    constructor(config)
        const me = this;
        me.initConfig(config)
        me.initLanguages();
        me.loadResources();
        console.log(AppConfig.lang)
    ,

    initLanguages()
        const me = this;
        let current = StorageService.get('lang');
        if(current) return;
        current = AppConfig.lang === 'zh-CN' ? 'zh-Hans' 
            : AppConfig.lang === 'zh-TW' ? 'zh-Hant' : AppConfig.lang;
        me.setCurrentLanguage(current);
        StorageService.set('lang', current);
    ,

    loadResources()
        const me= this;
        me.isReady = false;
        Ext.Ajax.request(
            url: URI.get('Configuration', 'localization'),
            headers: AuthService.getAuthorizationHeader(),
            scope: me
        ).then(me.loadSuccess, me.loadFailure, null, me);
    ,

    loadSuccess(response)
        const me = this,
            obj = Ext.decode(response.responseText,true);
        if(obj)
            Object.assign(me.remoteRawValue, obj);
            me.doOverride();
        
        me.isReady = true;
        Ext.fireEvent('localizedready', me);
    ,

    loadFailure(response)
        let obj  = Ext.decode(response.responseText, true),
            error = 'Unknown Error!';
        if(obj && obj.error) error = obj.error;
        Ext.Msg.alert('Error', error);
    ,

    get(key, resourceName)
        const me = this,
            defaultResourceName = me.remoteRawValue.defaultResourceName,
            values = me.remoteRawValue.values;
        return resourceName && values[resourceName] && values[resourceName][key] 
            || ( values['ExtResource'] && values['ExtResource'][key] )
            || ( values[defaultResourceName] && values[defaultResourceName][key] )
            || key;
    ,

    getLanguages()
        return this.remoteRawValue.languages();
    ,

    switchLanguages(value)
        const me = this;
        me.setCurrentLanguage(value);
        StorageService.set('lang', value);
        me.loadResources();
    ,

    localized(cls, name,  resourceName)
        name = Ext.String.capitalize(name);
        const get = cls[`get$name`],
            set = cls[`set$name`];
        if(!get || !set) return;
        const value = get.apply(cls);
        if(!value) return;
        set.apply(cls, [LocalizedService.get(value,resourceName)]);
    ,


    privates:
        remoteRawValue: ,

        doOverride()
            const me = this,
                values = me.remoteRawValue.values.ExtResource,
                newMonthNames = [],
                newDayNames = [],
                am = values['AM'] || 'AM',
                pm = values['PM'] || 'PM';
            Ext.Date.monthNames.forEach(month=>
                newMonthNames.push(values[month] || month);

            );
            Ext.Date.monthNames = newMonthNames;
            Ext.Date.dayNames.forEach(day=>
                newDayNames.push(values[day] || day);

            );
            Ext.Date.dayNames = newDayNames;

            //console.log(Ext.Date)
            Ext.Date.formatCodes.a = `(this.getHours() < 12 ? '$am' : '$pm')`;
            Ext.Date.formatCodes.A = `(this.getHours() < 12 ? '$am' : '$pm')`;
    
            const parseCodes = 
                g: 1,
                c: "if (/(" + am + ")/i.test(results[0])) \\n" +
                    "if (!h || h == 12)  h = 0; \\n" +
                    " else  if (!h || h < 12)  h = (h || 0) + 12; ",
                s: `($am|$pm)`,
                calcAtEnd: true
            ;
    
            Ext.Date.parseCodes.a = Ext.Date.parseCodes.A = parseCodes;
    
    
        ,
    


)

本地化服务类是一个单类模式的类,也就是可以直接调用。在类里主要实现了在构造函数了先判断当前需要加载什么语言的资源,然后调用loadResources方法从远程加载本地化资源。加载完成后会触发·localizedready事件。在调用switchLanguages方法切换语言后,可调用loadResources方法获取新的语言资源,并再加载完成后再次触发·localizedready事件更改语言,也就是说,可以不刷新页面实现语言更新,但很遗憾,那些没有setget方法的配置项是不能更新的,因为只要调用set方法才会刷新页面显示,如按钮调用setText方法就可更新按钮的显示文本。因而,这方法会有点小瑕疵。
经过对Ext JS包里的本地化文件的分析,已经可以实现绝大部分类的本地化了,但还是有小部分只能通过老的重写办法来更新,如Ext.Date类,这个只能在本地化类中通过doOverride方法来对它进行本地化了。
下面就是重点中的重点了,重写Ext.Component类了,代码如下:

Ext.define('CommonOverrides.shared.Component',
    override: 'Ext.Component',

    config:
        resourceName: null,
        localized: []
    ,

    initialize()
        const me = this;
        me.callParent(arguments);
        if(LocalizedService && LocalizedService.isReady)
            me.onLocalized();
        else
            Ext.on('localizedready', me.onLocalized, me);
        
    ,

    onLocalized()
        const me = this,
            xtype = me.xtype,
            resourceName = me.getResourceName(),
            service = LocalizedService,
            localized = me.getLocalized();
        if(me.isButton || me.isMenuItem) 
            service.localized(me, 'text');
            return;
        
        if(xtype === "loadmask")
            service.localized(me, 'message');
            return;
        ;
        if(me.isField)
            service.localized(me,'requiredMessage');
            service.localized(me,'validationMessage');
            service.localized(me,'label', resourceName);
            if(me.getPlaceholder) service.localized(me, 'placeholder', resourceName);
            if(me.getBoxLabel) service.localized(me ,'boxLabel', resourceName);
            if(xtype === 'datefield' || xtype === 'DatePicker')
                service.localized(me,'minDateMessage');
                service.localized(me,'maxDateMessage');
            
            if(xtype === 'numberfield')
                me.decimalsText = LocalizedService.get(me.decimalsText);
                me.minValueText = LocalizedService.get(me.minValueText);
                me.maxValueText = LocalizedService.get(me.maxValueText);
                me.badFormatMessage = LocalizedService.get(me.badFormatMessage);
            
            if(xtype === 'textfield')
                me.badFormatMessage = LocalizedService.get(me.badFormatMessage);
            
            me.setError(null);
            return;
        
        if(me.isContainer)
            if(me.isPanelTitle || me.isPanel) 
                service.localized(me , 'title', resourceName);
                if(xtype === 'tooltip')
                    service.localized(me , 'html', resourceName);
                
                const collapsible = me.getCollapsible && me.getCollapsible();
                if(collapsible)
                    service.localized(collapsible, 'collapseToolText');
                    service.localized(collapsible, 'expandToolText');
                
                me.doCustomLocalized(me, localized, resourceName);
                return;
            ;
            if(me.isGridColumn)                
                service.localized(me ,'text', resourceName);
                return;
            
            if(xtype === 'datepanel')
                service.localized(me, 'nextText');
                service.localized(me, 'prevText');
                return;
            
            if(me.isDataView)
                service.localized(me, 'loadingText');
                service.localized(me, 'emptyText');
                return;
            
        
        me.doCustomLocalized(me, localized, resourceName);
    ,

    doCustomLocalized(me, localized, resourceName)
        localized.forEach(key=>
            LocalizedService.localized(me, key, resourceName)
        );
    


)

代码中,定义了resourceNamelocalized两个属性。属性resourceName的作用是指定从哪个资源中获取本地化数据,譬如有用户和产品两个资源,在对产品的字段进行本地化时,就需要从产品资源中获取本地化信息,而对应的用户的字段就从用户资源中获取,从而减少冲突。属性localized的主要作用就是为一些自定义信息进行本地化,譬如使用一个组件的html属性来显示一些需要本地化的信息,在默认的情况下是不对这些信息进行本地化的,这时候就要通过localized属性来指定了。

onLocalized方法内,会看到一堆的判断语句,它的主要目的是对Ext JS自身组件进行全方位无遗漏的本地化。为什么要这样做呢?采用重写方式不是更好么?这里的重点还是不知道本地化资源什么时候加载完,在加载完成之前,可能有些组件已经实例化并显示了,这时候再去重写是不会改变已实例化的组件的显示的,而只有通过set方法的调用才会去刷新显示,因而只有采用这种折中的方式。

细心的读者可能会发现为什么有些需要在Ext JS包中本地化的类不在onLocalized内,这个很好理解,因为有些内部组件使用的是基础组件,如网格中的列标题的菜单,使用的就是菜单项,最终会同通过菜单项来text的属性来显示结果,只要在菜单项中对text属性进行本地化就行了,不需要单独再为这个类进行本地化。

组件的本地化工作已经完成,但这还不是全部,还要非组件类也需要本地化,这个下文再讲。

以上是关于基于Modern工具包的本地化方式(上)的主要内容,如果未能解决你的问题,请参考以下文章

《基于Modern工具包的本地化方式》的错误修正

《基于Modern工具包的本地化方式》的重构

内置的HTTP服务器Modern PHP

解决Modern工具包中Ext.field.Date的提交值问题

微服务选型之Modern Node.js

基于虚拟机的centos6.5 搭建本地光盘yum源