ExtJS4本地化

工作后干的活有点乱七八糟,被折磨死
每一段时间就要学新的语言。2011下半年是C#,2012年,轮换到了JavaScript

最近的任务是用ExtJS设计前端,这玩意强大到足以取代Silverlight,非常适合配合RESTful API使用,使用AJAX获取JSON或XML类型的数据,前端页面的生成完全不需要PHP/JSP,仅HTML+JS已经足够。这种情况下,前端可以和API所在服务端完全分离,部署在不同的服务器上,甚至前端可以放在用户本地运行

第一个任务是攻克多语言化(老大乃将这种任务扔给素人情何以堪)
网上搜索了一下,还有人专门写了插件(ext-locale-loader),但这种需要给每一自设计的页面弄一份语言拷贝的方式让余菊花一紧
后来阅读ExtJS的自带文档,发现有本地化的详细指引($EXTJS_FOLDER/docs/index.html#!/guide/localization)
一步步来就实现了ExtJS自身的UI元件在用户选择不同语言时的本地化

实现后的效果

演示页面:extlocalize.html
ExtJS语言列表RawData:languages.js(仅保留4个语言)
逻辑+UI:extlocalize.js
切换语言的逻辑其实非常简单,判断页面的传入参数(没错,静态页面也可以有参数),利用AJAX加载语言文件,然后执行语言文件中的代码,更新字符串

extlocalize.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Localization example</title>
    <!-- Ext Library Files -->
    <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
    <script src="ext/ext-all-debug.js"></script>
    <!-- App Scripts -->
    <script src="languages.js"></script>
    <script src="extlocalize.js"></script>
</head>
<body>
    <div id="languages"></div>
    <div id="datefield"></div>
    <div id="emailfield"></div>
    <div id="grid"></div>
</body>
</html>

languages.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
Ext.namespace('Ext.local');

Ext.local.languages = [
    ['en', 'English'],
    ['ja', 'Japanese(日本語)'],
    ['zh_CN', 'Simplified Chinese(简体中文)'],
    ['zh_TW', 'Traditional Chinese(繁體中文)']
];

extlocalize.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', 'ext/examples/ux/');
Ext.require([
    'Ext.data.*',
    'Ext.tip.QuickTipManager',
    'Ext.form.*',
    'Ext.ux.data.PagingMemoryProxy',
    'Ext.grid.Panel'
]);

Ext.onReady(function() {

    MultiLangDemo = (function() {
        return {
            init: function() {
            	var store = Ext.create('Ext.data.ArrayStore', {
            	    fields: ['code', 'language'],
            	    data  : Ext.local.languages //from languages.js
            	});

            	var combo = Ext.create('Ext.form.field.ComboBox', {
            	    renderTo: 'languages',
            	    margin: '10, 0, 0, 10',
            	    store: store,
            	    displayField: 'language',
            	    queryMode: 'local',
            	    emptyText: 'Select a language...',
            	    hideLabel: true,
            	    width: 200,
            	    listeners: {
            	        select: {
            	            fn: function(cb, records) {
            	                var record = records[0];
            	                window.location.search = Ext.urlEncode({"lang":record.get("code")});
            	            },
            	            scope: this
            	        }
            	    }
            	});
            	
            	var params = Ext.urlDecode(window.location.search.substring(1));

            	if (params.lang) {
            	    var url = Ext.util.Format.format('ext/locale/ext-lang-{0}.js', params.lang);

            	    Ext.Ajax.request({
            	        url: url,
            	        success: this.onSuccess,
            	        failure: this.onFailure,
            	        scope: this
            	    });

            	    // check if there's really a language with passed code
            	    var record = store.findRecord('code', params.lang, null, null, null, true);
            	    // if language was found in store, assign it as current value in combobox

            	    if (record) {
            	        combo.setValue(record.data.language);
            	    }
            	} else {
            	    // no language found, default to english
            	    this.setup();
            	}

            	Ext.tip.QuickTipManager.init();
            },
            onSuccess: function(response) {
                try {
                    eval(response.responseText);
                } catch (e) {
                    Ext.Msg.alert('Failure', e.toString());
                }
                this.setup();
            },
            onFailure: function() {
                Ext.Msg.alert('Failure', 'Failed to load locale file.');
                this.setup();
            },
            setup: function() {
                Ext.create('Ext.FormPanel', {
                    renderTo: 'datefield',
                    margin: '10, 0, 0, 10',
                    frame: true,
                    title: 'Date picker',
                    width: 380,
                    defaultType: 'datefield',
                    items: [{
                        fieldLabel: 'Date',
                        name: 'date'
                    }]
                });
                Ext.create('Ext.FormPanel', {
                    renderTo: 'emailfield',
                    margin: '10, 0, 0, 10',
                    labelWidth: 100,
                    frame: true,
                    title: 'E-mail Field',
                    width: 380,
                    defaults: {
                        msgTarget: 'side',
                        width: 340
                    },
                    defaultType: 'textfield',
                    items: [{
                        fieldlabel: 'Email',
                        name: 'email',
                        vtype: 'email'
                    }]
                });

                var monthArray = Ext.Array.map(Ext.Date.monthNames, function (e) { return [e]; });
                var ds = Ext.create('Ext.data.Store', {
                     fields: ['month'],
                     remoteSort: true,
                     pageSize: 6,
                     proxy: {
                         type: 'pagingmemory',
                         data: monthArray,
                         reader: {
                             type: 'array'
                         }
                     }
                 });

                Ext.create('Ext.grid.Panel', {
                    renderTo: 'grid',
                    margin: '10, 0, 0, 10',
                    width: 380,
                    height: 203,
                    title:'Month Browser',
                    columns:[{
                        text: 'Month of the year',
                        dataIndex: 'month',
                        width: 240
                    }],
                    store: ds,
                    bbar: Ext.create('Ext.toolbar.Paging', {
                        pageSize: 6,
                        store: ds,
                        displayInfo: true
                    })
                });
                // trigger the data store load
                ds.load();
            }
        };
    })();

    MultiLangDemo.init();
});

以上仅是ExtJS UI自身的本地化。

自己系统中的文字如何本地化呢?
余的做法基本是沿着ExtJS本地化的思路,将系统用到的字符串集中在单个文件中,这也有利于后期扩展更多的语言
像Date picker/Email Field/Month Browser/Month of the year就是系统自身的语言,不应该硬编码到UI中
在加载ExtJS语言文件的时候,同时也加载系统自身的语言文件进行刷新
系统语言文件也必须使用和ExtJS语言文件相同的后缀,如en/ja/zh_CN/zh_TW

效果:

演示页面:multiplelanguages.html
逻辑+UI:multiplelanguages.js
语言列表RawData:languages.js(和上面一样)
系统默认语言文件(必须):myproject-js/myproject-lang.js(余将默认语言弄成了日文)
系统的本地化语言文件(可选):(没有参数时会保留默认语言文件中的设定)
locale/myproject-lang-en.js
locale/myproject-lang-ja.js
locale/myproject-lang-zh_CN.js
locale/myproject-lang-zh_TW.js

multiplelanguages.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Localization example</title>
    <!-- Ext Library Files -->
    <script src="myproject-js/myproject-lang.js"></script>
    <link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
    <script src="ext/ext-all-debug.js"></script>
    <!-- App Scripts -->
    <script src="languages.js"></script>
    <script src="multiplelanguages.js"></script>
</head>
<body>
    <div id="languages"></div>
    <div id="datefield"></div>
    <div id="emailfield"></div>
    <div id="grid"></div>
</body>
</html>

multiplelanguages.js
将UI的生成全部集中到setup函数里了

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
Ext.Loader.setConfig({
    enabled: true
});
Ext.Loader.setPath('Ext.ux', 'ext/examples/ux/');
Ext.require(['Ext.data.*', 'Ext.tip.QuickTipManager', 'Ext.form.*', 'Ext.ux.data.PagingMemoryProxy', 'Ext.grid.Panel']);

Ext.onReady(function() {

    var params;
    MultiLangDemo = (function() {
        return {
            init: function() {
                // load ExtJS locale
                params = Ext.urlDecode(window.location.search.substring(1));
                if (params.lang) {
                    var url = Ext.util.Format.format('ext/locale/ext-lang-{0}.js', params.lang);
                    Ext.Ajax.request({
                        url: url,
                        success: this.onLoadExtLocaleSuccess,
                        failure: this.onLoadExtLocaleFailure,
                        scope: this
                    });
                } else {
                    // no language found, locale of ExtJS will be english as default
                    this.loadmyprojectLocale();
                }
            },
            onLoadExtLocaleSuccess: function(response) {
                try {
                    eval(response.responseText);
                } catch (e) {
                    Ext.Msg.alert('Failure', e.toString());
                }
                this.loadmyprojectLocale();
            },
            onLoadExtLocaleFailure: function() {
                Ext.Msg.alert('Failure', 'Failed to load locale file.');
                this.loadmyprojectLocale();
            },
            loadmyprojectLocale: function() {
                // load locale for myproject
                if (params.lang) {
                    var urlmyprojectLocale = Ext.util.Format.format('locale/myproject-lang-{0}.js', params.lang);
                    Ext.Ajax.request({
                        url: urlmyprojectLocale,
                        success: this.onLoadmyprojectLocaleSuccess,
                        failure: this.onLoadmyprojectLocaleFailue,
                        scope: this
                    });
                } else {
                    this.setup();
                }
            },
            onLoadmyprojectLocaleSuccess: function(response) {
                try {
                    eval(response.responseText);
                } catch (e) {
                    Ext.Msg.alert('Failure', e.toString());
                }
                this.setup();
            },
            onLoadmyprojectLocaleFailue: function() {
                Ext.Msg.alert('Failure', 'Failed to load myproject locale file.');
                this.setup();
            },
            setup: function() {
                var store = Ext.create('Ext.data.ArrayStore', {
                    fields: ['code', 'language'],
                    data: Ext.local.languages //from languages.js
                });

                var combo = Ext.create('Ext.form.field.ComboBox', {
                    renderTo: 'languages',
                    margin: '10, 0, 0, 10',
                    store: store,
                    displayField: 'language',
                    queryMode: 'local',
                    emptyText: myproject.Message.SelectALanguage,
                    hideLabel: true,
                    width: 200,
                    listeners: {
                        select: {
                            fn: function(cb, records) {
                                var record = records[0];
                                window.location.search = Ext.urlEncode({
                                    "lang": record.get("code")
                                });
                            },
                            scope: this
                        }
                    }
                });
                if (params.lang) {
                    // check if there's really a language with passed code
                    var record = store.findRecord('code', params.lang, null, null, null, true);
                    // if language was found in store, assign it as current value in combobox
                    if (record) {
                        combo.setValue(record.data.language);
                    }
                }

                Ext.create('Ext.FormPanel', {
                    renderTo: 'datefield',
                    margin: '10, 0, 0, 10',
                    frame: true,
                    title: myproject.Message.PickDate,
                    width: 380,
                    defaultType: 'datefield',
                    items: [{
                        fieldLabel: myproject.Message.Date,
                        name: 'date'
                    }]
                });
                Ext.create('Ext.FormPanel', {
                    renderTo: 'emailfield',
                    margin: '10, 0, 0, 10',
                    labelWidth: 100,
                    frame: true,
                    title: myproject.Message.EmailFieldTitle,
                    width: 380,
                    defaults: {
                        msgTarget: 'side',
                        width: 340
                    },
                    defaultType: 'textfield',
                    items: [{
                        fieldlabel: 'Email',
                        name: 'email',
                        vtype: 'email'
                    }]
                });

                var monthArray = Ext.Array.map(Ext.Date.monthNames, function(e) {
                    return [e];
                });
                var ds = Ext.create('Ext.data.Store', {
                    fields: ['month'],
                    remoteSort: true,
                    pageSize: 6,
                    proxy: {
                        type: 'pagingmemory',
                        data: monthArray,
                        reader: {
                            type: 'array'
                        }
                    }
                });

                Ext.create('Ext.grid.Panel', {
                    renderTo: 'grid',
                    margin: '10, 0, 0, 10',
                    width: 380,
                    height: 203,
                    title: myproject.Message.MonthList,
                    columns: [{
                        text: myproject.Message.MonthTitle,
                        dataIndex: 'month',
                        width: 240
                    }],
                    store: ds,
                    bbar: Ext.create('Ext.toolbar.Paging', {
                        pageSize: 6,
                        store: ds,
                        displayInfo: true
                    })
                });
                // trigger the data store load
                ds.load();
            }
        };
    })();

    MultiLangDemo.init();
});

myproject-js/myproject-lang.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
var myproject = {};
myproject.Message = {};
myproject.Message.SelectALanguage = '言語を選択ください...';
myproject.Message.PickDate = '日付を選択';
myproject.Message.Date = '日付';
myproject.Message.EmailFieldTitle = 'メールアドレス';
myproject.Message.MonthList = '月の一覧';
myproject.Message.MonthTitle = '月順';

locale/myproject-lang-en.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = 'Select a language...';
	myproject.Message.PickDate = 'Date Picker';
	myproject.Message.Date = 'Date';
	myproject.Message.EmailFieldTitle = 'Email';
	myproject.Message.MonthList = 'Month Browser';
	myproject.Message.MonthTitle = 'Month of the year';
}

locale/myproject-lang-ja.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = '言語を選択ください...';
	myproject.Message.PickDate = '日付を選択';
	myproject.Message.Date = '日付';
	myproject.Message.EmailFieldTitle = 'メールアドレス';
	myproject.Message.MonthList = '月の一覧';
	myproject.Message.MonthTitle = '月順';
}

locale/myproject-lang-zh_CN.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = '请选择一种语言...';
	myproject.Message.PickDate = '选择日期';
	myproject.Message.Date = '日期';
	myproject.Message.EmailFieldTitle = '电子邮件地址';
	myproject.Message.MonthList = '月份一览';
	myproject.Message.MonthTitle = '月份';
}

locale/myproject-lang-zh_TW.js

/**
 * by kuyur@kuyur.info
 * 2012.2.3
 */
if (myproject.Message) {
	myproject.Message.SelectALanguage = '請選擇一種語言...';
	myproject.Message.PickDate = '選擇日期';
	myproject.Message.Date = '日期';
	myproject.Message.EmailFieldTitle = '電子郵件地址';
	myproject.Message.MonthList = '月份一覽';
	myproject.Message.MonthTitle = '月份';
}

注意事项:
1.ExtJS库的解压目录名要一致,代码中的为ext
2.由于AJAX的本地请求会因为安全问题被浏览器禁止,需要将文件放到服务器才能测试

源文件下载:localize.rar

2012年2月3日 | 归档于 技术, 程序
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: