Change locale programmatically in Kotlin - android

I had some codes for change locale programmatically in Java. But when my application migrated to Kotlin, I can't change locale any more.
For example this code in Java worked very good :
public static final void setAppLocale(String language, Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Resources resources = activity.getResources();
Configuration configuration = resources.getConfiguration();
configuration.setLocale(new Locale(language));
activity.getApplicationContext().createConfigurationContext(configuration);
} else {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration config = activity.getResources().getConfiguration();
config.locale = locale;
activity.getResources().updateConfiguration(config,
activity.getResources().getDisplayMetrics());
}
}
I tried many codes in Kotlin but non of them worked for me. This is my last try:
fun changeLanguage(context: Context, language : String) {
val locale = Locale(language)
Locale.setDefault(locale)
val config = context.resources.configuration
config.setLocale(locale)
context.createConfigurationContext(config)
context.resources.updateConfiguration(config, context.resources.displayMetrics)
}
How can I change application's local in Kotlin? Old codes that were written in Java did not work in Kotlin application.

Create a Context helper class let's say
class ApplicationLanguageHelper(base: Context) : ContextThemeWrapper(base, R.style.AppTheme) {
companion object {
fun wrap(context: Context, language: String): ContextThemeWrapper {
var context = context
val config = context.resources.configuration
if (language != "") {
val locale = Locale(language)
Locale.setDefault(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale)
} else {
setSystemLocaleLegacy(config, locale)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLayoutDirection(locale)
context = context.createConfigurationContext(config)
} else {
context.resources.updateConfiguration(config, context.resources.displayMetrics)
}
}
return ApplicationLanguageHelper(context)
}
#SuppressWarnings("deprecation")
fun setSystemLocaleLegacy(config: Configuration, locale: Locale) {
config.locale = locale
}
#TargetApi(Build.VERSION_CODES.N)
fun setSystemLocale(config: Configuration, locale: Locale) {
config.setLocale(locale)
}
}
}
And in your Activity you can override attachBaseContext
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(ApplicationLanguageHelper.wrap(newBase!!, "fa"))
}
To Change the language you can use a Spinner or any other preferred way, to call the following method OnClick
private fun changeApplicationLanguage(language:String){
val sharedPreferencesEditor = sharedPreferences.edit()
when (language) {
ENGLISH -> sharedPreferencesEditor?.putString(SELECTED_LANGUAGE, ENGLISH)
PERSIAN -> sharedPreferencesEditor?.putString(SELECTED_LANGUAGE, PERSIAN)
PASHTO -> sharedPreferencesEditor?.putString(SELECTED_LANGUAGE, PASHTO)
}
sharedPreferencesEditor.putBoolean(LANGUAGE_IS_SELECTED, true)
sharedPreferencesEditor?.apply()
recreate()
}
make sure you define sharedPreferences and also SELECTED_LANGUAGE , ENGLISH etc, consts
const val SELECTED_LANGUAGE = "language"
const val ENGLISH = "en"
const val PERSIAN = "fa"
const val PASHTO = "ps"
private lateinit var sharedPreferences: SharedPreferences
and also initialize sharedPreferences in onCreate method after setContent
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this#Your_Activity_Name)
also Override the base context by adding the following code
// Overwrite the context
override fun attachBaseContext(newBase: Context?) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(newBase)
val lang = sharedPreferences.getString(SELECTED_LANGUAGE, "en")
super.attachBaseContext(ApplicationLanguageHelper.wrap(newBase!!, lang!!))
}
I am sure this method is not an optimal solution, but, this might help

Try this
fun setAppLocale(languageFromPreference: String?, context: Context)
{
if (languageFromPreference != null) {
val resources: Resources = context.resources
val dm: DisplayMetrics = resources.displayMetrics
val config: Configuration = resources.configuration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLocale(Locale(languageFromPreference.toLowerCase(Locale.ROOT)))
} else {
config.setLocale(Locale(languageFromPreference.toLowerCase(Locale.ROOT)))
}
resources.updateConfiguration(config, dm)
}
}
You can set the App Locale within activities by accessing this function from the activities
..
super.onCreate(savedInstanceState)
setAppLocale(pref.getLanguageFromPreference().toString(), this)
setContentView(R.layout.activity_main)
...
pref.getLanguageFromPreference() returns the langugae string (For example : "en")

Related

Why does my Activity language not change on devices before Android 8?

When my Activity gets started I want to make sure it is using the correct language. I currenttly use this code to do that:
override fun attachBaseContext(newBase: Context) {
val newContext = updateBaseContextLocale(newBase)
super.attachBaseContext(newContext)
}
private fun updateBaseContextLocale(context: Context): Context {
val locale = getMyLocale() // getMyLocale() gets the locale that should be used.
Locale.setDefault(locale)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResourcesLocale(context, locale)
} else {
updateResourcesLocaleLegacy(context, locale)
}
}
#TargetApi(Build.VERSION_CODES.N)
private fun updateResourcesLocale(context: Context, locale: Locale): Context {
val configuration = context.resources.configuration
configuration.setLocale(locale)
return context.createConfigurationContext(configuration)
}
#Suppress("DEPRECATION")
private fun updateResourcesLocaleLegacy(context: Context, locale: Locale): Context {
val resources = context.resources
val configuration = resources.configuration
configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
This produces the correct result on Android 8 and newer, but for some reason the language stays unchanged on older versions of Android.
Calling the updateBaseContextLocale(context: Context) function from onCreate doesn't seem to change anything either.
I have already seen this question but I failed to turn the answers there into a working solution.
What am I doing wrong?
Edit:
My ultimate goal with this is to change the language of just that Activity but if changing the language of one Activity alone is not possible, changing the language of the entire App is also acceptable.

Changing Locale/Language of the app in Android 12

I have a feature in my app where user can change the language from inside the app. The code was working fine till Android 11. But from Android 12, I am not able to change the language programmatically. But the app language is being changed when the language of the OS is changed.
Is the Locale or any other support is deprecated for Android 12?
Any help is much appreciated. Thanks in Advance.
// Below code is used to override configuration when the locale is changed.
override fun attachBaseContext(base: Context) {
super.attachBaseContext(updateBaseContextLocale(base))
}
open fun updateBaseContextLocale(context: Context): Context? {
val languageCode: String
languageCode = "de"
val locale = Locale(languageCode)
Locale.setDefault(locale)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResourcesLocale(context, locale)
} else updateResourcesLocaleLegacy(context, locale)
}
#TargetApi(Build.VERSION_CODES.N)
open fun updateResourcesLocale(context: Context, locale: Locale): Context? {
val configuration: Configuration = context.resources.configuration
configuration.setLocale(locale)
return context.createConfigurationContext(configuration)
}
open fun updateResourcesLocaleLegacy(context: Context, locale: Locale): Context? {
val resources: Resources = context.resources
val configuration: Configuration = resources.getConfiguration()
configuration.locale = locale
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
return context
}
I have faced the same problem on Android-12, I just fixed it by implement attachBaseContext method on the activity..
override fun attachBaseContext(newBase: Context) {
/**
* handle locale
*/
val currentLang = "en" // to get from sharedPref or whatever
newBase.resources.configuration.setLocale(Locale(currentLang))
applyOverrideConfiguration(newBase.resources.configuration)
super.attachBaseContext(newBase)
}

Localization sometimes reset to system locale

I have a single activity application, language setting mostly works as intended. I set every view's texts in onCreateView() by the resource id of the text. But sometimes, when I enter my application, the language is the system default instead of the selected language. The same happens when I navigate to the app from deep link (widget or notification).And when I navigate to another fragment and return, everything becomes right with right locale.
I tried to debug my application to discover the reason. Every text by resource id I got according to system default, not the language selected. When I instead wrap my fragment context and then fetch the text, I get the corect result.
This is how I set my language:
private fun changeLang(lang: String, country: String) {
activity?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResources(it, lang, country)
}
updateResourcesLegacy(it, lang, country)
}
}
#TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String, country: String) {
val locale = Locale(language, country)
Locale.setDefault(locale)
val resources: Resources = context.resources
val configuration: Configuration = resources.configuration
configuration.setLocale(locale)
context.createConfigurationContext(configuration)
}
private fun updateResourcesLegacy(context: Context, language: String, country: String) {
val locale = Locale(language, country)
Locale.setDefault(locale)
val resources: Resources = context.resources
val configuration: Configuration = resources.configuration
configuration.locale = locale
resources.updateConfiguration(configuration, resources.displayMetrics)
}
In my activity I overrode the following method:
override fun attachBaseContext(newBase: Context?) {
newBase?.let {
val pref = Preference(PreferenceHelper.customPrefs(it))
super.attachBaseContext(ContextWrapper.wrap(newBase, pref.language, pref.country))
}
}
class ContextWrapper(base: Context?) : android.content.ContextWrapper(base) {
companion object {
fun wrap(context: Context?, lang: String? = "uz", country: String? = ""): ContextWrapper =
wrap(context, Locale(lang, country))
fun wrap(context: Context?, newLocale: Locale): ContextWrapper {
var localizedContext = context
val res = localizedContext?.resources
val configuration = res?.configuration
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
configuration?.setLocale(newLocale)
val localeList = LocaleList(newLocale)
LocaleList.setDefault(localeList)
configuration?.setLocales(localeList)
localizedContext = localizedContext?.createConfigurationContext(configuration!!)
}
else -> {
configuration?.setLocale(newLocale)
localizedContext = localizedContext?.createConfigurationContext(configuration!!)
}
}
res?.updateConfiguration(configuration, res.displayMetrics)
return ContextWrapper(localizedContext)
}
}
}
Try to write both at the same time:
configuration.setLocale(locale)
configuration.locale = locale
try this in ContextWrapper class and updateResources function
The reason was not setting the language for application context. Application resources did not get updated. I applied the locale on attachBaseContext method of my application class.

Android context.getResources.updateConfiguration() deprecated

Just recently context.getResources().updateConfiguration() has been deprecated in Android API 25 and it is advised to use context.createConfigurationContext() instead.
Does anyone know how createConfigurationContext can be used to override android system locale?
before this would be done by:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
Inspired by Calligraphy, I ended up creating a context wrapper.
In my case, I need to overwrite system language to provide my app users with the option of changing app language but this can be customized with any logic that you need to implement.
import android.annotation.TargetApi;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Build;
import java.util.Locale;
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
#SuppressWarnings("deprecation")
public static ContextWrapper wrap(Context context, String language) {
Configuration config = context.getResources().getConfiguration();
Locale sysLocale = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
sysLocale = getSystemLocale(config);
} else {
sysLocale = getSystemLocaleLegacy(config);
}
if (!language.equals("") && !sysLocale.getLanguage().equals(language)) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSystemLocale(config, locale);
} else {
setSystemLocaleLegacy(config, locale);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
return new MyContextWrapper(context);
}
#SuppressWarnings("deprecation")
public static Locale getSystemLocaleLegacy(Configuration config){
return config.locale;
}
#TargetApi(Build.VERSION_CODES.N)
public static Locale getSystemLocale(Configuration config){
return config.getLocales().get(0);
}
#SuppressWarnings("deprecation")
public static void setSystemLocaleLegacy(Configuration config, Locale locale){
config.locale = locale;
}
#TargetApi(Build.VERSION_CODES.N)
public static void setSystemLocale(Configuration config, Locale locale){
config.setLocale(locale);
}
}
and to inject your wrapper, in every activity add the following code:
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));
}
UPDATE 22/12/2020
After android Material library implementation of ContextThemeWrapper to support dark mode, the language setting would break and language setting is lost. After months of head scratching, problem was resolved by adding the following code to Activity and Fragment onCreate method
Context context = MyContextWrapper.wrap(this/*in fragment use getContext() instead of this*/, "fr");
getResources().updateConfiguration(context.getResources().getConfiguration(), context.getResources().getDisplayMetrics());
UPDATE 10/19/2018
Sometimes after orientation change, or activity pause/resume the Configuration object resets to default system Configuration and in result we will see the app displaying English "en" text even though we wrapped the context with French "fr" locale. Therefore and as a good practice, never retain the Context/Activity object in a global variable in activities or fragments.
furthermore, create and use the following in a MyBaseFragment or MyBaseActivity:
public Context getMyContext(){
return MyContextWrapper.wrap(getContext(),"fr");
}
This practice will provide you with 100% bug free solution.
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()
I have resolved this without creating any custom ContextWrapper.
First I created an extension function
fun Context.setAppLocale(language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val config = resources.configuration
config.setLocale(locale)
config.setLayoutDirection(locale)
return createConfigurationContext(config)
}
Then in the activity's attachBaseContext method, simply replacing the context with the new one.
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(ContextWrapper(newBase.setAppLocale("bn")))
}
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
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:
Use 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".
Here "langPref" is a key of preference where you put user current language.
There is no need to recreate the context if it already uses needed
locale.
There is no need for ContextWraper as posted here, just set new context returned from createConfigurationContext as baseContext
This is very important! When you call createConfigurationContext 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.
It is not enough only to 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)
}
}
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))
}
there is a simple solution with contextWrapper here : Android N change language programatically
Pay attention to the recreate() method
Try this:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);

Change language programmatically (Android N 7.0 - API 24)

I'm using the following code to set specific language in my app. Language is saved into SharedPreferences within the app. And it works perfectly up to API level 23. With Android N SharedPreferences works well too, it returns the correct language code-string, but it does not change the locale (sets default language of the phone). What could be wrong?
Update 1: When I use Log.v("MyLog", config.locale.toString()); immediately after res.updateConfiguration(config, dm) it returns correct locale, but language of the app does not changed.
Update 2: I also mentioned that if I change locale and then restart the activity (using new intent and finish on the old one), it changes the language properly, it even shows correct language after rotation. But when I close the app and open it again, I get default language. It's weird.
public class ActivityMain extends AppCompatActivity {
//...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set locale
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
String lang = pref.getString(ActivityMain.LANGUAGE_SAVED, "no_language");
if (!lang.equals("no_language")) {
Resources res = context.getResources();
Locale locale = new Locale(lang);
Locale.setDefault(locale);
DisplayMetrics dm = res.getDisplayMetrics();
Configuration config = res.getConfiguration();
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
res.updateConfiguration(config, dm);
setContentView(R.layout.activity_main);
//...
}
//...
}
Update 3: THE ANSWER IS ALSO HERE: https://stackoverflow.com/a/40849142/3935063
Create a new class extends ContextWrapper
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
#TargetApi(Build.VERSION_CODES.N)
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.isAfter24()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.isAfter17()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
override Activity's attachBaseContext method
#Override
protected void attachBaseContext(Context newBase) {
Locale languageType = LanguageUtil.getLanguageType(mContext);
super.attachBaseContext(MyContextWrapper.wrap(newBase, languageType));
}
finish the activity and start it again,new locale will become effective.
demo:https://github.com/fanturbo/MultiLanguageDemo
I faced this issue when i started to target SDK 29. The LocaleHelper that i created worked on every version from my min SDK(21) to 29 but specifically on Android N it didn't use to work. So after searching many stack overflow answers and visiting https://issuetracker.google.com/issues/37123942 , i managed to find/modify a solution which is now working on all android versions.
So, first i took help of https://gunhansancar.com/change-language-programmatically-in-android/ to get a locale helper. Then i modified it to my needs like this:
object LocaleHelper {
const val TAG = "LocaleHelper"
fun updateLocale(base: Context): Context {
Log.e(TAG, "updateLocale: called");
val preferenceHelper = PreferenceHelper(base)
preferenceHelper.getStringPreference(PreferenceHelper.KEY_LANGUAGE).let {
return if (it.isNotEmpty()) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
updateResources(base, it)
} else {
updateResourcesLegacy(base, it)
}
} else {
base
}
}
}
private fun updateResources(base: Context, language: String): Context{
val loc = Locale(language)
Locale.setDefault(loc)
val configuration = base.resources.configuration
configuration.setLocale(loc)
return base.createConfigurationContext(configuration)
}
#Suppress("DEPRECATION")
private fun updateResourcesLegacy(base: Context, language: String): Context{
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = base.resources.configuration
configuration.locale = locale
configuration.setLayoutDirection(locale)
base.resources.updateConfiguration(configuration, base.resources.displayMetrics)
// Log.e(TAG, "updateLocale: returning for below N")
return base
}
}
Then, in the base activity, override the attachBaseContext method like:
/**
* While attaching the base context, make sure to attach it using locale helper.
* This helps in getting localized resources in every activity
*/
override fun attachBaseContext(newBase: Context?) {
var context = newBase
newBase?.let {
context = LocaleHelper.updateLocale(it)
}
super.attachBaseContext(context)
}
But i found that this used to work on Nougat and above and not in marshmallow and lollipop. So, I tried this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Update the locale here before loading the layout to get localized strings.
LocaleHelper.updateLocale(this)
In the oncreate of base activity i called this method. And it started working. But there were instances where if we kill the app from recent tasks and then start it, the first screen wasn't localized. So to tackle that, i created a static temp variable in app class that held the value for initial app instance.
companion object {
val TAG = "App"
var isFirstLoad = true
}
And in my first screen of app, i did this in oncreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Need to call this so that when the application is opened for first time and we need to update the locale.
if(App.isFirstLoad) {
LocaleHelper.updateLocale(this)
App.isFirstLoad = false
}
setContentView(R.layout.activity_dash_board)
Now i tested on redmi 4a (Android 7) on which it wasn't working previously, emulators of sdk 21,23,27,29 and in redmi note 5 pro and pixel devices. In all these, the in app language selection works properly.
Sorry for the long post, but please do suggest for an optimized way!
Thanks!
EDIT 1:
So i faced this issue where it was not working on android 7.1 (sdk 25). I asked gunhan for help and then made this change.
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
super.applyOverrideConfiguration(LocaleHelper.applyOverrideConfiguration(baseContext, overrideConfiguration))
}
I added this function in my LocaleHelper:
fun applyOverrideConfiguration(base: Context, overrideConfiguration: Configuration?): Configuration? {
if (overrideConfiguration != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(base.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
return overrideConfiguration
}
And now it's finally working.
Thanks Gunhan!
I had the same problem but with Android 5, 6 and 7. After a lot of searches, the solution is in this post : Change Locale not work after migrate to Androidx (see the comment of Ehsan Shadi and not the "solution").
First, you have to update your library appcompat to 1.2.0 (fix for locale, see here : https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0) and you have to override the applyOverrideConfiguration function in your base activity.
I have tested this solution on all API (19 to 30 - Android 4.4 to 11) and it's worked perfectly :)
This approach will work on all api level device, Make sure to recreate activity on changing language programatically.
1.Use Base Activity for attachBaseContext to set the locale language And extend this activity for all activities
open class BaseAppCompactActivity() : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.onAttach(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
2.Use Application attachBaseContext and onConfigurationChanged to set the locale language
public class MyApplication extends Application {
private static MyApplication application;
#Override
public void onCreate() {
super.onCreate();
}
public static MyApplication getApplication () {
return application;
}
/**
* overide to change local sothat language can be chnaged from android device nogaut and above
*/
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.INSTANCE.onAttach(base));
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
/**
* also handle change language if device language changed
*/
super.onConfigurationChanged(newConfig);
}
}
3.Use Locale Helper for handling language changes , this approch work on all device
object LocaleHelper {
fun onAttach(context: Context, defaultLanguage: String): Context {
return setLocale(context, defaultLanguage)
}
fun setLocale(context: Context, language: String): Context {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResources(context, language)
} else updateResourcesLegacy(context, language)
}
#TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.getResources().getConfiguration()
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
private fun updateResourcesLegacy(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val resources = context.getResources()
val configuration = resources.getConfiguration()
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale)
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
return context
}
}

Categories

Resources