Changing Locale within the app itself
Asked Answered
A

6

207

My users can change the Locale within the app (they may want to keep their phone settings in English but read the content of my app in French, Dutch or any other language ...)

Why is this working perfectly fine in 1.5/1.6 but NOT in 2.0 anymore ???

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
    case 201:
        Locale locale2 = new Locale("fr"); 
        Locale.setDefault(locale2);
        Configuration config2 = new Configuration();
        config2.locale = locale2;
        getBaseContext().getResources().updateConfiguration(
            config2, getBaseContext().getResources().getDisplayMetrics());
        // loading data ...
        refresh();
        // refresh the tabs and their content
        refresh_Tab ();   
     break;
     case 201: etc...

The problem is that the MENU "shrinks" more and more everytime the user is going through the lines of code above ...

This is the Menu that gets shrunk:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(0, 100, 1, "REFRESH").setIcon(android.R.drawable.ic_menu_compass);
    SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
        langMenu.add(1, 201, 0, "Nederlands");
        langMenu.add(1, 202, 0, "Français");
    menu.add(0, 250, 4, R.string.OptionMenu2).setIcon(android.R.drawable.ic_menu_send);
    menu.add(0, 300, 5, R.string.OptionMenu3).setIcon(android.R.drawable.ic_menu_preferences);
    menu.add(0, 350, 3, R.string.OptionMenu4).setIcon(android.R.drawable.ic_menu_more);
    menu.add(0, 400, 6, "Exit").setIcon(android.R.drawable.ic_menu_delete);

    return super.onCreateOptionsMenu(menu);
}

What should I do in API Level 5 to make this work again ?

HERE IS THE FULL CODE IF YOU WANT TO TEST THIS :

import java.util.Locale;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.Toast;

public class Main extends Activity {
    /** Called when the activity is first created. */


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
            langMenu.add(1, 201, 0, "Nederlands");
            langMenu.add(1, 202, 0, "Français");

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()){

        case 201:

            Locale locale = new Locale("nl"); 
            Locale.setDefault(locale);
            Configuration config = new Configuration();
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
            Toast.makeText(this, "Locale in Nederlands !", Toast.LENGTH_LONG).show();
            break;

        case 202:

            Locale locale2 = new Locale("fr"); 
            Locale.setDefault(locale2);
            Configuration config2 = new Configuration();
            config2.locale = locale2;
            getBaseContext().getResources().updateConfiguration(config2, getBaseContext().getResources().getDisplayMetrics());

            Toast.makeText(this, "Locale en Français !", Toast.LENGTH_LONG).show();
            break;  

        }
        return super.onOptionsItemSelected(item);
    }
}

AND HERE IS THE MANIFEST :

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.cousinHub.ChangeLocale"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".Main"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" /> 
    </manifest>

THIS IS WHAT I FOUND :

<uses-sdk android:minSdkVersion="5" />

=> IT WORKS JUST FINE ...

<uses-sdk android:minSdkVersion="3" />

=> Menu shrinks every time you change the locale !!!

as I want to keep my application accessible for users on 1.5, what should I do ??

Alexio answered 15/2, 2010 at 9:13 Comment(8)
What do you mean by "shrinks"?Divorce
it gets smaller and smaller each time. should I maybe use something else than getBaseContext ?Alexio
Maybe it's not these lines creating the problem as when I do a very basic app doing just the change in Locale, I don't have the same "shrinking of the menu". I thought it was maybe because my Activity was in fact a TabActivity, but even with that, I can't re-create the problem. I'll have to investigate further what is the exact cause of this bug... don't search further then. I'll post the answer here when I find it. Cheers, H.Alexio
I edited my first post, giving an exemple on how to create the problem. And I noticed in fact that when I change the line <uses-sdk android:minSdkVersion="5" /> to "3" ... indeed the problem appears !Alexio
You can use the following library, which provides the language list, the preference for your settings screen, and overrides the language in your application: github.com/delight-im/Android-LanguagesDonnie
Also similar: https://mcmap.net/q/129152/-android-get-device-localeSeymour
I allready answered this question in a other thread, check here.Hemipterous
I found my solution from your question itself, ThanksShiite
A
63

After a good night of sleep, I found the answer on the Web (a simple Google search on the following line "getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());"), here it is :

link text => this link also shows screenshots of what is happening !

Density was the issue here, I needed to have this in the AndroidManifest.xml

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
/>

The most important is the android:anyDensity =" true ".

Don't forget to add the following in the AndroidManifest.xml for every activity (for Android 4.1 and below):

android:configChanges="locale"

This version is needed when you build for Android 4.2 (API level 17) explanation here:

android:configChanges="locale|layoutDirection"
Alexio answered 16/2, 2010 at 7:5 Comment(3)
I had android:anyDensity="false" due to the advice of Google (Strategies for Legacy Applications - point 9 : developer.android.com/intl/fr/guide/practices/…). Be carefull then !Alexio
The texts on buttons don't change after I change locale. I wonder is there any solution to refresh all widgets which contain string resources. Now I'm redrawing the text by using setText(getString(R.string.button_label)) in OnRestart(). (Of course it changes after restart Application.)Rouge
you can call recreate activity activity.recreate()Turpin
N
153

Through the original question is not exactly about the locale itself all other locale related questions are referencing to this one. That's why I wanted to clarify the issue here. I used this question as a starting point for my own locale switching code and found out that the method is not exactly correct. It works, but only until any configuration change (e.g. screen rotation) and only in that particular Activity. Playing with a code for a while I have ended up with the following approach:

I have extended android.app.Application and added the following code:

public class MyApplication extends Application
{
    private Locale locale = null;

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        if (locale != null)
        {
            newConfig.locale = locale;
            Locale.setDefault(locale);
            getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
        }
    }

    @Override
    public void onCreate()
    {
        super.onCreate();

        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);

        Configuration config = getBaseContext().getResources().getConfiguration();

        String lang = settings.getString(getString(R.string.pref_locale), "");
        if (! "".equals(lang) && ! config.locale.getLanguage().equals(lang))
        {
            locale = new Locale(lang);
            Locale.setDefault(locale);
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
        }
    }
}

This code ensures that every Activity will have custom locale set and it will not be reset on rotation and other events.

I have also spent a lot of time trying to make the preference change to be applied immediately but didn't succeed: the language changed correctly on Activity restart, but number formats and other locale properties were not applied until full application restart.

Changes to AndroidManifest.xml

Don't forget to add android:configChanges="layoutDirection|locale" to every activity at AndroidManifest, as well as the android:name=".MyApplication" to the <application> element.

Null answered 21/11, 2010 at 19:2 Comment(14)
Is you preference activity being called from you main activity? you could place this in the on resume... @Override protected void onResume() { if (!(PreferenceManager.getDefaultSharedPreferences( getApplicationContext()).getString("listLanguage", "en") .equals(langPreference))) { refresh(); } super.onResume(); } private void refresh() { finish(); Intent myIntent = new Intent(Main.this, Main.class); startActivity(myIntent); }Asphalt
This is actually what I need, I used to to call updateConfiguration() in MainActivity but the menus doesn't update when Application terminates then starts. You solution is right. If you want to refresh strings immediately, I think you still must reset strings manually. (e.g reset button label or recreate menu).Rouge
Does this interferre with Android system locale?Mareah
+1 Big Thumbs Up for this solution! onConfigurationChanged() made the difference. Thanks!Rhiannonrhianon
There is one BIG problem with this solution. When the user leaves the application (click back button) and then return to the app, onCreate from Application is not always called. So even when the user changed preferred language in preferences and then made a restart of the application, changes may not be seen. Any solution?Warrantor
how do you change the locale of the OS itself?Moonlighting
@Warrantor Check the android app life cycle. You can put those codes in onCreate() to onResume() instead.Jonathonjonati
@DanteY Application class has only onCreate and onConfigurationChange methods. You mistook Activity lifecycle for Application lifecycle.Warrantor
You gotta do more for MyApplication to do automatically apply the locale changes. In AndroidManifest.xml, <application android:name=".MYApplication" ~~ is needed. After that, that code will called on every Activities.Prostitution
Note that you must never modify a Configuration object that Android gives you (config and newConfig). You should make a copy with config = new Configuration(config); first. I just spent an hour debugging this code before finding out the problem (it was causing the activity to flash endlessly).Singband
@Singband I make a copy of config but activity still flash endlessly)Araby
if I change locale, onConfigurationChanged of Application is called only if I rotate screen. any ideas ?Turpin
I had problems with the null == locale check. I created a common method called handleLanguage() and it did what was in onCreate each and every time. I also did what imgx64 said and I new'd up a copy of the Configuration.Pointdevice
This technique has glitches on Android 7+ when starting the app from a cold start, with the system font size set to large. Google has changed the way updateConfiguration() works, so sometimes your Activity will be displayed in 2 different languages (especially in Dialogs). More info: #39706239Melina
A
63

After a good night of sleep, I found the answer on the Web (a simple Google search on the following line "getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());"), here it is :

link text => this link also shows screenshots of what is happening !

Density was the issue here, I needed to have this in the AndroidManifest.xml

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
/>

The most important is the android:anyDensity =" true ".

Don't forget to add the following in the AndroidManifest.xml for every activity (for Android 4.1 and below):

android:configChanges="locale"

This version is needed when you build for Android 4.2 (API level 17) explanation here:

android:configChanges="locale|layoutDirection"
Alexio answered 16/2, 2010 at 7:5 Comment(3)
I had android:anyDensity="false" due to the advice of Google (Strategies for Legacy Applications - point 9 : developer.android.com/intl/fr/guide/practices/…). Be carefull then !Alexio
The texts on buttons don't change after I change locale. I wonder is there any solution to refresh all widgets which contain string resources. Now I'm redrawing the text by using setText(getString(R.string.button_label)) in OnRestart(). (Of course it changes after restart Application.)Rouge
you can call recreate activity activity.recreate()Turpin
T
59

In Android M the top solution won't work. I've written a helper class to fix that which you should call from your Application class and all Activities (I would suggest creating a BaseActivity and then make all the Activities inherit from it.

Note: This will also support properly RTL layout direction.

Helper class:

public class LocaleUtils {

    private static Locale sLocale;

    public static void setLocale(Locale locale) {
        sLocale = locale;
        if(sLocale != null) {
            Locale.setDefault(sLocale);
        }
    }

    public static void updateConfig(ContextThemeWrapper wrapper) {
        if(sLocale != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Configuration configuration = new Configuration();
            configuration.setLocale(sLocale);
            wrapper.applyOverrideConfiguration(configuration);
        }
    }

    public static void updateConfig(Application app, Configuration configuration) {
        if (sLocale != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //Wrapping the configuration to avoid Activity endless loop
            Configuration config = new Configuration(configuration);
            // We must use the now-deprecated config.locale and res.updateConfiguration here,
            // because the replacements aren't available till API level 24 and 17 respectively.
            config.locale = sLocale;
            Resources res = app.getBaseContext().getResources();
            res.updateConfiguration(config, res.getDisplayMetrics());
        }
    }
}

Application:

public class App extends Application {
    public void onCreate(){
        super.onCreate();

        LocaleUtils.setLocale(new Locale("iw"));
        LocaleUtils.updateConfig(this, getBaseContext().getResources().getConfiguration());
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        LocaleUtils.updateConfig(this, newConfig);
    }
}

BaseActivity:

public class BaseActivity extends Activity {
    public BaseActivity() {
        LocaleUtils.updateConfig(this);
    }
}
Triceratops answered 16/8, 2015 at 16:58 Comment(12)
This should be accepted answer. Make sure updateConfig should called from constructor not from onCreate of activity class, I was doing this mistake..Awe
This is the only solution that worked in ALL situations as of 08/16. I just added a method to also retrieve saved locale as a sharedPrefAdministrator
@Roberto @RJFares Which of these should I call to change the locale from inside a fragment ? Just LocaleUtils.setLocale(new Locale("de")); is not enough, but if I also call LocaleUtils.updateConfig(getActivity()); I get an IllegalStateException: getResources() has already been called. Any ideas?Eunuchize
it depends on how you implemented this in your program. If you have overriden onConfigurationChanged() you don't need to call .updateConfig() and that's why you are getting the error, because it's being called twice. if you are saying that only calling .setLocale() is not working, this is because you need to refresh your UI, either by calling recreate() on the activity or your own method in activities/fragments (I do prefer the former even if it's not smooth but you'll never worry about problems)Administrator
//import android.support.v7.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;//correct namespaceAvila
not working for me any idea. =s . Anyone has this project in github ? =sBasset
=S I fixed forgot to add the translations XML for each language (values-fr ,etc. ). Thanks a lotBasset
What is "the top solution"? The answers on this page are presented in different order depending on various factors.Gaily
Shouldn't the BaseActivity() constructor call super()?Gaily
Does this solution implicitly include putting <application android:name=".App"> in the manifest? How about adding android:configChanges="layoutDirection|locale" to every activity in the manifest?Gaily
@LarsH, yes you are right. You need to add application android:name=".App" otherwise app won't use language from shared preferences on startup. I also added android:configChanges="layoutDirection|locale" in the settings activity where I change language.Germain
If you changed the locale in the app, but the system has a different locale, when permission dialogs are displayed on Android M+, which language is it used?Congruency
A
10

This is for my comment on Andrey's answer but I cant include code in the comments.

Is you preference activity being called from you main activity? you could place this in the on resume...

@Override
protected void onResume() {
    if (!(PreferenceManager.getDefaultSharedPreferences(
            getApplicationContext()).getString("listLanguage", "en")
            .equals(langPreference))) {
        refresh();
    }
    super.onResume();
}

private void refresh() {
    finish();
    Intent myIntent = new Intent(Main.this, Main.class);
    startActivity(myIntent);
}
Asphalt answered 3/2, 2011 at 1:3 Comment(5)
You mean if i want to change the Language at runtime then i have to finish that activity and start that activiy again to get changes ? is there any other way to get it change as it is not good to every time finish the activity and start it again.Binetta
better do activity.recreate()Turpin
@trgradlia , i want to use above code. Please tell me , what is langPreference ? I think we are comparing recently changed lang and previous lang.Jardiniere
Activity has recreate() method so use recreate() in place of refresh() in your code. This will save your 3 line of code in refresh()methodPractitioner
@InzimamTariqIT, Thanks for the tip... The answer is almost 7 years old so I will leave it as is and people can find your improvement here in the comments.Asphalt
O
7

I couldn't used android:anyDensity="true" because objects in my game would be positioned completely different... seems this also does the trick:

// creating locale
Locale locale2 = new Locale(loc); 
Locale.setDefault(locale2);
Configuration config2 = new Configuration();
config2.locale = locale2;

// updating locale
mContext.getResources().updateConfiguration(config2, null);
Oldster answered 27/3, 2010 at 19:55 Comment(1)
This solution is best for me just used this code Locale.setDefault(mLocale); Configuration config = getBaseContext().getResources().getConfiguration(); if (!config.locale.equals(mLocale)) { config.locale = mLocale; getBaseContext().getResources().updateConfiguration(config, null); }Delvecchio
K
1

If you want to effect on the menu options for changing the locale immediately.You have to do like this.

//onCreate method calls only once when menu is called first time.
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    //1.Here you can add your  locale settings . 
    //2.Your menu declaration.
}
//This method is called when your menu is opend to again....
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    menu.clear();
    onCreateOptionsMenu(menu);
    return super.onMenuOpened(featureId, menu);
}
Khanate answered 6/2, 2012 at 15:52 Comment(1)
But doesn't this solution clears menu on every menu open? I suppose onMenuOpened should be called only once, after locale change.Seymour

© 2022 - 2024 — McMap. All rights reserved.