Translations.. Translations.. everywhere
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, I’m gonna show you how our Android team generally implements dynamic localization at Codigo
Before we dive into the implementation, why the title “Dynamic Localization”?
It’s simply because we 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. (ie 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!
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
(ie 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
- Keep our default localization with a
localization.jsonfile as raw asset.
- Create an abstract
LocalizedViewModelProviding localization-related information and functionalities, so that any ViewModel can extend from it quite easily and take advantage of it.
- Expose an observable Localization data and persist user’s selected language as a
- 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.
Now that we’ve set our goals, let’s dive into the implementation. I’ve set up 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)
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.
To supply our app with localization data, I’ve setup a
localization.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 a
Localizationclass 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.
Finally, we’ll create an enum class to hold our supported languages.
And that’s it for the data models, we can now move on to the fun part,
Our data layer is set up this way…
LocalizationRepository will consume data from both local (raw json file) and remote (a dummy network api) sources
LocalizationRepository exposes the following…
localizationFlowto consume the localization in real-time
currentAppLanguageas the name describes
updateLanguagefunction to switch between languages
And our implementation of
LocalizationRepository is as follows…
Wah, there’s a lot going on here! Let me break it down a bit for you.
- First, we have our Flows and cached states setup. You’ll see private and public flows here. The private one’s a
MutableStateFlowand the public one’s just a
StateFlow. This way, we restrict external classes from modifying our state.
updateLanguagefunction emits the respective language localization object and saves in
- We have helper functions named
LocalizationBundle.getLocalization(). The former returns either the cached Translations or the one from
localization.json,if the cache is empty. The latter extracts the correct
Localizationobject from a
LocalizationBundlefor the selected language.
getLocalizationFromRemote()shows the example of fetching
Localizationfrom a remote Localization.
Local Data Source
You can see the implementation of
LocalizationLocal classes below.
It basically deserializes the
localization.json file from Raw Asset into a
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…
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 the
ViewModel class to extend
You can see our Demo App’s Home screen implementation as below…
Since we’re using Jetpack Compose, we just use
collectAsState() extension function to consume the Localization Flow.
If you use Android’s
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
That’s it! Cheers!
We’ve successfully implemented the “Dynamic Localization” feature and our UI will reflect the selected language in real-time. You can read or try out my full implementation of this demo app in this Github repository. You can freely comment any mistakes or misunderstandings regarding the article, too!