A word of warning upfront: The following post is a rough summary of a week’s trial and error. Some of the points made may be correct, but there may also be better ways of doing things than what’s described here. Use at your own risk.
Some links that I found helpful but which admittedly also confused me greatly at times include The flash.globalization package in Flash Player as well as what I consider one of the best intros to the subject, an article by Charles Bihis on Devnet. Charles’ article should be your first stop as it covers important setup steps such as compiler options and steps needed to compile framework resources (a challenge in itself and one I will get back to later in this post).
Generally speaking, Flex makes it easy to localise applications for multiple languages. Classes such as the ResourceManager are very helpful, and implementing localised labels, strings and so on is pretty simple. What I found not so simple is to determine the correct initial locale that should be set. And in typical flex fashion that was only the tip of the iceberg…
Challenge 1: Detecting the correct locale
AIR based applications (with their deeper integration with the operating system) can ‘sniff’ the user’s preferred locale via and LocaleID.DEFAULT will generally give you the right choice. Not so with browser based Flex applications – Flex only gives you Capabilities.language which returns the user’s language code but not the country, region, variant of any other details. In short it’s insuffcient.
So this means that you should find a means of detecting a user’s preferred locale (or series of locales called LocaleChain( by other means, for example by checking the Accept-Language HTTP Header returned by the browser when the user makes a request to your site.
The Accept-Language Header will either return a single locale such as en-us, or series thereof with a preference weighting such as ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4.
This page has some hints on the topic.
It is then down to your application server of choice (basically the page that embeds the SWF) to pass this local or localeChain to Flex.
Challenge 2: Passing the detected locale to Flex
In order for a localised Flex application to become ‘active’ (by active I mean setting the correct locale) you need to essentially do the following:
resourceManager.localeChain = YOUR_CHOSEN_LOCALE
YOUR_CHOSEN_LOCALE in this case could be a single locale such as ‘en_US’ or a chain of locales in preference order such as ‘en_US, en, de_DE’
Your first impulse may be to simply pass this ti the SWF as it stands via a variable named localeChain. But watch out. The flashvar called ‘localeChain’ is a special case. If you pass (say via SWFObject) a variable (and accompanying value) called localeChain to a SWF containing a Flex application then it will force that locale to be set inside the app, regardless of whether the application has actually been localised for said locale. For example:
flashvars.localeChain = “hi_IN”;
will essentially force your app to load the Hindi version of your app. This can be problematic if said locale does not exist within the application as it will result in no labels being displayed and your app essentially breaks.
Therefore if you choose to pass and set the locale in this way then make sure to specify a fallback locale that is present, usually en_US. So the complete flashvar line would look like this:
flashvars.localeChain = “hi_IN, en_US”;
This will ensure that en_US is used if hi_IN is not available.
Challenge 3: Passing localeChain via flashvars
Oh dear, this is a minefield. You may thinking ‘great, I just use the browser’s header information, extract the locales and pass them to my app. Job done.’ But this would not be Flex if it would simply work 🙂
Notice the format for the locales that is being returned by the Accept-Language HTTP Header? Here’s a selection of locales I captured:
For those using ColdFusion, here’s the code to capture the Header:
<cfset v = getHttpRequestData().headers[ “Accept-Language” ]>
<cffile action=“append” file=“#GetDirectoryFromPath(thisPath)#\log.txt” output=“#v#” addnewline=“Yes”>
But back to the topic. Notice anything about those locales? Yes, they are inconsitent in their formatting, some have upper and lowercase letters, some contain a weighting, and they all have a dash instead of an underscore (en-us instead of en_US for example). Small detail, big impact.
Remember the format being used when you compile your framework resources (check if you donlt remember)? If you are on OSX have a look in /Applications/Adobe Flash Builder 4/sdks/188.8.131.5277/frameworks/locale/
The bundles are usually in the format of xx_XX and not xx-XX. I thought I’d be slick and compile them with a dash instead, but that did not work:
/Applications/Adobe Flash Builder 4/sdks/184.108.40.20677/frameworks/projects/spark/bundles/tr-TR/textLayout.properties(10): col: 23 Error: Syntax error: expecting leftparen before minus.
public function tr-TR$textLayout_properties()
More about creating resources here.
I thought it would be best to have a consistent formatting, but as it appears only the formatting with underscores is acceptable for the framework bundles.
And if this was not complicated enough, you are in for a surprise if you use the latest LocaleID class and methods. Here Adobe decided to go with the Unicode Technical Standard #35and uses the dash syntax such as en-US. And to be honest that’s probably a good move as the browsers implement this latest standard, preferring dashes over underscores.
For good measure I tried passing a localeChain of ‘hi-IN’ (using a dash) and this resulted in the app breaking. It was clearly trying to apply a bundle that did not exist… I think it’s therefore save to say that if you plan on setting the localeChain using the dash syntax locale then it’s very likely NOT to work.
Challenge 3: Matching the locale in your app
With the above in mind, I figured it’s probably best for me to pass the Accept-Language HTTP Header to my app via another flashvar (I named it ‘preferredLocales’) and attempt to perform some match-making myself. Whenever I set the localeChain on ResourceManager now I make sure that ‘en_US’ is the last entry, meaning if all else fails the app launches in US English.
The Flex docs suggest that matching the user’s preferred locale to one of the ones present in your app is easy (in Player 10.1 at least): use LocaleID.determinePreferredLocales()
“ Returns a list of acceptable locales based on a list of desired locales and a list of the locales that are currently available. The resulting list is sorted according in order of preference.“
Perfect, just what we need. Well if I could get it to work. Whatever I tried I got 0 results from this method. What am I missing? Here’s an example code snippet:
var want:Vector.<String> = new Vector.<String>( [ “en_US”, “de”, “de_DE”, “en-US”, “nl_NL”] );
var result:Vector.<String> = LocaleID.determinePreferredLocales( want, have );
// always returns 0 trace(result.length);
So for now I am moving a very manual approach which looks something like this:
1) Let server detect locales via Header
2) Pass to SWF
3) Try and match the first preferred locale to one that’s available in the app
4) If no match, try and match language
5) In all cases use en_US as second item in localeChain, and always fall back onto en_US
So that’s my approach, admittedly in more than a nutshell. Hopefully this post will attract some comments and we can all make more sense out of this topic. I’m also hoping to add to this post at some point.