16

I have a multilingual app with primary language English and secondary language Arabic.

I am calling setLocale() in the onCreate() of every Activity in my app:

public static void setLocale(Locale locale){
    Locale.setDefault(locale);
    Context context = MyApplication.getInstance();
    final Resources resources = context.getResources();
    final Configuration config = resources.getConfiguration();
    config.setLocale(locale);
    context.getResources().updateConfiguration(config,
            resources.getDisplayMetrics());
}

where locale is one of the following:

![enter image description here

The above method is called before super.onCreate(savedInstanceState) gets called.

As described in the documentation,

  • I have added android:supportsRtl="true" in the manifest.
  • I have changed all xml properties with left and right attributes to start and end respectively.
  • I have put Arabic-language strings in res\values-ar\strings folder and drawable resources in res\drawable-ar folder (and similarly for other resources).

The above setup works properly. After changing the Locale to ar-AE, Arabic text & resources are correctly displayed in my Activities.

However, there is a problem with both resources and layout direction for all Android devices with version 8.0 and above.

On a device with version less than 8.0, an RTL screen correctly looks like this:

enter image description here

And on all devices with 8.0+, the same screen turns up looking like this:

enter image description here

which is wrong.

It turns out that both the direction and the resources are getting displayed incorrectly.

There are two problems here:

  • The correct Locale does not seem to be updated across the app configuration.
  • The direction of the text and drawables is opposite of what it should be.

With respect to direction, a curious method called setLayoutDirection() exists which I had not noticed before.

I would like to know what this problem is, why it happens in Oreo and what is the solution for it. Please help / comment on this.

EDIT:

According to the API Differences report, the updateConfiguration() method was indeed deprecated in Android 7.1 (API level 25).

Also, found all the relevant posts on this. In order of importance:

1. Android N change language programmatically.

2. Android context.getResources.updateConfiguration() deprecated.

3. How to change Android O / Oreo / api 26 app language.

4. Android RTL issue in API 24 and higher on locale change

5. Change language programmatically (Android N 7.0 - API 24).

6. Android N - Change Locale in runtime.

7. RTL layout bug in android Oreo.

Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
  • I know you said all versions less than 8.0 are fine, but have you tested 7.0? The resources framework changed how it handled locales starting with API 24. – Ben P. Sep 05 '18 at 14:50
  • What changes are you talking about? – Yash Sampat Sep 05 '18 at 14:54
  • https://developer.android.com/guide/topics/resources/multilingual-support – Ben P. Sep 05 '18 at 14:57
  • 1
    @BenP. Thank you, but it is working properly on devices with Android 7.0 / 7.1. Multiple locale support does not seem to be the problem. – Yash Sampat Sep 06 '18 at 06:32
  • Hm, okay. I was just surprised that a lot of the text changed to English. – Ben P. Sep 06 '18 at 14:21
  • can u check device Developer Options in Settings, check Force RTL layout Direction is on or not – Bhuvaneshwaran Vellingiri Sep 08 '18 at 12:47
  • @BenP. What you have pointed out is really something we should think about when dealing with these kind of situations. However, the changes brought in API level 24 (Android 7.0) will only result in better matching of locales. I don't think it will cause any harm even for apps made for lower versions. But in API level 25 (Oreo), things are little bit different. The `updateConfiguration ()` method itself is deprecated. See [my answer](https://stackoverflow.com/a/52227377/7594961). – Bertram Gilfoyle Sep 08 '18 at 17:29
  • Does Force RTL work for those devices? – Aaditya Brahmbhatt Sep 10 '18 at 05:37
  • @AadityaBrahmbhatt and BhuvaneshwaranVellingiri - force RTL helps somewhat with layout direction, but the core problem is intact. Incorrect resources are still getting loaded. – Yash Sampat Sep 10 '18 at 07:56
  • @Y.S. I had similar problem just realised you have multiple locale for single country. Let me post an answer. – Aaditya Brahmbhatt Sep 10 '18 at 09:39

6 Answers6

4

The method Resources#updateConfiguration (Configuration config, DisplayMetrics metrics) is deprecated in API level 25.

The doc suggests to use Context#createConfigurationContext (Configuration overrideConfiguration)


You can simply make a base activity which is a common parent of all the activities as shown below.

public class BaseActivity
        extends AppCompatActivity {

    private static final String LANGUAGE_CODE_ENGLISH = "en";
    private static final String LANGUAGE_CODE_ARABIC = "ar";

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(getLanguageAwareContext(newBase));
    }

    private static Context getLanguageAwareContext(Context context) {
        Configuration configuration = context.getResources().getConfiguration();
        configuration.setLocale(new Locale(getLanguageCode()));
        return context.createConfigurationContext(configuration);
    }

    // Rewrite this method according to your needs
    private static String getLanguageCode() {
        return LANGUAGE_CODE_ARABIC;
    }
}

Notes

  • getLanguageCode() should return language code. Typically the language code or any other data representing it is stored in preferences.
  • To change languages dynamically, recreate activity after setting appropriate language code into preferences.
  • Use activity context rather than application context to access any locale specific resources. In other words, use this or ActivityName.this from activities and getActivity() from fragments instead of getApplicationContext().
Zain
  • 37,492
  • 7
  • 60
  • 84
Bertram Gilfoyle
  • 9,899
  • 6
  • 42
  • 67
4

The updateConfiguration() method was deprecated

Now we need to use createConfigurationContext()

I have managed this way

create a new class ContextWrapper

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;

import java.util.Locale;

public class ContextWrapper extends android.content.ContextWrapper {

    public ContextWrapper(Context base) {
        super(base);
    }

    public static ContextWrapper wrap(Context context, Locale newLocale) {

        Resources res = context.getResources();
        Configuration configuration = res.getConfiguration();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configuration.setLocale(newLocale);

            LocaleList localeList = new LocaleList(newLocale);
            LocaleList.setDefault(localeList);
            configuration.setLocales(localeList);

            context = context.createConfigurationContext(configuration);

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(newLocale);
            context = context.createConfigurationContext(configuration);

        } else {
            configuration.locale = newLocale;
            res.updateConfiguration(configuration, res.getDisplayMetrics());
        }

        return new ContextWrapper(context);
    }}

create a new class of BaseActivity

import android.content.Context;

import android.support.v7.app.AppCompatActivity;

import java.util.Locale;

/**
 * Created by nilesh on 20/3/18.
 */

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void attachBaseContext(Context newBase) {

        Locale newLocale;

        String lang = new PrefManager(newBase).getLanguage();

        if (lang.equals("zh_CN")) {
            newLocale = new Locale("zh");
        } else {
            newLocale = new Locale(lang);
        }


        Context context = ContextWrapper.wrap(newBase, newLocale);
        super.attachBaseContext(context);
    }
}

Create a PrefManager class to store locale

import android.content.Context;
import android.content.SharedPreferences;

public class PrefManager {

    private SharedPreferences.Editor editor;
    private Context mContext;
    private SharedPreferences prefs;
    private final String LANGUAGE = "language";
    private final String PREF = "user_data";

    public PrefManager(Context mContext) {
        this.mContext = mContext;
    }

    public String getLanguage() {
        this.prefs = this.mContext.getSharedPreferences(PREF, 0);
        return this.prefs.getString(LANGUAGE, "en_US");
    }

    public void setLanguage(String language) {
        this.editor = this.mContext.getSharedPreferences(PREF, 0).edit();
        this.editor.putString(LANGUAGE, language);
        this.editor.apply();
    }

}

Now you need to extends your BaseActivity in your all activity like

public class OrdersActivity extends BaseActivity

Now when your need to change Locale just update the value in PrefManager and restart your activity

    PrefManager prefManager= new PrefManager(this);
    prefManager.setLanguage("zh_CN");
    //  restart your activity

NOTE

You can download source code from github repo

AskNilesh
  • 67,701
  • 16
  • 123
  • 163
3

The complete solution to this problem consists of three steps:

STEP 1:

In the onCreate() of your BaseActivity (or all your Activitys), set the Locale as follows:

@Override
protected void onCreate(Bundle savedInstanceState) {

    // set the Locale the very first thing
    Utils.setLocale(Utils.getSavedLocale());
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    super.onCreate(savedInstanceState);

    ......
    ......

}

where getSavedLocale() is the Locale corresponding to the current region (this will be specific for your project ... ).

And the method Utils.setLocale(...) is defined as follows:

public static void setLocale(Locale locale){
    Context context = MyApplication.getInstance();
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    Locale.setDefault(locale);
    configuration.setLocale(locale);
    configuration.setLayoutDirection(locale);

    // updateConfiguration(...) is deprecated in N
    if (Build.VERSION.SDK_INT >= 25) {
        context = context.getApplicationContext().createConfigurationContext(configuration);
        context = context.createConfigurationContext(configuration);
    }

    context.getResources().updateConfiguration(configuration,
            resources.getDisplayMetrics());
}

This sets the correct Locale in every Activity. This is enough for apps supporting API level 25. For API level 26 & above, STEP 2 and STEP 3 are also required.

STEP 2:

Override the following method in your BaseActivity:

@Override
protected void attachBaseContext(Context newBase) {
    newBase = Utils.getLanguageAwareContext(newBase);
    super.attachBaseContext(newBase);
}

where the function getLanguageAwareContext(...) is defined as follows:

public static Context getLanguageAwareContext(Context context){
    Configuration configuration = context.getResources().getConfiguration();
    Locale locale = getIntendedLocale();
    configuration.setLocale(locale);
    configuration.setLayoutDirection(locale);
    return context.createConfigurationContext(configuration);
}

This, along with STEP 1, sets the correct Locale in every Activity of your app for API level 26 and above.

One more step, however, is required for setting the language direction correctly ...

STEP 3:

In the onCreate() of your BaseActivity, add the following code:

@Override
protected void onCreate(Bundle savedInstanceState) {

    ....
    ....

    // yup, it's a legit bug ... :)
    if (Build.VERSION.SDK_INT >= 26) {
        getWindow().getDecorView().setLayoutDirection(Utils.isRTL()
                ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);


    }

    ....
    ....
}

where the isRTL() function is defined as follows:

public static boolean isRTL(){
    return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
}

The above steps should take care of all issues (at least regarding setting the Locale and text direction) on all extant versions of Android.

Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
0
public void setLocale(final Context ctx, final String lang) {
    AppSettings.getInstance(ctx).save(PrefKeys.language, lang);
    final Locale loc = new Locale(lang);
    Locale.setDefault(loc);
    final Configuration cfg = new Configuration();
    cfg.locale = loc;
    ctx.getResources().updateConfiguration(cfg, null);
}

Change to English: setLocale(getActivity(), "en";

Change to Arabic : setLocale(getActivity(), "ar");

After this you need to restart the application to get the Language change effects.

SHIDHIN TS
  • 1,557
  • 3
  • 26
  • 58
0

Resources.updateConfiguration is deprecated use this instead :

 fun setLocale(old: Context, locale: Locale): Context {
    val oldConfig = old.resources.configuration
    oldConfig.setLocale(locale)
    return old.createConfigurationContext(oldConfig)
}

override fun attachBaseContext(newBase: Context?) {
    super.attachBaseContext(newBase?.let { setLocale(it, Locale("ar")) })
}

In Java

private Context setLocale(Context old, Locale locale) {
    Configuration oldConfig = old.getResources().getConfiguration();
    oldConfig.setLocale(locale);
    return old.createConfigurationContext(oldConfig);
}

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(setLocale(newBase, new Locale("ar")));
}
svkaka
  • 3,942
  • 2
  • 31
  • 55
0

Totally closed the app because i think it is making cache in background.

Use below code thats how I achieved it in my case, you also can give it a try:

 Intent mStartActivity = new Intent(ctc, SplashActivity.class);
                int mPendingIntentId = 123456;
                PendingIntent mPendingIntent = PendingIntent.getActivity(ctc, mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
                AlarmManager mgr = (AlarmManager)ctc.getSystemService(Context.ALARM_SERVICE);
                mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
                System.exit(0);
Diwakar Singh
  • 267
  • 3
  • 12