Requirements

   
Prerequisite knowledge
To complete the steps in this article you'll need to have a comfortable level of understanding and experience with Flex development, either with Flash Builder formerly Flex Builder or with the command line using the SDK. Previous knowledge of localization is not required
 
 
User level
Intermediate
 
Required 
Flash Builder 4(Download trial)
Sample files
In Part 1 of this series, I described how to enable localization in Flex by compiling the resources directly into the application. Now, in Part 2, I'll continue where Part 1 left off and show an alternative method of localization in which you compile the resource properties files separately and load them at run time. I will also discuss the benefits and drawbacks of each method, as well as a general approach to localization for all applications.
 

 
A look back

Before moving on, here is a recap of the steps you completed for Part 1.
 
  1. Set up the project with the appropriate directory structure.
  2. Create the properties files for the locales that you wish to support.
  3. Ensure that the localization framework files are in place.
  4. Set Flex compiler options to enable localization and specify the locales that you're supporting.
  5. Start localizing!
At this point, you have successfully enabled localization in your application by creating properties files and compiling them into your application. Once compiled into your application, they are called resource bundles and are accessed by the Flex resource manager to extract localized values. In this part of the tutorial, I will explain how to remove your compiled resource bundles from your application, and instead compile them into resource modules, which are collections of resource bundles that can be loaded at run time and accessed by the resource manager.
 

 
Moving on

Part 2 of this series is a continuation of Part 1, and as such, uses the resulting project from the end of Part 1. The starter project localization-part-ii-start is actually equivalent to the end project of Part 1 localization-part-i-end . Continuing from this point, you can follow the steps in this article to externalize your properties files and load them at run time.
 
After completing the steps in Part 1, there are only four additional steps required to externalize and load your resource bundles. They are:
 
  1. Determine which required resource bundles to include into your project.
  2. Create your own resource modules, one for each locale, and replace the properties files with them.
  3. Modify your compiler options to include the appropriate resource bundles as well as to specify that you will be using resource modules exclusively.
  4. Load the resource modules at runtime within your application.

 
Determine required resource bundles

Your application will use many resource bundles during its execution. Even without application-specific localization, many core Flex framework classes rely on resource bundles to localize run-time and compile-time warnings and errors. Because of this, before you can create your own resource module which is essentially a collection of resource bundles , you must first find out which resource bundles to include. This can be done quite easily using the Flex compiler. Simply use the -resource-bundle-list compiler option when compiling your application; for example:
 
mxmlc -locale= -resource-bundle-list=used-resource-bundles.txt Main.mxml
Note: You can do this by executing mxmlc via the command line directly, or you can add this compiler option to the Additional Compiler Arguments setting in Flash Builder.
 
bundles = collections components controls core effects layout resources skins styles textLayout

 
Create your own resource module

Now that you know what resource bundles your application uses, you can compile your own resource module. To do this, you must use mxmlc via the command line. You can't compile resource modules in Flash Builder yet.
 
To compile your resource module, simply specify four compiler options:
 
  • locale—This is the locale for which you are creating the resource module.
  • source-path—The location of the properties file to use for the resource module.
  • include-resource-bundles—A list of the required resource bundles that your application uses you found this in the last step .
  • output—The output SWF file that will be generated. This is your resource module!
Note: You do not specify an MXML file to compile.
 
  1. To compile the resource module for the sample application for the locale en_US execute the following command:
mxmlc -locale=en_US -source-path=locale/{locale} -include-resource-bundles=collections,components,controls,core,effects,layout,resources,skins,styles,textLayout -output resources.swf
Note: You can name your resource modules however you like in whatever directory structure you like, as long as you load them accordingly in your application which you will see how to do in Load your resource module at runtime .
 
This will create your resource module resources.swf with the locale en_US using the properties file in locale/en_US/ and including the additional resource bundles that you identified earlier.
 
  1. Once the resources.swf file for the en_US locale is created, simply place it alongside en_US properties file in locale\en_US\.
Note: The properties file is no longer needed in the project, but it's a good idea to keep it around in case changes are required in the future.
 
  1. Repeat steps 1 and 2 for the other three locales see Figure 1 .
Note: You may see the following error in your project: Unable to resolve resource bundle 'resources'. This is because you are now referencing a resource bundle that no longer exists it has been replaced with your newly created resource module . You will fix this error in the next section when you modify your compiler options.
 

 
Modify compiler options

With your newly created resource modules in place, you can now modify your compiler options to include the appropriate resource bundles as well as to specify that you will be using resource modules exclusively.
 
  • To include the appropriate resource bundles, use the -include-resource-bundles compiler argument and specify the list of resource bundles that you generated earlier.
  • To specify that you will be using resource modules exclusively, use the -locale compiler flag followed by the equal "=" sign, and nothing else. This step is optional, but since this application will indeed be using resource modules exclusively, it's good practice to let the compiler know.
Follow these steps to set the compiler options:
 
  1. Right-click your project and select Properties.
  2. Select Flex Compiler on the left, and use the following as the Additional Compiler Arguments settings:
-locale= -source-path ./locale/{locale} -include-resource-bundles collections components controls core effects layout resources skins styles textLayout
Note: When modifying the compiler options through Flash Builder, the values are not comma-separated.
 
Note: You may receive a warning stating that the source path for your locale is a subdirectory of the project's source path. You can resolve this by allowing source-path overlap using the following compiler option: -allow-source-path-overlap=true.
 

 
Load your resource module at run time

The final step is to modify your application to load your resource modules at runtime. Previously, in Part 1, you added a change event handler on the locales ComboBox that would change the locale based on the selected language in the ComboBox. The event handler function looked like this:
 
private function comboChangeHandler():void { resourceManager.localeChain = [localeComboBox.selectedItem.locale]; }
This was possible because the required resource bundles including your own custom resources resource bundle were compiled and loaded into the application for you. As a result, your resource bundle was accessible by the resource manager immediately. Now, however, since you didn't compile the resource bundles directly into the application, they must be loaded manually. To do this, you call the ResourceManager's loadResourceModule function. Then, once the resource module has been loaded, you can change the locale by changing the resource manager's localeChain property, just as in the comboChangeHandler function above.
 
There is really only one change that needs to be made to the comboChangeHandler event handler function to achieve this, and the logic is quite simple. First, check to see if the resource module for the desired locale has been loaded yet. If not, then load it. Otherwise, change the locale as normal.
 
  1. Edit Main.mxml in Source View.
  2. Replace the existing comboChangeHandler function with the following:
import mx.events.ResourceEvent; private function comboChangeHandler():void { var newLocale:String = String(localeComboBox.selectedItem.locale); // Ensure that you are not loading the same resource module more than once. if (resourceManager.getLocales().indexOf(newLocale) != -1) { completeHandler(null); } else { // Build the file name of the resource module. var resourceModuleURL:String = "./locale/" + newLocale + "/" + "resources.swf"; var eventDispatcher:IEventDispatcher = resourceManager.loadResourceModule(resourceModuleURL); eventDispatcher.addEventListener(ResourceEvent.COMPLETE, completeHandler); } function completeHandler(event:ResourceEvent):void { resourceManager.localeChain = [localeComboBox.selectedItem.locale]; } }
The code starts with a simple if statement that checks to see if the resource module for the selected locale has already been loaded. If so, then it invokes the completeHandler function, which simply changes the locale of the resource manager using the same line of code used in the original comboChangeHandler function . If the resource has not yet been loaded, then it proceeds to load it. First, it identifies the location of the resource module, and then it calls the resourceManager.loadResourceModule function. The completeHandler function is invoked once the resource module is loaded and ready to use.
 
Note: Do not try to use the resource module immediately after calling loadResourceModule . The ResourceManager's loadResourceModule function makes calls asynchronously, and so the resource module may not yet be loaded and ready for use by the time the call is complete. Instead, always attach an event handler to listen for the ResourceEvent.COMPLETE event. Only when this event is dispatched can you be sure that your resource module is loaded and ready for use.
 
  1. Run the application.
You'll notice that when the application loads, no text is shown in the form at all.
 
As it stands now, the application will load an appropriate resource module and change locales whenever a language is selected from the combo box. However, when the application first runs, no resource modules are loaded, so no text is displayed.
 
To fix this, simply add a creationComplete event handler function to your main application that, when invoked, will initiate the loading of the default locale en_US . Since the combo box by default displays the English language, you can simply invoke the comboChangeHandler function to simulate that the language has just been selected.
 
  1. Add the following function to your Main.mxml:
private function init():void { comboChangeHandler(); }
  1. Finally, add creationComplete="init " to the main <s:Application> tag.
Once you've completed all of these changes, your main MXML file for the sample application should look like this:
 
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" creationComplete="init()"> <fx:Script> <![CDATA[ import mx.events.ResourceEvent; [Bindable] private var locales:Array = [{label:"English (United States)", locale:"en_US"}, {label:"German (Germany)", locale:"de_DE"}, {label:"French (France)", locale:"fr_FR"}, {label:"Japanese (Japan)", locale:"ja_JP"}]; private function init():void { comboChangeHandler(); } private function comboChangeHandler():void { var newLocale:String = String(localeComboBox.selectedItem.locale); // Ensure that you are not loading the same resource module more than once. if (resourceManager.getLocales().indexOf(newLocale) != -1) { completeHandler(null); } else { // Build the file name of the resource module. var resourceModuleURL:String = "./locale/" + newLocale + "/" + "resources.swf"; var eventDispatcher:IEventDispatcher = resourceManager.loadResourceModule(resourceModuleURL); eventDispatcher.addEventListener(ResourceEvent.COMPLETE, completeHandler); } function completeHandler(event:ResourceEvent):void { resourceManager.localeChain = [localeComboBox.selectedItem.locale]; } } ]]> </fx:Script> <fx:Metadata> [ResourceBundle("resources")] </fx:Metadata> <s:layout> <s:VerticalLayout horizontalAlign="center" verticalAlign="middle" /> </s:layout> <s:Panel title="{resourceManager.getString('resources','contact.title')}" color="black" borderAlpha="0.15" width="350"> <s:layout> <s:VerticalLayout horizontalAlign="center" paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10" /> </s:layout> <mx:Form width="100%" color="0x323232"> <mx:FormItem label="{resourceManager.getString('resources','contact.field.name')}"> <s:TextInput /> </mx:FormItem> <mx:FormItem label="{resourceManager.getString('resources','contact.field.streetAddress')}"> <s:TextInput /> </mx:FormItem> <mx:FormItem label="{resourceManager.getString('resources','contact.field.city')}"> <s:TextInput /> </mx:FormItem> <mx:FormItem label="{resourceManager.getString('resources','contact.field.state')}"> <s:TextInput /> </mx:FormItem> <mx:FormItem label="{resourceManager.getString('resources','contact.field.zipCode')}"> <s:TextInput /> </mx:FormItem> <mx:FormItem label="{resourceManager.getString('resources','contact.field.country')}"> <s:TextInput /> </mx:FormItem> <mx:FormItem> <s:Button label="{resourceManager.getString('resources','contact.submit')}" /> </mx:FormItem> </mx:Form> </s:Panel> <mx:Spacer height="15" /> <s:HGroup width="350" verticalAlign="middle"> <mx:Spacer width="100%" /> <mx:Image source="{resourceManager.getString('resources','contact.flagImg')}"/> <mx:ComboBox id="localeComboBox" dataProvider="{locales}" change="comboChangeHandler()"/> </s:HGroup> </s:Application>
You're done! Build your application, run it, and select different languages to see the contact form load the appropriate resource modules at run time. If you run into any problems, refer to the completed project files available in localization-part-ii-end.zip.
 

 
Benefits and drawbacks and general approach for Flex developers

You've now completed both parts of this two-part tutorial! You've covered and implemented localization with the Flex framework using two common methodologies:
 
  • Compiling the resource bundles directly into the application.
  • Compiling the resource bundles externally, and loading them into the application at runtime.
Now that you've learned them, it would be beneficial to recap the benefits and drawbacks of each approach so that you can use the right technique when building localization into your next application.
 
Table 1. Pros and cons of compiling directly.
 
Pros
 
Cons
 
  • Definitely the easier and quicker approach.
  • Properties files are easy to read and modify.
  • No increased load-time added to the application to load resource modules.
  • The properties files are turned into resource bundles which are then compiled directly into the application. Depending on the number and size of each properties file, this can have a noticeable effect on the size of your application's deliverable SWF.
  • Any change to the properties files requires a complete recompilation of your entire project.
Table 2. Pros and cons of compiling externally.
 
Pros
 
Cons
 
  • Since your resource bundles are compiled into resource modules separately from you application, they have no effect on the size of your application's main SWF.
  • You can modify your localization strings without having to recompile your entire project.
  • If your application is set up to read the available locales dynamically, then you can actually add new locales at any time.
 
 
  • This requires a more involved process for generating and loading resource modules.
  • Modifying a localization string requires a recompilation of the resource module.
  • Loading the resource modules at runtime, initially, will take longer than if they were compiled into the application. Also, depending on how your application is set up, this may also require additional network calls. It is worthwhile to test out this scenario to see what kind of improvement, if any, your application sees at startup.
You might be wondering what approach you should take with your applications. In general, the use of compile-time resources is most suited for applications with a limited number of locales to support. Larger applications and applications that support many locales with a large number of localization strings would benefit more from the use of resource modules.
 
However, this doesn't mean that you must take only one approach. A hybrid approach can be very useful for many applications. For instance, you may want to use compile-time resources for your default or most commonly used locales, say English and French. For the rest, you can use resource modules and load them at run time. This would give you quick access for your common two locales, as well as support for many other locales via resource modules without having to increase the size of your application SWF.
 

 
Where to go from here

Localization is an important feature for any application with a global audience. It is important to know the two approaches available and how to implement them to ensure that your application not only supports the appropriate locales, but also makes efficient use of the framework. Knowing both of these approaches, as well as their benefits and drawbacks, will help you make better-informed decisions on the most suitable approach to take with your own applications.
 
For a more thorough look at localization in Flex, including alternative methods, additional features, and common pitfalls that are beyond the scope of this article, refer to the Flex 4 Localization documentation.