4

I want to change my app language programatically. I have some code which is working on older phones ( Android 6) but it is not working on Android 8 and Android 9. Any working solution for app language change? After calling setLocal I call recreate() inside Activity. Still no changes in strings.

In my MainActivity which is extending BaseActivity in onCreate() If I call Locale.getDefault().language it is returning correct language code but strings are still in English which is default string.xml.

fun setLocale(context: Context, language: String?): Context {
    app.setLanguage(language)

    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 =
    if (language == null){
        Locale(Locale.getDefault().language)
    } else {
        Locale(language)
    }

    Locale.setDefault(locale)
    val configuration = context.resources.configuration
    configuration.setLocale(locale)

    return context.createConfigurationContext(configuration)
}

@Suppress("DEPRECATION")
private fun updateResourcesLegacy(context: Context, language: String?): Context {
    val locale =
        if (language == null){
            Locale(Locale.getDefault().language)
        } else {
            Locale(language)
        }

    Locale.setDefault(locale)
    val resources = context.resources

    val configuration = resources.configuration
    configuration.locale = locale

    resources.updateConfiguration(configuration, resources.displayMetrics)

    return context
}

UPDATE: Ive used combination of both solutions below, but still without success. I made BaseActivity class which is extended by all of my activities. And there I call changeLocale function which is similar to LocaleHelper. app.getSavedLanguage() returns saved language code in my sharedPrefs. This code is overwritten based on which language user choose in app. App is Application class which is working with shared preferences.

override fun onCreate(si: Bundle?) {
        super.onCreate(si)
        app = application as App
        changeLocale(this, app.getSavedLanguage())
    }

    open fun changeLocale(context: Context, lang: String) {
        val newLocale = Locale(lang)
        Locale.setDefault(newLocale)

        val res: Resources = context.resources
        val conf: Configuration = res.configuration

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
            conf.apply {
                setLocale(newLocale)
                setLayoutDirection(newLocale)
            }

            context.createConfigurationContext(conf)
        } else {
            conf.apply {
                locale = newLocale
                setLayoutDirection(newLocale)
            }

            res.updateConfiguration(conf, res.displayMetrics)
        }
    }
martin1337
  • 2,384
  • 6
  • 38
  • 85

4 Answers4

2

You can use this configuration in the MainActivity onCreate() before setContentView()

@Override
protected void onCreate(Bundle savedInstanceState) {
    SharedPreferences sharedPreferences = getPreferences(MODE_PRIVATE);
    String appLang = sharedPreferences.getString("appLanguage", Locale.getDefault().getLanguage());

    Locale myLocale = new Locale(appLang);
    Resources res = getResources();
    DisplayMetrics dm = res.getDisplayMetrics();
    Configuration conf = res.getConfiguration();
    conf.locale = myLocale;
    Locale.setDefault(myLocale);
    conf.setLayoutDirection(myLocale);
    res.updateConfiguration(conf, dm);

    setContentView(R.layout.activity_main);
}
ZIRES
  • 276
  • 3
  • 10
1

I had the same problem, I always needed to pin the screen from right to left for users in Hebrew and Arabic. After countless attempts, I came up with the suggested solution:

Create base Activity and extends your activities from this class.

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.view.View;

import java.util.Locale;

public abstract class Activity_Base extends AppCompatActivity {

    int q = 0;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        changeLocale(this, "iw");


        Resources res = getResources();
        // Change locale settings in the app.
        DisplayMetrics dm = res.getDisplayMetrics();
        android.content.res.Configuration conf = res.getConfiguration();
        conf.setLocale(new Locale("iw")); // API 17+ only.
        // Use conf.locale = new Locale(...) if targeting lower versions
        res.updateConfiguration(conf, dm);


        getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_RTL);

        Locale locale = new Locale("iw");
        Locale.setDefault(locale);
        Configuration config = new Configuration();
        config.locale = locale;
        getApplicationContext().getResources().updateConfiguration(config, getApplicationContext().getResources().getDisplayMetrics());

        super.onCreate(savedInstanceState);
    }

    public static void changeLocale(Context context, String locale) {
        Resources res = context.getResources();
        Configuration conf = res.getConfiguration();
        conf.locale = new Locale(locale);
        res.updateConfiguration(conf, res.getDisplayMetrics());
    }
}

Using - just extends your activity:

public class Activity_Article extends Activity_Base {
Guy4444
  • 1,411
  • 13
  • 15
  • Instead of getWindow (). getDecorView (). setLayoutDirection () It is best to use config.setLayoutDirection (locale). – ZIRES Aug 15 '21 at 10:17
0

See my answer here (the second one): Why my app gets wrong strings from resources for localization?

It's a bit of a pain to get it working properly on Android 8 and 9, but so far this has been working fine for me. Hope it helps! In short: You need to make a Locale Helper that will take care of all the resource loading. You need to refresh the activity from which you changed the language and you also might need to select the correct context when using getResources().

EDIT: OK, so SOME devices seem stubborn when it comes to changing the language. Especially on first entry (eg. Huawei Mediapad M3). So, to solve this, I figured out calling

public static Resources getResources(Context context) {
    return LocaleHelper.onAttach(context).getResources();
}

in your activity seems to solve the issue. I call this in my SPLASH screen activity so that it is ready for the rest of the app, which only looks like this in the onCreate():

    super.onCreate(savedInstanceState);
    Utils.getResources(SplashScreenActivity.this);
    Intent intent = new Intent(this, LoginActivity.class);
    startActivity(intent);
    finish();

Additionally, I had to REMOVE this from LocaleHelper:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return updateResources(context, language);       
}

I don't know why all this works. And, my testing devices are limited - but, so far this seems to work for everything (that I have the ability to test).

Zee
  • 1,592
  • 12
  • 26
  • Ive actually used your solution with LocaleHelper year ago. But this exact solution doesnt work now. Probably because sometimes I use app.getString and not activity.getString when activity context is not present. Could this be a problem? – martin1337 Nov 18 '19 at 09:21
  • Ah ok! Well I'm not an expert myself, so unsure - it might indeed be the problem. Maybe do a little test and see if it occurs only when you use app.getString. Sorry! Wish I could help a bit more. – Zee Nov 18 '19 at 09:23
  • 2
    Hey Martin, I know this was forever ago. But, happened to have the same problem as you again. Updating my answer for incase :) – Zee Mar 04 '20 at 07:55
-1

There is a workaround to update a language of an Activity without restarting it or replacing its context. If you use fragments to display UI, you can use next steps:

  1. Create a localised context

  2. Re-create your fragments

  3. When you inflate a fragment use this context to get an Inflater

abstract class YourBaseFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return super.onCreateView(fragmentLayoutInflater, container, savedInstanceState)
    }


    fun getFragmentLayoutInflater(): LayoutInflater  {
        return LayoutInflater.from(getContextForLocale(your_locale));
    }
}
  1. As a result your fragment is inflated using resources for a required locale