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.
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!":
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:
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.
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:
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:
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:
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:
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:
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():
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 catsI have 1 catI have 3 cats
In Latvian, it should display:
Man ir 0 kaķuMan ir 1 kaķisMan 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:
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:
Once the project is created, click Upload files:
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.
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.
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.
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.
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:
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.
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.lokalisei18nimport android.content.Contextimport android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport androidx.appcompat.app.AppCompatDelegateimport com.lokalise.sdk.LokaliseContextimport com.lokalise.sdk.LokaliseContextWrapperimport dev.b3nedikt.viewpump.ViewPumpAppCompatDelegateclass 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.
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.
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.
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?
Android localization is the process of adapting an app’s content, including strings, date formats, pluralization rules, and UI layouts, to work correctly in different languages and regions. Android stores localizable strings in XML resource files inside res/values/ directories, with language-specific subfolders such as res/values-de/ for German.
How does Lokalise help with Android app localization?
Lokalise connects to GitHub, GitLab, Bitbucket, or Azure Repos and syncs Android string resource files automatically on every push. It translates new strings with AI and flags only the content that requires human review, removing the need for manual file exports and imports.
What are over-the-air updates for Android?
Over-the-air (OTA) updates let you push translation changes to live Android apps without submitting a new release to the Google Play Store. The Lokalise Android SDK delivers updated strings directly to the app, allowing teams to fix copy or launch new translations between development cycles.
Can Lokalise integrate with my Android CI/CD pipeline?
Yes. Lokalise integrations connect with GitHub Actions, GitLab CI, Bitbucket Pipelines, and Azure Repos. String files can sync on every push, while approved translations are exported back to the relevant branch without manual download or upload steps.
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.
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.
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
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
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