Dec 20, 2012

f Comment

Android: How to Support Multiple Languages?

Amazon I don't know why but something as trivial as supporting multiple languages in an Android app can become such a daunting task. In fact even Android's official tutorial contains mistakes about how to implement this function. At http://developer.android.com/training/basics/supporting-devices/languages.html it states:

To add support for more languages, create additional values directories inside res/ that include a hyphen and the ISO country code at the end of the directory name.
This is untrue! I had to add a directory whose name includes a hyphen and the two-letter LANGUAGE CODE for the language you'd like to support instead.

Another big challenge is how to immediately make every applicable text and label in your app reflect the language the user selects. It turns out nobody on Google knows how to do it.

Below I'll give you a step by step tutorial on adding support for multiple languages, aka localizations.

Add the appropriate language XML file
As I mentioned you need to create a directory whose name is the two-letter lowercase ISO language code following 'values-'.

For example I'd like to support the Chinese language and the language code for it is 'zh'. Therefore I create a folder called 'values-zh' under my 'res' folder. Inside 'values-zh' I create a file called strings.xml which contains a list of Chinese texts I'd like to use in my app. The complete path to this file is [workspace name]\res\values-zh\strings.xml.

Here's part of the content:

<resources>
...
<string name="addressOrPoi">地址或地標物名稱</string>
<string name="language">語言</string>
...
</resources>

As for naming convention I use camel casing and the name I use is simply the camel casing version of the English text I use. For example in strings.xml inside 'values' folder I have the following definition:

<resources>
...
<string name="mapMode">Map Mode</string>
<string name="addressOrPoi">Address or POI</string>
...
</resources>

'mapMode' is simply the camel casing of 'Map Mode'. This convention makes things easier to name. Trust me.

The language codes are two-letter lowercase ISO language codes (such as "en" and "zh") as defined by ISO 639-1. The country codes are two-letter uppercase ISO country codes (such as "US" and "TW") as defined by ISO 3166-1.
Setting text in layout XML versus Setting text in activity
Now that we've defined a list of strings for every language we'd like to support, let's proceed. You usually have two ways to support localization:

1. Specify the string you want to display in layout XML. Here's an example in my activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
...
<EditText
android:id="@+id/et_location"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:hint="@string/addressOrPoi"
android:inputType="text" />
...
</LinearLayout>

As you can see in the text field I include a string identified by 'addressOrPoi' as the hint of the text field. When the layout view is being rendered Android framework will pick the locale defined in the activity's Resources object and use the corresponding strings.xml to display text.

Later I'll tell you exactly how to change the language or locale.
2. In the activity class I get the reference to the layout element and set its text programmatically. Here's an example in my MainActivity.java:
public class MainActivity extends MapActivity  {
  EditText mEtLocation;
...
  public void onCreate(Bundle savedInstanceState) {
    mEtLocation = (EditText) findViewById(R.id.et_location);
  }
  public void onStart() {
    mEtLocation.setHint(getResources().getString(R.string.addressOrPoi));
  }
...
}
Note that you need to reply on getResources().getString() to get the original text you've defined. Your locale is defined in the Resources object.

Each method's pros and cons should be obvious. In the 1st method you cannot easily change the language while the app is running but your code is much cleaner. In the 2nd method your activity will be full of code that sets the text or label of your layout elements but you'll be able to change the language dynamically.
Change language dynamically while the App is running!
Let's say you provide a function for the user to select a language while the app is running. When user picks English you'd like the app to display English texts right away. When user picks Chinese you'd like the app to display Chinese texts right away.

Here's the code that does the first half of the task:
public static void setLanguage(Context context, String twoLetterLanguageCode){
  Resources res = context.getResources();
  DisplayMetrics dm = res.getDisplayMetrics();
  android.content.res.Configuration conf = res.getConfiguration();
  conf.locale = new Locale(twoLetterLanguageCode.toLowerCase());
  res.updateConfiguration(conf, dm);
}
When this method finishes executing your activity's locale will be set to the new language. However if you use 1st method above then the changes will NOT be reflected UNTIL you refresh or repaint the layout screen!

If you use 2nd method then you'll simply call the appropriate set* method of any layout element that you display text or label for. For example the following sets the hint message of an EditText accordingly:

mEtLocation.setHint(context.getResources().getString(R.string.addressOrPoi));
One good practice is to call all these set methods inside onResume() because onResume() gets called very often. Suppose you use a new activity or the activity's options' menu to allow the user to change the language when user returns to the app onResume() will get called.

If you have any questions let me know and I will do my best to help you!
Please leave a comment here!
One Minute Information - by Michael Wen
ADVERTISING WITH US - Direct your advertising requests to Michael