Dealing with localized applications in Flex 2
When talking about application localization, we should distinct two kinds of data :
1. Remote data that come, say, from a database, is dynamic by nature, and should then be localized via your application server logic.
2. User Interface data, however, is very different. Most of the time, it is set statically during development. It is spread all over the application, making it quite difficult to treat as normal "business" data.
Flex 2 deals with application UI localisation via static BundleRessources. There are several limits to this approach.
First, the nature of the translation data : ressource bundle files are (as far as I know) the way java deals with application localization. While this is good, as it so can be considered as a standard, it's definitely not the unique standard.
There are many other options when choosing translation file formats, the first of which being XLIFF.
http://docs.oasis-open.org/xliff/xliff-core/xliff-core.html
All in all, it would be better if Flex application developers could have the choice over what kind of file format to use.
The second limit is the fact that Flex 2 bundle ressources are intended to be static. It's supposed to be compiled with your application, hence forbidding you to dynamically load them at run time.
We should mention that there are some workaround to this :
http://mannu.livejournal.com/300260.html
At its best, a localisation strategy should allow you to create only one application that would be able to not only load the desired localized data at start up, but also to change the current language at runtime.
Please note that Flex 3 has partially adressed this problem. Eric Feminella has created an API, which you can check here:
http://www.ericfeminella.com/blog/2007/09/01/flex-3-resourceinspector/
While there is definetly no "best way" to achieve this, we can imagine what a simple implementation could look like.
In this example, all translation data will be stored in a single XML file. Yet, we could easily change this and use multiple translation files (ie one per language).
This is what my data looks like :
<?xml version="1.0" encoding="utf-8"?> <lang> <main> <item id="language"> <en>english</en> <fr>french</fr> </item> </main> <navigation> <item id="saveBtn"> <fr>Enregistrer</fr> <en>Save</en> </item> <item id="prodTitle"> <fr>Produit</fr> <en>Product</en> </item> </navigation> </lang>
What we'll need then is an object that is capable of setting the current language, storing the translated data and spread this data all over our application.
This object will have a method that is able to return a localized string when provided with the adequate item ID.
Since we want all localizable strings to be set dynamically at runtime, we're going to use dataBinding for each and every one of them. This means that our views will have to store a reference to the Localizer object.
But what if our translation file format were to change later in our development process? We would have to re-write the Localizer so that its getString() method can perform the requested parsing. But all our views have a reference to the Localizer, and we definetly don't want to have to re-code every single view in our application.
In a word, to provide greater flexibility to our application, we need to de-couple the Localizer and its users. The best way to achieve this is to use an Interface that our Localizer will have to implement.
package com.dehats.localize { import flash.events.IEventDispatcher; /* Interface to implement so that the views can access localized data */ [Event(name="change")] public interface ILocalizer extends IEventDispatcher { function get language():String; function set language(pLang:String):void; function get langXML():XML function set langXML(pXML:XML):void [Bindable("change")] function getString(pKey:String):String; } }
This way, our views will maintain a reference to the Localizer via a property which type will be that of the interface, not the Localizer.
The easiest way to have an object accessible from anywhere in our application, and in the meantime make sure that there is only one instance of this class in our application, is to create a Singleton.
This is what my Localizer class looks like :
package com.dehats.localize { import flash.events.EventDispatcher; import flash.events.Event /* Implementation of ILocalizer. Each translation file format should have its own Localizer, implementing ILocalizer. In this example, we'll get the translated data by searching the XML nodes by an attribute named id, which is our key, using E4X. */ [Event(name="change")] [Bindable] public class Localizer extends EventDispatcher implements ILocalizer { private var _language:String; private var _langXML:XML; private static var instance:Localizer; public function Localizer() { if(instance!=null) throw new Error("Localizer is a Singleton"); } public static function getInstance():Localizer { if(instance==null) instance=new Localizer(); return instance ; } /* string search method */ [Bindable("change")] public function getString(pKey:String):String { // string to display while waiting for data to load if(langXML==null) return "..."; var s:String = langXML..item.(@id==pKey)[_language]; // If there is no translation available, return the key if(s=="") return pKey ; return s; } public function get langXML():XML { return _langXML; } public function set langXML(pXML:XML):void { _langXML = pXML; dispatchEvent(new Event("change")); } public function get language():String { return _language; } public function set language(pLg:String):void { _language = pLg; dispatchEvent(new Event("change")); } } }
Now we can create our views. In this example, I have created a separate view (mxml component) so that we can see our loose coupling between it and the Localizer.
This is my main application file. It's the only view that has a reference to the Localizer class. It will pass it to its views by databinding.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:view="view.*" creationComplete="start()" width="691"> <mx:Script> <![CDATA[ import com.dehats.localize.Localizer; [Bindable] public var myLoc:Localizer = Localizer.getInstance(); private function start():void { myLoc.langXML = this.langData ; myLoc.language = "fr"; } ]]> </mx:Script> <mx:XML id="langData" source="locale/lang.xml" /> <view:LPanel x="10" y="10" loc="{myLoc}"> </view:LPanel> <mx:Button click="myLoc.language='fr'" x="10" y="318" label="French"/> <mx:Button click="myLoc.language='es'" x="10" y="378" label="Spanish (not translated)"/> <mx:Button click="myLoc.language='en'" x="10" y="348" label="English"/> </mx:Application>
And finally, my localizable view :
<?xml version="1.0" encoding="utf-8"?> <mx:Panel title="{loc.getString('prodTitle')}" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300"> <mx:Script> <![CDATA[ import mx.utils.StringUtil; import flash.utils.getQualifiedClassName; import flash.utils.describeType; import com.dehats.localize.*; [Bindable] public var loc:ILocalizer ; ]]> </mx:Script> <mx:Button x="10" y="10" label="{loc.getString('saveBtn')}"/> </mx:Panel>
I should probably say that another approach could have been to have public properties in my views for each and every string to be translated. But, in my humble advice, it wouldn't be a great choice since we could end up with dozens of properties, hence polluting the rest of our code.
Again, I'm not saying that this is the only solution to the localization issue, nor the best. I just think it's a convenient way to put this.
Please feel free to tell me what you think about it.
Oh and here is the archive file. ;)
http://www.dehats.com/drupal/files/Localisation.zip
| Attachment | Size |
|---|---|
| Localisation.zip | 374.08 KB |


Anonymous on April 14th 2008
Vladimir (not verified) on March 09th 2009