Software localization

Key Dos & Don’ts for Developing Multilingual Android Apps

Implementing multi-language support kills your motivation? Let our Android app development tutorial guide you through all the dos and don'ts!
Software localization blog category featured image | Phrase

Android has been the leading mobile OS worldwide for quite a while. In the summer of 2020, it controlled 74.6 percent of the entire mobile OS market. If you're looking for best practices for building a multilingual Android app, the following Android app development tutorial will walk you through all the dos and don'ts.

Before we get it started, let's note that the Android application framework is powered by several i18n and l10n libraries/utilities. This means you don't need to import any external i18n or l10n libraries to enable multi-language support for your Android app. We can now start digging into the details!

The Android I18n & L10n Framework

The internationalization framework is part of the wider Android application framework, in which some – if not all – components rely on internationalization. Put another way, Android already provides a set of libraries within its SDK, such as date-time formatting, number formatting, message formatting, currency, measurement units, timezone, collation rules, plural rules, gender rules, transliteration, Bidi layout support, Emoji support, etc.

Let's say you're developing an app focused on European financial markets and need to handle all date-time differences, as well as identify both the currency and numbering system of each region. Ideally, you can refer to SDK classes, which provide full, standardized support. Additionally, Android has the localization framework, responsible for localizing the OS itself, as well as all applications. It also provides mechanisms for locale awareness, resource loading, locale matching, fallback, etc.

In general terms, Android leverages the ICU library and CLDR project to provide Unicode and other internationalization support.

🔗 Resource » Get a brief overview of ICU and CLDR in The Missing Guide to the ICU Message Format.

Android i18n flow | Phrase

The relation between Unicode CLDR, ICU, and Android

Implementing I18n & L10n in Your Android App

Now that we're familiar with the basics of the Android i18n & l10n framework, we'll focus on four topics crucial to the development of multilingual Android apps:

  1. Configuration/Preparation,
  2. Localizability,
  3. Bidirectionality,
  4. Formatting.

1. Configuration/Preparation

To make sure an Android app is global-ready, we need to take care of a few key configurations both in the Manifest and Gradle file.

In the AndroidManifest.xml file, we need to set the android:supportsRtl property to true. This property declares whether your application is willing to support right-to-left (RTL) layouts. Its default value is false.

<manifest ... >

...

   <application ...

      android:supportsRtl="true">

   </application>

</manifest>

🗒️ Note » In the section on bidirectionality below, you can find the complete information on RTL layout support.

In the build.gradle file, we need to set the following configurations:

  • targetSdkVersion17 (or higher)

An integer designating the API level that the application targets, where 17 is the minimal value accepted because prior to API 17, Android APIs didn't support right-to-left layouts.

  • resConfigslist of supported locales

A list of the locales that the app supports.

android {

    ...

    defaultConfig {

        targetSdkVersion 17 // Or higher

        ...

        resConfigs "en", "da_DK", "de_DE", "it_IT" …

    }

}

Why is the resConfigs property necessary at all? Well, you need to make sure your application will work properly for the second-preferred locale setting. In other words, in case a user sets additional languages on their mobile device, your app will try to match all configurations of the device, including secondary, tertiary languages, etc.

See the scenario below:

User Settings

  1. nl_BE
  2. de_BE

App Resources

  • default (en)
  • da_DK
  • de_DE
  • it_IT

Which language content should be picked and displayed for the user considering these settings?

Since Dutch (Belgium) is the primary option, and there are no resources in this language, as expected, the secondary option, which has content for the German language, is picked.

You may wonder why de_DE is picked despite de_BE being the user locale. Let's explain the resource-resolution strategy used by Android to define the best matching:

  1. Check the first choice: Try nl_BE→ Fail
  2. Remove region and try locale only: Try nl→ Fail
  3. Try locale children of nl: children of nl→ Fail
  4. Go to the second choice: Try de_BE→ Fail
  5. Remove region and try locale only: Try de→ Fail
  6. Check locale children of de: children of de→ de_DE

    Use de_DE

  7. The steps above are repeated for all configurations. If none of them matches, then the default locale is used.

With that, the user still has a language they understand, even though the app doesn’t support Dutch. However, it only happens due to the resConfigs property.

In case your app imports any Android library, and you don't add the resConfigs property, it will not work properly, and probably the default content (English) will be displayed, which wouldn't be that good from a UX perspective.

2. Localizability

Localizability is the practice of enabling software to be localized into different languages without changing its source code. At this point, you should pay attention to the following steps:

  1. Keep in mind the externalization of resources,
  2. Include a full set of default resources,
  3. Store default resources without language or locale qualifiers (whatever the default language used in your app, store default resource directories without language or locale qualifiers),
  4. Make sure a res/values/strings.xml file exists and defines every string needed.

The steps mentioned above are also applicable to other Android resources, such as layouts, sounds, graphics, and other data your app may need.

For more details on Android resources, feel free to check out the App resources overview.

Let's move on to some best-practice examples of localizability!

✋🏽 Heads up! » In the following overview, we're using the approach of do’s and don’ts. Please note that some of the don'ts below may work or even produce the same result for certain languages. Thereby, our goal isn't to point out errors but provide better solutions to localizability regardless of the locale selected.

Avoid using Java toUpperCase()/toLowerCase() for user visible strings

Do

strings.xml

<string name="home">HOME</string>

Java:

String strHome = res.getString(R.string.home);

Log.d("phrase_i18n", strHome);

Output (en-US):

"HOME"

Don't

strings.xml

<string name="home">Home</string>

Java:

String strHome = res.getString(R.string.home);

String curHOME = strHome.toUppercase(strHome);

Log.d("phrase_i18n", curHOME);

Output (en-US):

"HOME"

Using those methods without specifying a concrete locale can be a hot source of bugs. It happens because those methods will use the current locale on the user's device, and even though the code appears to work correctly when you're developing the app, it will fail in some locales.

Some case mappings depend on language or locale. A good example is Turkish, where the uppercase letter 'I' maps to the dotless lowercase 'ı', and the uppercase replacement for 'i' is not 'I'.

Example: (en) QUIT ⇒ quit / (tr) QUIT => quıt

If you want the methods to just perform an ASCII replacement for converting an enum name, call toUpperCase(Locale.ENGLISH)/toLowerCase(Locale.ENGLISH) instead. If you really want to use the current locale, call toUpperCase(Locale.getDefault())/toLowerCase(Locale.getDefault()) instead.

For further details, click here.

Avoid concatenating string resources

Do

strings.xml

<string name="hello_phrase_friend">Say hello to your Phrase's friend</string>

<string name="hello_phrase_colleague">Say hello to a Phrase's colleague</string>

Java:

String helloFriend = res.getString(R.string.hello_phrase_friend);

String helloColleague = res.getString(R.string.hello_phrase_colleague);

Log.d("phrase_i18n", helloFriend);

Log.d("phrase_i18n", helloColleague);

Output (en-US):

"Say hello to your Phrase's friend"

"Say hello to a Phrase's colleague"

Don't

strings.xml

<string name="say_hello">Say hello to</string>

<string name="phrase_friend"> your Phrase's friend</string>

<string name="phrase_colleague"> a Phrase's colleague</string>

Java:

String sayHello = res.getString(R.string.say_hello);

String friend = res.getString(R.string.phrase_friend);

String colleague = res.getString(R.string.phrase_colleague);

Log.d("phrase_i18n", sayHello + friend);

Log.d("phrase_i18n", sayHello + colleague);

Output (en-US):

"Say hello to your Phrase's friend"

"Say hello to a Phrase's colleague"

Different languages have different rules for word order, capitalization, gender, singular, or plural, which may result in terrible grammar mistakes.

Avoid placing non-translatable strings as translatable

Do

- Place non-translatable resources in a separated file (donottranslate.xml)

or

Use translatable="false" property

<string name="phrase_act_name" translatable="false">PhraseActivity</string>

Don't

strings.xml

<string name="phrase_act_name">PhraseActivity</string>

values-fr-rFR:

<string name="phrase_act_name">PhraseActivity</string>

Provide sufficient context for declared strings, e.g., by adding comments

Do

strings.xml

<!-- The action for submitting a form. This text is on a button that can fit 30 chars -->

<string name="login_submit_button">Sign in</string>

Don't

strings.xml

<string name="login_submit_button">Sign in</string>

In general, a translation tool only shows the comment for the string currently being edited/translated. Consider providing context information that may include:

  • What is this string for? When and where is it presented to the user?
  • Where is this in the layout? For example, translations are less flexible in buttons than in text boxes.

Mark all message parts that should not be translated

Do

values/strings.xml

<!-- Example placeholder for a name -->

<string name="prod_name">Learn more at <xliff:g id="company">Phrase</xliff:g> blog</string>

<!-- Example placeholder for a literal -->

<string name="promo_message">Use \"<xliff:g id="promotion_code">PHRASEDISCOUNT</xliff:g>\" code to get a discount</string>

<string name="countdown"><xliff:g id="time" example="5">%1$d</xliff:g> days until promotion</string>

values-pt/strings.xml

<string name="prod_name">Saiba mais no blog da <xliff:g id="company">Phrase</xliff:g></string>

<string name="promo_message">Utilize o código \"<xliff:g id="promotion_code">PHRASEDISCOUNT</xliff:g>\" para obter um desconto</string>

<string name="countdown"><xliff:g id="time" example="5">%1$d</xliff:g> dias até a promoção</string>

Java:

String title = res.getString(R.string.prod_name);

String promoMessage = res.getString(R.string.promo_message);

int days = getSharedPreferences("prefs", MODE_PRIVATE).getInt("days", 0); // 5

String countdownPromo = res.getString(R.string.countdown, days);

Log.d("phrase_i18n", title);

Log.d("phrase_i18n", promoMessage);

Log.d("phrase_i18n", countdownPromo);

Output (en-US):

"Learn more at Phrase blog"

"Use "PHRASEDISCOUNT" code to get a discount"

"5 days until promotion"

Output (pt):

"Saiba mais no blog da Phrase"

"Utilize o código "PHRASEDISCOUNT" para obter um desconto."

"5 dias até a promoç˜ão"

Don't

values/strings.xml

<!-- Example placeholder for a name -->

<string name="prod_name">Learn more at Phrase blog</string>

<!-- Example placeholder for a literal -->

<string name="promo_message">Use \"PHRASEDISCOUNT\" code to get a discount</string>

<string name="countdown">%s until holiday</string>

values-pt/strings.xml

<string name="prod_name">Saiba mais no blog da Frase</string> 

<string name="promo_message">Use o código DESCONTOFRASE para obter um desconto</string>

<string name="countdown">%s dias até a promoção</string>

Java:

String title = res.getString(R.string.prod_name);

String promoMessage = res.getString(R.string.promo_message);

int days = getSharedPreferences("prefs", MODE_PRIVATE).getInt("days", 0);

String countdownPromo = res.getString(R.string.countdown, days);

Log.d("phrase_i18n", title);

Log.d("phrase_i18n", promoMessage);

Log.d("phrase_i18n", countdownPromo);

Output (en-US):

"Learn more at Phrase blog"

"Use "PHRASEDISCOUNT" code to get a discount"

"5 days until promotion"

Output (pt):

"Saiba mais no blog da Frase"

"Use o código "DESCONTOFRASE" para obter um desconto"

"5 dias até a promoção"

To mark the non-translatable content, use the <xliff:g> tag. Common examples might be a piece of code, a placeholder for a value, a special symbol, or a name. As you prepare your strings for translation, look for, and mark text that should remain as it is, so that the translator doesn't change it.

When you declare a placeholder tag, always add an id attribute that explains what the placeholder is for. If your app replaces the placeholder value later on, be sure to provide an example attribute to clarify the expected use.

In the example above, the lack of XLIFF tags can lead to unexpected translations: Phrase > Frase, PHRASEDISCOUNT > DESCONTOFRASE, etc.

Leave extra space for translation

Do

Leave enough space for expansion

(15% - 30%) or allow text wrapping and scrolling

Don't

Otherwise:

“Одговори на сите”

will truncate

As you can see in the example above, the English “Reply all” has a quite long counterpart in Macedonian: “Одговори на сите”.

Avoid placing newline characters

Do

Use Android Layout

to organize the UI elements

Don't

/res/values/strings.xml

<string name="header">Watch our Free Webinars to\nlearn all\nabout the newest Phrase features.</string>

/res/values-es/strings.xml:

<string name="header">Vea nuestros seminarios web gratuitos\npara aprender todo\nsobre las nuevas funciones de frase</string>

Text may wraps incorrectly (error prone)

Avoid using quantity strings

Do

strings.xml

<string name =”personsFound”>

{count, plural,

= 0 {No persons in the list.}

= 1 {There is one person in the list.}

= other {There are %d persons in the list.}

}

</string>

Java:

int personsCount = 18;

String msg = getResources().getString(R.plurals.personsFound);

String personsFound = MessageFormat.formatNamedArgs(msg, "count", personsCount);

Log.d("phrase_i18n", personsFound);

Output (en-US):

"There are 42 persons in the list."

Don't

strings.xml

<plurals name=”personsFound”>

   <item quantity=”zero”>No persons in the list.</item>

   <item quantity=”one”>There is one person in the list.</item>

   <item quantity=”other”>There are %d persons in the list.</item>

<plurals>

Java:

int personsCount = 42;

String msg = getResources().getQuantityString(R.plurals.personsFound, personsCount, personsCount);

Output (en-US):

"There are 42 persons in the list."

The recommended way is using the ICU MessageFormat class, a small library extracted from ICU.

3. Bidirectionality

When content is presented horizontally, most scripts display text/characters from left to right, but there are several scripts (such as Arabic, Persian, Hebrew, Urdu) in which the natural ordering of horizontal alignment in the display is from right to left.

The main difference between left-to-right (LTR) and right-to-left (RTL) language scripts is the direction in which content is displayed:

  • Text
    • LTR → Sentences are read from left to right.
    • RTL → Sentences are read from right to left.
  • Timeline
    • LTR → An illustrated sequence of events progresses left to right.
    • RTL → An illustrated sequence of events progresses right to left.
  • Imagery
    • LTR → An arrow pointing left to right indicates forward motion: (→)
    • RTL → An arrow pointing right to left indicates forward motion: (←)

When a UI is changed from LTR to RTL (or vice versa), it’s often called "mirroring". An RTL layout is the mirror image of an LTR layout, and it affects layout, text, and graphics.

Clarifying Mirroring changes

When a UI is mirrored, the following changes occur:

  • Text field icons are displayed on the opposite side of a field,
  • Navigation buttons are displayed in reverse order,
  • Icons that communicate direction, e.g., arrows, are mirrored,
  • Text (if it is translated into an RTL language) is aligned to the right.

These items are not mirrored:

  • Untranslated text (even if it’s part of a phrase),
  • Icons that don't communicate direction, e.g. a camera,
  • Numbers, such as time, phone numbers, URLs,
  • Charts and graphs.

Android Bidi instructions

Now, let’s clarify the instructions to enable Bidi support for your app:

  1. Check the build and manifest files

    As mentioned in the section on configuration above, the android:supportsRtl property needs to be set, as RTL mirroring is only supported on devices running on Android 4.2 (API level 17) or higher.

  2. Update/standardize layout attributes

    Instead of left and right, use start and end, respectively, to set the positions of UI elements in the layout resource files. It allows the FW to align app’s UI elements based on the user’s language settings.

Do

<TextView

   android:id="@+id/text"

   android:gravity="start"

   android:layout_marginStart

   android:layout_paddingEnd

   ...

Don't

<TextView

   android:id="@+id/text"

   android:gravity="left"

   android:layout_marginLeft

   android:layout_paddingRight

   ...

If you're wondering what would it be the case when all layout files of your app were implemented using left/right properties. Don't worry, Android Studio can do this for you!

Add Right-to-Left Support | Phrase

Here are some further tips for other UI elements:

  • ViewPager

The well-known and widely used ViewPager class does not give native support for RTL layouts. If your app has paging behavior using this class, and you want to enable native right-to-left orientation, Google released the 2nd version of ViewPager class last year, bringing several improvements, including RTL layout support. You can find more details here. Google also released a document clarifying the migration process from ViewPager to ViewPager2.

  • Drawables

For instance, in case your app has drawables that need to be mirrored for an RTL layout, complete one of these steps based on the target Android version:

  • Android 4.3 (API level 18) and lower
    • add and define the -ldrt resource files.
  • Android 4.4 (API level 19) and higher
    • use android:autoMirrored="true" when defining drawable, which allows the system to handle RTL layout mirroring automatically
  • Texts
    • android:textAlignment (reference here)
      • Property values: inherit, center, gravity, textStart, textEnd, viewStart, viewEnd
    • android:textDirection (reference here)
      • Property values: inherit, anyRtl, firstStrong, firstStrongLtr, firstStrongRtl, locale, ltr, rtl

To provide specialized assets for RTL languages, you can use direction- and language-specific resources. Resources inside such directories will only be used for RTL languages or for one specific locale. See below:

res/

    layout/

        main.xml

    layout-ldrtl/

        main.xml

res/

    layout/

        main.xml

    layout-ar/

        main.xml

    layout-ldrtl/

        main.xml

🗒️ Note I » Language-specific resources take precedence over layout-direction-specific resources, which take precedence over the default resources.

🗒️ Note II » Although you can define specific layouts to a specific language, this is recommended only when strictly necessary. Since Android supports auto mirroring, in majority of the cases, you may be able to define a flexible layout that will work perfectly for both LTR and RTL layouts, so you will not need to maintain several layout files.

4. Formatting

To achieve world-readiness, it’s strongly recommended to use Android locale-sensitive APIs. The Android ecosystem leverages the ICU library and CLDR project to provide Unicode and other internationalization support.

Check out the following set of powerful libraries recommended to use during the development of your app:

Class Alternatives
java.lang.Character android.icu.lang.UCharacter
java.text.BreakIterator android.icu.text.BreakIterator
java.text.MessageFormat android.icu.text.MessageFormat
java.text.DecimalFormat android.icu.text.DecimalFormat
java.util.Calendar android.icu.util.Calendar
java.util.TimeZone android.icu.util.TimeZone
android.text.BidiFormatter android.icu.text.Bidi
android.text.format.DateFormat android.icu.text.DateFormat
android.text.format.DateUtils android.icu.text.DateFormat android.icu.text.RelativeDateTimeFormatter
java.text.NumberFormat android.icu.text.NumberFormat
java.util.Currency android.icu.util.Currency
android.icu.text.MeasureFormat NA
android.icu.util.MeasureUnit NA
android.icu.util.Measure NA
android.telephony.PhoneNumberUtils

Let’s have a look at how to best use some of the libraries mentioned above!

Don’t hard-code the Date Format

Do

Java:

String datePattern = DateFormat.getBestDateTimePattern(locale, "MMMMd");

String curDate = DateFormat.format(datePattern , new Date()).toString();

Output:

en-US "December 23"

zh-CN "12月23日"

es-US "23 de diciembre"

Don't

Java:

SimpleDateFormat df = new SimpleDateFormat("MMMM d");

String curDate = df.format(new Date());

Log.d("phrase_i18n", curDate);

Output:

en-US "December 23"

zh-CN "12月 23"

es-US "diciembre 23"

On Don't output, via SimpleDateFormat, note that the order of the content remained the same as the skeleton (MMMM d month day), regardless of the locale, which means the locale rule was not respected. The month and day data was localized, but the style wasn't.

While using DateFormat.getBestDateTimePattern, we can observe the whole date, i.e., both style and content are properly localized.

With date, it's very important to pay attention to the pattern date style, which will indicate the output data. You can see the Date/Time format syntax here.

Don’t hard-code the Time format

Do

Java:

String skeleton = DateFormat.is24HourFormat(context) ? "Hm" : "hm";

String timePattern = DateFormat.getBestDateTimePattern(locale, skeleton);

String curtime = DateFormat.format(skeleton, new Time());

Output:

en-US "10:44 PM"

zh-CN "下午10:44"

es-US "10:44 p. m."

Don't

Java:

String curTime = DateFormat.format("hh: mm a", new Date()).toString();

Log.d("phrase_i18n", curTime);

Output:

en-US "10:44 PM"

zh-CN "10:44 PM"

es-US "10:44 PM"

Similarly to date, it's very important to keep an eye on the time style pattern, which will indicate the output data. You can see the Date/Time format syntax here.

🗒️ Note » ICU on Android does not observe the user's 24h/12h time format setting (obtained from DateFormat.is24HourFormat()). To observe the setting, either use the DateFormat or DateUtils time formatting method, or use the ICU time formatting patterns with appropriate hour pattern symbols ('h' for 12h, 'H' for 24h) for different is24HourFormat() return values.

🔗 Resource » Our dedicated tutorial takes you through localizing date and time formats in Android step-by-step.

Don’t assume that Number format is the same for each locale

Do

Java:

NumberFormat nf = NumberFormat.getPercentInstance();

nf.setMaximumFractionDigits(1);

String curNumber = nf.format(0.149);

Log.d("phrase_i18n", curNumber);

Output:

en-US "14.9%"

fr-FR "14,9 %"

tr-TR "%14,9"

Don't

Java:

String curNumber = String.format(“%.1f”, 0.149*100) + “%”;

Log.d("phrase_i18n", curNumber);

Output:

en-US "14.9%"

fr-FR "14.9%"

tr-TR "14.9%"

Avoid hard-coded measurement units

Do

Java:

MeasureFormat.FormatWidth fWidth = MeasureFormat.FormatWidth.SHORT;

MeasureFormat mf = MeasureFormat.getInstance(Locale.getDefault(), fWidth);

String size = mf.formatMeasures(new Measure(45.7, MeasureUnit.GIGABYTE));

String speedKm = mf.formatMeasures(new Measure(50, MeasureUnit.KILOMETER_PER_HOUR));

String speedMph = mf.formatMeasures(new Measure(50, MeasureUnit.MILE_PER_HOUR));

String tempF = mf.formatMeasures(new Measure(62, MeasureUnit.FAHRENHEIT));

Log.d("phrase_i18n", size);

Log.d("phrase_i18n", speedKm);

Log.d("phrase_i18n", speedMph);

Log.d("phrase_i18n", tempF);

Output:

en-US "45.7 GB, 50 kph, 50 mph, 62°F""

ru-RU "45,7 ГБ, 50 км/ч, 50 миль/час, 62°F"

pt-BR "45,7 GB, 50 km/h, 50 mph, 62 °F"

ar-EG "٤٥٫٧ غيغابايت |٥٠ كم/س |٥٠ ميل/س |٦٢°ف"

Don't

strings.xml

<string name=”gb”>GB</string>

Java:

String size = String.format(“%f %s”,45.7, getString(R.string.gb));

int spValue = 50;

String speedKm = String.format (“%d kph”, spValue);

String speedMph = String.format (“%d mph”, spValue);

String tempF = String.format (“%d ºF”, 62);

Log.d("phrase_i18n", size);

Log.d("phrase_i18n", speedKm);

Log.d("phrase_i18n", speedMph);

Log.d("phrase_i18n", tempF);

Output:

en-US "45.7 GB, 50 kph, 50 mph, 62°F"

ru-RU "45.7 GB, 50 kph, 50 mph, 62°F"

pt-BR "45.7 GB, 50 kph, 50 mph, 62°F"

ar-EG "45.7 GB, 50 kph, 50 mph, 62°F"

Note that the MeasureFormat class is able to format not only the measure units according to the locale but also numbers, spaces, the occurrence of white spaces, etc.

For more information on available measure units, click here. You also can use different widths for the measure units (NARROW, SHORT, WIDE, NUMERIC).

Use BidiFormatter to format mixed (LTR and RTL text) content

Do

res/values/strings.xml

<string name=”did_you_mean”>Did you mean %s?</string>

res/values-iw/strings.xml

<string name=”did_you_mean”>האם התכוונת ל %s?</string>

String mySuggestion = "15 Bay Street, Laurel, CA";

BidiFormatter bidiFormatter = BidiFormatter.getInstance();

String content = String.format(R.string.did_you_mean, bidiFormatter.unicodeWrap(mySuggestion));

Log.d("phrase_i18n", content);

Output:

iw-IL "?15 Bay Street, Laurel, CA האם התכוונת ל"

Don't

res/values/strings.xml

<string name=”did_you_mean”>Did you mean %s?</string>

res/values-iw/strings.xml

<string name=”did_you_mean”>האם התכוונת ל %s?</string>

String mySuggestion = "15 Bay Street, Laurel, CA";

String.format(R.string.did_you_mean, mySuggestion);

Output:

iw-IL "?Bay Street, Laurel, CA 15 האם התכוונת ל"

If you take a closer look, the house number (15) appears to the right of the address, and not to the left as expected, making the house number look more like a strange postal code. The same problem may occur if you include RTL text within a message that uses the LTR text direction.

To deal with that, the unicodeWrap() method detects the direction of a string and wraps it in Unicode formatting characters that declare that direction, so the content will be displayed accordingly.

Wrapping Up Our Android App Development Tutorial

We sincerely hope this Android localization tutorial helped clarify the most pressing questions and, most importantly, made you fit for taking your Android app global. Here's a short recap:

  • Never hard-code strings in your source code, and externalize all resources,
  • Avoid using the concatenation and use the FormatMessage class,
  • Support RTL layouts and Bidi text,
  • Write code with high localizability and use the Android APIs,
  • Handle plurals correctly,
  • Provide contextual information in string resources,
  • Test drive with pseudolocales,
  • Follow the CxD guideline.

If you feel ready to start using best practices for developing multilingual Android apps, let Phrase Localization Suite do the heavy lifting and stay focused on the code you love. The world’s most powerful, connective, and customizable localization platform will save you tons of developers’ time, reduce manual errors, and increase translation quality to let you unlock the full potential of your Android app in the long run.