Software localization

The Ultimate Guide to Android Localization

Learn best practices for Android internationalization and localization to make your app accessible to users worldwide in the language they speak.
Android i18n blog post featured image | Phrase

In 2021, the number of mobile users worldwide surpassed 7B, and it will likely rise to roughly 7.5B in 2025. People across the globe spend more time than ever before using mobile apps—with China and India overtaking the US in terms of revenue generated by mobile apps. Non-English usage is even higher for Android-based applications. In fact, the highest app download rates in Google Play are in non-English speaking countries.

For most Android app development companies, this figures make it obvious that running an English-only app can hardly meet the expectations of all users across markets. Therefore, making an app adaptable to multiple cultures and languages can play a key role in staying ahead of the competition. This tutorial will walk you through the process of Android internationalization step by step to help you make the most of your app for global users.

How does Android handle localization?

When it comes to multilingual Android apps, the mobile operating system relies on a combination of language and locale. Language is what gets translated, and locale dictates how regional settings related to the language are displayed: currency, time, dates, etc. Android users can go to Settings ➞ Languages and select a combination of language/locale, which would make all the installed apps adhere to them.

🗒 Note » A user can have multiple languages/locales selected and list them in order of priority. If the app doesn’t support the default language/locale, the next language/locale in the list will be used instead. If the app doesn’t support any of the languages/locales in the user’s list, it will display content from project’s default resources.

How do I add languages to an Android application?

To add multiple languages to your Android app, go to Module Name ➞ res ➞ values, right-click the strings.xml file, and select Open Translations Editor. The editor will give you the overview of all the string keys and values used in the application.

🗒 Note » You will notice that there is already a strings.xml file and a key (app_name) added in the translations editor. This is the default strings.xml file which will be used if your app doesn’t support user’s preferred Android OS language. We will use English (en) as the default language for this tutorial.

Translation editor default setup with default file strings.xml and first key created | Phrase

To add a new locale, click on the “Add Locale” button, i.e. the globe icon. You can either choose a language or a combination of a language and locale (for API versions higher than 24), depending on your use case.

Dropdown menu from which to select desired locales for the project | Phrase

After adding locales, the translations editor will list the newly added locales (Japanese and Spanish in this example).

Translation editor's project overview table with added columns for Spanish and Japanese | Phrase

How do I add a new string and its translations in the Translations Editor?

We will now add a new string key “greeting” and its values in all the locales we’ve chosen. Click the “+” icon, and an Add Key dialog box will appear. In the Key field, enter “greeting,” add “Hello” to the Default Value field, and click OK. We will use this key to reference the string when we remove the hardcoded string from the app code.

Dialog window for adding a new key with fields for key name, default value and resource folder | Phrase

You will now see the newly added key in the translations editor.

Project overview table with added row for new key | Phrase

Now we need to add the translations for the key “greeting” in Japanese and Spanish (our supported languages).

🗒 Note » The translations editor will mark the key in red if you miss to add the translation for any of the locales. You can mark a key untranslatable from the checkbox for any strings that you do not want to translate and only use default values instead.

After adding the keys as well as their values, the translation editor will look like this.

Project overview table with added Japanese and Spanish translations for greetings key | Phrase

🔗 Resource » Get the code for the Android demo app used in this tutorial on GitHub.

🗒 Note » You can also add locales and their translated strings manually without using the Translations Editor using Strings.xml files.

We have added string keys and values for multiple locales, now we need to reference those keys.

Currently, our Android application is using hardcoded strings in a Text composable located in  MainActivity.kt. The text value is set to “Hello”.

Column() {
    Text(text = "Hello", fontSize = 60.sp)
   }

When we run the app on an Android Phone, whose default language is set to English, Japanese or Spanish, we can see that the app always displays the same piece of text.

Demo phone screen displaying the word Hello | Phrase

We need to replace the hardcoded text with the string keys we had defined for our Android app to be able to display content in the user’s preferred language. To do that, go to MainActivity.kt and replace the hardcoded string with the following:

Text(
 // getString is a function that will return the
 // translated String using the "greeting" key
  text = getString(R.string.greeting), fontSize = 60.sp
)

If we run the application again, we will see that it is able to display translated content, based on the user’s preferred language (from left to right in English, Japanese, and Spanish).

Demo phone screen displaying the word Hello | Phrase

English

Demo phone screen with language selection option in Japanese | Phrase

Japanese

Demo phone screen with greeting in Spanish | Phrase

Spanish

How can I support a user’s app-specific locale preference in Android?

In the past, Android used to apply the user’s preferred language to all apps installed on a smartphone, but as of Android 13, users can set their preferred language for each app. To take advantage of these new APIs, we need to list all supported languages for our application: Create a file called res/xml/locales_config.xml and specify your app’s languages as follows:

<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
    //add all the supported lanaguges here
    <locale android:name="ja"/>
    <locale android:name="es"/>
    <locale android:name="en"/>
</locale-config>

In the manifest, add a line pointing to this new file:

<manifest
    <application
    ...
    android:localeConfig="@xml/locales_config">
    </application>
</manifest>

🗒 Note » You might face build issues while linking this file on Android Gradle plugin (AGP) version 7.3.0-alpha07 through 7.3.0-beta02. Use AGP 7.3.0-beta04 or later, AGP 7.4.0-alpha05 or later, or a prior AGP release.

Users can now chose the language of our Android application in 2 ways:

  • System Settings: Go to Settings ➞ System ➞ Languages & Input ➞ App Languages ➞ (select app). Here you will see the all languages our Android application supports. By default, it will pick up the system’s default language.

Setting language preferences in the system settings | Phrase

  • In the App Language Picker: Newer APIs make it possible for a user to select their preferred language from within the application. We’ll cover that next.

How do I add a language picker to an Android app?

Let’s implement now an in-app language picker for our Android application. In the app, we have a dropdown menu where users can select a locale from those supported by our app.

Demo screen showing in-app language picker while displaying default English greeting | Phrase

In the  MainActivity.kt file, add the following code to the OnCreate() method:

import android.app.LocaleManager
import android.content.Context

class MainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle ? ) {
        super.onCreate(savedInstanceState)

        val localeManager = getSystemService(Context.LOCALE_SERVICE) as LocaleManager
        val currentLocale = localeManager.applicationLocales.toLanguageTags()
        // ...
    }
}

LocaleManager helps us get the current locale or set another one. The changes to the locale made using LocaleManager persist even after closing and restarting the app. We use LocaleManager to get the applicationLocales, which is the current locale of our app. We then convert it to a language tag (“en”, “ja,” etc.), which is a string.

🗒 Note » localeManager.applicationLocales will return empty if there is no language set for our app. In this case, our app will use the system’s default language.

Now we need to display a dropdown menu for the user to be able to select a locale. In the composable inside MainActivity.kt, add the following code to the setContent method:

class MainActivity : ComponentActivity() {
  @RequiresApi(Build.VERSION_CODES.TIRAMISU)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val localeManager = getSystemService(Context.LOCALE_SERVICE) as LocaleManager
    val currentLocale = localeManager.applicationLocales.toLanguageTags()

    setContent {
      PhraseTheme {
        Surface() {

                    // This list contains the supported locales displayed
          // in dropdown menu.
          val supportedLocales = listOf("en", "es", "ja")
          var expanded by remember { mutableStateOf(false) }

                    // This represents the current locale of our app.
          var selectedLocale by remember {
            mutableStateOf(currentLocale.ifEmpty { "Not Set" })
          }

          Column() {

                        // Now add the DropDown Menu Composable.
            ExposedDropdownMenuBox(
              expanded = expanded,
              onExpandedChange = {
                expanded = !expanded
              }
            ) {
              TextField(
                readOnly = true,
                value = selectedLocale,
                onValueChange = { },
                trailingIcon = {
                  ExposedDropdownMenuDefaults.TrailingIcon(
                    expanded = expanded
                  )
                },
                colors = ExposedDropdownMenuDefaults.textFieldColors()
              )
              ExposedDropdownMenu(
                expanded = expanded,
                onDismissRequest = {
                  expanded = false
                }
              ) {
                supportedLocales.forEach { selectionOption ->

                  // onClick will be triggered when a locale is selected 
                                    // from the dropdown menu
                  DropdownMenuItem(onClick = {
                    selectedLocale = selectionOption

                                        // Set the selected locale using localeManager.
                    localeManager.applicationLocales =
                      LocaleList(Locale.forLanguageTag(selectedLocale))
                    expanded = false
                  }
                  ) {
                    Text(text = selectionOption)
                  }
                }
              }
            }
                    
          	// This text Composable is refreshed whenever the app
            // locale is changed.
            Text(
              text = getString(R.string.greeting),
              fontSize = 60.sp,
              modifier = Modifier.padding(top = 30.dp)
            )

                        // We also need a way to reset the selected locale ie. 
                        // to make our application use default System language instead. 
                        // For this, we add a Button Composable -
            Button(onClick = {
              localeManager.applicationLocales = LocaleList.getEmptyLocaleList()
            }, modifier = Modifier.padding(top = 200.dp)) {
              Text(text = "Reset")
            }
          }
        }
      }
    }
  }
}

We are done, feel free to check out the project on GitHub.

🗒 Note » To support newer APIs on Android 12 or lower, use the AndroidX support library.

How to localize numbers in Android?

Numbers are formatted differently depending on the locale. For example, “1000000” will be display as

  • 1.000.000 in Brazil (pt-BR)
  • 1,000,000 in the US (en-US)
  • 10,00,000 in India (en-IN)

🤿 Go deeper » A Concise Guide to Number Localization covers numeral systems, separators, percentages and more.

At the moment, we have an application that only shows an Integer in a Text composable by directly converting it into a string in the MainActivity.kt file.

class MainActivity : ComponentActivity() {
  @RequiresApi(Build.VERSION_CODES.TIRAMISU)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
        // ...
    val number = 1000000

    setContent {
                        // ...
            Text(
              text = number.toString(),
              fontSize = 60.sp,
            )
        }
      }
    }

Demo app with locale switcher displaying unformatted number | Phrase

As we can see above, without internationalization in place, the number looks the same in all locales, and it doesn’t have any formatting. To change that, we will format the number based on the locale using the NumberFormat class. Add the following code to the MainActivity.kt file:

import java.text.NumberFormat

class MainActivity : ComponentActivity() {
  @RequiresApi(Build.VERSION_CODES.TIRAMISU)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
        // ...
    val number = 1000000
        val formattedNumber = NumberFormat.getInstance().format(number)

    setContent {
                        // ...
            Text(
              text = formattedNumber,
              fontSize = 60.sp,
            )
        }
      }
    }

Demo app showing differently formatted numbers depending on locale selected | Phrase

The number is now formatted according to the locale. Check out the final code on GitHub.

🔗 Resource » When using TalkBack, Google’s screen reader included on Android devices, numbers are always read based on the user’s locale. Find out more about it in our Android Accessibility Tutorial for Multilingual Apps.

How do I localize currency in Android?

Whenever we want to display an amount of money, we use a currency symbol ($, €, etc.) or an ISO 4217 currency code (USD, EUR, etc.) next to the formatted amount. When it comes to Android, the NumberFormat class provides a method to format a sum depending on the user’s locale. We will keep using the previous example to format an amount of 1000000 units in the respective currency. Add the following code to the MainActivity.kt file:

import java.text.NumberFormat
import java.util.*

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...
    //Add this
    val amount = 1000000
    val formattedCurrency = NumberFormat.getCurrencyInstance().format(amount)

    setContent {
       // ...
       Text(
           text = formattedCurrency,
           fontSize = 60.sp,
           )
  
          }
        }
    }

Demo app showing numbers correctly formatted as monetary amounts inlcuding currency symbol | Phrase

The amount is now formatted based on the locale.

By default, .getCurrencyInstance() will take in the current locale of your app. You can also pass any other locale tags in the function, for example, getCurrencyInstance(Locale(LANGUAGE, COUNTRY)) to format an amount in any locale.

🗒 Note » To get a currency code in the ISO 4217 format, you can use the NumberFormat.getCurrencyInstance().currency.currencyCode method.

How does localizing date and time formats work in Android?

To give an overview of localizing date and time formats in Android, we will convert the standard time format into a human-readable format, taking the user’s time zone and locale into consideration. We will use the ISO 8601 format, which is a string used to represent the Coordinated Universal Time (UTC). For example, the UTC format of January 31, 2022, 17:55:50, is represented as 2022-01-31T17:55:50Z in the ISO 8601 format. The Z at the end represents a zero UTC offset.

We will use java.time APIs to internationalize and localize the time format.

🗒 Note » To use java-time APIs for Android versions lower than 26, you will need to add a backwards compatibility.

First, we will convert the UTC timestamp into the user’s time zone and display it (without localization). Go to the MainActivity.kt and add the following code:

import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

  val UtcTimeStamp = "2022-01-31T16:27:23Z"
  val timestampInstant = Instant.parse(UtcTimeStamp)
  val zonedDateTime = ZonedDateTime.ofInstant(timestampInstant, ZoneId.systemDefault())
    // ...
        }
}

Instant.parse(UtcTimeStamp) takes in the UTC string as a parameter and returns a Java time Instant object. The Java time Instant class represents time passed in seconds since the origin (epoch) 1970-01-01T00:00:00Z .

With the help of the ZonedDateTime.ofInstant(timestampInstant, ZoneId.systemDefault()) method, we convert timestampInstant from UTC time into the user’s time zone.

🗒 Note » Both ZonedDateTime and Instant represent a moment in time. The only difference is that ZonedDateTime has extra helper methods related to time zones and time offsets while Instant has no timezone or offset specified.

Inside the Text composable in the MainActivity.kt file, we convert zonedDateTime into a string.

import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val UtcTimeStamp = "2022-01-31T16:27:23Z"
    val timestampInstant = Instant.parse(UtcTimeStamp)
    val zonedDateTime = ZonedDateTime.ofInstant(timestampInstant, ZoneId.systemDefault())

    setContent {
                      // ...
            Text(
              text = zonedDateTime.toString(),
              fontSize = 20.sp,
            )
                }
      }
}

Let’s run the application with users in different time zones. Here is what users in India (on the left) and the UK (on the right) get displayed on their screens:

Demo app for users in India with Indian time stamp in UTC format | Phrase

India

Demo app for UK users with time stamp of UK time in UTC format | Phrase

United Kingdom

As you can see, the UTC timestamp is converted to the user’s default time zone (in the ISO 8601 format). We can now localize this timestamp taking the user’s locale settings into account with the help of the DateTimeFormatter class. In the MainActivity.kt file, make the following changes:

import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val UtcTimeStamp = "2022-01-31T16:27:23Z"
    val timestampInstant = Instant.parse(UtcTimeStamp)
    val zonedDateTime = ZonedDateTime.ofInstant(timestampInstant, ZoneId.systemDefault())
    val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
    val localizedDate = zonedDateTime.format(dateFormatter)

    setContent {
                        // ...
            Text(
              text = localizedDate.toString(),
              fontSize = 20.sp,
              modifier = Modifier.padding(top = 30.dp)
            )
           }
      }
}

Let’s run the app again. In the hi-IN locale, the date is displayed in the following way:

Demo app with timestamp formatted after local Indian conventions | Phrase

Users whose locale is set to en-UK, will see the same date as follows:

Demo app with UK time format localized for a UK user | Phrase

You can get the final version of the app on GitHub. In the example above, we have added absolute date and time, but you can add relative date and time formats (today, yesterday, etc.) as well.

How to handle plurals in translated strings in Android?

Different languages have different grammatical rules for expressing quantity. In English, for example, you write “1 minute”, but for any other quantity, including 0, you write “n minutes”. Other languages like Arabic make finer distinctions.

At the moment, our application lets users type in a number of minutes for a timer.

Timer demo app with field for typing in the number of minutes | Phrase

As you can see, the “minute” label does’t change even when you type in “0” in the text field. The same can be observed from the code inMainActivity.kt:

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {

                    // Current value of count entered by user in the TextField
          var count by remember { mutableStateOf("") }

          Row(){
            // ...
            TextField(
              modifier = Modifier.width(80.dp),
              value = count,
                        
               // We set the value of the count to the value entered by user.
              onValueChange = { count = it },
              keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
            )
            Text(
              text = getString(R.string.label_minutes), fontSize = 20.sp, modifier = Modifier
                .padding(start = 10.dp)
            )
          }
        }
      }
    }

And the associated key in strings.xml file in resvalues.

<string name="label_minutes">minute</string>

To make sure that the minute labels respond to the quantity, we need to define all possible labels within the <plurals> tag in the strings.xml file for the default language of our app, i.e. English.

// Remove this
<!--    <string name="label_minutes" translatable="false">minute</string>-->

// Add this
  <plurals name="label_minutes">
     <item quantity="one">minute</item>
     <item quantity="other">minutes</item>
 </plurals>

🗒 Note » Besides one and other, Android also supports more attributes, including zero, two, few ,many. This allows for complex plurals, like those in Arabic, for example.

Now, in the Mainactivity.kt file, do the following:

import androidx.compose.ui.platform.LocalContext

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
                    // Current value of count entered by user in the TextField
          var count by remember { mutableStateOf("") }
                    val resources = LocalContext.current.resources

          Row(){
            // ...
            TextField(
              modifier = Modifier.width(80.dp),
              value = count,
                            // If it (value entered by user) is empty, we set it to 0
              onValueChange = { count = it.ifEmpty { "0" } },
              keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
            )
            Text(
                            // Pass the count/quantity to the getQuantityString() method
                text =  resources.getQuantityString(R.plurals.label_minutes, count.toInt()),
            )
          }
        }
      }
    }

On running the final application, we can see that the correct string is loaded, respecting the entered quantity.

Timer demo app with dynamic adjustment of the word minute, depending on the quantity of minutes specified by the user | Phrase

How do I work with dynamic values in translated strings in Android?

Strings may sometime contain text that should not be translated into other languages. This could be a brand name, URL address, Unicode emoji character, an address, etc. You can use the <xliff:g> placeholder tag in the strings.xml file to mark text that should not be translated.

For example, our application displays the following text:

Demo app saying "Visit our website xy.com to learn more" | Phrase

Inside the Mainactivity.kt file, we have a Text composable.

class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {

                 // ...
         Text(
           text =  getString(R.string.landing_url),
           fontSize = 20.sp,
           modifier = Modifier.padding(start = 10.dp)
            )
         }
       }
     }

And the associated key in strings.xml file in resvalues.

<string name="landing_url">Visit our website https://xyz.com to know more.</string>

Here, you can notice that the website URL, https://xyz.com, must remain same in all languages. However, the relative position of the URL might differ depending on the language. To mark the text that should not be translated, we use the <xliff:g> placeholder tag. Go to Module Name ➞ res ➞ values ➞ strings and make the following changes to the xml files.

//For default strings.xml file 
<string  name="landing_url"> Visit us at <xliff:g  id="application_homepage">https://xyz.com/</xliff:g> to know more.</string>

//For the German strings.xml file, similarly add this string
<string  name="landing_url">Erfahren Sie mehr unter <xliff:g  id="application_homepage">https://xyz.com/</xliff:g></string>

Always add an id attribute to the placeholder tag which explains what the placeholder is for. This is for your translators to understand the context of the untranslatable string. Let’s run the final version of the app in the English and German locales to see the difference.

Demo app saying "Visit our website xy.com to learn more" | Phrase

English

Demo app saying "Visit our website xy.com to learn more" in German | Phrase

German

Note the relative position of the URL is different in both languages.

🗒 Note » You can also use dynamic strings with <xliff:g> tags which will be displayed in run time.

How to localize accessibility for Android apps?

As briefly noted above, Android is using Talkback, Google’s reader included on all Android devices, to cater to people with disability. Talkback helps users navigate with gestures and describes the content displayed on the screen. It can also take advantage of all the strategies for i18n and l10n we have already discussed without any extra work. However, there are some APIs that are exclusively meant for accessibility.

When you define an Image or Icon composable, Talkback needs a textual description of the visual element. This can be achieved by adding a contentDesription parameter. For example, for an IconButton composable, contentDescription can be set by passing a string to the composable.

IconButton(onClick = onClick) {
    Icon(imageVector = Icons.Filled.Share, contentDescription = "share")
}

When the user now hovers over the share icon, Talkback says “share” to the user. However, because we used a hard coded string, Talkback will not respect the user’s locale settings. To fix this, do the following:

IconButton(onClick = onClick) {
    Icon(imageVector = Icons.Filled.Share,
    //define "label_share" key in all the supported string.xml files
    contentDescription = stringResource(R.string.label_share)
  }

🗒 Note » You can also set contentDescription to null for decorative images.

Now, Talkback will narrate the label in the user’s language. Check out our Android Accessibility Tutorial for Multilingual Apps to learn more.

Other best practices for Android internationalization

Let’s review briefly some other Android i18n best practices so you don’t miss anything:

  • Provide a sufficient description for string resources declared in the strings.xml file for the translator to understand the context in which a string is used. Instead of just declaring a string key in strings.xml file, provide comments explaining the context.
    <!-- The action for signing up after filling a form. This text is displayed on a button. -->
    <string  name="sign_up_button">Sign Up</string>
  • Design a flexible layout. It can be challenging to guess the exact width or height that a piece of text in any language will require on screens. For example, the English word “skip” takes only 4 characters but its German equivalent, “überspringen,” takes up 12 characters. Avoiding making widths and heights fixed—unless really important. Instead, make Composables wrap content or make them scrollable, depending on your use case. Check out a few examples of how to make localization-friendly layouts using Jetpack Compose.
  • Just as we defined different string.xml files for all locales, you can also add different images for all locales. Android will switch to default images in case it cannot find images for the user’s locale.

How do I test my Android app with pseudolocales?

Pseudolocalization is a method to simulate internationalization of text that might lead to UI, layout, and other translation-related issues while maintaining readability. Pseudolocalization helps you test localization without actually needing to translate your Android application. To use Android pseudolocales, you need to be running an API level 18 or higher.

Let us run an application whose default language is set to English, installed on a phone whose locale is set to en-UK.

Screen of demo app saying "Visit us at xyz.com to learn more" as well as a Next and Back button | Phrase

To enable pseudolocale testing on an Android device, go to the build.gradle file of your project and add the following code:

buildTypes {
  debug {
    pseudoLocalesEnabled true
  }
}

🗒 Note » Only enable pseudoLocalesEnabled for debug builds and not for a release build.

Sync and run the project. Now, turn on the developer options from the phone settings and restart your device or emulator. Android provides the following 2 pseudolocales to test your application.

  • English(XA): Adds Latin accents to base English UI text and expands on original text by adding non-accented text to showcase any UI bugs that might have originated because of expanded text.
  • AR(XB): Sets the direction of content from left-to-right(LTR) to right-to-left(RTL), reversing the order of characters; this is to mimic behaviour for various RTL languages like Hebrew, Urdu, etc.

Spotting localization issues

Navigate the phone settings and select English(XA) as the default system language. On running the app, when you select English(XA) as the default locale, you will notice the following:

Demo app with accents added to all of the English text except the Back button | Phrase

As we can see:

  • The Text composable and the Next button now contain added accents and expanded texts, which does not break out the UI. ✅
  • The Back button is same as before instead of using accents and expanded text. This means we have hard coded the string. ❌

Similar to that, you can select to run the app again after selecting AR(XB) as the default locale to check if there are any UI issues when viewing RTL locales.

🔗 Resource » Check out our guide to supporting RTL layouts in Android apps.

In conclusion, pseudolocalization has helped us fix UI issues without needing to translate our application.

Wrapping up our Android localization guide

And that about does it for this one. We hope you’ve enjoyed this foray into some best practices for making Android apps ready for global markets. As soon as your translators are ready to take over the process, have a look at Phrase Strings.

With its CLI and Bitbucket, GitHub, and GitLab sync, your i18n can be on autopilot. The fully-featured Phrase web console, with machine learning and smart suggestions, is a joy for translators to use. Once translations are ready, they can sync back to your project automatically. You set it and forget it, leaving you to focus on the code you love.

Check out all Phrase features for developers and see for yourself how they can streamline your app localization workflows.