Localization refers to enabling an application to display different resources at different locations. The most obvious and common example of localization is displaying text in different languages based on location or user selection. Localization is different from building entirely unique applications for each locale. Instead, the same code base is used for all locales, but different resource bundles are used for different locales. There are two basic variations on how this is implemented: compile time and runtime.
Compile-time localization means that you create a unique .swf for each locale. For example, if you want to support both French and Spanish, with compile-time localization you must create two .swf files: one for French and one for Spanish. Although with compile-time localization you create unique .swf files for each locale, each file uses the same code base. That means you don’t have to build and maintain separate application code bases. What is different between the .swf files is the resource bundle(s) that gets compiled into the .swf. We’ll talk more about resource bundles in just a minute.
Runtime localization allows you to create and deploy just one .swf containing your main application architecture. Runtime localization still uses resource bundles just like compile-time localization. However, rather than compiling the resource bundles for just one locale into the application runtime localization, necessitating different .swf files for each locale, runtime localization means one of two things: compiling in all resource bundles for all locales (allowing for changes between locales at runtime), or loading resource modules at runtime. We’ll talk more about resource modules later in this chapter when we talk about the details of loading resources at runtime.
All Flex applications use resource bundles, whether you are aware of them or not. The Flex framework has a bunch of resource bundles for things such as containers, controls, effects, skins, and styles. These resource bundles are compiled into an application by default. You can also create your own custom resource bundles to use for things such as localization. You create a resource bundle by creating a new UTF-8 encoded text file and populating it with keys and values. Each key and value is delimited by an equals sign, and each key-value pair should be on its own line in the file. The following is an example of such a file:
instruction=Select a value color=Color send=Send
The keys in this example are instruction
, color
, and send
. You probably noticed that all the values
are strings. However, you can also embed objects and classes in a
resource bundle using the Embed()
and ClassReference()
directives.
The following example not only uses string values, but also embeds an image:
instruction=Select a value color=Color send=Send flag=Embed('../../usa.jpg')
All resource bundle files should be saved using UTF-8 encoding. This is important because it allows any character in the Unicode standard to be accurately stored. That means you can store and use characters outside the 128 characters used in the US-ASCII standard, allowing for use of characters in other languages and character sets.
You must save resource bundle files using the .properties extension. For example, you can save a resource bundle file as languageResources.properties. You will almost always want to save resource bundle files maintaining parity in naming, but placing them in subdirectories each named by locale. For example, if you want to support both French and Spanish, you might create two resource bundle files, both called languageResources.properties, but place them in different subdirectories called fr_FR and es_ES, respectively. Generally, you should place all the localization resource bundle files in subdirectories of one parent directory. For example, in the preceding scenario, the paths to the files might be locale/fr_FR/languageResources.properties and locale/es_ES/languageResources.properties. You could continue this pattern for as many locales as you need to support. For example, you could support English in the United States by adding a new file called locale/en_US/languageResources.properties. Usually, each file will contain the same set of keys, but with different, locale/language-specific values. For the examples that follow in the next few sections, we’ll use the resource bundle files shown in Example 5-2, Example 5-3, and Example 5-4. For each resource bundle we’ll assume that the files are saved in subdirectories of a locale directory that is a sibling of the src directory for the Flex project.
The code in Example 5-2 is saved in locale/en_US/languageResources.properties. This also assumes that there’s an assets directory in the project root, and that the .jpg files referenced in the following code examples are stored in that assets directory.
Example 5-2. U.S. English resource bundle
instruction=Select a value color=Color send=Send flag=Embed('../../assets/usa.jpg')
The code in Example 5-3 is saved in locales/en_CA/languageResources.properties.
Example 5-3. Canadian English resource bundle
instruction=Select a value color=Colour send=Send flag=Embed('../../assets/canada.jpg')
The code in Example 5-4 is saved in locales/es_ES/languageResources.properties.
Example 5-4. Spanish (Spain) resource bundle
instruction=Seleccione un valor color=Color send=Envíe flag=Embed('../../assets/spain.jpg')
As we mentioned earlier, Flex includes a bunch of resource bundles
that it uses when compiling most Flex applications. For example, there
are bundles for controls, containers, styles, and effects. Some of these
bundles are locale-specific, and the Flex SDK has only the bundles for
the locale for which the SDK was intended. As an example, in the United States some of the Flex
resource bundles are stored in the frameworks/locale/en_US directory of the SDK
because they are specific to U.S. English. If you want to use resource
bundles other than the locales already supported by the version of the
SDK you have, you will need to create copies of the existing resource
bundles, saving them to directories within frameworks/locale with the locale names you
intend to use. For example, if you want to support Spanish (from Spain),
you'll need to make sure you have a frameworks/locale/es_ES directory in your SDK
containing all the necessary resource bundles (compiled and packaged as
.swc files). The simplest way to
accomplish this is to use the copylocale
utility provided in the bin directory of the SDK. The copylocale
utility will copy an existing locale’s resource bundles to a new
locale. The syntax is as follows:
copylocale existingLocale newLocale
For example, if you already have the en_US
locale and you want to support French,
you could run copylocale
as
follows:
copylocale en_US fr_FR
The preceding code assumes you are running the command from a command prompt and that you have added the bin directory of your SDK installation to your system’s path.
For the examples that we’ll see in the following sections, we’ll
assume that we’ve already run copylocale
as necessary to ensure that we have
the following locales in the SDK: en_US
, en_CA
, and es_ES
.
One of the many manager classes that are included in the Flex
framework is the ResourceManager
class. As the name implies, this class is responsible for
managing resources from resource bundles. In the next few sections,
we’ll see ways in which you can tell a ResourceManager
what resource bundle(s) to
use. However, first we’ll look at how to access a ResourceManager
as well as how to access the
data from resource bundles via a ResourceManager
.
The ResourceManager
class uses
the Singleton design pattern; therefore, there is just one instance per application. You
can access that instance in one of two ways:
Use the ResourceManager.getInstance()
method.
Use the resourceManager
property of a UIComponent
instance, including an Application
instance.
A ResourceManager
object
contains all the data for all the resource bundles that have been loaded
into an application. You can retrieve that data in two ways:
Use the Resource()
directive.
Use the get
methods of the
ResourceManager
instance.
The Resource()
directive
allows you to access data once, at application startup.
That means you should use the Resource()
directive only for compile-time
localization. The directive requires that you pass it two attributes:
bundle
and key
. The bundle
is the name of the resource bundle file
(minus the .properties extension)
in which the key is defined. The key
is the name of the key from the file for which you want the value. For
example, the following uses the Resource()
directive to retrieve the color
value from the languageResources
resource
bundle:
<mx:Label text="@Resource(bundle='languageResources', key='color')" />
When using the Resource()
directive for an attribute value in an MXML tag, you must precede the
directive with an @
character.
If you would like to retrieve a value from ResourceManager
using ActionScript or in a
data binding expression, you will need to use one of the following
get
methods of ResourceManager
:
getString()
, getNumber()
, getInt()
, getUint()
, getBoolean()
, getObject()
, getStringArray()
, or getClass()
. All of these methods require two
parameters: the resource bundle name and the key name. For example, the
following code retrieves the value for the color
key from the languageResources
resource bundle:
var color:uint = ResourceManager.getInstance().getInt("languageResources", "color");
As you’ve already seen, most data is stored in a .properties file as strings. The exceptions
to that are embedded assets (using the Embed()
directive) and classes (using the
ClassReference()
directive). Since there is no formal way to distinguish values as
string, number, integer, Boolean, and so on, when storing the values in
a .properties file, the
responsibility of differentiating is left to the ResourceManager
and you, the developer. You
can use the get
methods to specify
how you’d like the ResourceManager
to
coerce the value for a key. For example, getBoolean()
will attempt to coerce the value
to a Boolean value whereas getInt()
will attempt to coerce the value to an integer. You should use the
getClass()
method to retrieve all
values stored using the Embed()
or
ClassReference()
directive.
When you want to implement compile-time localization, you need to do two things:
Add the Resource()
directive or appropriate ResourceManager
get
method call at the point in the code where you need to
retrieve the value from the resource bundle.
Tell the compiler what resource bundles to include in the .swf.
The following code illustrates how an MXML document might look when using compile-time localization:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"> <mx:Form> <mx:FormHeading label="@Resource(key='instruction', bundle= 'languageResources')" /> <mx:FormItem label="@Resource(bundle='languageResources', key='color')"> <mx:ColorPicker id="color" /> </mx:FormItem> <mx:FormItem> <mx:Button label="@Resource(bundle='languageResources', key='send')" /> </mx:FormItem> </mx:Form> <mx:Image source="@Resource(bundle='languageResources', key='flag')" /> </mx:Application>
When compiling the application, you must tell it what resource
bundles to use by adding a locale
attribute to the compiler options. You also need to tell the compiler where it can find
custom resource bundle .properties
files by adding the directory for the resource bundle files to
the source path. The following code tells the compiler to compile an
application using the en_CA
resource
bundle, and it tells the compiler where it can find custom resource
bundle files:
mxmlc -locale en_CA -source-path=../locale/{locale} Example.mxml
The {locale}
in the preceding
code is a variable that tells the compiler to use the value of the
locale
attribute.
If you are using Flex Builder, you should do the following:
Runtime localization occurs in one of two ways:
Two or more resource bundles get compiled into the .swf.
No resource bundles are compiled into the .swf because they are loaded at runtime from external resource modules.
We’ll look at each option in the following sections.
You can compile two or more resource bundles into an .swf by adding a space-delimited list of
locales to the locale
compiler
attribute. For example, if you’d like to compile the en_US
, en_CA
, and es_ES
resource bundles into an .swf, the compiler attributes might look
like the following:
-locale en_US en_CA es_ES -source-path=../locales/{locale}
That tells the compiler to include all the resource bundles for
the locales in the main application .swf file. Then, at runtime, you can use
ActionScript to change which locale is currently being used. The
localeChain
property of the
ResourceManager
instance controls which of the available resource bundles the
Flex application should use. The localeChain
property is an array of strings.
Therefore, if you want to use the en_US
resource bundles, you should set the
localeChain
property as
follows:
resourceManager.localeChain = ["en_US"];
If you specify more than one locale in the localeChain
array, any key-value pairs not
found in the first locale will be retrieved from the next
locale.
When you allow for runtime selection of locales, you cannot use
the Resource()
directive to retrieve resource bundle values. You must use only
the get
methods of the ResourceManager
instance. Because of this, the Flex application must also specify
one more piece of information via a metadata tag. That piece of data is the name of the resource bundle(s)
to use. The ResourceBundle
metadata
tag will tell the compiler to include a particular custom resource
bundle.
The following is an example of such a metadata tag in an MXML document:
<mx:Metadata> [ResourceBundle("languageResources")] </mx:Metadata>
The following code illustrates how you can build an application that allows the user to select a locale at runtime. In Example 5-5, the user selects a locale and the language of the application changes.
Example 5-5. Selecting a locale at runtime
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"> <mx:Metadata> [ResourceBundle("languageResources")] </mx:Metadata> <mx:Script> <![CDATA[ private function selectLanguage():void { resourceManager.localeChain = [language.value]; } ]]> </mx:Script> <mx:ComboBox id="language" change="selectLanguage();"> <mx:dataProvider> <mx:ArrayCollection> <mx:Object label="English (US)" data="en_US" /> <mx:Object label="English (Canada)" data="en_CA" /> <mx:Object label="Espanol (Espana)" data="es_ES" /> </mx:ArrayCollection> </mx:dataProvider> </mx:ComboBox> <mx:Form> <mx:FormHeading label="{resourceManager.getString('languageResources', 'instruction')}" /> <mx:FormItem label="{resourceManager.getString('languageResources', 'color')}"> <mx:ColorPicker id="color" /> </mx:FormItem> <mx:FormItem> <mx:Button label="{resourceManager.getString('languageResources', 'send')}" /> </mx:FormItem> </mx:Form> <mx:Image source="{resourceManager.getClass('languageResources', 'flag')}" /> </mx:Application>
Resource modules are resource bundles compiled into .swf files that are external to the main application .swf file. Resource modules allow you to build applications that can load resources at runtime. This allows users to change the locale at runtime, just as we saw in the preceding section. Resource modules are ideal when the number and size of the resource bundles are significant enough that compiling them all into the main .swf file would unnecessarily increase the main .swf file size beyond acceptable limits. Resource modules allow you to keep the main .swf file relatively small. Resource modules are loaded only when requested, as we’ll see shortly.
Resource modules include not only custom resource bundles, but
all the framework resource bundles for a locale as well. That means
that to create a resource module you must know which resource bundles
the application is using. To determine that, run mxmlc
with the
locale
attribute set to an empty string using the resource-bundle-list
attribute. The resource-bundle-list
attribute specifies a
file to which the compiler will write a list of resource bundles. The
following is an example:
mxmlc -locale= -resource-bundle-list=listOfResourceBundles.txt Example.mxml
The preceding line of code will write to the specified file (e.g., listOfResourceBundles.txt) a space-delimited list of resource bundles, such as the following:
bundles = collections containers controls core effects languageResources skins styles
Next, you can use that list of resource bundles to compile the
resource module using mxmlc
. The
syntax is as follows:
mxmlc -locale=locale
-source-path=locale/{locale} -include-resource- bundles=commaDelimitedListOfResourceBundles
-outputresourceModuleName
.swf
The following is an example:
mxmlc -locale=en_US -source-path=locale/{locale} -include-resource-bundles=languageResources,collections,containers, controls,core,effects,skins,styles -output en_US_ResourceModule.swf
You must create the resource modules for each locale. Then, in
the application, you can use the loadResourceModule()
method of ResourceManager
to load a resource
module. The loadResourceModule()
method returns an
instance of an event dispatcher object that you can use to register a
listener for a complete event to receive notification when the
resource module has loaded. At that point, you can set the localeChain
property.
When you compile an application using resource modules, you have available two basic strategies:
You can compile with locale
set to an empty string to compile
no resource bundles into the application.
You can compile with locale
set to a default locale. The
resource bundles for that locale will get compiled into the
application, and all other locales can be loaded at runtime using
resource modules.
If you're using the first strategy, consider that no resource
bundles will be available by default on application initialization.
That can cause errors if your application depends on resource bundles
on startup. If you’d like to load a default module automatically on
startup, you can specify that via the FlashVars variables localeChain
and resourceModuleURLs
. The following FlashVars value will tell the Flex
application to load a resource module named en_US_ResourceModule.swf and set the
localeChain
to en_US
:
localeChain=en_US&resourceModuleURLs=en_US_ResourceModule.swf
If you’re not familiar with FlashVars, you can read more about it in Chapter 20.
The following example is an application that loads resource modules at runtime when the user selects a locale from a combo box. The example assumes that the default locale and resource module are specified via FlashVars.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"> <mx:Metadata> [ResourceBundle("languageResources")] </mx:Metadata> <mx:Script> <![CDATA[ import mx.events.ResourceEvent; private var _selectedResourceName:String; private function changeResourceHandler(resourceName:String):void { _selectedResourceName = resourceName; var eventDispatcher:IEventDispatcher = resourceManager. loadResourceModule(resourceName + "_ResourceModule.swf"); eventDispatcher.addEventListener(ResourceEvent.COMPLETE, languageModuleLoadedHandler); } private function languageModuleLoadedHandler(event:Event):void { resourceManager.localeChain = [_selectedResourceName]; } private function selectLanguage():void { changeResourceHandler(language.value as String); } ]]> </mx:Script> <mx:ComboBox id="language" change="selectLanguage();"> <mx:dataProvider> <mx:ArrayCollection> <mx:Object label="English (US)" data="en_US" /> <mx:Object label="English (Canada)" data="en_CA" /> <mx:Object label="Espanol (Espana)" data="es_ES" /> </mx:ArrayCollection> </mx:dataProvider> </mx:ComboBox> <mx:Form> <mx:FormHeading label="{resourceManager.getString('languageResources', 'instruction')}" /> <mx:FormItem label="{resourceManager.getString('resources', 'color')}"> <mx:ColorPicker id="color" /> </mx:FormItem> <mx:FormItem> <mx:Button label="{resourceManager.getString('languageResources', 'send')}" /> </mx:FormItem> </mx:Form> <mx:Image source="{resourceManager.getClass('languageResources', 'flag')}" width="60" height="40" /> </mx:Application>