Sunday, 2 October 2011

Android: Localizing your Android app - Part 2

Part two or our little localization tutorial. For a quick recap, we learned first here how to use string resources to leverage translation in localized applications.
The image below can help refresh your memory.

This solves the problem of translating the content of strings we put in your layout xml files. But what if we need to access those strings programmatically? Thanks to the fantastic Android framework, it couldn't be easier.
Using the Resources class you can access and resource embedded in your app, including strings.
Call Resources.getString(int resourceID) to get the desired string. Resource strings are defined under R.string.*.

For instance, consider the following resource string

    <string name="app_title">Test app</string>

calling getString(R.string.app_title) returns "Test app".
You can call getResources() from the the app context to get ta resource object. More information here.

Now, what if I need different images and icons for different countries? Like the exit signs in English and French.
No problem, just name the drawable directory the same way we learned to name the values directory.
For instance, drawable-fr contains icons/images for the French locale, drawable-es for Spanish and you got the idea.
But we all learned that we should use different drawables for different screen densities, that's why we have by default the directories drawable-ldpi, drawable-mdpi and drawable-hdpi. In order to have localizable icon/images taking density under consideration, combine both qualifiers in the name of the directory. Like: drawable-fr-mdpi, which contains drawable resources for French and high density devices. More details about how to combine different qualifiers can be found here.

Ok, enough with translation, let's go talk about something more interesting now.
No, wait! Just a few more things about translating your app:
  1. Keep in mind that translated text will most likely have a different length than the original one. People say sentences in English are shorter than in French. I can't vouch for that :) but design the layout of your app considering text of variable size;
  2. it's a good idea to use resource strings for any text that will be displayed, even if that text does not need translation
  3. translating is not just about "replacing words". Be careful with the cultural significance of sentences and words.

To the other good stuff now: localizing: making your app support other cultural attributes like currency, date/time and number format.
1) The Locale class
All starts with the Locale class, which let's you get information about the current locale and all other available locales installed. To get an instance of the default Locale call the static method Locale.getDefault().
To get an instance of another available locale use one of the public fields in Locale, like Locale.GERMAN (default locale for Germany).
We'll talk more about it in the next sessions but you can find all you need to know about Locale here.

2) Date and Time
If you just want to access date or time formatted for the current locale, the easiest way is to use Calendar. (Actually I recommend calendar for and Date/Time reference. Never instantiate a Date object directly).
To get a Calendar object for a specific locale call the factory method Calendar.getInstance(Locale)
For instance
Calendar.getInstance(Locale.getDefault()) returns the calendar for the default locale
Calendar.getInstance(Locale.FRENCH) returns the calendar for the default French locale. Note that the locale in this case is more about country/culture than language.

Remember when dealing with dates you have to consider the time zone (no, just setting the locale isn't enough). To get a Calendar for a specific locale and time zone call Locale.getInstance(Timezone, Locale).
More information here.

More formatting
Formatting date and time is a very common task and to do it properly - taking the Locale under consideration - it's better to use SimpleDateFormat.This class provides facilities to format date and time anyway you need.
To instantiate a SimpleDateFormat object with a pattern and locale use SimpleDateFormat(String template, Locale locale). Don't forget to set the time zone calling SimpleDateFormat.setTimeZone(). More information here.

3) Currency and number
Formatting currency and numbers in general kind of go together. But not quite :)

First, to get general information about number formatting (like decimal separator, infinity representation, etc), use DecimalFormalSymbols. To get the decimal symbols for a particular locale use the constructor DecimalFormatSymbols(Locale).

The CurrencyClass helps us to get currency symbols for several locales (although you can get the almost the same things from DecimalFormatSymbol). Use CurrencyClass.getInstance(Locale) to get an instance representing currency for the target locale.

Formatting currency and numbers in general is a more elaborate task. The class used to format numbers in general is DecimalFormat.
DecimalFormat will take in consideration the current locale so the code prints

DecimalFormat formatter=new DecimalFormat("##.#");

123.4 for a US (English) locale and
123,4 for a French locale.

Consult the documentation to learn more about formatting patterns but on you want to keep in mind is "$", used to format currency.

The problem with DecimalFormat is that it's not possible to get a localized instance of a DecimalFormat object through the class itself (to the moment, AFAIK).To get a localized object we need to use NumberFormat.
A call to NumberFormat.getInstance(Locale) will return an localized version of an object that has NumberFormat as base class. The documentation says it's possible that this object will not be a DecimalFormat object but so far the Android SDK always returns DecimalFormat (since it's the only numeric subclass of NumericFormat).

Calling NumberFormat.getInstance(Locale) will return a NumberFormat object that can be used to format numbers in general. If you don't care about a pattern, just call format(...) and you're done. If you need to specify a custom pattern for formatting, call applyLocalizedPattern if the pattern is already localized or applyPattern if the pattern is not localized (not using localized numeric symbols).

And this concludes our localization chat.
Have fun!

Programming tricks and tips for android developers