Chris Cai's Tech Blog

博客园 首页 新随笔 联系 订阅 管理

Flex 3:Feature Introductions: Runtime Localization

From Adobe Labs (http://labs.adobe.com/wiki/index.php/Flex_3:Feature_Introductions:_Runtime_Localization)

Runtime Localization

Gordon Smith
September 25. 2007

Table of contents [hide]

Introduction

In Flex 2, the localized resources for an application had to be linked into the application SWF. Also, the resources could only be for a single locale, so if you wanted to localize for multiple locales you had to build multiple application SWFs.

With Flex 3, localization support will become much more Flexible! Here’s how:

  • You can put your resources into resource modules -- similar to style modules -- rather than into the application itself. You can preload the module for an appropriate locale when the application starts, and you can load modules for additional locales later.

  • You can compile resources for multiple locales into a single application or module.

  • You access all resources through a new ResourceManager, which can manage resources for multiple locales. You can change at runtime which locales get searched for resources.

  • Binding expressions that reference resources via the new ResourceManager will automatically update if the locale changes. Components are notified when the locale changes so that they can update themselves appropriately.

  • You can also use images, sounds, etc. as resources rather than just strings.

  • You can programatically create your own resources at runtime and use them just like ones that were compiled from .properties files. So you can create resources at runtime from downloaded XML files, database results, etc.

Sounds cool. Can I see a demo?

To see what you’ll be able to do with runtime localization, take a look at the FlightReservation1 and FlightReservation2 demo applications. They accomplish the same thing, but they use two different localization strategies. Both versions have been localized for U.S. English (the en_US locale) and for Japanese (the ja_JP locale). They are provided as Flex Builder 3 projects which you can import into a Flex Builder workspace. Choose File > Import, select General > Existing Projects into Workspace, and browse for each project. Please read each project's README.txt file for more information. If you simply copy files into a new Flex Builder project rather than importing the projects, be sure to follow the instructions in these files or you may get compilation or runtime errors.

In FlightReservation1, both the en_US and the ja_JP resources are compiled into the application SWF. The first time that the application is launched, the en_US resources will be used by default. A Language combobox allows the user to select Japanese, at which point the application tells the ResourceManager to use the ja_JP resources instead of the en_US ones. As a result, the user interface switches — on the fly! — to Japanese. The application also saves the user’s locale choice in a local SharedObject. The next time the application starts, it reads this saved locale and uses its resources by default. So if you switch to Japanese and quit, this application will restart in Japanese.

In FlightReservation2, the application has no resources inside it. Instead they are compiled into two separate resource module SWFs, Resources_en_US.swf and Resources_ja_JP.swf. (Note: You cannot create resource modules in Flex Builder 3; you must use command-line tools to do so.) The HTML template (html-template/index.template.html) has been customized to preload the English resource module by specifying properties named resourceModuleURLs and localeChain in the FlashVars. Make sure that you use this template rather than Flex Builder’s default one. (In general, server-side logic could decide which locale to initially load, and generate the appropriate HTML wrapper.) When the user switches the locale at runtime, the application determines whether the new locale has already been loaded, and, if not, loads the appropriate resource module. So, the first time that the user chooses Japanese, the Japanese module will get loaded. But then the user can continue to switch back and forth between English and Japanese with no further module loading.

The on-the-fly locale switching in both versions is accomplished primarily by simple binding expressions which automatically update when the locale is changed. However, these applications use some other interesting localization techniques:

  • The XML data — which simulates back-end data that in the real world would come from a database — is locale-independent. For example, the “from” and “to” locations for a route are expressed as airport codes. But the user interface localizes this info into locale-dependent city names using labelFunctions, and it sorts the lists based on the localized names.

  • Dates are displayed with locale-dependent formatting.

  • The flight distance is displayed in locale-dependent units — miles for U.S. English, kilometers for Japanese — with locale-dependent formatting.

  • Prices are displayed in locale-dependent units — U.S. dollars for U.S. English, Yen for Japanese — with locale-dependent formatting (i.e., currency symbol and precision).

How about a simpler tutorial?

Sure. There is quite a bit going on in the FlightReservation demo, so I’ll let you dig into that by yourself when you’re ready. For now, lets go step-by-step through a really simple “Hello” app that we’ll localize for English and French. It will give you a good introduction to various aspects of localization in Flex 3.

Create a new Flex Project called RunLoc in Flex Builder. Enter the following code for RunLoc.mxml:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Label text="@Resource(bundle='myResources', key='GREETING')" fontSize="48"/>
</mx:Application>

Just as in Flex 2, you can use the @Resource() MXML compiler directive to specify that the Label’s text property is to be set to a localized value from a resource bundle, rather than simply writing

<mx:Label text="Hello!" fontSize="48"/> 

with the hard-coded English word “Hello!”. The bundle and key attributes inside the @Resource() directive tell the compiler that we want to use the resource named GREETING inside a resource bundle named myResources.

The simplest way to create a resource bundle is to provide a .properties file defining the keys and values in the bundle. The MXML compiler will compile a .properties file into a subclass of the mx.resources.ResourceBundle class. So, create a folder named locale inside the project (as a sibling of the src folder) and, inside locale, create a folder named en_US for our U.S. English resources. Inside the en_US folder, create a file called myResources.properties. Open the Properties dialog for the myResources.properties file and set its "Text file encoding" to UTF-8. (If this causes a warning, ignore it.) The MXML compiler expects all .properties files to have UTF-8 encoding. Now edit the file and put one English resource in it:

GREETING=Hello!

Now open the project’s Properties dialog, select the Flex Compiler pane, and enter the following as “Additional compiler arguments”:

-locale=en_US -source-path=../locale/{locale}

If you now compile and run the application, you should see “Hello!” displayed.

What are all those compilation options?

I knew you’d ask.

From the @Resource() directive, the compiler knows that the application uses a resource bundle named myResources. The -source-path option tells the compiler where to look for additional source files such myResources.properties. The locale, en_US, specified by the -locale option is automatically subsituted for the special {locale} token in -source-path. When we change the locale, we won’t have to also change the source path. You need the .. in the source path because, in Flex Builder 3 Beta 2, a relative source path is resolved relative to the src directory, not relative to the project directory. This may change before we ship.

The -locale=en_US option also tells the compiler to use the en_US version of the framework resources. What are framework resources? Well, framework components such as Label use resources, just like your application and your custom components can use resources. Label and other framework classes have been precompiled into component libraries in the frameworks/libs folder of the Flex SDK. The resources required by these classes are located in separate resource bundle libraries in the frameworks/locale folder. For example, the English resources for the frameworks/libs/framework.swc library are in the resource bundle library frameworks/locale/en_US/framework_rb.swc.

Now we’d like to localize our RunLoc app for French — by providing resources for the fr_FR locale. But first we need to deal with the fact that the Flex SDK doesn't provide French framework resources.

Huh? French framework resources?

In the past, the MXML compiler was happy as long as you provided application resources for the locale you specified with the -locale option; it would fall back and use the en_US framework resources if it didn’t find those for the locale you specified. But the Flex 3 compiler is pickier: if you specify -locale=fr_FR then it will expect to find French framework resources.

Don’t worry, though... it’s not hard to make an fr_FR framework resource library. You can just use a command-line script which will compile the en_US .properties files for the fr_FR locale!

Set the current directory to the Flex SDK that your Flex Builder project is using. By default, this will be the directory sdks/3.0.0 inside your Flex Builder installation. Now execute

bin\copylocale.exe en_US fr_FR

on Windows or

bin/copylocale.sh en_US fr_FR

on Macintosh. Check to make sure that you now have a framework_rb.swc file inside frameworks\locale\fr_FR.

Now that we’ve made a French framework resource library, it’s really easy to localize our app for French.

Note that for some reason the data visualization src folder is missing from build 3.0.183654.
So if you try to run copylocale you will get this message:
C:\Program Files\Adobe\Flex Builder 3\sdks\3.0.0\bin>copylocale en_US fr_FR
Error: Directory "C:\Program Files\Adobe\Flex Builder 3\sdks\3.0.0\frameworks\projects\datavisualization\bundles\en_US\src" does not exist

I simply created an empty src folder at that location and then copylocale works.

C:\Program Files\Adobe\Flex Builder 3\sdks\3.0.0\bin>copylocale en_US fr_FR
In Flex SDK at C:\Program Files\Adobe\Flex Builder 3\sdks\3.0.0 ...

After doing all these steps I got an error "resource bundle charts not available". Then I copied the "charts_rbc.swc" from sdk 2.0.1 folder and placed inside the folder "C:\Program Files\Adobe\Flex Builder 3\sdks\3.32.0\frameworks\locale\fr_FR". Then I compiled the project again. Now it was working. Thanks to "http://www.herrodius.com/blog/123"

Hello? Non... Bonjour!

Inside the locale folder of the RunLoc project, create a folder called fr_FR. Inside the fr_FR folder, create a file called myResources.properties and sets its encoding to UTF-8. Enter the French version of the GREETING resource in it:

GREETING=Bonjour!

Open the project’s Properties dialog, select the Flex Compiler pane, and in the “Additional compiler arguments” simply change

-locale=en_US

to

-locale=fr_FR

Now run the application... you should see “Bonjour!” instead of “Hello!”.

OK, OK. How do I switch locales on the fly?

You’re right to be bored so far... up to this point we haven’t done anything that wasn’t possible in previous versions of Flex. But now we’re ready to talk about Flex 3’s improvements. To start, let's compile our RunLoc demo for both English and French, and add some code to switch between them.

Change RunLoc.mxml to look like this:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:Metadata>
[ResourceBundle("myResources")]
</mx:Metadata>

<mx:Script>
<![CDATA[
        [Bindable]
private var locales:Array = [ "en_US" , "fr_FR" ];
        private function localeComboBox_initializeHandler(event:Event):void
{
localeComboBox.selectedIndex = locales.indexOf(resourceManager.localeChain[0]);
}

private function localeComboBox_changeHandler(event:Event):void
{
// Set the localeChain to either the one-element Array
// [ "en_US" ] or the one-element Array [ "fr_FR" ].
resourceManager.localeChain = [ localeComboBox.selectedItem ];
}
    ]]>
</mx:Script>

<mx:Label text="{resourceManager.getString('myResources', 'GREETING')}" fontSize="48"/>
    <mx:ComboBox id="localeComboBox" dataProvider="{locales}"
initialize="localeComboBox_initializeHandler(event)"
change="localeComboBox_changeHandler(event)"/>

</mx:Application>

Change the -locale option to be

-locale=en_US,fr_FR
Note from Jason: For Flex Builder 3.0.183654 I had to use space instead of column. i.e. "-locale=en_US fr_FR".
Also take away the <br> in the source code shown above. Great Tutorial. Thank you.

When you compile and run, the application will start in English (because en_US is specified as the first locale). But when you choose fr_FR in the combobox, the “Hello!” changes on the fly to “Bonjour!”. Next we’ll explain how this happens.

What’s this resourceManager thing?

A new manager in the Flex framework — the ResourceManager — now handles access to all localized resources in an application. Any components that extend UIComponent, Formatter, or Validator now have a new resourceManager property, which lets you easily access the singleton instance of this manager. If you’re writing some other kind of class that needs to use the ResourceManager, you can call ResourceManager.getInstance() to get a reference to it.

We compiled the RunLoc application in such a way that various resource bundles — our own myResources bundle, but also framework bundles such as core, controls, formatters, etc. — were linked into the application SWF. When the application started, instances of these resource bundles were automatically added to the ResourceManager. After they’ve been added, we can look up resources in them using ResourceManager methods such as getString().

Note that in the binding expression

text="{resourceManager.getString('myResources', 'GREETING')}"

we specify a bundle name and a resource key, but not a particular locale. Instead, the locales that get searched for resources are determined by the localeChain property of the ResourceManager. The handler for the locale combobox’s "change" event sets resourceManager.localeChain. The setter for this property dispatches a "change" event from the ResourceManager, which is a signal that the application’s resources have changed in some way. This event causes two things to happen:

  • Binding expressions involving resource-access methods of ResourceManager such as getString() are updated. This is why “Hello!” changed to “Bonjour!”.

  • Components that extend UIComponent, Formatter, or Validator execute their resourcesChanged() method. This is how components such as CurrencyFormatter can update their default value for resource-backed properties such as currencySymbol. If you are writing another kind of class that needs to respond to resource changes, you can simply listen for "change" events from the ResourceManager.

Why do I need to write a binding expression instead of @Resource()?

We could have made the @Resource() directive generate a binding expression that would update when the localeChain changes, but we decided not to. Binding expressions have some overhead associated with them and we don’t want to penalize developers who need localization but not on-the-fly locale changes.

Although switching locales on the fly is cool, it’s not something that every localized application will need. For many or even most apps, it will be good enough to start up in an appropriate locale, and perhaps allow the user to select a different locale for the next time that the application starts. In that case, @Resource() will work just fine.

Our thinking is that developers who want to support on-the-fly locale changes should “opt in” to that by writing explicit binding expressions.

What’s up with [ResourceBundle]?

I was wondering when you were going to ask why I put the tag

<mx:Metadata>
[ResourceBundle("myResources")]
</mx:Metadata>

in the RunLoc app. The answer is that when you use an expression like resourceManager.getString(bundleName, key) instead of the directive @Resource(bundle='myResources', key='GREETING') to access resources, the compiler isn’t clever enough to figure out what resource bundles your application (or component, or AS class) needs. You might be determining bundleName in some very convoluted way. So when you don’t use @Resource(), you have to tell the compiler in another, very explicit, way which bundles you’re accessing.

The way to do that now is to put [ResourceBundle()] metadata with the name of the bundle into your application’s (or component’s) <mx:Metadata> tag. If you’re writing an ActionScript class instead, put this metadata on the class declaration, like this:

[ResourceBundle("myResources")]
public class MyComponent extends UIComponent
{
...
}

If you’ve localized Flex applications in the past, you may have used [ResourceBundle()] metadata on static or instance variables:

[ResourceBundle("myResources")]
static var rb:ResourceBundle;

This would initialize rb to be a reference to the myResources ResourceBundle instance, and you could then write expressions such as rb.getString("GREETING"). Although this should still work if you compile for one and only one locale, it is very strongly discouraged.

The new localization scheme relies on all resource bundles being owned by the ResourceManager, and all resource access going through the ResourceManager. You should revise any old code which access resources directly from resource bundles.

Why is the localeChain an Array?

Ah, you noticed that too! For example, if you want to search only the French resources you have to write

resourceManager.localeChain = [ "fr_FR" ];

not

resourceManager.localeChain = "fr_FR";

The reason that the localeChain is an Array is so that the ResourceManager can support incomplete locales.

Suppose that you’re localizing an application for English as spoken in India (i.e., the en_IN locale). Most — but not all — of the resources will be the same as for U.S. English, so there’s no reason to duplicate them all in the en_IN resources — this just bloats your application’s size. The en_IN .properties files need only to have the resources that differ between en_US and en_IN. Then, if the ResourceManager has bundles for both en_US and en_IN and you set

resourceManager.localeChain = [ "en_IN", "en_US" ];

the ResourceManager will search for a resource first in the en_IN bundle and, if it isn’t found there, then in the en_US bundle. You can “fall back” from one locale to another until you find a resource. If getString() doesn’t find the resource you specify in any locale, it returns null.

Can resource values only be Strings?

The short answer to this is No, but it’s a little complicated so I’ll give a fuller explanation.

When you compile a .properties file with resources such as

COUNTRY=United States
PRICE=19.99
AGE=21
SENIOR=true

the MXML compiler autogenerates a subclass of ResourceBundle with a content Object containing the key/value pairs for those resources. The generated code is equivalent to

content["COUNTRY"] = "United States";
content["PRICE"] = "19.99";
content["AGE"] = "21";
content["SENIOR"] = "true"

Note that all of these resource values are Strings. The compiler doesn’t look at SENIOR=true and decide that the value should be the Boolean true rather than the String "true". So if you call resourceManager.getString("myBundle", "SENIOR") you’ll get the String "true".

However, the ResourceManager has other resource-access methods besides getString(). For example:

resourceManager.getNumber("myResources", "PRICE"); // returns the Number 19.99, not the String "19.99"
resourceManager.getInt("myResources", "AGE"); // returns the int 21, not the String "21"
resourceManager.getBoolean("myResources", "SENIOR") // returns the Boolean true, not the String "true"

These methods, like getString(), are actually all wrappers around the fundamental resource-access method getObject(), which returns the resource value with no conversion.

Beginning with Flex 3, you can now use Embed() and ClassReference() directives in .properties files, as in

LOGO=Embed("logo.png")
SORTER=ClassReference("sorters.Urdu")

These directives work just the same as in CSS files or <mx:Style> tags. When you use these directives, the resource value is of type Class. This is obvious for ClassReference(); for Embed(), it’s because the MXML compiler autogenerates a class to represent embedded data such as a PNG graphics file. You can use the ResourceManager’s getClass() method to access these resource values..

If you create your own resource bundles at runtime rather than having the MXML compiler compile them from .properties files, you can put any key/value pairs you want into their content Object. You’re not limited to Strings. This is discussed in a later section.

Determining the initial locale

When an application is compiled for multiple locales, such as with the compilation option

-locale=en_US,fr_FR

the ResourceManager's localeChain property is initialized to this sequence of locales. For example, in this case it would find the English resources because en_US is listed first. However, you can override this default initial value by specifying the localeChain as an application parameter in the FlashVars in the HTML template.

In the html-template folder of the RunLoc project, open the index.template.html file and insert the line

"FlashVars", "localeChain=fr_FR",

after the line

"src", "${swf}",

in the second call to the JavaScript function AC_FL_RunContent().

Save the file. Now clean the project (with Project > Clean in the menubar) to force the RunLoc.html file in the bin folder (which is simply a renamed copy of the index.template.html file) to be regenerated. It should automatically update when the index.template.html file is saved, but due to a bug in Beta 3 this doesn't happen.

When you run, the application should start up in French. If you change fr_FR to en_US and regenerate RunLoc.html, the app should start up in English.

You can specify multiple locales in the FlashVars, as in

"FlashVars", "localeChain=en_IN,en_US",

which would initialize the localeChain property to [ "en_IN", "en_US" ].

To support browsers that have JavaScript disabled, you need to also specify the FlashVars in two other places (in the <object> and <embed> tags). See the index.template.html file for the FlightReservation2 application for an example of doing this.

Tell me about resource modules!

I’ve shown you how to compile your application for multiple locales, such as English and French, but if you’re localizing for twenty locales it might not be reasonable to bloat your application’s size by linking in resources for all of them. This is where resource modules can be useful. You can go to the other extreme, such as compiling no resources into your application and instead loading them as needed from resource modules. (Or you can link some into your application and load others. It’s all up to you.)

Resource modules are SWFs, separate from your application SWF, which contain resources bundles for one or more locales. Your application can preload one or more resource modules as it starts up, before it displays its user interface. It can also load resource modules later, such as in response to the user selecting a new locale. The ResourceManager doesn’t care whether the resource bundles it manages were originally compiled into the application or loaded from resource modules.

Before you can compile a resource module, you need to know which resource bundles to put into it. In other words, you need to know which resource bundles your application — and all of its framework classes — actually needs. The MXML compiler can tell you that, if you specify the -resource-bundle-list option; when it compiles an application, it determines which bundles it needs by looking at the [ResourceBundle] metadata on all the classes in your application.

Open the RunLoc project’s Properties dialog, select the Flex Compiler pane, and in the “Additional compiler arguments” field, change the -locale option to

-locale=

to tell the application not to compile any resources into itself, and add the option

-resource-bundle-list=bundles.txt

This option tells the MXML compiler to write out a file named bundles.txt containing a list of the resource bundle names used by RunLoc. In Beta 2, this file will appear in the project's bin folder, although this may change before we ship. If you open this file you’ll see the list

bundles = collections containers controls core effects myResources skins styles

Notice that only the myResources bundle contains application resources; the others are bundles containing framework resources.

If you run the application now, you won't see either “Hello!” or “Bonjour!” because both the English and the French resources are missing.

How do I compile a resource module?

Unfortunately, due to the fact that resource modules require a different set of compilation options from other kinds of modules, you can’t compile them with Flex Builder. You have to use command-line compilation.

We’ll use the bundle names we obtained to compile two resource modules as follows. First change the current directory to the RunLoc project directory. Then execute these two commands:

mxmlc -locale=en_US
-source-path=locale/{locale}
-include-resource-bundles=collections,containers,controls,core,effects,myResources,skins,styles
-output=src/Resources_en_US.swf
mxmlc -locale=fr_FR
-source-path=locale/{locale}
-include-resource-bundles=collections,containers,controls,core,effects,myResources,skins,styles
-output=src/Resources_fr_FR.swf

If you haven’t placed the SDK’s bin directory on your PATH, you’ll need to specify an absolute path for mxmlc.

Windows commandline: If -source-path does not work, and return unable to open 'en_US': specify the absolute path to your project locale folder instead.

Windows commandline: -output also expect an absolute path to your project src folder, otherwise it will create the swf in c:\src\ There is maybe a better solution, but I could not make it work otherwise: ps: great tutorials! thanks.

The project's src directory should now contain two resource modules named Resources_en_US.swf and Resources_fr_FR.swf.

How do I preload a resource module?

The Flex framework can handle preloading one or more resource modules for you automatically. All you have to do is set the resourceModuleURLs application parameter in the FlashVars of the HTML wrapper. You should also set the localeChain parameter to tell the application what locale to initially use. For example, if you change the FlashVars in index.template.html to

"FlashVars", "resourceModuleURLs=Resources_en_US.swf&localeChain=en_US",

(and save the file and clean the project) then the resource module Resources_en_US.swf will be preloaded (this URL is relative to the application SWF) and the ResourceManager's localeChain will be initialized to [ "en_US" ].

When you run the application, it should load the English resource module and you should see “Hello!”. If you change this line to

"FlashVars", "resourceModuleURLs=Resources_fr_FR.swf&localeChain=fr_FR",

(and save the file and clean the project) it should load the French resource module and you should see “Bonjour!”.

You can specify a comma-spearated list of resource module URLs to preload more than one resource module.

How do I load a resource module programmatically?

You use the ResourceManager’s loadResourceModule() to start asynchronously loading a resource module. It works similarly to the loadStyleDeclarations() of the StyleManager, which loads style modules.

The loadResourceModule() method returns an event-dispatching object. You can listen for "progress", "complete", and "error" events — of type ResourceEvent — which it dispatches. Primarily, you’ll be interested in knowing when the load is complete, because that’s when you’ll probably want to set the localeChain to use the resources that you just loaded. You’ll generally want to do something like this:

private function someMethod():void
{
...
var url:String = "Resources_fr_FR.swf";
var eventDispatcher:IEventDispatcher = loadResourceModule(url);
eventDispatcher.addEventListener(ResourceEvent.COMPLETE, completeHandler);
...
}

private function completeHandler(event:ResourceEvent):void
{
resourceManager.localeChain = [ "fr_FR" ];
}

Can I see a complete example?

Sure. Change RunLoc.mxml to have the following code

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:Metadata>
[ResourceBundle("myResources")]
</mx:Metadata>

<mx:Script>
<![CDATA[
import mx.events.ResourceEvent;

[Bindable]
private var locales:Array = [ "en_US" , "fr_FR" ];

private function localeComboBox_initializeHandler(event:Event):void
{
localeComboBox.selectedIndex = locales.indexOf(resourceManager.localeChain[0]);
}

private function localeComboBox_changeHandler(event:Event):void
{
var newLocale:String = String(localeComboBox.selectedItem);

if (resourceManager.getLocales().indexOf(newLocale) != -1)
{
completeHandler(null);
}
else
{
var resourceModuleURL:String = "Resources_" + newLocale + ".swf";
var eventDispatcher:IEventDispatcher =
resourceManager.loadResourceModule(resourceModuleURL);
eventDispatcher.addEventListener(
ResourceEvent.COMPLETE, completeHandler);
}
}

private function completeHandler(event:ResourceEvent):void
{
resourceManager.localeChain = [ localeComboBox.selectedItem ];
}

]]>
</mx:Script>

<mx:Label text="{resourceManager.getString('myResources', 'GREETING')}" fontSize="48"/>
    <mx:ComboBox id="localeComboBox" dataProvider="{locales}"
initialize="localeComboBox_initializeHandler(event)"
change="localeComboBox_changeHandler(event)"/>

</mx:Application>

Set the FlashVars in index.template.html back to preload the English resources (and save the file and clean the project).

When you run the application, it loads Resources_en_US.swf and displays “Hello!”. When you choose fr_FR in the combobox, it loads Resources_fr_FR.swf and displays “Bonjour!”.

Note that the logic

if (resourceManager.getLocales().indexOf(newLocale) != -1)

determines whether we’ve already loaded the locale that the user wants to switch to.

I’m tired! Anything else?

There are just a few more things you should know.

I’ve already mentioned that the ResourceManager manages ResourceBundles, but it doesn’t care where the ResourceBundles come from. They can be compiled into the application. Or they can be loaded from a resource module. Or you can create them yourself at runtime, rather than compiling them from .properties files at compile time. For example, this code creates a new bundle, populates it with two resources, and adds it to the ResourceManager.

var moreResources:ResourceBundle =
new ResourceBundle("fr_FR", "moreResources");
moreResources.content["OPEN"] = "Ouvrez";
moreResources.content["CLOSE"] = "Fermez";
resourceManager.addResourceBundle(moreResources);

Once a resource bundle is in the ResourceManager, methods like getString() can be used to find its resources:

resourceManager.localeChain = [ "fr_FR" ];
trace(resourceManager.getString("moreResources", "OPEN"));
// outputs "Ouvrez"

The ability to create resource bundles yourself means that you can “roll your own” ways of loading resources at runtime. We’ve chosen to support doing it via compiled resource modules, because this makes it possible to localize images, sounds, classes, etc. rather than just strings. But if you want, you can download XML files or fetch resource data from a database, put it into ResourceBundles, and access it through the ResourceManager.

You can use the removeResourceBundle() method to remove a resource bundle from the ResourceManager, so that its resources can no longer be found.

You can find out which resource bundles exist in the ResourceManager by calling getLocales() and getBundlesNamesForLocale() and you can get a reference to a particular bundle by calling getResourceBundle(). These methods make it possible to enumerate all the resources in the ResourceManager. (Once you have a ResourceBundle, use a for-in loop to iterate over it’s content Object.)

When you set the localeChain, the ResourceManager automatically dispatches a "change" event. (Remember, this event causes your application’s user interface to update with the new resource values.) This event is also dispatched after a module completes loading, if you passed true as the second parameter to loadResourceModule(). If you want to tell the ResourceManager to dispatch this event in other circumstances, such as after you have added a bundle yourself, you can call the ResourceManager’s update() method.

I hope you find Flex 3’s Runtime Localization feature to be useful in your applications!

 

posted on 2010-01-30 21:22  Chris Cai  阅读(1153)  评论(0编辑  收藏  举报