Developer Guides & Tutorials

Android localization: How to localize Android apps with examples

Ilya Krukowski,Updated on March 8, 2026·14 min read
Android localization

With Android’s global reach, getting Android localization and localized time right is an important part of app development

Whether you're a new developer working on your first app or an experienced programmer adding another one to your collection, the key question is: Who are you building this app for? In other words, who's your target audience?

If you want your mobile apps to grow beyond your local area, possibly reaching users across different countries, here’s something to consider: most of the world doesn’t speak English. Eventually, you’ll need to support multiple languages.

Incorporating software internationalization into your development process from the beginning will help streamline localization efforts, making it easier to adapt your app for different languages and cultural contexts later on.

That’s why localizing your Android app is necessary. In this article, we’ll show you how to get started with Android localization using clear, step-by-step examples.

The source code is available on GitHub.

Managing Android translations at scale

Android resource files are straightforward to manage when an app supports one or two languages. As the number of languages, strings, and releases grows, keeping multiple XML files synchronized across development and translation workflows becomes increasingly difficult.

Lokalise is a software localization platform that syncs Android resource files with GitHub, GitLab, Bitbucket, or Azure Repos on every push. It automatically translates new strings with AI and flags only the content that requires human review.

With the Lokalise Android SDK, teams can also deliver translation updates to live Android apps over the air. This makes it possible to fix copy or release new translations without publishing a new version through the Google Play Store.

Preparing for Android localization

Step 1: Create a new Android project

To start your Android localization journey, first create a new Android project using the "Empty Views Activity" template in Android Studio. This template creates a traditional View-based project with an XML layout file, which we’ll use throughout this tutorial.

Android Studio not only simplifies project setup but also provides a built-in emulator for testing your mobile application translation on various devices and configurations, which is essential when verifying localization across different locales.

For this example, we’ll create a Kotlin-based project with the following configuration:

  • Name: LokaliseI18n
  • Package name: com.example.lokalisei18n
  • Language: Kotlin
  • Minimum SDK: API 31

After entering these values, click Finish and wait for Android Studio to create and synchronize the project.

Step 2: Add some elements

Open activity_main.xml in the app/src/main/res/layout/ directory. Replace its contents with a simple TextView that displays "Hello world!":

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/test_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello world!"
        android:textSize="26sp" />
</LinearLayout>

The "Hello world!" value is currently hardcoded directly in the layout. Hardcoded UI text is difficult to maintain and cannot be translated through Android’s resource system.

To localize the text, we’ll move it into a string resource and reference that resource from the layout. Android can then select the appropriate translation based on the user’s locale.

How to add Android language resources

Step 1: Add string resources

Let’s start by moving the app’s text into Android string resources. String resources separate user-facing text from layouts and Kotlin code, making it easier to maintain and translate the app.

Android stores default value resources in the res/values/ directory. In a newly created project, the strings.xml file already contains resources such as the app name.

Open res/values/strings.xml and add a resource for the "Hello world!" text:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name" translatable="false">LokaliseI18n</string>
    <string name="hello_world">Hello world!</string>
</resources>

The name attribute defines the resource identifier. In this example, Android generates the identifier R.string.hello_world for use in Kotlin and the reference @string/hello_world for use in XML.

We've also makred the app_name as non-translatable.

Descriptive resource names become especially important as the number of strings grows. For more recommendations, check out our guide to working with translation keys.

Step 2: Add resources for another language

Next, let’s add a Latvian translation. Android Studio includes a Translations Editor for managing default and localized string resources in one place.

Open res/values/strings.xml, then click Open editor in the upper-right corner. Alternatively, right-click strings.xml in the Project window and select Open Translations Editor.

In the Translations Editor, click the globe icon and select Latvian. Keep the region set to Any Region. Android Studio creates a new strings.xml file inside the res/values-lv/ directory.

Add the Latvian translation for hello_world:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello_world">Sveika, pasaule!</string>
</resources>

The app now has default English resources in res/values/ and Latvian resources in res/values-lv/. When Latvian is the active locale, Android loads matching values from values-lv and falls back to the default resources for any strings that are not translated.

Step 3: Reference the resource from XML

Once the string resources are available, reference hello_world from the TextView in activity_main.xml:

<TextView
    android:id="@+id/test_text_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world"
    android:textSize="26sp" />

The @string/hello_world value is a reference to the string resource named hello_world. At runtime, Android selects the best matching version from the available resource directories based on the current locale.

For example, Android loads the default value from res/values/strings.xml when English is active and the Latvian value from res/values-lv/strings.xml when Latvian is active.

Step 4: Access string resources from Kotlin

You can also retrieve string resources programmatically when UI text needs to change while the app is running.

Open MainActivity.kt and use getString() to retrieve the localized value:

package com.example.lokalisei18n

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val textView = findViewById<TextView>(R.id.test_text_view)
        textView.text = getString(R.string.hello_world)
    }
}

R.string.hello_world is the generated resource ID, while getString() retrieves the corresponding localized text.

You do not normally need to set the same value in both XML and Kotlin. The Kotlin example is useful when the displayed text depends on user actions or application state.

Step 5: Test Android localization

Run the app on an Android device or emulator and set Latvian as the preferred system language. Android should display Sveika, pasaule! instead of the default English text.

The text may look unchanged when English is active, but it is no longer hardcoded. Android now loads it from the resource directory that best matches the current locale.

Changing the entire device language is not the only option. In the next section, we’ll configure per-app language support and let users select the app language without changing the language used by the rest of the device.

How to change the app language programmatically

By default, Android selects app resources based on the user’s preferred system languages. However, a user may want to use your app in Latvian while keeping the rest of the device in English.

Android 13 and later support per-app language preferences in system settings. To provide an in-app language selector that also works on earlier Android versions, use the AndroidX AppCompatDelegate API.

Step 1: Enable per-app language support

Android Gradle Plugin 8.1 and later can generate the app’s locale configuration automatically from its resource directories.

Open the module-level build.gradle.kts file and enable locale configuration generation:

android {
    androidResources {
        generateLocaleConfig = true
    }
}

Next, create app/src/main/res/resources.properties and specify the language used by the default resources:

unqualifiedResLocale=en

Android can now detect English from the default res/values/ directory and Latvian from res/values-lv/. On Android 13 and later, both languages become available in the app’s system language settings.

Step 2: Store the selected language on older Android versions

On Android 12 and earlier, AndroidX can store the selected app language automatically.

Add the following service inside the <application> element of AndroidManifest.xml:

<service
    android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
    android:enabled="false"
    android:exported="false">
    <meta-data
        android:name="autoStoreLocales"
        android:value="true" />
</service>

This requires AppCompat 1.6.0 or later.

Step 3: Add language selection buttons

First, add labels for the language buttons to res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name" translatable="false">LokaliseI18n</string>
    <string name="hello_world">Hello world!</string>
    <string name="use_latvian">Use Latvian</string>
    <string name="use_system_language">Use system language</string>
</resources>

Add the corresponding Latvian translations to res/values-lv/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello_world">Sveika, pasaule!</string>
    <string name="use_latvian">Lietot latviešu valodu</string>
    <string name="use_system_language">Lietot sistēmas valodu</string>
</resources>

Next, add two buttons below the existing TextView in activity_main.xml:

<Button
    android:id="@+id/latvian_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="24dp"
    android:text="@string/use_latvian" />

<Button
    android:id="@+id/system_language_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:text="@string/use_system_language" />

Step 4: Handle language selection

Open MainActivity.kt and register click listeners for both buttons:

package com.example.lokalisei18n

import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val latvianButton =
            findViewById<Button>(R.id.latvian_button)

        val systemLanguageButton =
            findViewById<Button>(R.id.system_language_button)

        latvianButton.setOnClickListener {
            val latvianLocales =
                LocaleListCompat.forLanguageTags("lv")

            AppCompatDelegate.setApplicationLocales(
                latvianLocales
            )
        }

        systemLanguageButton.setOnClickListener {
            AppCompatDelegate.setApplicationLocales(
                LocaleListCompat.getEmptyLocaleList()
            )
        }
    }
}

LocaleListCompat.forLanguageTags("lv") creates a locale list containing Latvian. Passing that list to setApplicationLocales() changes the app language without changing the language used by the rest of the device.

Passing an empty locale list removes the app-specific preference and returns the app to the language selected in the device settings.

Step 5: Test the language change

Run the app while the device language is set to English. Tap Use Latvian to switch the app to Latvian. Android recreates the activity and reloads the strings from res/values-lv/.

Tap Lietot sistēmas valodu to remove the app-specific setting and return to the device language.

On Android 13 or later, you can also change the language under Settings > Apps > LokaliseI18n > Language. The system setting and the in-app language selector remain synchronized.

How to pluralize nouns

Different languages use different noun forms depending on the quantity. In English, for example, we say “1 cat” but “3 cats.” Android handles these grammatical differences through quantity string resources, also known as plurals.

Step 1: Add TextViews for the examples

Add three TextView elements to activity_main.xml. We’ll use them to display examples for the quantities 0, 1, and 3:

<TextView
    android:id="@+id/plural_view_zero"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="26sp" />

<TextView
    android:id="@+id/plural_view_one"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="26sp" />

<TextView
    android:id="@+id/plural_view_three"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="26sp" />

Step 2: Understand plural categories

Android supports six plural categories: zero, one, two, few, many, and other. These names represent grammatical categories rather than specific numeric values, and each language uses a different subset.

English uses two categories:

  • one: 1 cat
  • other: 0 cats, 3 cats, and other quantities

Latvian uses three categories:

  • zero: 0 kaķu
  • one: 1 kaķis
  • other: 3 kaķi

Android selects the appropriate category automatically based on the active locale and quantity.

Step 3: Define the plural resources

Add the following resource inside the existing <resources> element in res/values/strings.xml:

<plurals name="my_cats">
    <item quantity="one">I have %1$d cat</item>
    <item quantity="other">I have %1$d cats</item>
</plurals>

Next, add the Latvian forms inside the existing <resources> element in res/values-lv/strings.xml:

<plurals name="my_cats">
    <item quantity="zero">Man ir %1$d kaķu</item>
    <item quantity="one">Man ir %1$d kaķis</item>
    <item quantity="other">Man ir %1$d kaķi</item>
</plurals>

The category names do not correspond to one fixed number. For example, Latvian also uses the one form for quantities such as 21 and 31, while numbers such as 10 and 20 use the zero form.

Step 4: Display the pluralized strings

In MainActivity.kt, retrieve the appropriate strings with getQuantityString():

val pluralViewZero =
    findViewById<TextView>(R.id.plural_view_zero)
val pluralViewOne =
    findViewById<TextView>(R.id.plural_view_one)
val pluralViewThree =
    findViewById<TextView>(R.id.plural_view_three)

pluralViewZero.text = resources.getQuantityString(
    R.plurals.my_cats,
    0,
    0
)

pluralViewOne.text = resources.getQuantityString(
    R.plurals.my_cats,
    1,
    1
)

pluralViewThree.text = resources.getQuantityString(
    R.plurals.my_cats,
    3,
    3
)

The second argument passed to getQuantityString() determines which plural category Android selects. The third argument replaces %1$d in the selected string.

Step 5: Test pluralization

Run the app and test it in both English and Latvian.

In English, the app should display:

I have 0 cats
I have 1 cat
I have 3 cats

In Latvian, it should display:

Man ir 0 kaķu
Man ir 1 kaķis
Man ir 3 kaķi

Switch the app language using the buttons from the previous section and confirm that Android selects the correct forms for each locale.

How to localize dates and times

Date and time formats differ across languages and regions. Instead of hardcoding a pattern such as MM/dd/yyyy, use a localized formatter that follows the app’s active locale.

Because this project requires API 31 or later, we can use the modern java.time API.

Step 1: Add a TextView

Add a TextView to activity_main.xml for displaying the localized date and time:

<TextView
    android:id="@+id/localized_date_time_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="24dp"
    android:textSize="26sp" />

Step 2: Format the current date and time

Add the following imports to MainActivity.kt:

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

Then add the following code inside onCreate():

val localizedDateTimeView =
    findViewById<TextView>(R.id.localized_date_time_view)

val appLocale = resources.configuration.locales[0]

val formatter = DateTimeFormatter
    .ofLocalizedDateTime(
        FormatStyle.MEDIUM,
        FormatStyle.SHORT
    )
    .withLocale(appLocale)

localizedDateTimeView.text =
    formatter.format(ZonedDateTime.now())

ZonedDateTime.now() creates a value containing the current date, time, and system time zone.

ofLocalizedDateTime() selects a date and time pattern appropriate for the locale. FormatStyle.MEDIUM controls the date format, while FormatStyle.SHORT produces a compact time format.

The locale comes from the app’s current resource configuration. This means the displayed format follows the language selected through the app’s language buttons, even when it differs from the device language.

Step 3: Test the formatting

Run the app and compare the displayed date and time in English and Latvian. Tap the language buttons added earlier and confirm that the activity reloads with a format appropriate for the selected locale.

The exact result may vary depending on the locale and device settings, including whether the device uses a 12-hour or 24-hour clock.

Use Lokalise for Android localization

If you've made it this far, you're clearly serious about internationalizing your Android app. But managing localization manually becomes increasingly difficult as your team, language count, and release cadence grow.

That’s where a translation management system comes in handy. Lokalise connects to your code repository, translates new strings with AI, and flags only the content that needs human review. It can also deliver translation updates to live Android apps via OTA, without requiring a new Google Play Store release.

Let’s walk through how to set it up.

Step 1: Set up a Lokalise project

To get started, create a free account or log in to Lokalise. In the dashboard, click the New project button and create a Web and mobile project. Enter the following information:

Translation platform services tools

Once the project is created, click Upload files:

Lokalise upload

Upload your language resource files, such as the strings.xml files we created in the tutorial. If Lokalise detects the wrong language, select the correct one from the dropdown menu.

null

Click the Import files button to complete the resource uploading process. In the Editor tab, you’ll see all your project's resources neatly organized.

null

Step 2: Manage resources with Lokalise

In the Lokalise editor, you can:

  • Edit translations directly.
  • Translate new strings with AI.
  • Flag translations for human review.
  • Order professional translations.
  • Manage translations for multiple languages.
  • Connect Lokalise to repositories and other development tools.

For example, to replace “cat” with “dog,” open the corresponding translation and edit its value.

step 2 1

To download the updated resource files, open the Download page and select Android Resources (.xml) as the file format. Click Build and download to generate a ZIP archive containing the translated resource files.

step 2 2.webp

Extract the archive and copy the updated files into the appropriate res/values/ directories in your Android project.

Step 3: Over-the-air (OTA) updates

Lokalise also supports over-the-air (OTA) updates, allowing you to deliver new and updated translations to live Android apps without rebuilding the app or publishing another release through Google Play.

OTA is useful throughout the localization lifecycle. Teams can launch new languages, update product copy, correct terminology, fix translation issues, and publish content changes between regular development cycles. This keeps localized content moving independently of the app release schedule while reducing the need for emergency builds or store submissions.

To use OTA, go to the Download page and select Android SDK under the Mobile SDK section from the dropdown menu:

step 3.webp

Scroll down and click Build only to generate an OTA bundle. You can manage the bundle by going to More > Settings > OTA bundles. Lokalise redirects you to the Android bundle management page in the project settings. Enable the generated bundle for Prerelease and save the changes so you can test it before publishing it to production.

null

You can learn more about OTA in Lokalise’s Developer Hub.

Step 4: Integrate the Lokalise SDK

First, add the Lokalise Maven repository to settings.gradle.kts:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://maven.lokalise.com")
        }
    }
}

Next, add the SDK dependency to the module-level app/build.gradle.kts file:

dependencies {
    implementation("com.lokalise.android:sdk:2.4.2")
}

Create a new MyApplication.kt file and initialize the SDK:

package com.example.lokalisei18n

import android.app.Application
import com.lokalise.sdk.Lokalise

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        Lokalise.init(
            appContext = this,
            sdkToken = "YOUR_OTA_TOKEN",
            projectId = "YOUR_PROJECT_ID"
        )

        Lokalise.isPreRelease = true
        Lokalise.updateTranslations()
    }
}

isPreRelease tells the SDK to load the bundle published to the prerelease channel. Remove this line or set it to false when testing is complete and the bundle has been published to production.

You can find the project ID in the project settings. Generate an OTA token in the same settings area, then replace YOUR_OTA_TOKEN and YOUR_PROJECT_ID with the corresponding values.

Next, register the custom application class in AndroidManifest.xml:

<application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:theme="@style/Theme.LokaliseI18n">

    <!-- Activities and other application components -->

</application>

Finally, connect the SDK to the Activity context. With Lokalise Android SDK 2.4.0 and later, you must override both attachBaseContext() and getDelegate():

package com.example.lokalisei18n

import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.lokalise.sdk.LokaliseContext
import com.lokalise.sdk.LokaliseContextWrapper
import dev.b3nedikt.viewpump.ViewPumpAppCompatDelegate

class MainActivity : AppCompatActivity() {

    private val lokaliseDelegate: AppCompatDelegate by lazy {
        ViewPumpAppCompatDelegate(
            baseDelegate = super.getDelegate(),
            baseContext = this,
            wrapContext = LokaliseContext::wrapContext
        )
    }

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(
            LokaliseContextWrapper.wrap(newBase)
        )
    }

    override fun getDelegate(): AppCompatDelegate {
        return lokaliseDelegate
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // The rest of the code from the previous sections
    }
}

If your app contains multiple activities, move this integration code into a common base activity and extend that class from your other activities.

Step 5: Test the OTA updates.

Run the app and confirm that the translations from the prerelease bundle are available.

null

Next, change a translation in the Lokalise editor, return to the Download page, and build another Lokalise Android SDK bundle. Enable the new version for Prerelease, save the changes, and relaunch the app.

null

The SDK checks for an updated bundle when updateTranslations() runs. Once the update has been downloaded, the app can use the new translation without being rebuilt or reinstalled.

After testing the bundle, publish the approved version to Production and save the bundle settings. Production builds should use the production channel rather than setting Lokalise.isPreRelease to true.

You can learn more about Android SDK and OTA bundles in our Developer Hub. I would also recommend checking the best practices that will help you optimize OTA bundle sizes.

Conclusion

In this tutorial, we covered how to localize an Android app for multiple locales. We worked through API deprecations, switched languages through device settings and programmatically using a modified Context, and localized plural forms, dates, and times.

We also explored how Lokalise can deliver translation updates over the air, allowing teams to update localized content without publishing a new version of the app to the Google Play Store.

As your project grows beyond one or two languages, managing Android resource files manually can become a bottleneck. Lokalise connects to your repository, translates new strings with AI, flags content that needs human review, and delivers translation updates to live apps via OTA.

Frequently asked questions

What is Android localization?

How does Lokalise help with Android app localization?

What are over-the-air updates for Android?

Can Lokalise integrate with my Android CI/CD pipeline?

Developer Guides & Tutorials

Author

1517544791599.jpg

Lead of content, SDK/integrations dev

Ilya is the lead for content, documentation, and onboarding at Lokalise, where he focuses on helping engineering teams build reliable internationalization workflows. With a background at Microsoft and Cisco, he combines practical development experience with a deep understanding of global product delivery, localization systems, and developer education.

He specializes in i18n architectures across modern frameworks — including Vue, Angular, Rails, and custom localization pipelines — and has hands-on experience with Ruby, JavaScript, Python, Elixir, Go, Rust, and Solidity. His work often centers on improving translation workflows, automation, and cross-team collaboration between engineering, product, and localization teams.

Beyond his role at Lokalise, Ilya is an IT educator and author who publishes technical guides, best-practice breakdowns, and hands-on tutorials. He regularly contributes to open-source projects and maintains a long-standing passion for teaching, making complex internationalization topics accessible to developers of all backgrounds.

Outside of work, he keeps learning new technologies, writes educational content, stays active through sports, and plays music. His goal is simple: help developers ship globally-ready software without unnecessary complexity.

vercel

Build a smooth translation pipeline with Lokalise and Vercel

Internationalization can sometimes feel like a massive headache. Juggling multiple JSON files, keeping translations in sync, and redeploying every time you tweak a string… What if you could offload most of that grunt work to a modern toolchain and let your CI/CD do the heavy lifting? In this guide, we’ll wire up a Next.js 15 project hosted on Vercel. It will load translation files on demand f

Updated on August 13, 2025·Ilya Krukowski
Hero GitHub

Hands‑on guide to GitHub Actions for Lokalise translation sync: A deep dive

In this tutorial, we’ll set up GitHub Actions to manage translation files using Lokalise: no manual uploads or downloads, no reinventing a bicycle. Instead of relying on the Lokalise GitHub app, we’ll use open-source GitHub Actions. These let you push and pull translation files directly via the API in an automated way. You’ll learn how to: Push translation files from your repo to LokalisePull translated content back and open pull requests automaticallyWork w

Updated on August 4, 2025·Ilya Krukowski
Lokalise api and webhooks illustration

Building an AI-powered translation flow using Lokalise API and webhooks

Managing translations in a growing product can quickly become repetitive and error-prone, especially when dealing with frequent content updates or multiple languages. Lokalise helps automate this process, and with the right setup you can build a full AI-powered translation pipeline that runs with minimal manual input. In this guide, you’ll learn how to: Upload translation files to Lokalise automaticallyCreate AI-based translation tasksUse webhooks to downloa

Updated on July 22, 2025·Ilya Krukowski

Stop wasting time with manual localization tasks.

Launch global products days from now.

  • Lokalise_Arduino_logo_28732514bb (1).svg
  • mastercard_logo2.svg
  • 1273-Starbucks_logo.svg
  • 1277_Withings_logo_826d84320d (1).svg
  • Revolut_logo2.svg
  • hyuindai_logo2.svg