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

微信扫一扫,分享到朋友圈

基于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
事件更改语言,也就是说,可以不刷新页面实现语言更新,但很遗憾,那些没有 set
get
方法的配置项是不能更新的,因为只要调用 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)
});
}
})

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

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

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

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

微信扫一扫,分享到朋友圈

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

我问面试官“还有机会吗?”,他让我看完这位大佬的文章再试试

上一篇

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

下一篇

你也可能喜欢

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

长按储存图像,分享给朋友