Menu

Runtime Localization Specification

SourceForge Editorial Staff

Runtime Localization Specification

PLEASE NOTE THAT THIS SPECIFICATION MAY BE OUT OF DATE, DOCUMENTATION PROVIDED WITH THE RELEASE SUPERCEDES ANYTHING IN THIS SPEC.

Summary of Feature

In Flex 2, developers creating applications to be used internationally can link localized strings for a single locale into their application's SWF. Although useful, this design is too limited, so this feature will give developers much greater flexibility in how they use localized resources. For Moxie, we will make the following improvements:

  • An application will be able to load resource module SWFs containing localized resources, either at launch time or later.
  • Developers will be able to specify images, sounds, fonts, and programmatic classes -- in addition to simple strings -- as resources.
  • Developers will be able to use resources as styles and skins -- in addition to simple properties -- using either declarative (MXML) or procedural (AS) syntax.
  • Resources for multiple locales will be able to exist simultaneously in the application.
  • Resource lookup will support locale chaining, meaning that if a resource is not found in one locale, others can be searched.
  • If a resource module is loaded after the application has launched, the application will automatically refresh with the new resources assuming that the resources were used declaratively. For resources that are used procedurally, developers will have to override a method or write event handlers to support a runtime locale change.
  • Developers will be able to create resources programmatically at runtime rather than compiling them.

In addition, Moxie will fix two problems associated with the current resource design:

  • Only one copy of each resource will be created.
  • The new design will not involve accessing resources at static-initialization time.

We will avoid changes that don't help us each these goals. In particular, we will continue

  • to use .properties files to specify resources;
  • to require that these .properties files undergo compilation, either into an application SWF, into a resource SWC, or into a resource module SWF;
  • to support the @Resource() directive in MXML, with no change except to what it autogenerates;
  • to have hardcoded English strings in the DownloadProgressBar which cannot be localized in a .properties file.

Usage Scenario

Scenario 1: Preloading a resource module

Craig is helping Oliver to localize an Apollo application, written in Flex, that will provide the application installer UI launched by the Apollo runtime. This UI will be localized for multiple languages.

He places the localizable strings into a .properties file and uses the @Resource() directive in his MXML code to access them. Localizers translate the strings in the .properties files for other locales. Craig uses mxmlc to build his app with no resources compiled into it, and he also uses mxmlc to compile the .properties files into resource modules - one for English, one for French, etc. He links the framework resources he needs into the modules as well.

When the user double-clicks an RIA file, the Apollo runtime will launch the installer app, which must then preload the appropriate resource module. If the OS language is French, the French resource module should be preloaded. The Apollo runtime uses OS APIs to determine the OS locale that the user is running under. Using a command-line option, it passes the URL of the French resource module to the app, which automatically preloads the French resources before the installer UI appears.

(For web apps, developers can specify the URL of the resource module to be preloaded as a FlashVar in the <object> or <embed> tag in the HTML wrapper, based on information such as browser preferences or user preferences stored in cookies.)

Scenario 2: Switching between locales

Greg is developing a Flex app for the Photoshop team that needs to present its UI in multiple languages. He decides to create a single application SWF with the resources for all the languages already linked in. To accomplish this, all he does is specify multiple locales using the -locale option.

His application has a popup user preferences panel in which the user can change the language. When the user closes the panel, the resource manager is told to use a different locale: the entire application updates and displays its UI in the new language.

(Alternately, he could link in or preload one language, and then wait to late-load a module for another language until the user selects it.)

Scenario 3: Programmatically creating and chaining resources

Peldi is helping to port Adobe Connect to Flex. Although his team localizes Connect for multiple languages, they want to allow corporate IT admins and meeting organizers to customize parts of the Connect UI, and they want to use Moxie's resource mechanism to handle these overrides.

They decide to allow the IT admins to customize the Connect UI by writing an XML file. The meeting organizer's customizations will be stored in a database, and a JSP script will produce an XML file for Connect to read.

When Connect runs, it downloads these XML files and uses them to produce new programmatically-created ResourceBundles in the resource manager. Connect can access the strings using the @Resource() directive. The programmatically-created resource bundles behave just like ones that are more often linked or loaded into an application.

Because the resource manager supports resource chaining, it can search first in the resource bundle created from the meeting organizer's XML file, then in the resource bundle created from the IT admin's XML file, and finally in the resource bundle with the default resources.

Scenario 4: Localizing styles and assets

Samantha is using resource modules to localize her application, but she needs to localize the background image and various skins as well as the strings of her application. For performance, she wants these assets to be linked into the resource module along with the strings, so that a single HTTP request loads everything required for single locale.

In her <mx:styles> tag, she uses the new CSS Resource() directive to declare that the value of the Application's backgroundImage style is a locale-dependent resource:</mx:styles>

Application
{
backgroundImage: Resource(bundle="myResources", key="BACKGROUND_IMAGE");
}

In her English myResources.properties file, she writes

BACKGROUND_IMAGE = Embed("englishBackground.jpg")

and in her French one she writes

BACKGROUND_IMAGE = Embed("frenchBackground.jpg")

The Embed() directives transcode the JPG backgrounds into asset classes which are linked into the resource module. The Resource() directive tells the StyleManager that the value of the backgroundImagestyle is a resource. When the locale is programatically switched from English to French, the application's background image automatically changes.

Detailed Description

Teminology and Concepts

locale

A one-, two-, or three-part specifier representing a particular geographical, political, or cultural region for which resources have been localized. The simplest type of locale (e.g., ja) is simply an ISO 639 language code. A more complex locale like en_US ("English as spoken in the United States") combines a language code with an ISO country code. The most complex locales have an additional "variant" part which is not standardized; it could be used to indicate a sublocale like en_US_DeepSouth ("English as spoken in the Deep South"), a platform like en_US_Mac, or a localization role like en_US_Administrator.

resource

A piece of data which is locale-dependent. Resources get "localized" to be suitable for use in particular locales. The most common type of localization is the translation into various languages of user-interface strings that are visible to end users.Each resource is part of some resource bundle. A particular resource within a bundle is specified by its key, which is a String which must be unique within that bundle. Resource bundles are specified by their bundle name, which is a String that must be unique within the application.In Flex 2 a resource value is always a String, but in Moxie resources compiled from a .properties file can have values of type String or Class. (Remember that assets like images and sounds in a SWF are all represented by classes.) Programatically created resources can actually have any type. The resource manager will have utility methods such as getBoolean() that can convert string values such as "true" types such as Boolean.

resource bundle

A named group of resources. The bundle is the unit of linkage for resources: an application might link in the "core" and "controls" bundles but not the "formatters" one. However, all the resources in the "core" and "controls" bundles would be included, whether they are actually used by the application or not.In Flex 2, the resource bundles for a single locale get linked into the application SWF, and all resources are accessed through the appropriate resource bundle. In Moxie, an application can have resource bundles for multiple locales, and they can be loaded from resource modules as well as, or instead of, being linked in. Resources are accessed through the resource manager, which may search more than one bundle for the resource, based on the locale chain.

resource module (new for Moxie)

A SWF containing one or more resource bundles for one or more locales, which can be loaded by an application at runtime. The module can be preloaded or late-loaded. A resource module is conceptually similar to a style module. The module's factory method creates a module instance which implements the new IResourceModule interface.

preloading of a resource module (new for Moxie)

Loading that occurs during frame 1 of the application, before the application's frame 2 UI appears.

late-loading of a resource module (new for Moxie)

Loading that occurs after application launch. In this case, the application's UI can be made to immediately update without relaunching.

resource manager (new for Moxie)

A framework object through which an application manipulates all of its resources. The resource manager owns all resources, whether compiled into the application, loaded from resource modules, or programatically created. It maintains a single instance of each resource bundle known to the application. It dispatches an event when the application's UI should update due to a runtime locale change. It has APIs for loading and unloading resource modules; for adding, enumerating, and removing resource bundles; for getting and setting the locale chain; and for accessing individual resources in that chain.

locale chain (new for Moxie)

A sequence of locales to be searched for resources. When the resource manager looks for a resource, it can, for example, first look for the Japanese version and then, if it isn't found, look for the English version.

resource-driven update (new for Moxie)

An update of the application's UI in response to some change in the resource manager, such as a new resource module being loaded or the locale chain being changed.

How resources work in 2.0.1

You write one or more Java-like .properties text files with a key/value String pair, separated by an equals sign, on each line. For example, foo.properties might contain

YES_LABEL = Yes
DATE_FORMAT = MM/DD/YYYY
AGE_OF_CONSENT = 16
SOME_FLAG = true

It's up to you to choose which resources go into which .properties file. The keys on the left hand side must be unique within the file.

The .properties files get sent to localizers. The localization tools that they use understand this format. Also, even if they aren't using a localization tool, the format is so simple that it is clear that they change only what is on the right-hand-side of the equals sign. There is minimal risk that they "break" the syntax so that the file doesn't compile.

foo.properties gets compiled by mxmlc or compc into a "resource bundle class", a class which extends ResourceBundle and which can be used to look up the name/value pairs:

public class foo_properties extends ResourceBundle
{
  override protected function getContent():Object
  {
    var properties:Object = {};
    properties<a href="%26quot%3BYES_LABEL%26quot%3B">"YES_LABEL"</a> = "Yes";
    properties<a href="%26quot%3BFORMAT%20%26quot%3B">"FORMAT "</a> = "MM/DD/YYYY";
    properties<a href="%26quot%3BAGE_OF_CONSENT%26quot%3B">"AGE_OF_CONSENT"</a> = "16";
    properties<a href="%26quot%3BSOME_FLAG%26quot%3B">"SOME_FLAG"</a> = "true";
    return properties;
  }
}

In MXML, you can use the @Resource() directive to fetch a resource value from a resource bundle by specifying the resource bundle name and the resource key:

<mx:Button label="@Resource(bundle='foo', key='YES_LABEL')"/>

If the name of the resource bundle is the same as the name of the MXML file, you can omit it and simply specify the key:

<mx:Button label="@Resource('YES_LABEL')"/>

In AS, you can programmatically create a new instance of a resource bundle class if you know its bundle name:

private static var fooBundle:ResourceBundle;
...
fooBundle = ResourceBundle.getResourceBundle("foo");

Normally you initialize bundle reference vars using metadata, because this allows the compiler to keep track of which bundles it needs to link in. The following initializes fooBundle to ResourceBundle.getResourceBundle("foo") and causes the foo_properties class to be linked in:

<a href="ResourceBundle%28%26quot%3Bfoo%26quot%3B%29">ResourceBundle("foo")</a>
private static var fooBundle:ResourceBundle;

When you use the @Resource directive in MXML, a static-var-with-ResourceBundle-metadata like this gets autogenerated by the MXML compiler.

You can fetch individual strings from a bundle using ResourceBundle instance APIs such as getString():fooBundle.getString("YES_LABEL") -> "Yes"
The MXML compiler compiles all resource values in .properties files as Strings, but you can use APIs such as getNumber() and getBoolean() to convert these Strings into other types:

fooBundle.getNumber("AGE_OF_CONSENT") \-> 16, not "16"
fooBundle.getBoolean("SOME_FLAG") \-> true, not "true"

When you use mxmlc to compile an application or a module containing code classes that use the @Resource() directive or [site:ResourceBundle]metadata, the compiler automatically find, compiles, and links in the necessary .properties files. If the .properties files have already been compiled into ResourceBundles in resource SWCs, it finds the resource SWCs and links in the appropriate bundles. It uses the -locale, -source-path, and -library-path options to locate the appropriate .properties files or resource SWCs.

In addition, it autogenerates code so that the Locale.getCurrent() method will return the locale specified by the -locale option.

When you use compc to compile a SWC containing code classes that use the @Resource() directive or [site:ResourceBundle] metadata, it does not automatically link in any resources. Instead, if you specify the -resource-bundle-listoption, the compiler writes out a file containing a list of the names of the resource bundles that the code classes use, such as

bundles = controls containers

You can then use this file to create resource SWCs for each locale to support your component SWC. (For example, we create an English and Japansese version of framework_rb.swc with the resource bundles required by the classes in framework.swc.) You do this by using compc's-include-resource-bundles option to specify which .properties files should be compiled and linked into the resource SWC. Note that when you use compc to create a resource SWC, you do not specify any target file to be compiled; the .properties files to be compiled are implicitly determined by -locale and -include-resource-bundles.

Problems and Solutions

Problem: Resources must be compiled into the application SWF.

This means that developers must compile and post multiple copies of their application, each localized for a different locale. If an end user wants to use the French version rather than the English version, she must download an entire new application rather than just the French resources.

Solution: We will allow one or more .properties files to be compiled into resource bundle classes in a resource module SWF. Such a module can then be loaded by a Flex application at runtime.

Problem: Resource values can only be strings.

Although translating text is generally the bulk of localization work, sometimes applications have more complex data -- such as images, sounds, fonts, or code (e.g. sorting tables) -- that is locale-specific.

Solution: We will support Embed() and ClassReference()directives in .properties files, just as in .css files. This will allow any ActionScript class -- or any kind of asset that can be transcoded by mxmlc or compc to a class -- to be a resource.

Problem: A style value in a CSS selector can't come from a resource.

Solution: We will support the Resource() directive in .css files. This will allow styles and skins to be localized.

Problem: Only one-locale-at-a-time is allowed.

Solution: We will allow resources for multiple locales to be simultaneously present in an application.

Problem: Every resource must be localized for each locale.

Solution: We will allow locales to be chained together so that they are searched in order when a resource is accessed. The locale chain can include any subset of the locales in the resource manager. Locale chaining will make it possible to do partial and hierarthical localization.

An example of partial localization would be omitting all the RTE strings, which end users never see, from the .properties files sent to a localizer,in order to save money. If the en_US locale were farther along in the locale chain, the English error messages would be found.

An example of hierarchical localization would be having different kinds of English resources override each other, as in Scenario 3.

Problem: ResourceBundles can only come from classes compiled from .properties files.

Solution: We will make it possible for developers to programmatically create resource bundles at runtime and populate them with resource key/value pairs by any technique they choose, such as parsing a downloaded XML or .properties file.

Problem: Flex apps have multiple instances of the same resources.

For example, every UIComponent creates a new instance of the core_properties bundle class.

Solution: A ResourceManager object will own a single copy of each ResourceBundle.

Problem: Using [site:ResourceBundle] metadata to initialize a static var with a bundle instance can cause bugs.

Roger Gonzalez says the problem is that the implementation of ResourceBundle.getResourceBundle(name) uses ApplicationDomain.getDefinition()to find a bundle class like foo_properties by name, but you can't reliably find other classes by name at class initialization time because not all classes have been fully inited. Unfortunately, most uses of [site:ResourceBundle] in the framework clsses, and in autogenerated classes when a developer uses @Resource(), are on static vars.

Solution: In Moxie, the resource manager will be initialized early during application startup, but not as early as static initailization. The use of [site:ResourceBundle] metadata on static vars will be deprecated but still supported.

Non-Problems

.properties files

We will continue to use .properties files as the way to separate resources from MXML and AS code. Localizers are familiar with them and often have localization tools which understand them. These files have a simple syntax which makes it easy for localizers to understand what to localize, without breaking something that they shouldn't have touched.

The possibility of using CSS syntax for resources was considered but rejected. This would be a major change from 2.0.1 without a compelling reason; would confuse the conceptual distinction between a style and a resource, which have entirely different lookup algorithms; would not be familiar to localizers or supported by their tools; and would present a more fragile syntax to localizers.

Resource compilation

We will continue to require .properties files to be compiled. We will support runtime loading resource module SWFs, but not .properties files. Developers will be able to compile .properties files into a resource SWC with compc (as in 2.0.1), or into a resource module SWF with mxmlc (new for Moxie).

The main reason to continue to require .properties files to be compiled is to minimize the number of HTTP requests. A loadable resource module SWF can contain any number of resource bundles, rather than having to load multiple .properties files. If images, sounds, or graphics skins are being localized, by compiling them into the SWF they don't have to be individually loaded. And if a programmatic class is being used as a resource, the only way to load that code at runtime is in compiled form, in a SWF.

Many localizers never run the application whose resources they localize, so having a compilation step is not a serious problem. If it is desirable to have a localizer compile the resources and run the app, a localization kit can include the free mxmlc compiler and a script with the command line for compiling the resource module SWF. If an application uses compiled-in resources, the localization kit does not need to supply the source code for the application; it could supply a SWC with the application's classes, to be linked with the resources.

DownloadProgressBar

In 2.0.1 the DownloadProgressBar uses hardcoded strings. I suspect that this is because the resources don't come in until frame 2. I don't plan to change this. If we decide we should do better for Moxie, I suggest that we allow its strings to be specified in the HTML wrapper and accessed via Application.parameters.

Compiling resources

Using mxmlc to compile an application with linked-in resources

The only change Moxie will make to this use case is allowing resources for multiple locales to be linked in:

mxmlc \-locale ja_JP en_US MyApp.mxml

Suppose that MyApp.mxml -- and the framework class that it depends on -- reference resource bundles named "core", "controls", and "MyApp", by using @Resource() or [site:ResourceBundle]. Then MyApp.swf will contain the six bundle classes

public class en_US__core extends ResourceBundle
public class en_US__controls extends ResourceBundle
public class en_US__MyApp extends ResourceBundle
public class ja_JP__core extends ResourceBundle
public class ja_JP__controls extends ResourceBundle
public class ja_JP__MyApp extends ResourceBundle

In Flex 2 the name of the resource bundle classes would be core_properties, controls_properties and MyApp_properties, but in Moxie we will include the locale in the class name because we are support multiple locales. This change should not present a compatibility problem because the name of the resource bundle classes has not been documented.

The autogenerated SystemManager subclass will initialize the resource manager with instances created from these six linked-in bundle classes, using the addResourceBundle() method of the new interface IResourceManager.. (This mechanism will replace the problemeatic one in 2.0.1 in which linked-in bundle classes are located by classname at static initialization time.)

The autogenerated SystemManager subclass will also set the locale chain in this case to [site: "ja_JP", "en_US" ].

If resources for more than one locale are linked in, the deprecated Locale.getCurrent() API will return null. But developers can use the new getLocales() method and localeChain property of IResourceManager to discover which locales were linked in.

Using compc to compile resources into a resource SWC

The only change Moxie will make to this use case is allowing resources for multiple locales to be linked into the SWC:

compc \-locale en_US ja_JP \-include-resource-bundles foo bar \-o myComponent_rb.swc

This command line will compile the four files locale/en_US/foo.properties, locale/en_US/bar.properties, locale/ja_JP/foo.properties, and locale/ja_JP/bar.properties to create the SWC file myComponent_rb.swc with the four classes

public class en_US__foo extends ResourceBundle
public class en_US__bar extends ResourceBundle
public class ja_JP__foo extends ResourceBundle
public class ja_JP__bar extends ResourceBundle

Using mxmlc to compile a resource module

This is a new use of mxmlc for Moxie. Compiling .properties files to create a resource module is similar to compiling a .css file to create a style module. However, because multiple .properties files for multiple locales may be compiled into a single resource module, they will be specified indirectly, following the same pattern as for creating a resource SWC with compc.

mxmlc \-locale en_US ja_JP \-include-resource-bundles foo bar \-o MyResourceModule.swf

This command line will compile the four files locale/en_US/foo.properties, locale/en_US/bar.properties, locale/ja_JP/foo.properties, and locale/ja_JP/bar.properties to create MyResourceModule.swf with five classes:

public class MyResourceModule extends ModuleBase implements IResourceModule
public class en_US__foo extends ResourceBundle
public class en_US__bar extends ResourceBundle
public class ja_JP__foo extends ResourceBundle
public class ja_JP__bar extends ResourceBundle

MyResourceModule is the autogenerated main class for the module, which is used to create instances of each resource bundle in the resource module, which can then be added to the resource manager. It implements the new IResourceModule interface, which has one method, createResourceBundles().

Enhancements to .properties files

In order to allow more than just strings to be localized, we will support the use of Embed() and ClassReference() directives in .properties files, similar to how they're used in CSS files:

LOGO = Embed("logo.gif")
BACKGROUND = Embed(source="Assets.swf", symbol="Background")
COLLATION_UTILS = ClassReference("CollationUtils")

Embed() will transcode the referenced file or SWF symbol into an ActionScript class and cause it to be linked in with the ResourceBundle subclass generated from the .properties file. ClassReference() will link in a specified code class. Note that, like in CSS, ClassReference(null) is a way to specify a null resource value.

Late-loading a resource module

A resource module is a normal module, meaning that you could use low-level module APIs such as ModuleManager.getModule(url) and moduleInfo.load() to load and unload it. However, this would require adding your own ready handler to create an IResourceModule instance by calling event.module.factory.create(), asking the module instance to create instances of its resource bundles by calling its createResourceBundles() method, and adding them to the resource manager with addResourceBundle().

Instead, the recommended way to load and unload a resource module will be via the resource manager, resourceManager.loadResourceBundles(url);
which will do all of this for you with one (asynchronous) call.

If a resource bundle loaded from a module has the same locale and bundle name as one already in the resource manager, it will replace the one in the resource manager.

After the resource bundles have been loaded from the resource module into the resource manager, subsequent calls to resource manager APIs such as getString() will be able to access the resources.

It would be typical to set the localeChain after loading a resource module. Doing so will automatically update the application to use the resources in the new chain. Alternatively, you can call resourceManager.update(); to update the entire application after any kind of change to the resource manager or its resource bundles.

You can later call resourceManager.unloadResourceBundles(url); to unload a resource module. This will also remove bundles from the resource manager that were created by that module.

The loadResourceBundles() and unloadResourceBundles() methods are conceptually similar to StyleManager.loadStyleDeclarations(url) and StyleManager.unloadStyleDeclarations(url) for runtime CSS, which load/unload one or more instance of CSSStyleDeclaration into/from the StyleManager.

Preloading a resource module

You can load resources at any time by calling loadResourceBundles(). However, if the initial state of the application requires resources, then those resources must be either linked in to the SWF or loaded very early, in frame 1, before the application runs.

For Moxie we will change the Preloader so that it is able to preload one or more resource modules after it preloads RSLs. In the same way that the preloader waits until each RSL is completely loaded before loading the next one, it will make sure that each resource module is completely loaded before continuing to the next one.

Of course, the main reason to put resources into a preloadable resource modules is that you want to decide at runtime, not at compile time, which modules should preloaded. Therefore the Preloader will obtain a comma-separated list of resource module URLs from Application.parameters.resourceModules. This property can be set from "outside" the SWF: from the HTML wrapper, from the URL query parameters, or from the command line (for Apollo).

If this parameter is not specified, and if no resources are compiled into the application SWF, then the Preloader will attempt to preload a single resource module. The module is assumed to be named Resources.swf and to be located relative to the application SWF in the directory locale/{locale} where {locale} is the value of the property Capabilities.language, a property which is Flash's best guess at the locale of the operating system.

Developers who need more complicated preloading functionality can write a custom preloader and override a new method, getResourceModuleURLs().

Should I compile, preload, or late-load my resources?

If you are supporting only a single locale, you should simply compile your resources into the application SWF. Putting your resources into a resource module wouldn't make sense, because the combined size of the two SWFs would be just as large as a single application SWF, and loading the resource module would require an additional HTTP request. In fact, you don't even need to be using resources in this case.

If you are supporting multiple locales, the simplest scheme is to compile resources for all the locales into the application SWF. This is simplest because you don't have to arrange for any separate resource modules to be compiled and loaded. But if you have many locales, the SWF size -- and therefore the application's downlaod and initialization time and your server's bandwidth charges -- might be too large, especially if your typical user is likely to use only one locale. This scheme therefore works well for Apollo applications but not for web applications.

Another simple scheme is to create English, Japanese, etc. versions of your application SWF, each with one locale's resources compiled in. But this is inelegant and non-optimal for users who might want to use your application in more than one language. Suppose they have already downloaded a 1 MB app, of which 50 KB is English resources. To get the Japanese version, they shouldn't be forced to download another 1 MB and you don't want to pay for this bandwidth. Also, in this case you can't use the locale-chaining feature of the resource manager.

Loading per-locale resource modules is generally the best way for browser applications to support multiple locales. Create one module per locale, then preload one of them based, for example, on a user's locale preference stored in a cookie, database record, LocalSharedObject, etc. With only two HTTP requests, you can load the application's code and all the resources that the user needs, without loading resources that the user doesn't need.

It does not make sense to put all locales into a preloaded resource module; loading the application and the module would be even slower than loading a monolithic application with all the locales linked into it.

If you need to support letting the user switch the locale after the application is running, without relaunching, then late-loading a resource module with the new locale's resources is the best way to accomplish this. The user might never switch the locale, so you want to wait until she does so. And you want to load only what is necessary for the new locale.

You can also use late-loading to load additional resources for the initial locale that aren't required at startup, rather than to switch locales.

Accessing resources

All resources should be accessed through the resource manager, not through individual resource bundles. because:

  • The resource manager supports locale chaining, so it can look in more than one resource bundle to find a resource.
  • Binding expressions using the resource manager (see below) will automatically update when appropriate.

In AS you will write code such as okButton.label = resourceManager.getString("controls", "OK_LABEL"); and okButton.setStyle("upSkin", resourceManager.getClass("controls", "UP_SKIN"));

In order to handle a resource-driven update, you override the resourcesChanged() method of UIComponent or handle the resourceChange event from the resource manager.

In MXML, the @Resource(bundle='...', key='...")directive will continue to be used in exactly the same way. However, it will generate a binding expression in order to support runtime locale-switching:

<mx:Button label="@Resource(bundle='controls', key='OK_LABEL')"/>

will be shorthand for

<mx:Button label="{resourceManager.getString('controls', 'OK_LABEL'}")/>

In order to support setting non-String properties and styles with Resource(), we will add a type=&quot;...&quot; attribute to theResource() directive, where the type can be specified to be Boolean, int, uint, Number, Class, or *.

Every UIComponent will have a resourceManager property, in the same way that it has a focusManager, a layoutManager, and a systemManager. If you need to access resources from components which are not UIComponents, use Application.application.resourceManager.

It is best practice to fetch resources just-in-time rather than cache them so that runtime locale-switching requires as little additional code as possible.

Resource-driven updates

After making a change to the resource manager or to its bundles, the developer can call the update() method of IResourceManager to dispatch a resourceChange event from the resource manager.

Every UIComponent handls this event by reassinging the resourceManager property, which causes binding expressions like [[ resourceManager.getString("OK_LABEL" ]]} in MXML components to re-evaluate, and by calling the protected method resourcesChanged()ActionScript components such as Alert and DateChooser which cache resource values (typically in TextFields) can update the cached values by overriding this method.

Changing the localeChain property will automatically call update().

Adding and removing resource bundles programmatically

Resource bundles won't necessarily have to be compiled into an application SWF, resource library SWC, or resource module SWF. Developers will be able to programmatically create them at runtime and then populate them with resource keys and values by whatever means they find useful for their application. Examples would include downloading a .properties or XML file via HTTPService and parsing it, or fetching resources from a database via RemoteObject or WebService. (Note: The framework will not have built-in support for runtime parsing of a .properties file.)

The process is:

  1. Make a new ResourceBundle with the new operator.
  2. Set resource key/value pairs on the content object of the ResourceBundle.
  3. Add the bundle to the resource manager with addResourceBundle().

For example:

var newBundle:ResourceBundle = new ResourceBundle("fr_FR", "myBundle");
newBundle.content<a href="%26quot%3BOPEN%26quot%3B">"OPEN"</a> = "Ouvrir";
newBundle.content<a href="%26quot%3BCLOSE%26quot%3B">"CLOSE"</a> = "Ferme";
resourceManager.addResourceBundle(newBundle);

If you use addResourceBundle() to add another bundle with the same locale and bundle name, the new one will overwrite the old one.

After doing resourceManager.localeChain = [site: "fr_FR" ], resourceManager.getString("myBundle", "OPEN") would return "Ouvrir".

Once a resource bundle has been added to the resource manager, you can use the getResourceBundle() method to get a reference to it if you know its locale and bundle name:var rb:ResourceBundle = resourceManager.getResourceBundle("fr_FR", "myBundle");

You can remove a particular resource bundle that you have added using removeResourceBundle():resourceManager.removeResourceBundle("fr_FR", "myBundle");

You can remove all resource bundles for a particular locale using removeResourceBundlesForLocale():resourceManager.removeResourceBundlesForLocale("fr_FR");

Neither addResourceBundle(), removeResourceBundle(), nor removeResourceBundlesForLocale() cause the application to update.

Adding, removing, or changing individual resources

Given a locale and a bundle name, the getResourceBundle(locale, bundleName) method will return a reference to a resource bundle in the resource manager.

Given a resource bundle, its content property is an Object with key/value pairs for its resources.

Therefore, you can add a new resource to a resource bundle like this,

var rb:ResourceBundle = resourceManager.getResourceBundle("en_US", "controls");
rb.content<a href="%26quot%3BFOO%26quot%3B">"FOO"</a> = "foo";

change it like this,

rb.content<a href="%26quot%3BFOO%26quot%3B">"FOO"</a> = "bar";

and delete it like this:

delete rb.content<a href="%26quot%3BFOO%26quot%3B">"FOO"</a>;

None of these operations will cause the application to update.

Enumerating resources

The resource manager has sufficient APIs to allow all resources to be enumerated.

The getLocales() method will return an Array such as [site: "en_US", "ja_JP" ] which contains (in no particular order) all of the locales for which bundles exist in the resource manager.

Given a locale, the getResourceBundleNamesForLocale() method will return an Array such as [site: "controls", "containers" ] which contains (in no particular order) all of the bundle names for that locale.

Given a locale and a bundle name, the getResourceBundle() method will return a reference to a resource bundle owned by the resource manager.

Given a resource bundle, its content property is an Object with key/value pairs for its resources.

Therefore, one can iterate over all resources in the resource manager as follows:

for each (var locale:String in resourceManager.getLocales())
{ 
  for each (var bundleName:String in resourceManager.getResourceBundleNamesForLocale(locale)) 
  { 
    trace("locale =", locale, "bundleName =", bundleName); 
    var bundle:ResourceBundle = resourceManager.getResourceBundle(locale, bundleName); 
    for (var key:String in bundle.content) 
    {             
      trace(key, bundle.content<a href="key">key</a>);         
    }
  }
}

Changes to framework classes

The current pattern that most framework classes use to access resources is:

loadResources(); // executes at class initialization time

<a href="ResourceBundle%28%26quot%3Bcore%26quot%3B%29">ResourceBundle("core")</a>
private static var packageResources:ResourceBundle;

private static var viewSourceMenuItem:String;

private static function loadResources():void
{ 
  viewSourceMenuItem = packageResources.getString("VIEW_SOURCE"); 
}

private function initContextMenu():void 
{ 
  // use viewSourceMenuItem 
}

All such code will be changed to no longer cache either bundle references or resource values in static or instance variables. Instead, we will fetch resources just-in-time from the resource manager:

private function initContextMenu():void 
{ 
  var viewSourceMenuItem:String = resourceManager.getString("core", "VIEW_SOURCE"); 
  ... 
}

Many resources, such as RTE message strings, are fetched and immediately used without being stored anywhere else. For such resources, this scheme requires no additional code to support a runtime locale change. (If there is an RTE after a locale change, the message will be fetched from a resource bundle for the new locale.) However, in a few cases (such as the context menu code above), the resource value gets stored elsewhere (such as in a menu). To support runtime locale changes in these classes, we will overriding resourcesChanged() and push the new resource values into whatever objects need to be updated.

The keys of all resources will be capitalized (VIEW_SOURCE rather than viewSource), to better indicate that they are conceptually constants.

There are several framework resources which are in fact unused; these will be removed.

XD

We should consider asking XD to redesign a globalized DownloadProgressBar that is suitable for use without localization.

API Description

Additions to MXML Language and ActionScript Object Model

Revisions to existing classes/interfaces

mx.core.UIComponent

Each UIComponent will have a new resourceManager property, which is a reference to the resource manager instance. It will be a bindable property to enable runtime locale changes to be implemented using binding expressions like {resourceManager.getString("Hello")}

UIComponent will also have a new protected resourceChangeHandler() method, which will get called whenever the resource manager dispatches a resourceChangeevent. Components should override this to do whatever is appropriate to update themselves based on possibly changed resources. The base method causes binding expressions like {resourceManager.getString("Hello")} to reevaluate.

package mx.core
{  
  public class UIComponent ...
  {     
    <a href="Bindable%28%26quot%3BresourceChange%26quot%3B%29">Bindable("resourceChange")</a>      
    /**      
     *  Documentation is not currently available.      
     */     
    public function get resourceManager:IResourceManager;

    /**      
     *  Documentation is not currently available.      
     */     
    protected function resourceChangeHandler(event:FlexEvent):void 
  }
}

mx.preloaders.Preloader

Preloader will have a new method, getResourceModuleURLs().

package mx.preloaders
{

public class Preloader 
{ 
  /** 
   * Returns an Array of URLs from which resource modules should be preloaded. 
   * The implementation obtains this Array by splitting the comma-separated 
   * list specified in the parameters property of the Application instance. 
   * If that property has not been set, then a Array with a single URL 
   * is returned; that URL is formed from the SWF's URL, with the 
   * SWF's name replaced by locale/{locale} where {locale} is the
   * string returned by Capabilities.language.
   * Developers may override this method to specify an arbitrary
   * list of resource modules URLs, which will be preloaded in the
   * order that they appear in the Array.
   */
   protected function getResourceModuleURLs():Array /* of String */;
}

}

mx.resources.Locale

The getCurrent() method will be deprecated, because it supports only a single current locale whereas in Moxie an application can have a locale chain. It will continue to work for applications with a compiled-in resources for a single locale.

package mx.resources
{

public class Locale
{
    /**
     *  This method is deprecated.
     *  Please use the getLocales() method and the localeChain property
     *  of the IResourceManager interface instead.
     *
     *  This method can still be used if your application
     *  has compiled-in resources for a single locale.
     *  If your application has compiled-in resources for multiple locales,
     *  or if it has no compiled-in resources because it loads all of them
     *  from resource modules at runtime, then this method will return null.
     */
    public static function getCurrent(sm:ISystemManager):Locale
}

mx.resources.ResourceBundle

The static getResourceBundle() method will be deprecated. It will continue to work for compiled-in resources for a single locale, with the same static-initialization problems that the current implementation has.

Three new properties will be added: locale, bundleName, and content.

The constructor will be changed to require locale and bundleName as arguments.

The accessor methods -- getContent(), getBoolean(), getNumber(), getString(), getStringArray(), and getObject() -- will be deprecated. Developers should now access resources via the resource manager instead.

package mx.resources
{

public class ResourceBundle
{
    /**
     *  This method has been deprecated.
     *  Please use the getResourceBundle() method of the IResourceManager interface instead.
     */
    public static function getResourceBundle(
                             baseName:String,
                             currentDomain:ApplicationDomain = null):
                             ResourceBundle

    /**
     *  The locale for this ResourceBundle,
     *  such as "en_US" or "ja_JP".
     */     
    public function get locale():String;

    /**
     *  The name of this ResourceBundle.
     *
     *  If this ResourceBundle was compiled from a .properties file,
     *  the bundleName will be the base file name.
     *  For example, compiling myResources.properties will create
     *  a bundle named "myResources".
     *
     *  Each ResourceBundle in an application must have
     *  a unique locale/bundleName pair.
     *  For example, you can have two bundles named "myResources",
     *  one for locale "en_US" containing U.S. English resources
     *  and one for locale "ja_JP" containing Japanese resources.
     */     
    public function get bundleName():String;

    /**
     *  An Object containing name/value pairs
     *  for the resources in this ResourceBundle.
     * 
     *  You can iterate over all of the resources in a ResourceBundle
     *  by using a for-in loop over this Object.
     *
     *  You can access a particular resource in a ResourceBundle
     *  as content<a href="resourceName">resourceName</a>.
     *  However, you should generally access all resources
     *  through the ResourceManager, using methods
     *  such as getString() on the IResourceManager interface.
     */     
    public function get content():Object

    /**
     *  Constructor.
     */
    public function ResourceBundle(locale:String, bundleName = null)

    /**
     *  This method has been deprecated.
     *  Please use the content property instead.
     */
    protected function getContent():Object

    /**
     *  This method has been deprecated.
     *  Please use the getBoolean() method of the IResourceManager interface instead.
     */
    public function getBoolean(key:String, defaultValue:Boolean = true):Boolean

    /**
     *  This method has been deprecated.
     *  Please use the getNumber() method of the IResourceManager interface instead.
     */
    public function getNumber(key:String):Number

    /**
     *  This method has been deprecated.
     *  Please use the getString() method of the IResourceManager interface instead.
     */
    public function getString(key:String):String

    /**
     *  This method has been deprecated.
     *  Please use the getStringArray() method of the IResourceManager interface instead.
     */
    public function getStringArray(key:String):Array

    /**
     *  This method has been deprecated.
     *  Please use the getObject() method of the IResourceManager interface instead.
     */
    public function getObject(key:String):*
}

}

New classes/interfaces

mx.events.ResourceEvent

A ResourceEvent is dispatched by the IEventDispatcher that is returned by the IResourceManager method loadResourceBundles(). It is similar to the StyleEvent that is dispatched by the IEventDispatcher that is returned by StyleManager.loadStyleDeclarations().

package mx.events
{

public class ResourceEvent extends ProgressEvent
{
    /**
     *  Dispatched when the resource module SWF has finished downloading.
     */   
    public static const COMPLETE:String = "complete";

    /**
     *  Dispatched when there is an error downloading the resource module SWF.
     */   
    public static const ERROR:String = "error";

    /**
     *  Dispatched while the resource module SWF is downloading.
     */   
    public static const PROGRESS:String = "progress";

    /**
     *  Constructor.
     */
    public function ResourceEvent(type:String, bubbles:Boolean = false,
                                  cancelable:Boolean = false,
                                  bytesLoaded:uint = 0, bytesTotal:uint = 0,
                                  errorText:String = null);

    /**
     *  The error message if the <code>type</code> is <code>ERROR</code>;
     *  otherwise, it is <code>null</code>.
     */
    public var errorText:String;
}

}

mx.resources.IResourceManager

package mx.resources
{

/**
 *  The interface for a resource manager.
 *
 *  An application has a single instance of a resource manager,
 *  which maintains a single copy of each resource
 *  available to the application.
 *  All resources should be accessed through the resource manager,
 *  using methods such as getString,
 *  which will search resource bundles for various locales
 *  not directly from a resource bundle.
 *
 *  A resource manager must
 *
 *  Each UIComponent has a resourceManager property
 *  which is a reference to the resource manager.
 *  Other types of components can obtain a reference
 *  to the resource manager as Application.application.resourceManager.
 */
public class IResourceManager extends IEventDispatcher
{
    /**
     *  An Array of locales, such as <a href="%20%26quot%3Bja_JP%26quot%3B%2C%20%26quot%3Ben_US%26quot%3B%20"> "ja_JP", "en_US" </a>,
     *  to be searched in order for resources.
     *
     *  For example, if you call getString("MyApp", "OPEN")
     *  then the ResourceManager will first look for a resource
     *  with key "OPEN" in the Japanese resource bundle named "MyApp".
     *  If one is not found there, it will then look for a resource
     *  with key "OPEN" in the English resource bundle named "MyApp".
     *  If one is not found there, getString() will return null.
     */
    function get localeChain():Array /* of String */;
    function set localeChain(value:Array /* of String */):void;

    /**
     *  Asynchronously loads a resource module from the specified URL.
     *  When the load completes, the resource bundles in the module
     *  are added to the resource manager using addResourceBundle().
     *
     *  @return An IEventDispatcher implementation that supports
     *  ResourceEvent.PROGRESS, ResourceEvent.COMPLETE, and
     *  ResourceEvent.ERROR.
     */
    function loadResourceBundles(url:String):IEventDispatcher;

    /**
     *  Unloads a resource module previously loaded from the specified URL.
     *  Any resource bundles in the resource manager that came from
     *  that module are removed from the resource manager using
     *  removeResourceBundle().
     */
    function unloadResourceBundles(url:String):void;

    /**
     *  Adds a resource bundle to the resource manager,
     *  so that its resources can be accessed by methods
     *  such as getString().
     */
    function addResourceBundle(bundle:ResourceBundle):void;

    /**
     *  Removes a resource bundle from the resource bundle
     *  so that its resources can no longer be accessed
     *  by methods such as getString().
     *  If there are no other references to the resource bundle,
     *  it is then subject to garbage collection.
     */
    function removeResourceBundle(locale:String, bundleName:String):void;

    /**
     *  Removes all resource bundles for a specified locale
     *  from the resource manager.
     */
    function removeLocale(locale:String):void;

    /**
     *  Dispatches a resourceChange event, which causes the entire
     *  application to re-lookup all resources.
     */
    function update();

    /**
     *  Returns an Array of Strings, such as <a href="%20%26quot%3Ben_US%26quot%3B%2C%20%26quot%3Bja_JP%26quot%3B%20"> "en_US", "ja_JP" </a>,
     *  specifying all the locales for which the ResourceManager owns resources.
     *  The ordering of locales in the Array is not specified;
     *  consider it a set rather than an ordered list.
     */
    function getLocales():Array /* of String */;

    /**
     *  Returns an Array of Strings, such as <a href="%20%26quot%3Bcore%26quot%3B%2C%20%26quot%3Bcontrols%26quot%3B%20"> "core", "controls" </a>,
     *  specifying all the bundle names for resource bundles 
     *  for the specified locale in the resource manager.
     *  The ordering of bundle names in the Array is not specified;
     *  consider it a set rather than an ordered list.
     */
    function getResourceBundleNamesForLocale(locale:String):Array /* of String */;

    /**
     *  Returns a reference to a resource bundle
     *  that is owned by the resource manager.
     */
    function getResourceBundle(locale:String, bundleName:String):ResourceBundle;

    /**
     *  Looks up the specified resource String or Class in the specified resource bundle
     *  using the current sequence of locales in the localeChain.
     *  If the resource is not found, this method returs undefined.
     */
    function getObject(bundleName:String, key:String):*;

    /**
     *  Looks up the specified resource string in the specified resource bundle
     *  using the current sequence of locales in the localeChain.
     *  If the resource is not found, this method returs null.
     */
    function getString(bundleName:String, key:String):String;

    /**
     *  Looks up the specified resource string in the specified resource bundle
     *  using the current sequence of locales in the localeChain
     *  and splits it into an Array of Strings.
     *  If the resource is not found, this method returs null.
     */
    function getStringArray(bundleName:String, key:String):Array /* of String */;

    /**
     *  Looks up the specified resource string in the specified resource bundle
     *  using the current sequence of locales in the localeChain
     *  and converts it to a Number.
     *  If the resource is not found, this method returs NaN.
     */
    function getNumber(bundleName:String, key:String):Number;

    /**
     *  Looks up the specified resource string in the specified resource bundle
     *  using the current sequence of locales in the localeChain
     *  and converts it to a Boolean.
     *  If the resource is not found, this method returs false.
     */
    function getBoolean(bundleName:String, key:String):Boolean;

    /**
     *  Looks up the specified resource class in the specified resource bundle
     *  using the current sequence of locales in the localeChain.
     *  If the resource is not found, this method returs null.
     */
    function getClass(bundleName:String, key:String):Class;
}

}

mx.resources.IResourceModule

package mx.resources
{

/**
 *  The interface for resource modules
 *  compiled from .properties files by the MXML compiler.
 */
public interface IResourceModule
{
    /**
     *  Creates an instance of each resource bundle in this resource module
     *  and returns them as an Array.
     *  The order of resource bundles in the Array is not specified.
     */
    public function createResourceBundles():Array;
}

}

Flex Feature Dependencies

{...} databinding syntax doesn't work inside XML and XMLList literals in MXML. (Bug 194124) This should be fixed so that menus can be easily localized using @Resource.
The XD team should design a text-free DownloadProgressBar that doesn't require localization, because this UI appears too early for runtime localization unless the text is passed in via Application.parameters.

Performance

Startup time should be slightly faster when using resources compiled into the application SWF, because we will create only one instance of each bundle.

Startup time will be somewhat slower if preloading a resource module, because an extra HTTP request is required, but this is just the normal overhead of using modules.

Accessing resources will be slightly slower than in the previous scheme, because we will not be cacheing anything. However, on my 1.8 GHz Pentium, I'm able to do about 350,000 calls to resourceManager.getString() per second when the resource is found in the first locale in the locale chain. This seems plenty fast enough given that most resources are fetched 0 or 1 times.

Memory usage will be less, because we will create only one instance of each bundle.

Globalization

Resource keys and string values are stored as standard Flash strings, whose characters lie in the Basic Multilingual Plane of Unicode.

The .properties files containing resources are parsed as UTF-8.

This feature does not use runtime text input, so IME issues don't arise.

[[ /resourceManager.getString("OK_LABEL" ]]

</object>

Related

Wiki: Flex 3.0 Planning

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.