124

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());
Bassel Mourjan
  • 3,604
  • 3
  • 26
  • 37
  • How about [applyOverrideConfiguration](https://developer.android.com/reference/android/view/ContextThemeWrapper.html#applyOverrideConfiguration(android.content.res.Configuration)) (untested)? –  Nov 06 '16 at 11:19
  • there is also a simple solution here , very similar to this one http://stackoverflow.com/questions/39705739/android-n-change-language-programatically/40849142#40849142 – Thanasis Saxanidis Mar 14 '17 at 18:13
  • [updateConfiguration was deprecated in API level 25] https://developer.android.com/reference/android/content/res/Resources – Md Sifatul Islam Sep 17 '18 at 13:59
  • for changing app locale in latest way, you can check this - https://stackoverflow.com/a/75388129/7728628 – Shakil Ahmed Shaj Feb 08 '23 at 15:37

8 Answers8

148

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.

Bassel Mourjan
  • 3,604
  • 3
  • 26
  • 37
  • 1
    if i place it more than 1 activity then it is not working for me, can't i just place it in BaseActivity to make effect on whole app? – Usman Rana Dec 03 '16 at 16:43
  • @UsmanRana I've applied this to a base activity and it seems to be working for me. – rfgamaral Dec 05 '16 at 15:29
  • 7
    I have one concern with this approach... This is currently being applied to activities only and not the whole application. What will happen for app components which might not start from activities, like services? – rfgamaral Dec 05 '16 at 15:30
  • @RicardoAmaral in that case you can wrap the service context before using it. pretty easy, since services don't have any GUI rendering to worry about. – Bassel Mourjan Dec 08 '16 at 14:56
  • This is still incomplete. What about BroadcastReceiver? How can you apply the change to the context that comes with it? – Curious Droid Dec 19 '16 at 14:32
  • 8
    Why would you extend the ContextWrapper? You don't have anything in it, just static methods? – vladimir123 Dec 19 '16 at 15:29
  • 1
    MyContextWrapper cannot be cast to android.app.ContextImpl – sergii.gudym Dec 28 '16 at 21:48
  • 8
    I had to take out createConfigurationContext/updateConfiguration from if-else branch and add below it, else in first Activity everything was ok, but when opened second, the language changed back to device default. Couldn't find the reason. – kroky Jan 05 '17 at 11:51
  • @BasselMourjan, have you tried this approach on Android 4.4? I have an app with language selection made using this approach. On Android 4.x each time I re-select the language, several Contexts leak (checked using tool `Memory usage`) and on some devices this leakage causes out of memory crash. See my post here: http://stackoverflow.com/questions/41478469/memory-possibly-context-leakage-in-4-4-while-setting-language-in-runtime – wilkas Jan 05 '17 at 14:36
  • 1
    while this is an awesome answer, it does not support direction changes, RTL <-> LTR – Muhammad Naderi Jan 07 '17 at 15:26
  • 3
    I added the needed line and post it as this gist: https://gist.github.com/muhammad-naderi/0ff264e6cc07df904bc88a9f7efbe57d – Muhammad Naderi Jan 07 '17 at 15:32
  • @wilkas sorry I still haven't applied this solution on my current active app and waiting for the last second to so do. – Bassel Mourjan Jan 09 '17 at 13:03
  • 2
    @kroky is right. The system locale is changed correctly, but the configuration goes back to default. As a result, the strings resource file gets back to default. Is there any other way, other than setting configuration everytime in every activity – Yash Ladia Jan 17 '17 at 20:36
  • 1
    @kroky Append with @Yash Landia 's answer, so you should add `attachBaseContext()` method like this post to all your activity (or base activity if you have). It will be called before `onCreate()`. – Soyeon Kim Mar 06 '17 at 07:01
  • 1
    @BasselMourjan Is "fr" is default language in your case? `super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr"));` how about `super.attachBaseContext(MyContextWrapper.wrap(newBase,prefs.getLang()));` – Qamar Mar 27 '17 at 06:54
  • @Qamar I specified "fr" just as an example for everyone. In my case I have an algorithm which checks if the device language is the same as the selected app language, if that is the case then I do nothing, else I wrap the context to overwrite system language. and my app supports English and Arabic – Bassel Mourjan Mar 27 '17 at 10:47
  • what if I need to change the configuration from a service? – user3290180 Apr 04 '17 at 08:17
  • 1
    @user3290180 you would do something like getContext or getBaseContext and wrap it with provided ContextWrapper then use it accordingly within the service itself – Bassel Mourjan Apr 05 '17 at 08:50
  • @BasselMourjan How should we use it now together with Calligraphy library? I used to call super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)). How should it work now? Thanks in advance1! :) – dor506 Jun 19 '17 at 18:15
  • 2
    @dor506 that is easy, all you have to do is to overwrite the attachBaseContext to look like this: super.attachBaseContext(CalligraphyContextWrapper.wrap(MyContextWrapper.wrap(newBase,"fr"))); – Bassel Mourjan Jun 29 '17 at 11:31
  • @UsmanRana you can override Application#attachBaseContext in exactly the same fashion, then the Service contexts will be wrapped. – click_whir Aug 30 '17 at 16:59
  • It is only important to do this in Activity to handle when the locale is changed after the Application is created. And yes, there is no reason to place these static methods in a ContextWrapper extended class. – click_whir Aug 30 '17 at 17:01
  • 1
    what method should I call when I want to change the locale for ex: en -> fr given that locale changed on button click, and do I need to recreate the activity after changing the locale, an – humazed Aug 30 '17 at 21:07
  • how to change language on user selection? – dharmx Sep 07 '17 at 09:31
  • @dharmx you have to save user selection and restart activity.. I hope you get the idea – Bassel Mourjan Sep 18 '17 at 12:02
  • this works but can you tell me how to implement this solution with Calligraphy library as they also use this method with their ContextWrapper . – Rahul Sep 20 '17 at 14:07
  • @Rahul you can easily do so like this super.attachBaseContext(CalligraphyContextWrapper.wrap(MyContextWrapper.wrap(newBase,"fr"))); – Bassel Mourjan Sep 22 '17 at 09:23
  • 2
    I had an issue: after second activity using `attachBaseContext` locale was not able to change anymore. Solution: I removed condition that checks current locale. So `get` methods were useless. That did the trick. – Mahdi-Malv Sep 28 '17 at 00:21
  • Bassel, could you please add more of your code? super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr")); does not help. I need to get proper string resources based on selected language by the user. – Yar Dec 02 '17 at 16:58
  • @Yar to be honest that was all I needed to support a different language. in case you have problems and wanna be more explicit.. before using myContext.getString(...) you could try and wrap your context with myContext=MyContextWrapper.wrap(newBase,"fr"); to ensure that it was wrapped properly – Bassel Mourjan Dec 04 '17 at 09:44
  • @BasselMourjan, thanks for the answer. Just want to ask you if ```context.createConfigurationContext(config);``` is actually switching string resources correctly like in case the user would like to change app language from english to arabic without changing the device language. I've been trying to use it, it's only changing the layout directions but not the string resources. Other answers also add ```activity.reacreate()``` in their solutions and I did not see that in yours, so just wondering how would the activity know the user wants to change the app language while the app is open. – ahasbini Dec 21 '17 at 12:35
  • @ahasbini of course after the user selects a different language for application (from your settings activity for example) you would save the language value in preferences and RESTART the activity then after in attachBaseContext you would read the language value from preferences and apply it. my coding was very basic and simple. but you can play around with it however suits your needs – Bassel Mourjan Dec 22 '17 at 08:19
  • None of the solutions worked in my case except this! Thanks, Man! – Nirav Dangi Sep 25 '18 at 08:02
  • 1
    @NiravDangi I will edit soon with some minor improvements, that toggles some bugs related to orientation change or resume/pause behavior or dialogs... so make sure to revisit this within 2 days or so.. cheers – Bassel Mourjan Sep 25 '18 at 19:20
  • For Samsung devices with 6.0.1, there is a issue. Locale is working for only 1st time.. from second time it is taking device default.. Any solution for this? – Pandiri Deepak Jun 06 '19 at 06:25
  • @PandiriDeepak that is why you should create a method to call for wrapped context and not use context directly – Bassel Mourjan Nov 05 '19 at 07:49
  • @BasselMourjan Where are you using `getMyContext()` method? – blueware Dec 02 '19 at 11:44
  • @blueware everywhere. whenever I need to call getActivity() or getContext() I call getMyContext() or getMyActivity() which always wraps the context before using it. – Bassel Mourjan Dec 03 '19 at 12:40
  • This is very buggy answer. If you pass existing configuration to `createConfigurationContext` method, you will end up with lots of bugs, for example device orientation will never change. Please see my answer – Oleksandr Albul Apr 06 '20 at 14:02
  • "100% bug free" are you sure? not working in androidx.appcompat:appcompat:1.1.0 – Rahmat Ihsan May 27 '20 at 01:55
  • @RahmatIhsan I am using androidx library and everything is still working smoothly. do review your implementation. there must be something that you are missing. – Bassel Mourjan May 30 '20 at 12:38
  • It works very well for Android 10. But it does not change the language for Android 6(LG Nexus 5). I think something is missing. Please check it out. – Serdar Didan Oct 13 '20 at 00:45
  • @SerdarDidan it seems that I had a bug caused by copy and paste from last edit. I fixed it by changing Build.VERSION_CODES.JELLY_BEAN_MR1 to Build.VERSION_CODES.N – Bassel Mourjan Oct 14 '20 at 08:26
  • @Bassel Mourjan Thanks. But i found another solution. – Serdar Didan Oct 15 '20 at 03:24
  • Reuse `context.getResources().getConfiguration()` or create new configuration? I have always done former, but it stoppped working with Android 10, where latter works – BeniBela Nov 18 '20 at 23:04
  • @BeniBela I don't know about your case, but I am facing problems with applying this solution while implementing Light/Dark mode theme.. other than that, this solution works – Bassel Mourjan Nov 19 '20 at 10:17
  • This seems to affect the original Context instance. Is there any way to prevent it? For example, to force using English for this function (in some languages it shows differently, such as Arabic) : https://developer.android.com/reference/android/text/format/Formatter#formatShortFileSize(android.content.Context,%20long) ? – android developer Sep 15 '22 at 09:58
  • I'm confused... the 22/12/20 addition/edit suggests to add `getResources().updateConfiguration()` to every onCreate method... but this just reintroduces the deprecated function... what am I misunderstanding? – Fat Monk Nov 23 '22 at 14:19
  • @FatMonk all I know is that's what worked for me after couple of months being puzzled after implementing the dark mode theme. – Bassel Mourjan Nov 28 '22 at 08:24
45

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()

compte14031879
  • 1,531
  • 14
  • 27
  • Thank you for pointing me in the right direction, I guess eventually one will have to create a ContextWrapper and attach it to activities like it is done by Calligraphy. Anyways the award is yours, but will not consider it as a final answer not until I post the right coding of the workaround. – Bassel Mourjan Oct 28 '16 at 03:37
  • @BasselMourjan Thank you ! I look forward to reading your code – compte14031879 Oct 28 '16 at 10:04
  • 82
    API 24+... Stupid google, can't they just provide us with a simple way? – Ali Bdeir Feb 10 '17 at 13:18
  • @Ab_ yes, google has provided you the simple method `setLocale` which goes back to API 17. If you need to go back further see the complete solution from Mourjan. – click_whir Aug 30 '17 at 17:04
  • 7
    @click_whir Saying "It's simple if you only target these devices" doesn't really make it simple. – Vlad Sep 01 '17 at 08:03
  • 1
    @Vlad There is a simple method if you don't need to support devices that were made before 2012. Welcome to application development! – click_whir Sep 01 '17 at 16:34
  • I think i will stick to old deprecated methods, see how far that goes! – M.kazem Akhgary Nov 11 '18 at 23:49
  • 1
    from where did you get `LocaleList` – EdgeDev Feb 04 '19 at 06:42
  • If you pass existing configuration to `createConfigurationContext` method, you will end up with lots of bugs, for example device orientation will never change. Please see my answer – Oleksandr Albul Apr 06 '20 at 14:03
20

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")))
}
S Haque
  • 6,881
  • 5
  • 29
  • 35
  • 3
    It's should be the best answer in 2022. Thanks. – Md Imran Choudhury Jan 16 '22 at 10:58
  • So I have to do this for each of my activities? We do not have a base one (and should not) – Evgenii Vorobei Apr 01 '22 at 08:40
  • If you can not have a base Activity then yes, you have to do this in each activity. – S Haque Apr 01 '22 at 16:28
  • Hi. I'am curious. How you are inform your activity about configuration change? I mean, you are getting new context instance with the new configuration with that extension method but how your activity gets that new context? – Aytaj Apr 15 '22 at 15:44
  • `attachBaseContext()` is overridden in the Activity. There you are getting the new config and providing it to its superclass. Therefore, your activity knows about the new configuration. – S Haque Apr 15 '22 at 17:06
  • does this work with androidx.appcompat:appcompat:1.4.0 ? – Alex Nov 12 '22 at 10:23
  • @MdImranChoudhury not work for me on real phone android 11. but work in android studio on api v.30. You don't know what could be the problem? – Alex Nov 12 '22 at 11:11
6

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)
         }
}
Oleksandr Albul
  • 1,611
  • 1
  • 23
  • 31
  • This does not work. Also consider making a base activity for all activities instead of duplicating code in every activity. As well, the `recreateTask(Context context)` method is not working properly as I still see layout without any change. – blueware Dec 02 '19 at 10:18
  • @blueware I have updated the samples. There was some bugs previously. But currently it should work, this is the code from my production app. The fun recreateTask could not work, you could display a toast like "The language will be changed after restart".. – Oleksandr Albul Apr 06 '20 at 13:53
5

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

Mahpooya
  • 529
  • 1
  • 6
  • 18
  • This is not enough, you also need to override context in every activity. As you will end up with situation that your baseContext have one locale, and you application will have another. As a result you will got mixed languages in your ui. Please see my answer. – Oleksandr Albul Apr 06 '20 at 13:58
3

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))
}
Michael
  • 713
  • 10
  • 27
  • This line `val config = baseContext.resources.configuration` is very very wrong. You will end up with lots of bugs because of this. You need create new configuration instead. See my answer. – Oleksandr Albul Apr 06 '20 at 13:56
0

there is a simple solution with contextWrapper here : Android N change language programatically Pay attention to the recreate() method

Community
  • 1
  • 1
Thanasis Saxanidis
  • 141
  • 1
  • 2
  • 12
  • 1
    The link is helpful, and a good reference. I believe it's better to include the actual answer here though rather than requiring an additional click. – Marshall Davis Mar 14 '17 at 18:43
  • you are right i am just new to stackoverflow and i thought it would be wrong to take credits for the answer so i post the link of original author – Thanasis Saxanidis Mar 15 '17 at 00:34
-1

Try this:

Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);
Eric B.
  • 4,622
  • 2
  • 18
  • 33