Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 244
user35603 Avatar asked Aug 17 '16 12:08

user35603


2 Answers

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

like image 118
turbofan Avatar answered Nov 18 '22 13:11

turbofan


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!

like image 3
Rishabh Kamdar Avatar answered Nov 18 '22 14:11

Rishabh Kamdar