slobber

http://www.slobber.cn

引矢不引玉系列——动态加载外部XML语言文件构建多语言应用程序

slobber | 06 十月, 2006 10:03

首先解下题目,一般都说抛砖引玉,但是我扔出去这块砖,不知道能不能引来玉,不过就算引不来玉,大家跟贴给点建议,射过来几个箭头,我也会很满足了。另外一个要声明的,这个词并不是我创的,虽然网上还搜不到,不过确实是某网友的创意,我借用下。这篇东西算是我的第一篇成篇幅的文章了,不能说是教程,只是把我遇到的问题及解决的方法,和大家分享一下。这系列文章将会继续出下去,会是不同主题,不过都还是比较基础的内容,给初学Flex的朋友开拓思路。

在本文中,将要去探讨如何在Flex中利用XML构建多语言设置。

在ActionScript 2中,Flash使用了mx.lang.Locale类来进行多语言文本控制,当然,通过类似

myLabel.text = Locale.loadString("IDS_TITLE");

这样的ActionScript语句来获得当前语言的相关字符串,Locale所需要的语言文本是从一个XML文件中获得的,具体的方法大家可以去参考livedoc的相关内容,不过如果一个项目中有成百上千个Label需要进行本地化工作,通过ActionScript一个个的写,这也是一个大工程。

值得庆幸的,在ActionScript 3中我们不再需要mx.lang.Locale,因为Adobe已经把它取消了,采用ResourceBundle类,像JAVA借鉴的。不幸的是,Flex本身并不支持动态加载并显示语言文件,帮助原文中写道“The localization feature currently supports static inclusion of localized resources and not dynamic retrieval of resources at run time.”。关于如何使用ResourceBundle类以及如何利用.properties文件编译多语言文件,请参阅帮助和这篇文章(他也将要写一篇如何从XML加载并显示语言文本,应该会比我的这篇更有价值,我本来也打算看看他的,等了快一个月了,还不出来,希望不是太监文学)。

为了克服刚才提到ActionScript 2中的那样的问题,最好的解决方法是什么?是绑定。绑定可是一个好东西,可以让你省却很多代码,繁琐的操作。将文本与Label的text属性绑定,这样当你选择其他语言时,就可以自动修改内容,而不需要你去特别编写什么了。

源代码:MultiLanguage.zip

好了,来看代码吧。
首先是XML语言文件,很简单,在这里我做了两个文件,一个中文的zh-cn.xml,一个英文的en-us.xml。
locale/zh-cn.xml
  1. <locale>
  2. <phrase id="title" value="标题" />
  3. <phrase id="content" value="内容" />
  4. </locale>
locale/en-us.xml
  1. <locale>
  2. <phrase id="title" value="Title" />
  3. <phrase id="content" value="Content" />
  4. </locale>
每一个phrase就是一个条目,依次排下去就可以了。然后就是这个自定义的Locale类了。在这个类中,主要用到了以下知识,绑定元标记,XML读取及操作,鼠标指针,以及静态变量及方法。
Locale.as
  1. package
  2. {
  3. import flash.net.URLRequest;
  4. import flash.net.URLLoader;
  5. import mx.managers.CursorManager;
  6.  
  7. public class Locale
  8. {
  9. private static var_phrase:Object;
  10. public static function get phrase():Object
  11. {
  12. return _phrase;
  13. }
  14. [Bindable]
  15. public function get phrase():Object
  16. {
  17. return _phrase;
  18. }
  19. private function set phrase(p:Object):void
  20. {
  21. phrase = p;
  22. }
  23.  
  24. public function getLanguage(lang:String = 'zh-cn'):void
  25. {
  26. var xmlURL:String = "locale/" + lang + ".xml";
  27. var xmlRequest:URLRequest = new URLRequest(xmlURL);
  28. var xmlLoader:URLLoader = new URLLoader(xmlRequest);
  29. xmlLoader.addEventListener(Event.COMPLETE,
  30. loadedHandler);
  31. mx.managers.CursorManager.setBusyCursor();
  32. }
  33. private function loadedHandler(event:Event):void
  34. {
  35. var xml:XML = new XML(event.target.data);
  36. var p:Object = new Object();
  37. for each (var pxml:XML in xml.children())
  38. {
  39. p[pxml.@id] = pxml.@value;
  40. }
  41. phrase = p;
  42. CursorManager.removeBusyCursor();
  43. }
  44. }
  45. }
使用时,主程序的代码
LocaleDemo.mxml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  3. creationComplete="initApp();" xmlns:ns1="*">
  4. <mx:Script>
  5. <![CDATA[
  6. [Bindable]
  7. public var locale:Locale;
  8. private function initApp():void
  9. {
  10. locale = new Locale();
  11. locale.getLanguage();
  12. }
  13. ]]>
  14. </mx:Script>
  15. <mx:Label width="50%" height="50%" x="208" id="label1" y="25"
  16. text="{locale.phrase.title}"/>
  17. <mx:Label width="50%" height="50%" x="208" id="label2" y="55"
  18. text="{locale.phrase.content}"/>
  19. <mx:Button label="change"
  20. click="locale.getLanguage('en-us');" />
  21. </mx:Application>
代码其实很简单,但是为了让它更好工作,我其实尝试了很多的方法,首先我考虑,这个Locale应该是一个静态的对象,因为当你在整个应用程序里去布置语言字符串时,不应该使用类的实例,而是直接去调用类的静态方法,但是Flex跟我说声对不起,bindable isn’t support static。结果到最后,我不得不去使用一个折中的方法,为它准备了两套输出,一个静态的,用在ActionScript脚本中,这样写起来也短些;另一个就是用于绑定的,当你在一个自定义组件内使用时需要去调用application下的locale对象,不要去尝试在自定义组件中建立一个对locale的弱引用对象,没用的,弱引用之后以不支持绑定,必须写全,后面在具体说明。
  1. <mx:Lable text=”{Application.application.locale.somePhrase}” />
Flex调用文件需要使用flash.net.URLLoader和flash.net.URLRequest类,就像上面代码中getLanguage(lang:String)所写的
  1. var xmlURL:String = "locale/" + lang + ".xml";
  2. var xmlRequest:URLRequest = new URLRequest(xmlURL);
  3. var xmlLoader:URLLoader = new URLLoader(xmlRequest);
loader加载完毕后,文件内容会储存在loader.data中,等加载后就通过loadedHandler函数来继续处理。
  1. xmlLoader.addEventListener(Event.COMPLETE, loadedHandler);
  2. mx.managers.CursorManager.setBusyCursor();
这里,将鼠标变成忙的标识,告诉用户,程序正在工作。CursorManager一般常用的就是setBusyCursor()和removeBusyCursor(),当然也还有其他的功能,比如将鼠标换成其他图标等等,这个很简单,帮助里介绍的很详细了。
大家应该都很清楚Flex框架是事件驱动的异步传输结构,因此,当你写完new URLLoader(xmlRequest)之后,程序不可能马上得到数据,因此,如果你在主程序的initApp()函数中调用locale.getLanguage();之后马上去trace(locale.phrase.title);,那是什么也不会出现的,必须等加载完成之后,phrase中才会有数据,也才能去显示。这样就显示出绑定的好处了,当phrase改变时,由于它是可以绑定对象,它会dispatch一个propertyChanged的事件,然后,各种它绑定到组件就会更新它们的内容,多省事。就是主程序中的加粗部分。
  1. <mx:Label id="label1" text="{locale.phrase.title}"/>
  2. <mx:Label id="label2" text="{locale.phrase.content}"/>
当再次调用getLanguage()时,绑定的text也会自动更新内容,这样就成英语了。
还有一块源代码没说呢,就是Locale类中的loadedHandler。
  1. private function loadedHandler(event:Event):void
  2. {
  3. var xml:XML = new XML(event.target.data);
  4. var p:Object = new Object();
  5. for each (var pxml:XML in xml.children())
  6. {
  7. p[pxml.@id] = pxml.@value;
  8. }
  9. phrase = p;
  10. CursorManager.removeBusyCursor();
  11. }
这里首先将loader的数据由String转变为XML,然后利用for each,把这些语句条目存到一个对象里。
Flex的对象操作是非常灵活的。Object本身是一个动态类,也就是说,你可以声明对象是决定它的属性,也可以在声明后随时在这个对象的实例中添加属性。例如:
  1. var o:Object = new Object();
  2. o.prop1 = "string";
  3. o.prop2 = {label:'Label', value:'Value'};
  4. o.prop3 = 4;
  5. o.prop4 = new Array(1, 2, 3, 4);
除了这样添加属性之外,你还可以使用[]来添加属性,就像这样:
  1. var o:Object = new Object();
  2. o['prop1'] = "string";
  3. o['prop2'] = {label:'Label', value:'Value'};
  4. o['prop3'] = 4;
  5. o['prop4'] = new Array(1, 2, 3, 4);
而我们程序中所使用的就是这个方法,关于XML操作中“@”运算符,大家可以去看其他的文章,讲的都很详细。
  1. p[pxml.@id] = pxml.@value;
我想,现在你对这个程序的流程很了解了吧,很简单吧。下面我们继续尝试这个类去进行多语言开发。我刚刚提到,如果你自定义了一个组件,而不是在主程序Application中使用locale怎么办?很简单,就像这样:
Comp.mxml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
  3. <mx:Label id="label"
  4. text="{Application.application.locale.phrase.title}" />
  5. <mx:Button id="button" label="修改"
  6. click="clickHandler(event)" />
  7. <mx:Script>
  8. <![CDATA[
  9. import mx.core.Application;
  10.  
  11. public function clickHandler(e:Event):void
  12. {
  13. Label.text = Locale.phrase.content;
  14. }
  15. ]]>
  16. </mx:Script>
  17. </mx:VBox>

你在一个Script中打算修改Label.text的话,就可以使用Locale的静态方法,这样可以少打些字,还需要强调的是,你需要确保这时xml文件已经处理完毕。不过,一般情况下,运行这种事件中的代码时,肯定都已经把xml读进来了。

最后是修改后的主程式

LocaleDemo.mxml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  3. creationComplete="initApp();" xmlns:ns1="*">
  4. <mx:Script>
  5. <![CDATA[
  6. import mx.utils.ObjectUtil;
  7. [Bindable]
  8. public var lang:Array = new Array(
  9. {label:'中文',data:'zh-cn'},
  10. {label:'English',data:'en-us'});
  11. [Bindable]
  12. public var locale:Locale;
  13. private function initApp():void
  14. {
  15. locale = new Locale();
  16. locale.getLanguage();
  17. }
  18. ]]>
  19. </mx:Script>
  20. <mx:Label id="label1" text="{locale.phrase.title}"/>
  21. <mx:Label id="label2" text="{locale.phrase.content}"/>
  22. <ns1:Comp></ns1:Comp>
  23. <mx:ComboBox dataProvider="{lang}" id="cb"
  24. change="locale.getLanguage(cb.selectedItem.data)" />
  25. </mx:Application>

基本上就是这些了,在大家射箭矢之前,我先说下现在的问题,第一这只是一个Demo,没有丝毫容错性,比如,如果我尝试使用xml中没有的一个条目,那么它会在程序中留下空白,而正常情况下应该用那个id来代替字符串,还有,如果找不到xml文件呢。这些我都没有在这来写,如果把这些写上,我想大家会晕掉的。
更完备的代码等着大家了。

最新回复

发表评论

 authimage
 
Accessible and Valid XHTML 1.0 Strict and CSS
Powered by LifeType - Design by BalearWeb