Demystifying Locale on Android
At some point, every Android developer will end up using a Locale for the applications they are building. If you are dealing with dates and time zones, currencies or multiple language support, you probably faced some challenges to make everything work as intended. In this article, we are going to see how to use Locale to handle localization (L10N), what are the common pitfalls and how to deal with them.
What’s Locale on Android?
According to the Android documentation, a Locale is:
An object that represents a specific geographical, political, or cultural region. An operation that requires a
Localeto perform its task is called locale-sensitive and uses the
Localeto tailor information for the user.
To summarize, a Locale allows you to format the content (sentences, numbers, dates…) for a given localization, all languages have their own linguistic and cultural subtleties. For example, if you want to display 9999,99 dollars in French it won’t be the same as it would be for the English language:
9 999,99 $ in French
$ 9,999.99 in English
On Android devices running Android 7 and above, you can set multiple languages in the phone settings while on older devices only one language can be set. Which means that on newer phones, you can have your primary language, let’s say French, and one or multiple fallback languages (English, German, Chinese…).
There are several ways to get a Locale on Android. Let’s have a look at the common ones and see the differences between all these methods:
Android provides some static
Locale definitions for common usage such as FRENCH which would be the match for
fr, ENGLISH would be the match for
en or JAPANESE for
ja. You can also find some static Locales that match the country name (France, China…), which means that the language will be for a given country. For instance, FRANCE will be for French language spoken in France whereas CANADA_FRENCH will be for French language spoken in Canada.
Locale relies on the IETF BCP 47 language tags that are a combination of different subtags listed below.
Most of the Locales that are used in Android applications are built as the following
language-region where the language tag is two or three letter code generally from ISO 639-1, and the region tag is a two-letter code from ISO 3166–1 alpha-2. For example, the tag for English spoken in United Kingdom is
en-GB where en is for English language and GB for the region.
More complex Locales can be created using the script, variant or extension subtags. Let’s have a look at the script and variant subtags.
Script is a four-letter script code from ISO 15924 that describes the typeface to use for a given text. For example,
Latn for Latin alphabet and
Cyrl for Cyrillic writing system.
sr-Latn-RS for Serbian written with Latin alphabet
sr-Cyrl-RS for Serbian written in Cyrillic
Variant is a five to eight-letter code or a four-character code starting with a digit that represents a regional dialect of a primary language. This subtag must come after the language, script or region subtags.
ca-ES-valencia for Catalan spoken in Valencia
zh-Latn-TW-pinyin for Chinese language spoken in Taiwan in pinyin
On Android, such complex Locale can be built using
Locale.getLanguageTag() by passing the full tag to the function or by using the builder
Locale.Builder() if the tag is split into subtags. The builder will give you access to each subtag in order to define a new Locale.
Use Locales in Android applications
In this section, the focus will be made on the language resolution for the applications and how to deal with the number format and its currencies. Starting with the Android 7, multiple languages can be set on the device settings.
Let’s see how Android applications handle language switching and resolve Locales when a new language is settled into the app or to the operating system. There are many tools that allow us to manage translations and language switch for our Android applications(Lokalise, Phrase, Lingver…).
Let’s assume that we are using Lokalise for managing the app translations and an Android application implements the following languages we see below. Only Lollipop devices and versions above can target 3-letter code language tag, otherwise it will fallback to the default value.
|-values-fr-rFR // French from France
|-values-de // German
|-values-es // Spanish
|-values-es-rAR // Spanish from Argentina
|-values-iro // Iroquoian languages
And the user has set these languages on the operating system:
1 Français (Canada)
2 Italiano (Italia)
3 Español (Argentina)
4 English (United States)
Prior to Android 7, the Locale resolution would have failed because French-Canadian
fr-CA does not match any language in the application even if the application does support French. Default values will be used for displaying string resources in that case.
With Android 7 and above, things got better with major improvements of the language resolution and the support of multiple languages on the device. Thus, for the same application, the resolution will be different. The app will look at the resource that matches the exact tag at first, then the parent (
fr here for instance) and finally all children of
fr-FR is implemented into the application, all texts will be displayed in French but without Canadian variants.
If no languages match the first language, the application will use the second and so on, until a Locale matches the support languages. Otherwise, the default resources will be used.
For a better and faster resolution make sure to use the default language tags (
es …) when setting up your app resources and then localized tags.
Number format and currencies
Locales are very important when you are dealing with numbers and currencies. In order to be displayed correctly, numbers and prices must match the language of the application for consistency and reliability. The example below will show you some differences between the languages:
When there is no currency is available the generic symbol ¤ is used, if
Locale.ROOT is provided for example.
Common pitfalls with Locale
Locales can lead to tricky errors on Android when trying to apply the right Locale for your applications, supporting new languages or using WebViews.
Beware third party libraries
In most of applications, all developers are using third party libraries and some of them can embed resource files such as
strings.xml. If some of these strings are used in the application, it can lead to a sentence written with multiple languages.
Let’s say you are using the string resource
R.string.abc_search_hint from the Material Design Component library. The application developed and distributed worldwide supports English (as default), French and German. If the device is set to French, the application will be displayed in French, same behavior for German and if the device is set to another language it should fallback to English, right? Well, not really…
If the code below is not set in your
build.gradle you will have some surprises when launching the application. For example, on devices set to Italian, your texts from your resources will be in English (as you do not support Italian language) but the text from the library will be displayed in Italian as the library supports the language!
Be sure to scope the language you are using in your application to avoid displaying text with multiple languages.
Pro tip: when filtering resource files with
resConfig, don’t forget to update it when new languages are supported. It will save you some time not figuring out why your app is not translated in the new language.
Difference between app Locale and device Locale
In most cases, calling
Locale.getDefault() will allow the user to know the current language of the application that is usually the same as the device language. But it is a common a mistake to use this method to get the device language, especially if the application can switch language at runtime!
Locale.getDefault() is the current value of the default locale for the instance of the JVM, and not the language the user set in the phone settings. Switching language will set a new value to
Locale.getDefault() but the device language won’t be changed! So if you want to get the current device Locale use the methods below:
Even if the values might be the same most of the time, be sure to know the difference between these two methods.
Fear the WebView!
Starting with Android 7, Google decided to change the way WebView is launched and use Google Chrome instead. And there is a huge impact in your apps if multiple languages are supported. Opening a WebView will revert the application Locale to the device default language, so if you allowed your user to select a different language for your app, other than the device’s one, it will break your translations.
This issue has already been reported to Google but won’t be fixed as, according the Google, it is working as intended. So a workaround has to be implemented to avoid this behavior.
updateConfiguration() is deprecated from API 25, but you can stick to it if you don’t want to handle the restart of your Activities or override
attachBaseContext() in all Activities. It will add more code to your application but it provides a more reliable solution to fix the Locale issue.
I hope all these information will give you a better understanding of Locale on Android and how to use it correctly when developing your applications. Do not hesitate to ping me on Twitter I will share more stuff on Android in the coming weeks 😀 🚀
Big thanks to Corentin Evanno for helping me writing this article as well as Raúl Hernández and Robin Caroff for reviewing it 👏🙂