完美的Flex多语言支持解决方案
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
完美的Flex多语⾔⽀持解决⽅案
通过多番查阅资料和研究Flex下的组件编写、ResourceManager的使⽤,终于实现了Flex下国际化/多语⾔⽀持的完美解决⽅案:
* 很容易实现编译时类型检查
* ⽀持运⾏时实例化注⼊(延迟创建)
* 可注⼊参数化的值 (参看 account.email)
* ⽀持属性链
* 模型的更新可以使⽤当前的本地化值触发更新
* ⽀持⽪肤和嵌⼊资源(图像,声⾳)--⽬前未提供,可⾃⾏修改⽅法getResourceManagerChain来扩充
最开始准备使⽤参考⽂献中的amazing-i18n-solutions,发现引⽤了很多包、⽂件,略显繁琐,参考后实现了⾃⼰的⽅案,个⼈感觉更完美。
实现思路
1.最开始经典实现思路,可参考Tour de Flex中的例⼦ Localization,进⾏如下操作
设置编译参数
-locale=zh_CN,en_US -source-path=locale/{locale}
建⽴资源⽂件:example.properties
FIRST_NAME=First Name
LAST_NAME=Last Name
ADDRESS=Address
CITY=City
ZIP=Zip
DOB=Date of Birth
SAVE=Save
SELECT_LANGUAGE=Select a language
LOGO=Embed("assets/logo_en.png")
DATE_FORMAT=MM/DD/YYYY
如果是Flex3,⽀持中⽂时还需要⽣成本地资源。
Flex4跳过
Open a Command prompt/shell, navigate to the bin directory of your Flex SDK (for example:
C:\Program Files\Adobe\Flex Builder 3 Plug-in\sdks\3.0.1\bin), and execute the following command:
copylocale en_US zh_CN
添加资源引⽤
<fx:Metadata>
<!-- 引⽤的资源:注意参数,必须与example.properties对应 -->
[ResourceBundle("example")]
</fx:Metadata>
将需要显⽰⽂字的地⽅,改⽤ResourceManager⽅法
<!-- 不再使⽤这种⽅式 -->
<mx:Button id="btnLogin" label="登录" />
<!-- 使⽤ResourceManager⽅法绑定 -->
<mx:Button id="btnLogin" label="{resourceManager.getString('example', 'SELECT_LANGUAGE')}" />
需要切换语⾔时,执⾏下⾯语⾔
resourceManager.localeChain = [ "zh_CN" ];//["en_US"]
2.上述⽅法已经可以达到实时切换语⾔的操作,但是仍然有⼏个不太⽅便的地⽅
设计视图基本上不可⽤了,开发⼈员并不知道到底显⽰的标签是什么内容,⽽是⼀堆resoureManager.get...,“就像⼀滩滩的鸽⼦粪”……
对已经开发完成的应⽤添加国际化⽀持时,需要修改⼤量的代码
为此希望能够能够简化⼀些
3.⾸先实现了⼀个⽅法public static function injection(target:Object,bundleName:String=null):void,这个⽅法能够⾃动读取指定资源⽂件名(bundleName)的资源,并将键值属性绑定到相关的属性上,从⽽实现国际化⽀持的⾃动注⼊
4.资源⽂件的编写⽅式:原来的资源⽂件类似与传统ini⽂件,key=value,使⽤injection⽅法注⼊时,需要注意key的编写⽅式,需要提供属性链式的⽅式。
⽐如bel=登录,表⽰将“登录”注⼊到组件target的按钮bel上
5.更进⼀步:通过调⽤静态⽅法已经可以实现国际化⽀持的注⼊,但是每次创建Object之后都需要调⽤这个⽅法,⽐较繁琐,希望⾃定义⼀个组件,放置到mxml组件上之后可以⾃动进⾏国际化⽀持的注⼊。
于是编写了⼀个⾃定义组件,主要⽅法initialized,该⽅法在组件创建之后执⾏,通过addEventListener使得mxml组件(即document)在创建完成后调⽤国际化⽀持注⼊的⽅法doInjection
public function initialized(document:Object, id:String):void
{
if(!mEnabled) return;
//记录创建此对象的 MXML ⽂档
this.document = document;
var ui:UIComponent = document as UIComponent;
ui.addEventListener(FlexEvent.CREATION_COMPLETE,this.doInjection);
}
====================
I implement a class named "blogs.wideweide.utils.I18nInjection".Created in mxml components,such as application/Panel,it will automatic load the resource and bind to properties like title,label.
Haha,Is it very convenient?Just see the source code and examples.Note tested in Flash Builder 4 beta.
package blogs.wideweide.utils
{
import flash.events.Event;
import flash.events.EventDispatcher;
import mx.core.IMXMLObject;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import mx.resources.*;
import mx.binding.utils.BindingUtils;
import mx.utils.ObjectUtil;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
public class I18nInjection extends EventDispatcher implements IMXMLObject
{
/////////////////////////////////
//
// 继承⾃⽗类的⽅法
//
/////////////////////////////////
private var document:Object;
public function initialized(document:Object, id:String):void
{
if(!mEnabled) return;
//记录创建此对象的 MXML ⽂档
this.document = document;
var ui:UIComponent = document as UIComponent;
ui.addEventListener(FlexEvent.CREATION_COMPLETE,this.doInjection);
}
/** 实现多语⾔⽀持注⼊ */
public function doInjection(e:Event):void{
if(!mEnabled) return;
if(this.mTarget == null) this.Target = this.document;
injection(this.mTarget,this.mBundleName);
}
//////////////////////////////
//
// 应⽤多语⾔的⽬标对象
//
//////////////////////////////
protected var mTarget:Object;
public function set Target(value:Object):void{
mTarget=value;
}
public function get Target():Object{
return mTarget;
}
/////////////////////////////
//
// 资源⽂件名
//
////////////////////////////
protected var mBundleName:String;
public function set BundleName(value:String):void{
mBundleName = value;
}
public function get BundleName():String{
return mBundleName;
}
////////////////////////////
//
// 是否启⽤
//
/////////////////////////////
protected static var mEnabled:Boolean=true;
public static function get Enabled():Boolean{
return mEnabled;
}
public static function setEnabled(value:Boolean):void{
mEnabled = value;
}
//////////////////////////
//
// ⼏个静态⽅法
//
//////////////////////////
/**
* Determine the object endpoint based on target and property values
* e.g. target="{healthCare}" property="pnlQualification.txtSummary.text"
* object endpoint is healthCare.pnlQualification.txtSummary === txtSummary
*
* @param target Object instance
* @param chain Property or Property chain in target instance
*
* @return Object Reference to object instance whose property will be modified.
*
*/
static public function resolveEndPoint(target:Object, chain:String):Object {
var results : Object = target;
if (results != null) {
var nodes : Array = chain.split(".");
if (nodes && nodes.length > 1) {
// Scan all nodes EXCEPT the last (which should be the "true" property endPoint
for (var i:int=0; i<nodes.length-1; i++) {
// Is this a standard or "indexed" node;
// eg frmRegister.registrationValidators[0].requiredFieldError has node
// 'registrationValidators' as an indexed node
var pattern : RegExp = /(.+)\[(.+)]/;
var matches : Array = String(nodes[i]).match(pattern);
var node : String = (matches && (matches.length > 2)) ? matches[1] : nodes[i];
var j : int = (matches && (matches.length > 2)) ? int(matches[2]) : -1;
if (results.hasOwnProperty(node)) {
results = (j == -1) ? results[node] : results[node][j];
continue;
} else {
throw new Error(node);
}
// The scope chain is not valid. This is an UNEXPECTED condition
if (results == null) throw new Error(node);
}
}
}
return results;
}
/**
* Determine the "true" property to modify in the target endpoint
* e.g. "bel" --> resolved property === "label"
*
* @param map Current property chain
* @return String Property key in the "endPoint" target
*
*/
static public function resolveProperty(chain:String):String {
var results : String = chain;
if (results != "") {
var nodes : Array = chain.split(".");
if (nodes && (nodes.length>0)) {
results = nodes[nodes.length-1];
}
}
return results;
}
static public function resolveBundleName(obj:Object):String{
return getQualifiedClassName(obj).match(/[a-zA-z0-9]+$/i)[0];
}
public static function getResourceManagerChain(bundleName:String, resourceName:String ,method:String="getString"):Object{
var chain:Object = new Object();
= method;
chain.getter = function(rm:IResourceManager):String { return rm.getString( bundleName, resourceName ) };
return chain;
}
public static function injection(target:Object,bundleName:String=null):void{
if(target==null)return;
if(bundleName==null) bundleName = resolveBundleName(target);
var rm:IResourceManager = ResourceManager.getInstance();
var ar:Array = rm.getLocales();
if(ar.length==0) return;
var strLocale:String=ar[0];
if(strLocale.length==0) return;
var bundle:IResourceBundle = rm.getResourceBundle(strLocale,bundleName);
if(bundle==null) return;
var props:Array = ObjectUtil.getClassInfo(bundle.content).properties as Array;
for each (var key:String in props)
BindingUtils.bindProperty( resolveEndPoint(target,key), resolveProperty(key), rm, getResourceManagerChain(bundleName,key) ); }
}
}
⼀个简单的应⽤实例:
创建应⽤程序(i18n.mxml),添加对资源的引⽤、⾃定义组件,源码如下:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="/mxml/2009"
xmlns:s="library:///flex/spark"
xmlns:mx="library:///flex/halo"
xmlns:utils="blogs.wideweide.utils.*"
minWidth="1024" minHeight="768">
<fx:Metadata>
<!-- 引⽤的资源 -->
[ResourceBundle("i18n")]
</fx:Metadata>
<fx:Script>
<![CDATA[
import mx.events.ListEvent;
protected function localeComboBox_changeHandler(event:ListEvent):void
{
resourceManager.localeChain = [ localeComboBox.selectedItem.code ];
}
]]>
</fx:Script>
<fx:Declarations>
<!-- 引⼊多语⾔⽀持注⼊控件 -->
<utils:I18nInjection />
<!-- 多语⾔⽀持列表 -->
<fx:Array id="locales">
<fx:Object label="中⽂" code="zh_CN"/>
<fx:Object label="English" code="en_US"/>
</fx:Array>
</fx:Declarations>
<mx:Form
width="100%" height="100%"
defaultButton="{btnLogin}"
>
<mx:FormItem id="fiName" label="登录名称" required="true">
<mx:TextInput id="tiName" text="admin"/>
</mx:FormItem>
<mx:FormItem id="fiPassword" label="登录密码" required="true">
<mx:TextInput id="tiPwd" displayAsPassword="true" text="admin"/>
</mx:FormItem>
<mx:FormItem id="fiLocales" label="使⽤语⾔">
<mx:ComboBox id="localeComboBox" dataProvider="{locales}" change="localeComboBox_changeHandler(event)"/> </mx:FormItem>
<mx:FormItem>
<mx:Button id="btnLogin" label="登录"/>
</mx:FormItem>
</mx:Form>
</s:Application>
在项⽬的编译参数中设置语⾔和路径
-locale=zh_CN,en_US -source-path=locale/{locale}
添加并编写语⾔资源⽂件locale\zh_CN\i18n.properties bel=登录名称
bel=登录密码
bel=登录
bel=使⽤语⾔
添加并编写语⾔资源⽂件locale\en_US\i18n.properties bel= User Id
bel=Password
bel=Login
bel=Language
Just run it to see the effect,any idea please comment. References:。