Localization

Flex includes robust support for localizing your applications.

Introduction to localization

Localization is the process of including assets and formatting an application to support a locale. A locale is the combination of a language and a country code; for example, en_US refers to the English language as spoken in the United States, and fr_FR refers to the French language as spoken in France. To localize an application for the US and France, you would provide two sets of assets, one for the en_US locale and one for the fr_FR locale.

Locales can share languages. For example, en_US and en_GB (Great Britain) are different locales and therefore use different sets of assets. In this case, both locales use the English language, but the country code indicates that they are different locales, and might therefor use different assets. For example, an application in the en_US locale might spell the word “color”, whereas the word would be “colour” in the en_GB locale. Also, units of currency would be represented in dollars or pounds, depending on the locale, and the format of dates and times would also be different.

Localization goes beyond formatting numbers and translating strings that are used in your application. It can also include any type of asset such as audio files, images, styles, SWF files, and video files.

There are two ways to localize an application:
  • Using built-in localization support. Flex includes localization-aware classes for the following:

    • Currency formatters

    • Number formatters

    • Date and time formatters

    • Layout mirroring

    • Bidirectional text

    • Sorters and collators

  • Using resource bundles. Resource bundles let you define assets such as strings and images for various locales and use those assets at run time.

You can use a combination of these approaches, depending on how much of your application needs to be localized.

Setting the locale

When an application starts up, you should generally determine which locale the user wants and then set the application’s locale style property to that locale. This ensures that the localization-aware classes such as formatters and sorters behave as the user expects. If you use resource bundles, then you should use some method to determine the user’s preferred locale, and load the appropriate resource bundle.

To determine which locale your users want to use, you can use one or more of the following methods:

  • User prompt — You can start the application in a default locale, and then ask the user to choose their preferred locale from a list of pre-determined locales.

  • Capabilities.language — The Capabilities.language property in ActionScript provides the language code for Adobe® Flash® Player and Adobe AIR™. On English systems, this property returns only the language code, not the country code. This property returns the user interface language, which refers to the language used for all menus, dialog boxes, error messages, and help files in Flash Player and AIR.

  • Accept-Language header — When a browser makes an HTTP request, it sends a list of languages to the server in the Accept-Language header. You can parse this header in your HTML wrapper and pass it as a flashVars variable into your Flex application. If you use URLLoader to load an application, you will not have access to this header.

  • Browser preferences — You can access the browser’s language settings in the HTML wrapper by using JavaScript. You can then pass the values as flashVars variables to your application. You can also use the ExternalInterface API to access the values from within your Flex application. The language is typically accessible via the Navigator object’s language property. Depending on the browser, you also access the userLanguage or systemLanguage properties. For more information on using the ExternalInterface API, see Using the ExternalInterface API to access JavaScript.

Built-in localization support

Typically, you set the locale style property of the application or the component to the value of the LocaleID.DEFAULT property. This instructs Flex to use the client’s operating system for locale information (such as the currency symbol, date/time format, or preferred sorting rules).

You can set the locale style property on an individual component or on the entire application. The style is inheritable, which means if you set it on the application, then all components in that application inherit that style, unless they specifically override it. If you do not set the locale style property, the compiler uses the global defaults that are defined in the defaults.css style sheet. In this case, the default value is “en”.

The following example sets the value of the application’s locale style property to the LocaleID.DEFAULT constant. When you run this example, the value of the DateTimeFormatter’s actualLocaleIDName property will be whatever language preference you have set in your operating system.
<?xml version="1.0"?>
<!-- l10n/LocaleApp.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:mx="library://ns.adobe.com/flex/mx" 
    xmlns:s="library://ns.adobe.com/flex/spark"
    creationComplete="initApp()">

    <fx:Declarations>
        <s:DateTimeFormatter id="dateTimeFormatter"/>
    </fx:Declarations>

    <fx:Script>
        <![CDATA[
        import flash.globalization.LocaleID;
        
        public function initApp():void {
            this.setStyle("locale", LocaleID.DEFAULT);
        }
        
        public function doSomething():void {
            l1.text = dateTimeFormatter.actualLocaleIDName;
        }
        ]]>
    </fx:Script>    

    <s:VGroup>
        <s:Label id="l1"/>
        <s:Button id="b1" label="Click Me" click="doSomething()"/>
    </s:VGroup>
    
</s:Application>

The executing SWF file for the previous example is shown below:

You can create a LocaleID object to retrieve data about the locale. You can use methods such as getKeysAndValues(), getLanguage(), getRegion(), getScript(), getVariant(), and isRightToLeft() to set properties and create other objects in your application.

Resource bundles

For applications that use resource bundles, you must explicitly compile assets for the supported locales into the application. You can then let your users choose from a list of the compiled-in locales. After determining the user’s preferred locale, you set the value of the ResourceManager’s localeChain property so that that locale’s assets are used in your application.

Formatting dates, numbers, and currencies

Flex includes the following Spark classes for formatting dates, numbers and currencies:

Each of these classes is based on a similar class in the flash.globalization.* package. Flex lets you instantiate these formatters in MXML as well as ActionScript.

The formatters’ output might differ depending on the runtime environment of the client. The formatters call the underlying operating system which can return different results, depending on the device.

The Spark formatters inherit the locale style property, have bindable properties and methods, and generate events.

As with other non-visual classes, you declare formatters in the <fx:Declarations> block of your applications.

The following example uses a CurrencyFormatter and a NumberFormatter to display formatted values in the client’s default locale. The currency symbol and grouping separator (sometimes called the thousands separator) are defined by whatever the client machine’s locale is. The client’s preferred locale is obtained by using the LocaleID.DEFAULT property.
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/CurrencyFormatterApp.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx"               
               minWidth="955" minHeight="600">             
    <s:layout>
        <s:VerticalLayout/>
    </s:layout> 
    <fx:Declarations>
        <!-- Get the client's default locale from the OS 
            by using the LocaleID.DEFAULT constant. -->
        <s:CurrencyFormatter id="curFormatter" 
            useCurrencySymbol="true" 
            locale="{LocaleID.DEFAULT}"/>
        <s:NumberFormatter id="numFormatter" 
            locale="{LocaleID.DEFAULT}"/>
    </fx:Declarations>
    
    <fx:Script>
        <![CDATA[
            import flash.globalization.LocaleID;            
        ]]>
    </fx:Script>
    
    <s:Label id="la1" text="{curFormatter.format(4500000)}"/>
    <s:Label id="la2" text="{numFormatter.format(4500000)}"/>
    
</s:Application>

The executing SWF file for the previous example is shown below:

You can explicitly let a user select their locale, and bind your formatters to the selected locale. You can also create a new LocaleID object based on the selected locale so that you can get other details about the locale.

The following example lets a user select from a list of locales, and then returns various information about that locale, including the script, language, and currency symbol:
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/CurrencyFormatterSelector.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" 
               minWidth="955" minHeight="600">
    <fx:Declarations>
        <s:CurrencyFormatter id="curFormatter" 
            useCurrencySymbol="true" 
            locale="{myDDL.selectedItem}"/> 
    </fx:Declarations>
    
    <fx:Script>    
        import flash.globalization.LocaleID;
    
        private function updateLocaleDetails():void {
            var locale:LocaleID = new LocaleID(myDDL.selectedItem);
            l6.text = "Locale Script: " + locale.getScript();
            l7.text = "Locale Region: " + locale.getRegion();
            l8.text = "Locale Language: " + locale.getLanguage();
        }
    </fx:Script>
    
    <s:VGroup>
        <s:HGroup>
            <s:Label text="Select Locale ID: "/>
            <s:DropDownList id="myDDL"
                change="updateLocaleDetails()"
                width="200"
                prompt="Select One"
                labelField="product">
                <mx:ArrayCollection>
                    <fx:String>ar-SA</fx:String>
                    <fx:String>da-DK</fx:String>
                    <fx:String>de-DE</fx:String>
                    <fx:String>en-GB</fx:String>
                    <fx:String>en-US</fx:String>
                    <fx:String>es-ES</fx:String>
                    <fx:String>fi-FI</fx:String>
                    <fx:String>fr-FR</fx:String>
                    <fx:String>it-IT</fx:String>
                    <fx:String>ja-JP</fx:String>
                    <fx:String>ko-KR</fx:String>
                    <fx:String>nb-NO</fx:String>
                    <fx:String>nl-NL</fx:String>
                    <fx:String>pt-BR</fx:String>
                    <fx:String>ru-RU</fx:String>
                    <fx:String>sv-SE</fx:String>
                    <fx:String>zh-CN</fx:String>
                    <fx:String>zh-TW</fx:String>
                </mx:ArrayCollection>           
            </s:DropDownList>
        </s:HGroup>

        <s:Label id="currencyLabel" 
            fontWeight="bold" 
            fontSize="16" 
            text="{curFormatter.format(4500000)}"/>
        <s:Label id="l2" text="Actual Locale ID Name: {curFormatter.actualLocaleIDName}"/>
        <s:Label id="l4" text="Currency Symbol: {curFormatter.currencySymbol}"/>
        <s:Label id="l5" text="Currency ISO Code: {curFormatter.currencyISOCode}"/>
        <s:Label id="l6" text="Locale Script: "/>
        <s:Label id="l7" text="Locale Region: "/>
        <s:Label id="l8" text="Locale Language: "/>

        <s:Label text="All Available Locale ID Names: "/>
        <s:TextArea id="ta1" 
            text="{String(CurrencyFormatter.getAvailableLocaleIDNames())}" 
            width="300" height="400"/>

    </s:VGroup>
</s:Application>

The executing SWF file for the previous example is shown below:

In addition, this example shows a list of all available locales. These are the locales that are supported by the client’s operating system.

In some cases, you do not necessarily want your application to support all possible locales, or you want your application to use only one locale. In these cases, you can set the locale(s) programmatically. To do this, you can use the locale style property to apply a particular locale to individual components or to an entire application; for example:
<fx:Style> 
    @namespace s "library://ns.adobe.com/flex/spark"; 
    s|Application { 
        locale: 'fr-FR'; 
    } 
</fx:Style>

You can also use resource bundles to configure the formatters. For more information, see Custom formatting with resource bundles.

For more information on using the Spark formatter classes, see Formatting Data.

Mirroring and bidirectional text

One aspect of localizing applications is the direction of the text. Some languages are read from left to right, such as English, and other languages are read from right to left, such as Hebrew.

The layout of the controls and the direction of text inputs should match the direction of the language. For example, an application that uses an RTL language should layout on the right side of the application’s stage.

There are two primary ways to control the appearance of an application that supports both left-to-right (LTR) and right-to-left (RTL) languages:
  • Layout mirroring — Layout mirroring is the process of flipping the appearance of the layout controls. For example, in an RTL application, vertical scroll bars should appear on the left instead of the right. For horizontal scrollbars, the thumb should appear on the right side of the control instead of the left. Layout mirroring is commonly used to make a UI match the direction of an RTL language.

  • Text direction — Text direction defines the direction that text flows in the text controls. For RTL languages such as Hebrew and Arabic, the characters should render from the right side to the left. This also includes bidirectional text support, where a control might need to render some of the text RTL, and some of the text LTR in the same control. The Spark text controls handle directionality of the text internally. They do not use mirroring to render text.

To change an application’s layout from LTR to RTL, set the layoutDirection property of the application object to "rtl". The direction of text is automatically set based on the characters used, but you should also set the direction style property to "rtl" if the majority of your text is RTL. This property instructs the text control to apply RTL rules to the text.

For more information, see Mirroring and bidirectional text.

Transforming text

The spark.globalization.StringTools class provides methods to convert letters to minuscules (lowercase letters) or majuscules (uppercase letters) for languages based on writing systems that incorporate the concept of capitalization (essentially, the Armenian, Latin, Cyrillic, and Greek alphabets). Using the StringTools class enables your application to use language-specific rules to perform those transformations.

The Spark StringTools class is based on the flash.globalization.StringTools class. This lets you use this class in MXML as well as ActionScript, and also lets it inherit the value of the locale style property. In addition, the Spark StringTools class has bindable properties and methods and generates events.

As with formatters, you declare an instance of the StringTools class in the <fx:Declarations> block of your application.

The following example uses the StringTools class to convert a string to all lower case:
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/TransformingText.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    <fx:Declarations>
        <s:StringTools id="st1" locale="{LocaleID.DEFAULT}"/>
    </fx:Declarations>
            
    <fx:Script>
        <![CDATA[
            import flash.globalization.LocaleID;
        ]]>
    </fx:Script>

    <s:Label id="l1" text="{st1.toLowerCase('THIS LABEL WAS IN ALL CAPS.')}"/>
</s:Application>

The executing SWF file for the previous example is shown below:

Note that some characters for some languages do not have the same character length before and after case conversion. For example, the lower case sharp-s (“ß”) is represented with two characters, “SS”, when converted to upper case in the German language.

Sorting and matching

When ordering a list that is presented visually to the user, it is essential to follow the user’s expectations. It is not just a question of aesthetics: expecting it at the end of the list, a Swedish user might fail to find an element that begins with “ö” if placed next to the plain “o”. Fortunately, avoiding that problem is easy: sort the list with a Sort or SortingCollator class that is configured for the user’s locale.

The sorting and matching classes’ output might differ depending on the runtime environment of the client. The collators call the underlying operating system which can return different results, depending on the device.

As with formatters, the Spark Sort and SortField classes inherit the value of the locale style property on the application, or you can set the locale style on the component.

The following example creates a Spark Sort with two Spark SortFields. Select a locale from the the drop-down list to change the sort order to match the preferences of the selected locale.
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/SortLocale2.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" 
               minWidth="955" minHeight="600">
    <fx:Declarations>
        <s:Sort id="sortbyLastName_FirstName">
            <s:fields>
                <s:SortField name="last"/>
                <s:SortField name="first"/>
            </s:fields>
        </s:Sort>
        <mx:ArrayCollection id="collection" sort="{sortbyLastName_FirstName}">
            <mx:source>
                <fx:Object first="Anders" last="Öhlund"/>
                <fx:Object first="Eileen" last="Oehland"/>
                <fx:Object first="Aiden" last="Zorn"/>
                <fx:Object first="Steve" last="Ohlin"/>
            </mx:source>
        </mx:ArrayCollection>
    </fx:Declarations>
    
    <fx:Script>
        /* Sets the locale style on the document UI component.
            The SortField and Sort objects defined in the 
            fx:Declarations section will inherit this style. */
        private function updateSort(e:Event):void {
            setStyle('locale', myDDL.selectedItem); 
            collection.refresh();
        }
    </fx:Script>
    
    <s:VGroup>
        <s:HGroup>
            <s:Label text="Select Locale ID Name: "/>
            <s:DropDownList id="myDDL" prompt="Select One"
                width="200"
                labelField="product"
                change="updateSort(event)">
                <mx:ArrayCollection>
                    <fx:String>en-US</fx:String>
                    <fx:String>sv-SE</fx:String>
                    <fx:String>zh-CN</fx:String>
                </mx:ArrayCollection>           
            </s:DropDownList>
        </s:HGroup>

        <s:DataGrid 
            dataProvider="{collection}" 
            creationComplete="{collection.refresh()}">
            <s:columns>
                <s:ArrayList>
                    <s:GridColumn dataField="last"/>
                    <s:GridColumn dataField="first"/>
                </s:ArrayList>
            </s:columns>
        </s:DataGrid>

    </s:VGroup>
</s:Application>

The executing SWF file for the previous example is shown below:

The following example uses the Spark Sort and SortField classes in ActionScript to create a locale-dependent sort:
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/SweeterSwedishSort.mxml -->
<s:Application
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    width="500"
    creationComplete="initApp()">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    
    <fx:Script>
        <![CDATA[
            import mx.collections.ISort;
            import mx.collections.ISortField;
            import mx.collections.ArrayCollection;
            import spark.collections.Sort;
            import spark.collections.SortField;
            
            private var defaultSort:ISort = new Sort();
            private var swedishSort:ISort = new Sort();

            private var a:Array = new Array('Öhlund','Oehland','Zorn','Ohlin');
            private var names:ArrayCollection = new ArrayCollection(a);

            private var sortField:SortField = new SortField(null);                

            private function initApp():void {
                originalOrder.text = "Original Order: " + names.toString();

                /* Add a style client, so sortField can pick up the locale style. */
                addStyleClient(sortField as spark.collections.SortField);

                /* Add the sortField to the fields array of the sorts. */
                defaultSort.fields = [sortField];
                swedishSort.fields = [sortField];                
            }
            
            private function changeSortOrder(s:String):void {
                if (s=="en-US") {
                    /* Set the locale for the SortField. */
                    sortField.setStyle("locale","en-US");

                    names.sort = defaultSort;

                    /* Refresh the ArrayCollection after changing the sort property. */
                    names.refresh();

                    ta1.text = "Default Order: " + names.toString();                
                } else {
                    /* Set the locale for the SortField. */
                    sortField.setStyle("locale","sv-SE");

                    names.sort = swedishSort;

                    /* Refresh the ArrayCollection after changing the sort property. */
                    names.refresh();

                    ta1.text = "Swedish Order: " + names.toString();                
                }
            }
        ]]>
    </fx:Script>
    
    <s:Label id="originalOrder" paddingTop="10" fontWeight="bold"/>
    <s:TextArea id="ta1" height="200" width="400"/>
    <s:HGroup>
        <s:Button label="Show Default Sort Order" click="changeSortOrder('en-US')"/>
        <s:Button label="Show Swedish Sort Order" click="changeSortOrder('sv-SE')"/>
    </s:HGroup>
</s:Application>

The executing SWF file for the previous example is shown below:

The following example shows a custom sort that also depends on locale. This example uses the SortingCollator class and defines the logic for the sort. As with formatters and StringTools, you declare collators in the <fx:Declarations> block your application.
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/SwedishSort.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
               creationComplete="initApp()">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    <fx:Declarations>
        <s:SortingCollator id="defaultSorter" locale="{LocaleID.DEFAULT}"/>
        <s:SortingCollator id="swedishSorter" locale="sv-SE"/>
    </fx:Declarations>
    
    <fx:Script>
        <![CDATA[
            import flash.globalization.LocaleID;            
            import mx.collections.ArrayCollection;
            
            private var a:Array = new Array('Öhlund','Oehland','Zorn','Ohlin');
            [Bindable]
            private var names:ArrayCollection = new ArrayCollection(a);
            
            private function initApp():void {
                originalOrder.text = "Original Order: " + names.toString();

                sortNames(defaultSorter);
                label1.text = "Default Order: " + names.toString();

                sortNames(swedishSorter);
                label2.text = "Swedish Order: " + names.toString();
            }
            
            private function sortNames(sorter:SortingCollator):void {               
                var changed:Boolean = false;                
                while (!changed) {
                    changed = true;                 
                    for (var i:int = 0; i < names.length - 1; i++) {
                        var comparisonResult:int = sorter.compare(names[i],names[i+1]);
                        if (comparisonResult > 0) { 
                            var s:String = names[i];  
                            names[i] = names[i + 1];
                            names[i + 1] = s;                           
                            changed = false;
                        }
                    }
                }               
            }           
        ]]>
    </fx:Script>    
    <s:Label id="originalOrder" paddingTop="10" fontWeight="bold"/>
    <s:Label id="label1"/>
    <s:Label id="label2"/>
</s:Application>

The executing SWF file for the previous example is shown below:

Matching is typically performed when the user searches for an item (for example, searching for a word in a document). When matching, you might want to ignore minor differences to increase the chances of finding relevant matches. For example, capitalization is commonly ignored. Diacritical marks are also often disregarded because not all users enter them as expected (either because their keyboard layout makes it difficult, or as the result of a spelling mistake).

You use the <s:MatchingCollator> tag to configure your matching rules. The following example shows how the locale and settings on the MatchingCollator can affect string comparisons:
<?xml version="1.0" encoding="utf-8"?>
<!-- l10n/MatchingCollatorExample.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" 
               minWidth="955" minHeight="600">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    <fx:Declarations>
        <s:MatchingCollator id="matchingCollator" 
            ignoreCase="false" 
            ignoreCharacterWidth="false" 
            ignoreDiacritics="{cb1.selected}" 
            ignoreKanaType="false" 
            ignoreSymbols="false" 
            numericComparison="false"
            locale="{myDDL.selectedItem}"/>
    </fx:Declarations>
    
    <fx:Script>
        <![CDATA[
            private function doCompares():void {
                label1.text = matchingCollator.compare('ä','vu').toString();
                label2.text = matchingCollator.compare('a','ä').toString();
                label3.text = matchingCollator.compare('coté','côte').toString();
            }
        ]]>
    </fx:Script>    

    <s:HGroup>
        <s:Label text="Select Locale ID: "/>
        <s:DropDownList id="myDDL"
            change="doCompares()"
            width="200"
            prompt="Select One">
            <mx:ArrayCollection>
                <fx:String>en-US</fx:String>
                <fx:String>fr-FR</fx:String>
            </mx:ArrayCollection>           
        </s:DropDownList>
        <s:CheckBox id="cb1" 
            label="Ignore Diacritics"
            change="doCompares()"/>         
    </s:HGroup>

    <s:Form>
        <s:FormItem label="{matchingCollator.actualLocaleIDName}"/>
        <s:FormItem label="String1: ä, String2: vu">
            <s:Label id="label1" text=""/>
        </s:FormItem>
        <s:FormItem label="String1: a, String2: ä">
            <s:Label id="label2" text=""/>
        </s:FormItem>
        <s:FormItem label="String1: coté, String2: côte">
            <s:Label id="label3" text=""/>
        </s:FormItem>
    </s:Form>

    <s:Label text="-1: string1 is less than string 2; 0: strings are equal; 1: string1 is greater than string2"/>
</s:Application>

The executing SWF file for the previous example is shown below:

Resource bundles

A resource bundle is a list of assets that is organized by locale. These assets can be just about anything, from strings, numbers, formats, and images to styles. You typically have a separate resource bundle for each locale that your application supports.

To localize an application with resource bundles, you first create properties files that define the localized assets. You then compile these properties files into the application as resource bundles, or create resource modules from the properties files and load them at run time. To compile resources into an application, you pass a comma-separated list of locales to the the locale compiler option. This compiler option has no effect on the spark.globalization.* classes.

You can also load pre-compiled resource modules and define resource bundles at run time. For more information on creating and using resource bundles in your applications, see Resource Bundles.