Implementing Dynamic Localization On Android
In-app localization is inevitable in modern android app development if your app is targeting global users. There are multiple ways one could implement in-app localization in an android app.
In this blog, we're gonna to show you how our Android team generally implements dynamic localization at Codigo
Demo App UI - English[/caption]
[caption id="attachment_1310" align="aligncenter" width="307"]
Demo App UI - Chinese[/caption]
[caption id="attachment_1311" align="aligncenter" width="307"]
Demo App UI - Burmese[/caption]
Our
There’s a lot going on here! Let me break it down a bit for you.
Local Data Source You can see the implementation of
It basically deserializes the
View Model Now that our
Since we’re using Jetpack Compose, we just use
If you use Android’s
Before we dive into the implementation, why the title “Dynamic Localization”?
It’s simply because we try to load the translations for the entire app at runtime. The data source for the translations can be from anywhere! (Remote, local JSON file, or from any kind of local asset)What makes dynamic localization better than Android’s localized resources?
Android’s localized resources depend on your user’s device language settings. And it’s a hustle to manually control just the app-specific locale and reflect the changes in real-time. (i.e you’d have to restart your entire activity just to refresh the locale). Plus, your translated resources are entirely local because it’s packaged with the APK. It means you’ll have to build and release a new version of the app every time you want to update the translations. With Dynamic Localization, you can update your translations remotely from a CMS or an Admin Dashboard or anything, and your app will fetch and update at runtime without having to change your code or generate any new builds. Sounds promising? Let’s implement it!Our Goal
Let’s set our goals before we start. Our app should be able to :- Load default localization data once the app starts
- Provide easy access to translations everywhere in the app
- Reflect user’s preferred language in real-time (i.e app should reflect changes to selected language without restarting the app)
- Support text templating in translated texts
- Use a default language in case something went wrong
Our Solution
- Keep our default localization with a
localization.json
file as raw asset. - Create an abstract
LocalizedViewModel
providing localization-related information and functionalities, so that any ViewModel can extend from it quite easily and take advantage off it. - Expose an observable Localization data and persist user’s selected language as a
SharedPreference
. - Create a text formatting function, so that we can support text templating in our Localized Strings.
- Fallback to our cached translations in case remote fetching failed.
Demo project setup
Now that we’ve set our goals, let’s dive into the implementation. I’ve setup a project to demonstrate how we can do dynamic localization. You can clone or download the sample project from the following repository. The sample project uses the following stack:- MVVM + Repositories + Local & Remote Data Sources
- Jetpack Compose + Single Activity (for UI)
- Kotlin Flows (for reactivity)
- Moshi-Kotlin (for JSON serialization)
- Dagger Hilt (for dependency injection)
App UI
Demo app is setup with a minimal ui with just one screen to present translated texts and three buttons to switch between languages: English, Chinese and Burmese. Home screen of the Demo app with different language settings [caption id="attachment_1312" align="aligncenter" width="307"]


Data Models
To supply our app with localization data, I’ve setup alocalization.json
file as a raw
android resource.
If you checked the structure, it’s just three json objects, one for each translation. And each one of those contains the same set of keys. We’ll be using these translations in our demo app.
Now, let’s create aLocalization
class to hold the above translations.
Localization.getDefaultLocalization()
function is used for preview purposes in Jetpack Compose.
Now that we have the Localization
class, we’ll create a LocalizationBundle
class to hold the translations for all the languages.

Repository and Data Sources
Our data layer is set up this way…
Localization Repository
LocalizationRepository
will consume data from both local (raw json file) and remote (a dummy network api) sources

LocalizationRepository
exposes the following…
localizationFlow
to consume the localization in real-timecurrentAppLanguage
, as the name describesupdateLanguage
function to switch between languages
LocalizationRepository
is as follows…

- First, we have our Flows and cached states setup. You’ll see private and public flows here.The private one’s a
MutableStateFlow
and the public one’s just aStateFlow
. This way, we restrict external classes from modifying our state. updateLanguage
function emits the respective language localization object and saves inSharedPreference
- We have helper functions named
getLocalization
andLocalizationBundle.getLocalization()
.The former returns either the cached Translations or the one fromlocalization.json,
if the cache is empty.The latter extracts the correctLocalization
object from aLocalizationBundle
for the selected language. getLocalizationFromRemote()
shows the example of fetchingLocalization
from a remote Localization.
Local Data Source You can see the implementation of
LocalizationLocal
classes below.

localization.json
file from Raw Asset into a LocalizationBundle
.
View Model Now that our
LocalizationRepository
is ready, we can start integrating it in our ViewModels. We’re going to create an abstract class named LocalizedViewModel
as follows…

Let’s implement it in UI!
We’ve successfully setup our ViewModels and Repositories. Now we can start consuming the data in our UI! To use the localization data, we just need theViewModel
class to extend LocalizedViewModel
.
You can see our Demo App’s Home screen implementation as below…

collectAsState()
extension function to consume the Localization Flow.

DataBinding
feature, we can bind it easily too!


Cherry On Top with String templating
We can extend our implementation further by supporting string templates in our Translations. We can make use of the following method to replace variables in our Translated strings. In our implementation, we’re using%@
as our template handle. So our function replaces every %@
with passed arguments. We can use any kind of unique character or string for TEMPLATE_HANDLE
