Home > Adobe Flex > Adobe Flex 3: How to create localized UI component / control

Adobe Flex 3: How to create localized UI component / control

August 12th, 2009 Leave a comment Go to comments

In a previous post, I wrote an article describing some of the localization weaknesses in Flex 3.

–>  Adobe Flex 3.x Localization / Internationalization (i18n) Weaknesses

One of issues outlined in that post is that the Flex UI components are not localization aware, or more simply put, the UI components do not have any built in support for resource localization.   Below we will recap the current method you use to implement localization strings and then a proposed alternative embedding localization handling directly into the UI controls.

The current de-facto methods used to render localized text for each control include either a compiler directive or run-time code for each property you want localized.

In this example the compiler directive @Resource() is used to define the resource string for the text property:

<mx:Label text="@Resource(bundle='myResources', key='text')"/>


To support dynamic locale changes, a runtime binding expression accessing the ResourceManager class is used instead of the compiler directive: property:

<mx:Label text="{resourceManager.getString('myResources', 'text')}"/>


The two implementations above are perfectly functional, but very cumbersome to implement when localizing a large Flex application.


Why not embed the resource localization logic directly into each UIComponent and expose resourceName and resourceBundle as design time properties?

flex-resource-design


Well, that exactly what I did!  I created wrapper controls derived from the mx controls that add the resourceBundle and resourceName design time properties.



See the screenshot here where these properties are accessible in the FlexBuilder designer – control properties tool window.


With this concept (borrowed from ASP.NET) a resource string naming convention is also needed to actually provide localized strings for each string property of the control.  For example  with a Label control you may want to provide localized strings for the Label.text property and Label.toolTip property.  Since this implementation only exposes a single resourceName property for the control I adopted a convention that allows me to defined the resource strings in the resource property files with a dot property name appended to the control resourceName.

myLabel.text=This is the label text
myLabel.toolTip=This is the label toolTip


Note, the resource strings above are named with the control’s resourceName and then have “.text” and “.toolTip” appended.  This convention enables me to define a single resourceName on each control and gives me the flexibility to define multiple resource strings for the individual control properties.   The convention outlined here can greatly simplify the localization burden on the Flex developer in large Flex applications.

 

A complete detailed example and source code is available below.  
… However we can walk through the basic  logistics here:

First, create a UI control class that extends an existing UI Control such as ‘Label’
Then add private class member variables for a  resource name string and resource bundle string.

public class Label extends mx.controls.Label
{
   // resource localization variables
   private var _resourceName:String;
   private var _resourceBundle:String;

Add a class constructor to register two event listeners.
The first event listener listens for when the UI components is ADDED to the layout/stage.
The second event listener is on the resourceManager and listens for changes to the resourceManager.
Both of these event listeners define the same callback handler method.
public function Label()
{
   super();
   // register event listener to handle resource localization when object is added
   addEventListener(Event.ADDED,resources_LocalizationHandler);
   // register as a weak listener for 'change' events from the ResourceManager.
   // this is to capture runtime resource manager changes of locale
   resourceManager.addEventListener(Event.CHANGE, resources_LocalizationHandler, false, 0, true);
}

Next, we need to add public property getters and setters to expose the ‘resourceBundle’ and ‘resourceName’ component properties.  Note the ‘Inspectable’ annotation/metadata, this allows these properties to be exposed in the Flex 3 GUI Designer properties window.
[Inspectable(category="Localization")]
public function get resourceBundle():String
{
   return _resourceBundle;
}
 
public function set resourceBundle(value:String):void
{
   // set new resource bundle
   if (_resourceBundle != value)
   {
      _resourceBundle = value;
   }
}
 
[Inspectable(category="Localization")]
public function get resourceName():String
{
   return _resourceName;
}
 
public function set resourceName(value:String):void
{
   // set new resource name
   if (_resourceName != value)
   {
      _resourceName = value;
   }
}


The last step is to add the event handler method registered in the two event listeners defined earlier.
Here is where we will implement the actual resource lookup and replacement for the control’s properties.

private function resources_LocalizationHandler(event:Event):void
{
  // ensure the resource bundle and resource name properties are not empty
  if(resourceBundle != null && resourceName != null)
  {
    // get the localized resource strings for all localized properties
    var localizedText:String = resourceManager.getString(resourceBundle, resourceName + ".text");
    var localizedToolTip:String = resourceManager.getString(resourceBundle, resourceName + ".toolTip");
 
    // apply the localized resource string for 'text' property
    if(localizedText != null && localizedText != '')
       this.text = localizedText;
 
    // apply the localized resource string for 'toolTip' property
    if(localizedToolTip != null && localizedToolTip != '')
       this.toolTip = localizedToolTip;
  }
}

This type of implementation is highly dependent on establishing resource naming conventions for each control’s set of properties that you wish to localize, but it greatly simplifies the resource localization implementation of proliferated controls throughout a large Flex application.

Sample Application Links

Categories: Adobe Flex
  1. Gautam Dhar
    August 23rd, 2009 at 22:04 | #1

    The content proposed an excellent solution. With a wrapper class.

    How to handle a string which need unicode treatment in Flex,
    At present in Flex SDK there is no API where I can specify the local and string to display it in UI, we need to call resource manager to handle it.

    My problem:

    Flex Compiler verson : flex_sdk_3.3.0.4852_mpl

    In Flex Application I am receiving Unicode result from back end ( web service) an displaying it in Flex Label component

    Storing event.result in to a string Eg. Response string = [\u8868\u7af9\uff5eFulfillment~~~~ja]

    I am binding response to label component When I am displaying it in label its not showing me Japanese character .

    Note : Storing this information in properties file works
    Eg.
    Result.properties
    Res=[\u8868\u7af9\uff5eFulfillment~~~~ja]

    In side flex component

    1. define a metadata tag for Result.properties file
    2. call it in Label component as resourceManager.getString(‘Result’,,’Res’)

    This show the japaness data in UI
    Result ==[\u8868\u7af9\uff5eFulfillment~~~~ja]
    Need API to support for unicode string ; eg : resourceManager.getString(Result,”ja”);
    Presently there is no api to display unicode string in UI

    Actual Results:
    [\u8868\u7af9\uff5eFulfillment~~~~ja]

    Expected Results:
    [表竹~Fulfillment~~~~ja]

    Please let me know how to over come this problem

  2. August 24th, 2009 at 13:02 | #2

    Not sure I fully understand what you are asking. I did however update the sample here to include Japanese localized resources. No special method or utilities are needed to work with Unicode, the string type natively supports Unicode. All I did was add a “ja_JP” resource property file, set the file encoding to UTF-8 and entered the Japanese symbols for each resource key (I used Google translate to get Japanese symbols)

    Note, Flex 3 does not support Right-To-Left languages out of the box. For RTL languages, you will need to utilize the Text rendering engine in Flash Player 10. Use Flex 4 or use the Text Layout Framework in Flex 3. http://labs.adobe.com/technologies/textlayout

  3. Nick
    September 21st, 2009 at 12:07 | #3

    Hi Robert,

    A nice solution, I’ve found however that any bindable properties propogated through a repeater seem to return null to the label and button classes. e.g.

    It’s been quite frustrating as to why, a nod in the right direction would be appreciated.

    Also what attribution are you happy with, is your name in the header of the classes sufficient?

    Cheers

  4. Manu
    October 23rd, 2009 at 13:55 | #4

    Nice writeup!

    I thinks the standard way to localize an application using “mxmlc” to create the bundles is not a good solution for many usage scenarios. With that model, the product has to be shipped with swf’s embedded with resource bundles or swf’s that load compiled resource “modules”. Both of which require a mxmlc compilation step…

    What is you want to do things without going through the compilation step? That can be done by dynamically creating a Resource Bundle and adding resources using action script code shown below

    var moreResources:ResourceBundle = new ResourceBundle(“fr_FR”, “moreResources”);
    moreResources.content["OPEN"] = “Ouvrez”;
    moreResources.content["CLOSE"] = “Fermez”;
    resourceManager.addResourceBundle(moreResources);
    resourceManager.localeChain = [ "fr_FR" ];
    myLabel.text = resourceManager.getString(“moreResources”, “OPEN”);

    That solves part of the problem but in my case the perfect solution would be as follows
    1. Use your technique to create custom components, trap appropriate events, handle label assignments etc
    2. Create Resource bundles for certain parts of the application and embed it in the swf.
    3. Create Resource bundles dynamically (using code snippet shown above) and
    4. Hydrate the dynamic resource bundles by reading data from a “properties” file using a URLLoader. This data can be changed on the fly without recompiling the application.

    Essentially there is a static resource bundle and a dynamic resource bundle. The dynamic one can be changed by “field engineers” and applied to the application with a re-compile. Additional logic is required in the component to figure out whether to either give precedence to data in the static or dynamic bundle.

  5. Manu
    October 23rd, 2009 at 13:59 | #5

    The dynamic one can be changed by “field engineers” and applied to the application with a re-compile.
    SHOULD READ
    The dynamic one can be changed by “field engineers” and applied to the application WITHOUT a re-compile.

  6. October 23rd, 2009 at 15:29 | #6

    Hi Nick, sorry for the very delayed reply…

    Not sure on the repeater situation, I guess I have not run into that one yet.
    As for attribution, my name and a link to this page are perfectly sufficient!

    Thank You!

  7. October 23rd, 2009 at 17:01 | #7

    @Manu

    Hi Manu,

    Thanks for the comments. I have run into the very situation that you are talking about where you want to create a set up resources that can easily be edited in the field without requiring recompiling a SWF.
    Take a look at this article I just posted, this includes the ground work for loading resource strings from a XML file using the URLLoader and then creating the Flex resource bundles at runtime. This sample may get you a head start if you have not already implemented this logic in your codebase.

    http://www.savage7.com/index.php/2009/10/adobe-flex-3-load-localization-resources-at-runtime-using-xml/

    Enjoy!
    – Robert

  8. October 23rd, 2009 at 17:18 | #8

    @Manu

    If you are wanting to embed a default set of resources in the compiled SWF and then also expose a means for user customizable/overridable resource definitions, one idea that comes to mind is that you could use the localeChain to do this. For example you could define a locale chain of (‘en_US_custom”, ‘en_US’, ‘en_custom’, ‘en’) where both ‘en_US’ & ‘en’ are embedded and ‘en_US_custom” & ‘en_custom’ are loaded at runtime from XML. This would allow the built-in resource manager to deal with all the resource resolution across all sets of resources.

    Or maybe a better option after looking at my XMLResourceLoader class is just to embed the default resource bundles and then allow the XMLResourceLoader class to load the chained XML files. It looks like I wrote this to handle adding/updating resource keys in existing resource bundles that may have already been loaded. It first tries to look up an existing bundle by name and then updates the resource entries. If no bundle by that name exists, it then creates a new resource bundle.

    – Good Luck!

  9. Tom Gruszowski
    February 23rd, 2010 at 10:52 | #9

    I found that addEventListener(Event.ADDED,resources_LocalizationHandler); is terribly inefficient. Breakpointing this handler I saw it getting hit 5-7 times. So instead I added the initial seed of .text/.toolTip value to commitProperties

    So my setters are:

    public function set resourceBundle(value:String):void
    {
    // set new resource bundle
    if (_resourceBundle != value)
    {
    _resourceBundle = value;
    _resourceBundleChanged = true;
    invalidateProperties();
    }
    }

    And

    override protected function commitProperties():void
    {
    super.commitProperties();

    if(_resourceNameChanged && resourceBundle)
    {
    resourcesLocalizationHandler();
    }
    }

  10. April 8th, 2010 at 23:29 | #10

    @Tom Gruszowski
    Hi Tom,
    Thanks for pointing that out!
    I need to go back and review some of my implementation of this code for this optimization.

  1. No trackbacks yet.