Android (Kotlin) : Locale Helper
How to implement app-level localization in Android OS.
If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
In this article, we are going to talk about how to change the language programmatically in Android OS.
Android
by default uses the Locale
of the device to select the appropriate language-dependent
resources. And most of the time this behavior is enough for common applications.
However, there are cases where you would want to change the language
of your app and the UI of the app. As a result, LocaleHelperKt
has emerged.
The appropriate way of changing the locale of the application
There are some difficulties that you have to overcome to change the language programmatically.
Our application will not remember your language change after it is closed or recreated during the configuration change.
We should update the currently visible UI properly according to the selected language.
Coding part
Let's create a new interface
called LocaleHelperKt
which contains 3 public APIs.
interface LocaleHelperKt {
fun onAttach(defaultLanguage: String? = null): Context
fun getCurrentLocale(): String
fun setCurrentLocale(language: String): Context
}
onAttach("bn")
to be called from the*Activity
class.getCurrentLocale()
returned the current locale from the cache (SharedPref
).setCurrentLocale("bn")
to set a new locale for this application.
Now, let's create an abstract
class called BaseLocaleHelper
which wraps some internal implementation of SET/GET
of the current locale to/from SharedPref
and update locale configuration and return the updated Context
.
abstract class BaseLocaleHelper(internal val context: Context) :
LocaleHelperKt {
companion object {
private const val SELECTED_LANGUAGE = "LocaleHelperKt_SelectedLanguage"
}
internal fun getPersistedLocale(defaultLocale: String): String {
return cacheStorage.getString(SELECTED_LANGUAGE, defaultLocale) ?: defaultLocale
}
private val cacheStorage: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(
context
)
}
private fun updateResources(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.resources.configuration
configuration.setLocale(locale)
return context.createConfigurationContext(configuration)
}
private fun baseSetLocale(context: Context, newLocale: Locale): Context {
var tmpContext = context
val res = tmpContext.resources
val configuration = res.configuration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(newLocale)
val localeList = LocaleList(newLocale)
LocaleList.setDefault(localeList)
configuration.setLocales(localeList)
tmpContext = tmpContext.createConfigurationContext(configuration)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(newLocale)
res.updateConfiguration(configuration, res.displayMetrics)
} else {
configuration.locale = newLocale
res.updateConfiguration(configuration, res.displayMetrics)
}
return tmpContext
}
internal fun setLocale(context: Context, newLocale: String): Context {
cacheStorage.edit().apply {
putString(SELECTED_LANGUAGE, newLocale)
apply()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, newLocale)
}
val locale = Locale(newLocale)
return baseSetLocale(context, locale)
}
}
getPersistedLocale("bn")
to cached locale fromSharedPref
.updateResources(context, "bn")
update locale configuration and returns updatedContext
.baseSetLocale(context, "bn")
to set newLocale
according toAPI
level and returns updatedContext
.setLocale(context, "bn")
to cache newLocale
toSharedPref
and update theLocale Configuration
and return the newContext
.
Add a new class
called DefaultLocaleHelper
to provide an implementation of LocaleHelperKt
.
class DefaultLocaleHelper private constructor(context: Context) : BaseLocaleHelper(context) {
companion object {
@Volatile
private var instance: LocaleHelperKt? = null
private var LOCK: Any = Any()
fun getInstance(context: Context): LocaleHelperKt {
synchronized(LOCK) {
if (instance == null) instance = DefaultLocaleHelper(context)
return instance!!
}
}
}
override fun setCurrentLocale(language: String): Context {
return setLocale(context, language)
}
override fun onAttach(defaultLanguage: String?): Context {
val lang = getPersistedData(defaultLanguage ?: Locale.getDefault().language)
return setLocale(context, lang)
}
override fun getCurrentLocale(): String =
getPersistedLocale(Locale.getDefault().language)
}
getInstance(context)
provides an Instance ofLocaleHelperKt
and theAPI
implementsSynchronization
.instance
object marked asVolatile
.
Override
our *Activity
's attachBaseContext()
API.
class BaseActivity : ComponentActivity(){
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(DefaultLocaleHelper.getInstance(newBase!!).onAttach())
}
}
Remember:
To update Texts
Locale
to the UI, we can just update the text or any other language-dependent resources one by one.Or we can we call
activity.recreate()
to restart the currently loaded activity. Then the activity will reload the resources with the correct locale. However, if you select this option your users will notice the effect because it will close your application and you get a black screen for a very small amount of time and then recreate your activity again in meanwhile.
Full Code
Gist (https://gist.github.com/rommansabbir/aa254d0aa17770a4f10d6025952cc0e9)
That's it for today. Happy Coding...