Software Localization (also referred to as l10n) is the process of adapting or translating software to a specific locale's language, culture, and legal requirements.

In one of our Android projects, we were required to utilize a centralized data store, which contained the English & Spanish translations of various string resources. To achieve this, we needed to break down the entire process into 3 steps:

  1. Fetching the translated string resources for various locales from the server
  2. Updating the strings.xml file with the latest string resources
  3. Updating the view with the updated strings

As easy as it appears, this task was challenging comparatively, as we are always encouraged to use the conventional method of keeping separate string resources to support multiple languages in Android development.

After leafing through various blogs, StackOverflow threads, and whatnot, I finally found myself at the doorstep of this amazing library, Restring. As mentioned in the official documentation of the library,

Restring provides an easy way to replace bundled Strings dynamically, or provide new translations for Android.

1. Fetching the string resources from the server

I have created a demo project on Github in accordance with this blog. You can find it in the references section at the bottom of this article. I am using one TextView, which will display the greeting. The language in which the greeting is displayed can be changed using the two buttons, one for each language. This is how the basic UI will look:


The first step is to fetch the string resources from the server. For the sake of this project, I am not making an actual API call to fetch the JSON, instead, I am using a local JSON file to mock the response. Below are the JSON responses which contain localized strings for English & Spanish languages.

// English

{
	"text_greeting": "Good Morning!"
}
// Spanish

{
	"text_greeting": "Buenos días!"
}

2. Updating the strings.xml

Now the most interesting part of the blog, using Restring.

Configuration

// Replace bundled strings dynamically
implementation 'dev.b3nedikt.restring:restring:5.1.4'

// Intercept view inflation
implementation 'dev.b3nedikt.viewpump:viewpump:4.0.7'

// Allows to update the text of views at runtime without recreating the activity
implementation 'dev.b3nedikt.reword:reword:4.0.1'


Configuring Locale
Restring works on the concept of app locale, it checks the app locale and then accordingly updates the string resources. So our first task is to define various locales that our app will support. In this case, we will be supporting English & Spanish locales. I am utilizing the Locale class of java.util package to initialize the supported languages, as shown below:

private fun configureLocales() {
	englishLocale = Locale("en")
	spanishLocale = Locale("es")
}


Updating Strings HashMap
The Restring library utilizes a HashMap to replace the existing strings.xml file. So, the next step is to convert this JSON response that we got from our local JSON file into a HashMap, to be consumed later. You can find the link to the blog on how to flatten a complex JSON in the references below. I am using two variables here, englishStringsMap & spanishStringsMap, and populating them with the localized strings:

fun updateStringsHashmap() {
	getLocalStringJsonHashmap("en").forEach {
		englishStringsMap[it.key] = it.value
	}
    getLocalStringJsonHashmap("es").forEach {
		spanishStringsMap[it.key] = it.value
	}
}


Now we need to call the putStrings() method of the Restring library, as shown in the code snippet below. We will be passing the locale and the associated strings HashMap as arguments to the putStrings() method.

private fun updateAppLanguage(language: String) {
    if (language == "en") {
        Restring.putStrings(englishLocale, englishStringsMap)
        Restring.locale = englishLocale
    } else {
        Restring.putStrings(spanishLocale, spanishStringsMap)
        Restring.locale = spanishLocale
    }
}

3. Updating the view

Lastly, we need to dynamically update the view using the localized strings. For that, we start with first creating a base activity:

open class BaseActivity : AppCompatActivity() {
    private var appCompatDelegate: AppCompatDelegate? = null

    @NonNull
    override fun getDelegate(): AppCompatDelegate {
        if (appCompatDelegate == null) {
            appCompatDelegate = ViewPumpAppCompatDelegate(
                super.getDelegate(),
                this
            ) { base: Context -> wrapContext(base) }
        }
        return appCompatDelegate as AppCompatDelegate
    }
}

This is to inject the context into the view. If we don't create a base activity, we need to write this code for every activity separately. So it is preferred to create a base activity and extend all other activities with it.

Note: We want to make sure that we want to use the activity context while loading the string resources. If we use the application context, then we will have to use a third-party injecting tool to inject the context.

After that, we need to call the reword() method of the Reword Library. According to documentation,

Reword is a library to update the texts of views when the apps texts have changed due to a language change or an update of the apps string resources with a library like restring.
private fun updateView() {
    val rootView: View = window.decorView.findViewById(android.R.id.content)
    reword(rootView)
}

And that's it! Our work here is done!

Did you find the strategies listed in this blog helpful? If you enjoyed this article, share it with your friends and colleagues!

References

  1. Restring Library
  2. Reword Library
  3. How to flatten complex JSON objects
  4. Demo Project