Save 37% off PRO during our Black Friday Sale! »

Demystifying Locale on Android

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.

56047a7b11797f42c2e7030d771fe803?s=128

Julien Salvi

February 24, 2021
Tweet

Transcript

  1. Hi! Oi! Aluu! Jambo! Salut ! Demystifying Locale on Android

    Julien Salvi GDG Tbilisi x Android Worldwide - Android meetup
  2. Senior Android Engineer @ Aircall Julien Salvi 10 years into

    Android PAUG, Punk and IPAs! @JulienSalvi Bonjour !
  3. 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
  4. WHAT’S A LOCALE? Locale explained 01 ¡Hola!

  5. — 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.”
  6. 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()
  7. 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”)
  8. 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()
  9. So Locale are based on languages and countries? Well… yes

    but it is more complex
  10. 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)
  11. 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
  12. 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
  13. 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
  14. 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
  15. LOCALE IN ACTION! 02 Jambo! Localization and Locale in your

    Android apps
  16. Locale resolution on Android

  17. Multiple string resources 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
  18. 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>
  19. 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>
  20. 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 󾓦
  21. Where to use Locale on Android?

  22. Where Locale are used? გამარჯობა! Dates 28 Февраль 2021 г

    󾓬 Currencies ¥67,889,786.50 󾓭 Numbers 76 876,50 󾓧 Text TEŞEKKÜR EDERİM 🇹🇷
  23. 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
  24. None
  25. 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...
  26. 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 )
  27. 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)
  28. Lokalise + CI/CD Automate your translation process with Lokalise and

    your CI “https://medium.com/inside-aircall/4547855fce55”
  29. COMMON PITFALLS Beware the Locale! 03 Aluu!

  30. Beware 3rd party libraries ⚠ Third party libraries bring new

    string resources to your app What happens if unsupported string resources are added?
  31. 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?
  32. 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?
  33. Beware 3rd party libraries android { defaultConfig { resConfigs "en",

    "fr", "de" // All supported languages } }
  34. App Locale vs device Locale

  35. 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
  36. 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]
  37. Fear the WebView!

  38. 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...
  39. 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) }
  40. 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) }
  41. 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”
  42. CREDITS: This presentation template was created by Slidesgo, including icons

    by Flaticon, and infographics & images by Freepik Gmadlob! Do you have any questions? @JulienSalvi Aluu! გამარჯობა! Jambo! Julien Salvi