174

My app supports 3 (soon 4) languages. Since several locales are quite similar I'd like to give the user the option to change locale in my application, for instance an Italian person might prefer Spanish over English.

Is there a way for the user to select among the locales that are available for the application and then change what locale is used? I don't see it as a problem to set locale for each Activity since it is a simple task to perform in a base class.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Roland
  • 5,328
  • 10
  • 37
  • 55
  • 1
    If you need a way to restore the default locale later or if you need a language preference that contains a list of languages, and if you want to change the locale more conveniently, this may be helpful: https://github.com/delight-im/Android-Languages – caw Jan 21 '17 at 02:22

17 Answers17

194

Hope this help(in onResume):

Locale locale = new Locale("ru");
Locale.setDefault(locale);
Configuration config = getBaseContext().getResources().getConfiguration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
      getBaseContext().getResources().getDisplayMetrics());
Ani Fichadia
  • 267
  • 2
  • 10
Rubycon
  • 18,156
  • 10
  • 49
  • 70
182

EDIT 21st OCTOBER 2022

Starting from Android 13 you can now set your locale from the AppCompatDelegate with the method setApplicationLocales(appLocale) and it has backwards compatibility.

For API 33 and above:

The process is quite simple and you can achieve it as follow:

val appLocale: LocaleListCompat = 
LocaleListCompat.forLanguageTags("xx-YY")
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale)

While using this you should no longer need to call recreate() on the activity as the docs mention:

Note that calling setApplicationLocales() recreates your Activity, unless your app handles locale configuration changes by itself.

API lower than 33

If you are targetting an API lower than 33 you will need to also add a service to your manifest in order to handle locale storage:

<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
    android:enabled="false"
    android:exported="false">
    <meta-data
      android:name="autoStoreLocales"
      android:value="true" />
  </service>

This is only available for AndroidX from androidx.appcompat:appcompat:1.6.0-alpha01

You can find the documentation here

-------------- Old Answers ------------

For people still looking for this answer, since configuration.locale was deprecated from API 24, you can now use:

configuration.setLocale(locale);

Take in consideration that the minSkdVersion for this method is API 17.

Full example code:

@SuppressWarnings("deprecation")
private void setLocale(Locale locale){
    SharedPrefUtils.saveLocale(locale); // optional - Helper method to save the selected language to SharedPreferences in case you might need to attach to activity context (you will need to code this)
    Resources resources = getResources();
    Configuration configuration = resources.getConfiguration();
    DisplayMetrics displayMetrics = resources.getDisplayMetrics();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
        configuration.setLocale(locale);
    } else{
        configuration.locale=locale;
    }
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N){
        getApplicationContext().createConfigurationContext(configuration);
    } else {
        resources.updateConfiguration(configuration,displayMetrics);
    }
}

Don't forget that, if you change the locale with a running Activity, you will need to restart it for the changes to take effect.

EDIT 11th MAY 2018

As from @CookieMonster's post, you might have problems keeping the locale change in higher API versions. If so, add the following code to your Base Activity (BaseActivity extends AppCompatActivity / other activities) so that you update the context locale on every Activity creation:

@Override
protected void attachBaseContext(Context base) {
     super.attachBaseContext(updateBaseContextLocale(base));
}

private Context updateBaseContextLocale(Context context) {
    String language = SharedPrefUtils.getSavedLanguage(); // Helper method to get saved language from SharedPreferences
    Locale locale = new Locale(language);
    Locale.setDefault(locale);

    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
        return updateResourcesLocale(context, locale);
    }

    return updateResourcesLocaleLegacy(context, locale);
}

@TargetApi(Build.VERSION_CODES.N_MR1)
private Context updateResourcesLocale(Context context, Locale locale) {
    Configuration configuration = new Configuration(context.getResources().getConfiguration());
    configuration.setLocale(locale);
    return context.createConfigurationContext(configuration);
}

@SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    configuration.locale = locale;
    resources.updateConfiguration(configuration, resources.getDisplayMetrics());
    return context;
}

If you use this, don't forget to save the language to SharedPreferences when you set the locale with setLocale(locale)

EDIT 7th APRIL 2020

You might be experiencing issues in Android 6 and 7, and this happens due to an issue in the androidx libraries while handling the night mode. For this you will also need to override applyOverrideConfiguration in your base activity and update the configuration's locale in case a fresh new locale one is created.

Sample code:

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
        // update overrideConfiguration with your locale  
        setLocale(overrideConfiguration) // you will need to implement this
    }
    super.applyOverrideConfiguration(overrideConfiguration);
} 
Ricardo
  • 9,136
  • 3
  • 29
  • 35
  • one more hint change for context for layout and applicationContext for strings – amorenew Sep 27 '17 at 08:33
  • It doesn't work for me although this works https://stackoverflow.com/a/47223492/2002079. – ahmadalibaloch Jan 08 '18 at 15:43
  • This is not working any more. I found the working solution in an answer below. https://stackoverflow.com/a/44571077/4511297 – Prasad Pawar May 08 '18 at 15:03
  • 9
    This works for activities, but is there a way to update the application context? – alekop Jun 11 '18 at 23:45
  • With the method to set the locale you are updating the application context - `getApplicationContext().createConfigurationContext(configuration);` - and this should suffice for you to change the locale. However, if this doesnt work you have to handle the activities context. – Ricardo Jun 12 '18 at 06:53
  • 1
    @Ricardo: your solution works fine, but it does not update activity label that, in my case, is set in Manifest file: android:label="@string/act_title". Do you have any idea? – toni Dec 02 '18 at 09:31
  • `configuration.setLocale` is added in API level 17. https://developer.android.com/reference/android/content/res/Configuration#setLocale(java.util.Locale) – Bek Jun 25 '19 at 10:21
  • If you read carefully you can see the reference: Take in consideration that the minSkdVersion for this method is API 17. In other words, when it was added. – Ricardo Jun 25 '19 at 13:05
  • 4
    After changing `androidx.appcompat:appcompat:` version from `1.0.2` to `1.1.0` not working on android 7, but working on android 9. – Bek Sep 11 '19 at 04:24
  • 1
    I have a problem with android 6.0. – FarshidABZ Oct 02 '19 at 13:55
  • 4
    The same problem for me and `1.1.0` androidx – Alexander Dadukin Nov 25 '19 at 15:20
  • 1
    Are you guys updating the base context in `attachBaseContext` as described? – Ricardo Nov 25 '19 at 15:35
  • 2
    Same problem for me. After I changed to androidx.appcompat:appcompat:1.1.0' lib – Rahul Jidge Dec 09 '19 at 10:37
  • 6
    Problem with `appcompat:1.1.0` can be fixed with `appcompat:1.2.0-alpha02` and code `Set set = new LinkedHashSet<>(); // bring the target locale to the front of the list set.add(locale); LocaleList all = LocaleList.getDefault(); for (int i = 0; i < all.size(); i++) { // append other locales supported by the user set.add(all.get(i)); } Locale[] locales = set.toArray(new Locale[0]); configuration.setLocales(new LocaleList(locales));` inside `@TargetApi(Build.VERSION_CODES.N) updateResourcesLocale()` – Vojtech Pohl Jan 29 '20 at 22:35
  • 2
    And I'm sorry, I forgot in my previous comment how the configuration needs to be changed: `Configuration configuration = new Configuration(context.getResources().getConfiguration());` – Vojtech Pohl Jan 30 '20 at 08:51
  • Not Build.VERSION.SDK_INT >= Build.VERSION_CODES.N, but Build.VERSION.SDK_INT > Build.VERSION_CODES.N will work – Taras Vovkovych Mar 11 '20 at 15:54
  • 1
    @TarasVovkovych I've updated my answer, you are correct? Its API 25, not 24(N) – Ricardo Apr 07 '20 at 07:59
  • 1
    I have added a fix to the Android 6 and 7 issues. With this you wont need to downgrade your androidx libraries as some of the threads I found suggest – Ricardo Apr 07 '20 at 08:50
  • Still the same problem in `androidx.appcompat:appcompat:1.3.0-alpha01` And I tried @VojtechPohl solution but it doesn't help me, Anyone Fixed this bug ? – Islam Ahmed May 29 '20 at 21:11
  • How do you retrigger attachBaseContext if user chooses a language himself? – Quentin vk Jun 03 '20 at 09:30
  • this solution worked when I ran it in debug mode. but when I ran it in release mode, it didn't. and now, it isn't working either in debug or release mode. can anyone please suggest me the solution to this problem? – ganjaam Jun 10 '20 at 15:13
  • 1
    @Lawnio When you change the language you need to restart the activity in order for the changes to take place. – Ricardo Jun 11 '20 at 09:02
  • @ganjaam I need more information in order to be able to help you. Make sure you are storing the locale properly. I would suggest to debug and make sure when you get the current locale if it corresponds to the expected one – Ricardo Jun 11 '20 at 09:05
  • @Ricardo Indeed, but this flicker when you restart the activity is a bad UX. I noticed some apps that can switch without the flicker. Any idea how they do it? – Quentin vk Jun 11 '20 at 15:34
  • @Lawnio what you can do to avoid recreating your activity is reset all strings to your views after resetting the locale – Ricardo Jun 12 '20 at 08:37
  • 1
    @Lawnio use onNewIntent(Intent intent) method in Activity and set your data to view in that method. you won't see a flicker. – chathura Sep 04 '20 at 08:51
  • apparently `createConfigurationContext()` works in all devices lower than N too – shadygoneinsane Dec 02 '20 at 06:39
  • I'm not sure why, but this solution doesn't work anymore once I update the androidx constraintlayout dependency from 1.1.3 to 2.0.0 or higher. – Wirling Jun 30 '21 at 10:05
  • 1
    Implemented all the solutions worked like a charms many thanks ! – Ashraf Amin Sep 23 '21 at 09:08
29

I had a problem with setting locale programmatically with devices that has Android OS N and higher. For me the solution was writing this code in my base activity:

(if you don't have a base activity then you should make these changes in all of your activities)

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(updateBaseContextLocale(base));
}

private Context updateBaseContextLocale(Context context) {
    String language = SharedPref.getInstance().getSavedLanguage();
    Locale locale = new Locale(language);
    Locale.setDefault(locale);

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

    return updateResourcesLocaleLegacy(context, locale);
}

@TargetApi(Build.VERSION_CODES.N)
private Context updateResourcesLocale(Context context, Locale locale) {
    Configuration configuration = context.getResources().getConfiguration();
    configuration.setLocale(locale);
    return context.createConfigurationContext(configuration);
}

@SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    configuration.locale = locale;
    resources.updateConfiguration(configuration, resources.getDisplayMetrics());
    return context;
}

note that here it is not enough to call

createConfigurationContext(configuration)

you also need to get the context that this method returns and then to set this context in the attachBaseContext method.

CookieMonster
  • 1,723
  • 1
  • 15
  • 15
27

As no answer is complete for the current way to solve this problem, I try to give instructions for a complete solution. Please comment if something is missing or could be done better.

General information

First, there exist some libraries that want to solve the problem but they all seem outdated or are missing some features:

Further I think writing a library might not be a good/easy way to solve this problem because there is not very much to do, and what has to be done is rather changing existing code than using something completely decoupled. Therefore I composed the following instructions that should be complete.

My solution is mainly based on https://github.com/gunhansancar/ChangeLanguageExample (as already linked to by localhost). It is the best code I found to orientate at. Some remarks:

  • As necessary, it provides different implementations to change locale for Android N (and above) and below
  • It uses a method updateViews() in each Activity to manually update all strings after changing locale (using the usual getString(id)) which is not necessary in the approach shown below
  • It only supports languages and not complete locales (which also include region (country) and variant codes)

I changed it a bit, decoupling the part which persists the chosen locale (as one might want to do that separately, as suggested below).

Solution

The solution consists of the following two steps:

  • Permanently change the locale to be used by the app
  • Make the app use the custom locale set, without restarting

Step 1: Change the locale

Use the class LocaleHelper, based on gunhansancar's LocaleHelper:

  • Add a ListPreference in a PreferenceFragment with the available languages (has to be maintained when languages should be added later)
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.preference.PreferenceManager;

import java.util.Locale;

import mypackage.SettingsFragment;

/**
 * Manages setting of the app's locale.
 */
public class LocaleHelper {

    public static Context onAttach(Context context) {
        String locale = getPersistedLocale(context);
        return setLocale(context, locale);
    }

    public static String getPersistedLocale(Context context) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getString(SettingsFragment.KEY_PREF_LANGUAGE, "");
    }

    /**
     * Set the app's locale to the one specified by the given String.
     *
     * @param context
     * @param localeSpec a locale specification as used for Android resources (NOTE: does not
     *                   support country and variant codes so far); the special string "system" sets
     *                   the locale to the locale specified in system settings
     * @return
     */
    public static Context setLocale(Context context, String localeSpec) {
        Locale locale;
        if (localeSpec.equals("system")) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                locale = Resources.getSystem().getConfiguration().getLocales().get(0);
            } else {
                //noinspection deprecation
                locale = Resources.getSystem().getConfiguration().locale;
            }
        } else {
            locale = new Locale(localeSpec);
        }
        Locale.setDefault(locale);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return updateResources(context, locale);
        } else {
            return updateResourcesLegacy(context, locale);
        }
    }

    @TargetApi(Build.VERSION_CODES.N)
    private static Context updateResources(Context context, Locale locale) {
        Configuration configuration = context.getResources().getConfiguration();
        configuration.setLocale(locale);
        configuration.setLayoutDirection(locale);

        return context.createConfigurationContext(configuration);
    }

    @SuppressWarnings("deprecation")
    private static Context updateResourcesLegacy(Context context, Locale locale) {
        Resources resources = context.getResources();

        Configuration 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;
    }
}

Create a SettingsFragment like the following:

import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import mypackage.LocaleHelper;
import mypackage.R;

/**
 * Fragment containing the app's main settings.
 */
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
    public static final String KEY_PREF_LANGUAGE = "pref_key_language";

    public SettingsFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_settings, container, false);
        return view;
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        switch (key) {
            case KEY_PREF_LANGUAGE:
                LocaleHelper.setLocale(getContext(), PreferenceManager.getDefaultSharedPreferences(getContext()).getString(key, ""));
                getActivity().recreate(); // necessary here because this Activity is currently running and thus a recreate() in onResume() would be too late
                break;
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        // documentation requires that a reference to the listener is kept as long as it may be called, which is the case as it can only be called from this Fragment
        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
    }
}

Create a resource locales.xml listing all locales with available translations in the following way (list of locale codes):

<!-- Lists available locales used for setting the locale manually.
     For now only language codes (locale codes without country and variant) are supported.
     Has to be in sync with "settings_language_values" in strings.xml (the entries must correspond).
  -->
<resources>
    <string name="system_locale" translatable="false">system</string>
    <string name="default_locale" translatable="false"></string>
    <string-array name="locales">
        <item>@string/system_locale</item> <!-- system setting -->
        <item>@string/default_locale</item> <!-- default locale -->
        <item>de</item>
    </string-array>
</resources>

In your PreferenceScreen you can use the following section to let the user select the available languages:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/preferences_category_general">
        <ListPreference
            android:key="pref_key_language"
            android:title="@string/preferences_language"
            android:dialogTitle="@string/preferences_language"
            android:entries="@array/settings_language_values"
            android:entryValues="@array/locales"
            android:defaultValue="@string/system_locale"
            android:summary="%s">
        </ListPreference>
    </PreferenceCategory>
</PreferenceScreen>

which uses the following strings from strings.xml:

<string name="preferences_category_general">General</string>
<string name="preferences_language">Language</string>
<!-- NOTE: Has to correspond to array "locales" in locales.xml (elements in same orderwith) -->
<string-array name="settings_language_values">
    <item>Default (System setting)</item>
    <item>English</item>
    <item>German</item>
</string-array>

Step 2: Make the app use the custom locale

Now setup each Activity to use the custom locale set. The easiest way to accomplish this is to have a common base class for all activities with the following code (where the important code is in attachBaseContext(Context base) and onResume()):

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import mypackage.LocaleHelper;
import mypackage.R;

/**
 * {@link AppCompatActivity} with main menu in the action bar. Automatically recreates
 * the activity when the locale has changed.
 */
public class MenuAppCompatActivity extends AppCompatActivity {
    private String initialLocale;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initialLocale = LocaleHelper.getPersistedLocale(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_settings:
                Intent intent = new Intent(this, SettingsActivity.class);
                startActivity(intent);
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(LocaleHelper.onAttach(base));
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (initialLocale != null && !initialLocale.equals(LocaleHelper.getPersistedLocale(this))) {
            recreate();
        }
    }
}

What it does is

  • Override attachBaseContext(Context base) to use the locale previously persisted with LocaleHelper
  • Detect a change of the locale and recreate the Activity to update its strings

Notes on this solution

  • Recreating an Activity does not update the title of the ActionBar (as already observed here: https://github.com/gunhansancar/ChangeLanguageExample/issues/1).

    • This can be achieved by simply having a setTitle(R.string.mytitle) in the onCreate() method of each activity.
  • It lets the user chose the system default locale, as well as the default locale of the app (which can be named, in this case "English").

  • Only language codes, no region (country) and variant codes (like fr-rCA) are supported so far. To support full locale specifications, a parser similar to that in the Android-Languages library can be used (which supports region but no variant codes).

    • If someone finds or has written a good parser, add a comment so I can include it in the solution.
user905686
  • 4,491
  • 8
  • 39
  • 60
  • 3
    Excellent but king of nightmare – Odys Jan 09 '18 at 12:34
  • 3
    Hell no, my app is too complex already, this approach would be a nightmare to maintain in the future. – Josh Feb 28 '18 at 09:06
  • @Josh Can you explain this a little further? Actually only a few lines have to be added to each Activity base class you use. I see that it might not be possible to use the same base class for all activities but even bigger projects should be able to get along with a few. Aspect oriented programming could help here but composition (move the code from `attachBaseContext(Context base)` and `onResume()` to a separate class) can do the trick. Then all you have to do is declare one object in each activity base class and delegate those two calls. – user905686 Mar 08 '18 at 14:02
  • If the user changes its locale, can the locale of all previous activity pages be changed also ? – Jeff Bootsholz Sep 13 '18 at 05:22
  • This is the best answer on this issue. Thanks bro, it works – Alok Gupta Jun 10 '19 at 09:12
  • "Only language codes, no region (country) and variant codes (like fr-rCA) are supported so far." Thanks for this! – ataravati Oct 15 '19 at 16:26
  • Seems like with Android 13, applying the system default locale doesn't work any more as shown here. What I had to do was to add the line `locale = new Locale(locale.getLanguage());` at the end of the code that's executed when `localeSpec.equals("system")`. I also tried `locale = (Locale) locale.clone();`, but it didn't work. – David Scherfgen Sep 07 '22 at 16:52
  • Android 13 adds support for per-app language preferences and adds some new APIs which you probably have to switch to: https://developer.android.com/about/versions/13/features#app-languages – user905686 Oct 12 '22 at 16:52
16
@SuppressWarnings("deprecation")
public static void forceLocale(Context context, String localeCode) {
    String localeCodeLowerCase = localeCode.toLowerCase();

    Resources resources = context.getApplicationContext().getResources();
    Configuration overrideConfiguration = resources.getConfiguration();
    Locale overrideLocale = new Locale(localeCodeLowerCase);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        overrideConfiguration.setLocale(overrideLocale);
    } else {
        overrideConfiguration.locale = overrideLocale;
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        context.getApplicationContext().createConfigurationContext(overrideConfiguration);
    } else {
        resources.updateConfiguration(overrideConfiguration, null);
    }
}

Just use this helper method to force specific locale.

UDPATE 22 AUG 2017. Better use this approach.

localhost
  • 5,568
  • 1
  • 33
  • 53
8

There is a super simple way.

in BaseActivity, Activity or Fragment override attachBaseContext

 override fun attachBaseContext(context: Context) {
    super.attachBaseContext(context.changeLocale("tr"))
}

extension

fun Context.changeLocale(language:String): Context {
    val locale = Locale(language)
    Locale.setDefault(locale)
    val config = this.resources.configuration
    config.setLocale(locale)
    return createConfigurationContext(config)
}
Burak Dizlek
  • 4,805
  • 2
  • 23
  • 19
6

Add a helper class with the following method:

public class LanguageHelper {
    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.setLocale(locale);
            activity.getResources().updateConfiguration(config,
                    activity.getResources().getDisplayMetrics());
        }

    }
}

And call it in your startup activity, like MainActivity.java:

public void onCreate(Bundle savedInstanceState) {
    ...
    LanguageHelper.setAppLocale("fa", this);
    ...
}
Boken
  • 4,825
  • 10
  • 32
  • 42
Mohammad Zaer
  • 638
  • 7
  • 9
5

Valid for API16 to API28 Just place this method some where:

Context newContext = context;

Locale locale = new Locale(languageCode);
Locale.setDefault(locale);

Resources resources = context.getResources();
Configuration config = new Configuration(resources.getConfiguration());

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    config.setLocale(locale);
    newContext = context.createConfigurationContext(config);
} else {
    config.locale = locale;
    resources.updateConfiguration(config, resources.getDisplayMetrics());
}

return newContext;

Insert this code in all your activitys using:

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(localeUpdateResources(base, "<-- language code -->"));
    }

or call localeUpdateResources on fragments, adapters, etc. where you need the new context.

Credits: Yaroslav Berezanskyi

hugomg
  • 68,213
  • 24
  • 160
  • 246
nnyerges
  • 563
  • 4
  • 15
4

simple and easy

Locale locale = new Locale("en", "US");
Resources res = getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = locale;
res.updateConfiguration(conf, dm);

where "en" is language code and "US" is country code.

Makvin
  • 3,475
  • 27
  • 26
4

I found the androidx.appcompat:appcompat:1.1.0 bug can also be fixed by simply calling getResources() in applyOverrideConfiguration()

@Override public void
applyOverrideConfiguration(Configuration cfgOverride)
{
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
      Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
    // add this to fix androidx.appcompat:appcompat 1.1.0 bug
    // which happens on Android 6.x ~ 7.x
    getResources();
  }

  super.applyOverrideConfiguration(cfgOverride);
}
Sam Lu
  • 3,448
  • 1
  • 27
  • 39
3

Android 13 introduces Per-app language preferences which significantly simplifies handling in-app Locale changes.

Disclaimer: Android 13 is currently in "Developer Preview" at the time of writing and part of what mentioned here is in alpha (thus, subject to change).

As visible in the docs there are couple of new ways to handle such changes, for compatibility the best one seem to be through AppCompatDelegate:


val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags("xx-YY")
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale)

(Method is available from androidx.appcompat:appcompat:1.6.0-alpha01 )

This will take care of setting the locale to appLocale and restart any activities as needed. Moreover, as mentioned on setApplicationLocales docs

On API level 33 and above, this API will handle storage automatically

Meaning that what needs to be done for a custom in-app locale to work is just to offer the user a language picker and call setApplicationLocales once when a choice is performed.

What about API < 33?

The docs on developer.android.com do mention that devs can require to store this value automatically on older API levels

..by adding a special metaData entry in their AndroidManifest, similar to :

but at the time of writing this, the actual metadata that is needed isn't mentioned (maybe something went wrong in the doc generation).

It was instead visible in the source code for AppCompatDelegate published here and it looks as follows

   <service
       android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
       android:enabled="false"
       android:exported="false">
       <meta-data
           android:name="autoStoreLocales"
           android:value="true" />
   </service>

Adding the above to your Manifest should make this solution work for all API levels

Marino
  • 800
  • 1
  • 12
  • 26
  • Using this method after updating the locale layout direction of my current activity is not getting updated even though I call `recreate()` in the activity. – Alif Hasnain Aug 03 '22 at 07:39
  • I am not completely sure whether changing locale direction applies in the same way. In general, with a correct setup you should not need to call `recreate` explicitly anymore for locale changes - is it possible that your setup is still missing something compared to what detailed above? (eg. Are you saving the updated locale with `AppCompatDelegate.setApplicationLocales(updatedLocale)` after the change..?) – Marino Aug 03 '22 at 20:59
  • Unfortunately, this does not work on API < 33 for the strings resolved via application context! It works only with activity context: https://issuetracker.google.com/issues/243457462 – digrec Apr 28 '23 at 15:06
2
 /**
 * Requests the system to update the list of system locales.
 * Note that the system looks halted for a while during the Locale migration,
 * so the caller need to take care of it.
 */
public static void updateLocales(LocaleList locales) {
    try {
        final IActivityManager am = ActivityManager.getService();
        final Configuration config = am.getConfiguration();

        config.setLocales(locales);
        config.userSetLocale = true;

        am.updatePersistentConfiguration(config);
    } catch (RemoteException e) {
        // Intentionally left blank
    }
}
Zbigniew Mazur
  • 653
  • 7
  • 11
2

There's a new way to let users select app's default language since Appcompat 1.6.0-alpha04 and later. Lets say you have a button that should change the app's language to Italian when the user clicks it:

binding.button.setOnClickListener {
    val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags("it")
    AppCompatDelegate.setApplicationLocales(appLocale)
}

Also, to add support for older devices (< API level 32) add the following service inside the <application> tag of your AndroidManifest.xml file:

<service
  android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
  android:enabled="false"
  android:exported="false">
  <meta-data
    android:name="autoStoreLocales"
    android:value="true" />
</service>

Make sure to have the latest AppCompat dependency:


implementation 'androidx.appcompat:appcompat:1.6.0-alpha05'

Reference: https://developer.android.com/about/versions/13/features/app-languages

1

As of 2020 language management become easy! All you have to do is:

  1. Call to Activity.applyOverrideConfiguration
  2. And call to Locale.setDefault

You must call those from the activity constructor since you can call to applyOverrideConfiguration only once, and the system calls it pretty early.

And watch out from app-bundles, Google will split your APK by language resources automatically when using app-bundles. Check out the new API and the workaround here.


I created a helper class to help you with it. In my implementation G.app is the application context. Also, I need to access resources from the app context so I use the Res class for it, this one is optional, but I provide its code as well.

Usage

public BaseActivity(){
    LanguageUtility.init(this);
}

public void changeLanguage(Local local){
    // you must recreat your activity after you call this
    LanguageUtillity.setDefaultLanguage(local, this);
}

Source code

public class LanguageUtility {

    private static Configuration configuration;

    public static void setDefaultLanguage(Locale locale, Context context) {
        Locale.setDefault(locale);

        context.getSharedPreferences("LocaleSettings", Context.MODE_PRIVATE)
                .edit()
                .putString("language", locale.getLanguage())
                .putString("country", locale.getCountry())
                .putString("variant", locale.getVariant())
                .apply();

        configuration = createConfiguration(context);
        Res.updateContext();
    }

    /**
     * Used to update your app context in case you cache it.
     */
    public static Context createConfigurationContext(Context context) {
        return context.createConfigurationContext(getConfiguration(context));
    }

    public static void init(Activity activity) {
        activity.applyOverrideConfiguration(LanguageUtility.getConfiguration(G.app));
        // you can't access sharedPrefferences from activity constructor 
        // with activity context, so I used the app context.
        Locale.setDefault(getLocale(G.app));
    }

    @NotNull
    private static Configuration getConfiguration(Context context) {
        if (configuration == null) {
            configuration = createConfiguration(context);
        }
        return configuration;
    }

    @NotNull
    private static Configuration createConfiguration(Context context) {
        Locale locale = getLocale(context);
        Configuration configuration = new Configuration();
        configuration.setLocale(locale);
        LanguageUtility.configuration = configuration;
        return configuration;
    }

    @NotNull
    private static Locale getLocale(Context context) {
        Locale aDefault = Locale.getDefault();
        SharedPreferences preferences =
                context.getSharedPreferences("LocaleSettings", Context.MODE_PRIVATE);
        String language = preferences.getString("language", aDefault.getLanguage());
        String country = preferences.getString("country", aDefault.getCountry());
        String variant = preferences.getString("variant", aDefault.getVariant());
        return new Locale(language, country, variant);
    }
}

An optional Res class.

public class Res {

    @SuppressLint("StaticFieldLeak")
    public static Context appLocalContext = LanguageUtility.createConfigurationContext(G.app);

    public static void updateContext() {
        appLocalContext = LanguageUtility.createConfigurationContext(G.app);
    }

    public static String getString(@StringRes int id, Object... formatArgs) {
        return appLocalContext.getResources().getString(id, formatArgs);
    }

    public static int getColor(@ColorRes int id) {
        return G.app.getColor(id);
    }
}
Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
1

Call this method on BaseActivity -> onCreate() and BaseFragment -> OnCreateView()

Tested on API 22, 23, 24, 25, 26, 27, 28, 29...uptodate version

fun Context.updateLang() {
    val resources = resources
    val config = Configuration(resources.configuration)
    config.setLocale(PreferenceManager(this).getAppLanguage()) // language from preference
    val dm = resources.displayMetrics
    createConfigurationContext(config)
    resources.updateConfiguration(config, dm)
}
Arul
  • 1,031
  • 13
  • 23
0

For those who tried everything but not not working. Please check that if you set darkmode with AppCompatDelegate.setDefaultNightMode and the system is not dark, then Configuration.setLocale will not work above Andorid 7.0.

Add this code in your every activity to solve this issue:

override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
  if (overrideConfiguration != null) {
    val uiMode = overrideConfiguration.uiMode
    overrideConfiguration.setTo(baseContext.resources.configuration)
    overrideConfiguration.uiMode = uiMode
  }
  super.applyOverrideConfiguration(overrideConfiguration)
}
Yeahia2508
  • 7,526
  • 14
  • 42
  • 71
-2

Put this code in your activity

 if (id==R.id.uz)
    {
        LocaleHelper.setLocale(MainActivity.this, mLanguageCode);

        //It is required to recreate the activity to reflect the change in UI.
        recreate();
        return true;
    }
    if (id == R.id.ru) {

        LocaleHelper.setLocale(MainActivity.this, mLanguageCode);

        //It is required to recreate the activity to reflect the change in UI.
        recreate();
    }