androidlocaleuser-defined

Set Locale programmatically


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.


Solution

  • 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);
    }