Share
Embed
Demystifying Locale on Android
At some point, every Android developers 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’s working as intended. In this session, we are going to see what is a Locale and how to use it to handle localization (L10N), how to support multiple languages with Lokalize by example, what are the common pitfalls and how to deal with them.
Julien Salvi
July 23, 2021
Transcript
43 slides
-
Lead Android Engineer @ Aircall Julien Salvi Android GDE PAUG,
Punk and IPAs! @JulienSalvi Bonjour ! -
TABLE OF CONTENTS 01 WHAT’S A LOCALE? Locale explained and
what’s behind it 02 LOCALES IN ACTION! Use Locale in your Android app and handle localization 03 COMMON PITFALLS Beware the Locale! It can be very tricky -
— Android documentation “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.” -
Locale on Android // Default Locale of your application val
defaultLocale = Locale.getDefault() // Locale from a given IETF BCP 47 language tag val localeByTag = Locale.forLanguageTag("fr-CA") val localeFrCA = Locale("fr", "CA") // Create a Locale with Locale.Builder() val bLocale = Locale.Builder() .setLanguage("sr") .setScript("Latn") .setRegion("RS") .build() -
Locale on Android // Used as the language/country neutral locale
// for the locale sensitive operations val root = Locale.ROOT // French language Locale without country restriction val fr = Locale.FRENCH // French Locale for France val frFR = Locale.FRANCE // Similar to Locale(“fr”, “FR”) -
Locale on Android // First Locale of your device since
API 24 val fLocale = Resources.getSystem().configuration.locales[0] // Compat method val fLocale = ConfigurationCompat.getLocales(Resources.getSystem().configuration)[0] // The application Locale + all device Locales since API 24 val list = LocaleList.getDefault() -
So Locale are based on languages and countries? Well… yes
but it is more complex -
IETF Language tag zh-Latn-TW-pinyin (Chinese spoken in Taiwan in pinyin)
language-extlang-script-region-variant-extension-privateuse th-TH-u-nu-thai (Thai language with thai numbers) -
Constructing language tags language: • Primary subtag that identifies the
language. fr (French), en (English) or hi (Hindi) extlang: • Secondary language subtag. Associated to the primary tag. zh-yue (Cantonese Chinese) or ar-afb (Gulf Arabic) language-extlang-script-region-variant-extension-privateuse -
Constructing language tags script: • ISO 15924 alpha-4 script code
that describes how the text is written. Latn (Latin alphabet), Cyrl (Cyrillic) or Hans (Simplified Chinese) region: • ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code CA (Canada), GE (Georgia) or 029 (Caribbean) language-extlang-script-region-variant-extension-privateuse -
Constructing language tags variant: • Subtags that represents dialects or
script variation not covered by combinations of language, script and region subtags. sl-nedis (Nadiza dialect), de-CH-1901 (German variant from 1901 reform ) language-extlang-script-region-variant-extension-privateuse -
Constructing language tags language-extlang-script-region-variant-extension-privateuse extension & privateuse: • Used for
additional information about the language. It often starts with “u-” for extension and “x-” for privateuse de-DE-u-co-phonebk or en-US-x-twain -
Where Locale are used? გამარჯობა! Dates 28 Февраль 2021 г
Currencies ¥67,889,786.50 Numbers 76 876,50 Text TEŞEKKÜR EDERİM -
Locale on Android // Dates SimpleDateFormat("dd MMMM yyyy", Locale.FRENCH) //
Texts "teşekkür ederim".toUpperCase(Locale("tr", "TR")) // Numbers NumberFormat.getInstance(Locale.ENGLISH) NumberFormat.getInstance(Locale.ROOT) // Currencies NumberFormat.getCurrencyInstance(Locale.GERMANY) NumberFormat.getCurrencyInstance(Locale.CHINA) NumberFormat.getCurrencyInstance(Locale.ROOT) 28 Février 2021 TEŞEKKÜR EDERİM 10,000.50 67.889.786,50 € ¥67,889,786.50 ¤ 67,889,786.50 -
Locale resolution res ├── values ├── values-fr-rFR // French from
France ├── values-de // German ├── values-es // Spanish ├── values-es-rAR // Spanish from Argentina └── values-iro // Iroquoian languages Only from API 21 -
Locale resolution Prior to Android 7 🛑 Failure because French
Candian does not match any language in the app Since Android 7 🟢 Success! The resolution changed: it looks at the parent tag first, then the children of the language 1 Français 2 Italiano 3 Español 4 English -
Multiple string resources <!-- Simple string translatable --> <string name="myAwesomeString">Awesome
String : %1$s</string> <!-- Simple string non translatable --> <string name="myUniqueString" translatable="false">Unique String</string> -
Multiple string resources <!-- Array string --> <string-array name="myArrayString"> <item>First
row</item> <item>Second row</item> </string-array> <!-- Plural string --> <plurals name="myPluralString"> <item quantity="one">String</item> <item quantity="other">Strings</item> <item quantity="two">String</item> <item quantity="zero">No strings</item> </plurals> -
What’s Lokalise? • Manage translations and handle language switch in
your app • Dynamic update, cross-platform tool (Android, iOS…) • Can be fully automated with your CI/CD • Other alternatives: PhraseApp, Transifex, Smartling... -
Setup Localise implementation("com.lokalise.android:sdk:2.0.0-beta10") // In the onCreate() of your Application.kt
Lokalise.init( appContext, BuildConfig.LOKALIZE_TOKEN, BuildConfig.LOKALIZE_PROJECT_ID ) -
Handle language switch // Wrap your base context with Lokalize
class BaseActivity : AppCompatActivity() { override fun attachBaseContext(newBase: Context) { super.attachBaseContext(LokaliseContextWrapper.wrap(newBase)) } } // Switch language at runtime val language = "fr" val region = "CA" Lokalise.setLocale(language, region) -
Lokalise + CI/CD Automate your translation process with Lokalise and
your CI “https://medium.com/inside-aircall/4547855fce55” -
Beware 3rd party libraries ⚠ Third party libraries bring new
string resources to your app What happens if unsupported string resources are added? -
Beware 3rd party libraries ?? Let’s say we have the
string “developer” R.string.abc_string translated in many languages. Our app supports English (default), French and German. What’s going to be the value of the string if we switch the device to Italian? -
Beware 3rd party libraries sviluppatore Bringing 3rd party libraries to
your app without control can lead to multiple language texts. Make sure to use English as string default. How can we add some restriction? -
Beware 3rd party libraries android { defaultConfig { resConfigs "en",
"fr", "de" // All supported languages } } -
App Locale vs device Locale ⚠ Locale.getDefault() == app language
⚠ Locale.getDefault() ≠ device language Locale.getDefault() is the current value of the default locale for the instance of the JVM -
App Locale vs device Locale // Default Locale of your
application val defaultLocale = Locale.getDefault() // Current Locale of your device // Since API 24 val fLocale = Resources.getSystem().configuration.locales[0] // Compat method val fLocale = ConfigurationCompat .getLocales(Resources.getSystem().configuration)[0] -
Fear the WebView! Starting with Android 7, Google decided to
change the way WebView is launched and use Google Chrome instead. ⚠ Opening a WebView will revert the app Locale to the device default language 😃 This issue has already been reported to Google but... -
Fear the WebView! // init and load webview setupWebview() if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val res = context.resources val config = res.configuration // App locale from pref, storage... val appLanguage = languageGateWay.getAppLocale() config.setLocale(appLanguage) res.updateConfiguration(config, res.displayMetrics) } -
Fear the WebView! override fun attachBaseContext(newBase: Context) { var base:
Context = newBase if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val config = resources.configuration val locales = languageGateWay.getSupportedLocales() config.setLocales(locales) base = createConfigurationContext(config) } super.attachBaseContext(base) } -
Want to know more? Demystifying Locale on Android “https://medium.com/inside-aircall/95450adf5aec” Automate
your translation process with Lokalise and your CI “https://medium.com/inside-aircall/4547855fce55” IETF's BCP 47 “https://www.rfc-editor.org/rfc/bcp/bcp47.txt” -
CREDITS: This presentation template was created by Slidesgo, including icons
by Flaticon, and infographics & images by Freepik Thanks! Do you have any questions? @JulienSalvi Aluu! გამარჯობა! Jambo! Julien Salvi