Demystifying Locale on Android

Julien Salvi
7 min readDec 15, 2020

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 Locale to perform its task is called locale-sensitive and uses the Locale to 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.

IETF subtags that compose a language tag

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.

Language resolution

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.

res
|-values
|-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. As 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 (fr , 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!

Indeed, 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 👏🙂

--

--

Julien Salvi

Google Developer Expert for Android — Lead Android Engineer @ Aircall (Paris) — Startup way of life, beer lover and world traveler.