Just recently context.getResources().updateConfiguration() has been deprecated in Android API 25 and it is advised to use context.createConfigurationContext() instead.
Here's @bassel-mourjan's solution with a bit of kotlin goodness :) :
import android.annotation.TargetApi
import android.content.ContextWrapper
import android.os.Build
import java.util.*
@Suppress("DEPRECATION")
fun ContextWrapper.wrap(language: String): ContextWrapper {
val config = baseContext.resources.configuration
val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.getSystemLocale()
} else {
this.getSystemLocaleLegacy()
}
if (!language.isEmpty() && sysLocale.language != language) {
val locale = Locale(language)
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.setSystemLocale(locale)
} else {
this.setSystemLocaleLegacy(locale)
}
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
val context = baseContext.createConfigurationContext(config)
ContextWrapper(context)
} else {
baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics)
ContextWrapper(baseContext)
}
}
@Suppress("DEPRECATION")
fun ContextWrapper.getSystemLocaleLegacy(): Locale {
val config = baseContext.resources.configuration
return config.locale
}
@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.getSystemLocale(): Locale {
val config = baseContext.resources.configuration
return config.locales[0]
}
@Suppress("DEPRECATION")
fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) {
val config = baseContext.resources.configuration
config.locale = locale
}
@TargetApi(Build.VERSION_CODES.N)
fun ContextWrapper.setSystemLocale(locale: Locale) {
val config = baseContext.resources.configuration
config.setLocale(locale)
}
And here is how you use it:
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language))
}
Try this:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);
Probably like this :
Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration();
overrideConfiguration.setLocales(LocaleList);
Context context = createConfigurationContext(overrideConfiguration);
Resources resources = context.getResources();
Bonus : A blog article who use createConfigurationContext()
Inspired by Calligraphy & Mourjan & myself, i created this.
first you must create a subclass of Application:
public class MyApplication extends Application {
private Locale locale = null;
@Override
public void onCreate() {
super.onCreate();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
Configuration config = getBaseContext().getResources().getConfiguration();
String lang = preferences.getString(getString(R.string.pref_locale), "en");
String systemLocale = getSystemLocale(config).getLanguage();
if (!"".equals(lang) && !systemLocale.equals(lang)) {
locale = new Locale(lang);
Locale.setDefault(locale);
setSystemLocale(config, locale);
updateConfiguration(config);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (locale != null) {
setSystemLocale(newConfig, locale);
Locale.setDefault(locale);
updateConfiguration(newConfig);
}
}
@SuppressWarnings("deprecation")
private static Locale getSystemLocale(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return config.getLocales().get(0);
} else {
return config.locale;
}
}
@SuppressWarnings("deprecation")
private static void setSystemLocale(Configuration config, Locale locale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
@SuppressWarnings("deprecation")
private void updateConfiguration(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
getBaseContext().createConfigurationContext(config);
} else {
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
}
}
}
then you need set this to your AndroidManifest.xml application tag:
<application
...
android:name="path.to.your.package.MyApplication"
>
and add this to your AndroidManifest.xml activity tag.
<activity
...
android:configChanges="locale"
>
note that pref_locale is a string resource like this:
<string name="pref_locale">fa</string>
and hardcode "en" is default lang if pref_locale is not setted
there is a simple solution with contextWrapper here : Android N change language programatically Pay attention to the recreate() method
Here is no 100% working solution. You need to use both createConfigurationContext
and applyOverrideConfiguration
. Otherwise even if you replace baseContext
in every activity with new configuration, activity would still use Resources
from ContextThemeWrapper
with old locale.
So here is mine solution which works up to API 29:
Subclass your MainApplication
class from:
abstract class LocalApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(
base.toLangIfDiff(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
)
}
}
Also every Activity
from:
abstract class LocalActivity : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
}
override fun applyOverrideConfiguration(overrideConfiguration: Configuration) {
super.applyOverrideConfiguration(baseContext.resources.configuration)
}
}
Add LocaleExt.kt
with next extension functions:
const val SYSTEM_LANG = "sys"
const val ZH_LANG = "zh"
const val SIMPLIFIED_CHINESE_SUFFIX = "rCN"
private fun Context.isAppLangDiff(prefLang: String): Boolean {
val appConfig: Configuration = this.resources.configuration
val sysConfig: Configuration = Resources.getSystem().configuration
val appLang: String = appConfig.localeCompat.language
val sysLang: String = sysConfig.localeCompat.language
return if (SYSTEM_LANG == prefLang) {
appLang != sysLang
} else {
appLang != prefLang
|| ZH_LANG == prefLang
}
}
fun Context.toLangIfDiff(lang: String): Context =
if (this.isAppLangDiff(lang)) {
this.toLang(lang)
} else {
this
}
@Suppress("DEPRECATION")
fun Context.toLang(toLang: String): Context {
val config = Configuration()
val toLocale = langToLocale(toLang)
Locale.setDefault(toLocale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(toLocale)
val localeList = LocaleList(toLocale)
LocaleList.setDefault(localeList)
config.setLocales(localeList)
} else {
config.locale = toLocale
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLayoutDirection(toLocale)
this.createConfigurationContext(config)
} else {
this.resources.updateConfiguration(config, this.resources.displayMetrics)
this
}
}
/**
* @param toLang - two character representation of language, could be "sys" - which represents system's locale
*/
fun langToLocale(toLang: String): Locale =
when {
toLang == SYSTEM_LANG ->
Resources.getSystem().configuration.localeCompat
toLang.contains(ZH_LANG) -> when {
toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) ->
Locale.SIMPLIFIED_CHINESE
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
Locale(ZH_LANG, "Hant")
else ->
Locale.TRADITIONAL_CHINESE
}
else -> Locale(toLang)
}
@Suppress("DEPRECATION")
private val Configuration.localeCompat: Locale
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.locales.get(0)
} else {
this.locale
}
Add to your res/values/arrays.xml
your supported languages in array:
<string-array name="lang_values" translatable="false">
<item>sys</item> <!-- System default -->
<item>ar</item>
<item>de</item>
<item>en</item>
<item>es</item>
<item>fa</item>
...
<item>zh</item> <!-- Traditional Chinese -->
<item>zh-rCN</item> <!-- Simplified Chinese -->
</string-array>
I want to mention:
config.setLayoutDirection(toLocale);
to change layout direction
when you use RTL locales like Arabic, Persian, etc."sys"
in the code is a value that means "inherit system default language".ContextWraper
as posted here, just set new context returned from createConfigurationContext
as baseContextcreateConfigurationContext
you should pass configuration crated from scratch and only with Locale
property set. There shouldn't be any other property set to this configuration. Because if we set some other properties for this config (orientation for example), we override that property forever, and our context no longer change this orientation property even if we rotate the screen.recreate
activity when user selects a different language, because applicationContext will remain with old locale and it could provide unexpected behaviour. So listen to preference change and restart whole application task instead:fun Context.recreateTask() {
this.packageManager
.getLaunchIntentForPackage(context.packageName)
?.let { intent ->
val restartIntent = Intent.makeRestartActivityTask(intent.component)
this.startActivity(restartIntent)
Runtime.getRuntime().exit(0)
}
}