Change the language of an app depending on the user's choice


#1

So, I’m having this issue of changing the language of the app depending on the user’s choice in the register phase.

Since I already wrote this on StackOverflow, I will post the link here so I don’t copy paste the entire thing in here.

Thanks in advance for the help!


#2

I have not attempted to change the locale of a process. This isn’t all that common of a thing to do (I hope), as I am unconvinced that it is really officially supported.

Also, I’m not certain what “I have no guarantee that it will work on all devices” means. More specifically, I don’t know what happens when it does not work. I am going to guess that you mean that the language change does not have any visible impact, and that the UI is still using the language set in Settings for the device. If you mean that your code crashes or has other effects, that would be good to know.

In your RestaurantListFragment, you are going through your change-the-locale code in the end of onViewCreated(). I am not aware that any of that code actually triggers a configuration change, though it’s possible that updateConfiguration() does. I would try moving your code to happen before you inflate layouts or otherwise set up the UI. So, for example, I would try having your code early in onCreateView(), so that the Locale is set before you start trying to use resources based on the Locale.

You might also:

  • Check the existing Locale and only attempt to change it if there is an actual difference

  • Experiment with calling recreate() on the Activity to force a configuration change, if there indeed is a difference

  • See how people are using createConfigurationContext(), which is the replacement for updateConfiguration() on Android 7.1+ (e.g., are they creating a custom ContextWrapper that applies this?)

But because this may not be officially supported, do not be surprised if you do encounter some devices or custom ROMs where your approach breaks down, because they short-circuited something that you were relying upon to create this language change.


#3

On devices where it doesn’t work, if you select Romanian in the registration process, you will get the app in English when you enter the main screen.

The locale is verified at register time, here:

final UserInfo userInfo = complexPreferences.getObject("user_info", UserInfo.class);
String language;

if (userInfo != null) {
    language = userInfo.language;
}
else {
    String phoneLanguage = Locale.getDefault().getLanguage();
    if (phoneLanguage.equals("ro")) {
        language = "ro";
    } else {
        language = "en";
    }
}

setLocale(language);

I tried to put recreate() after setting the locale, but the application just freezed, and stayed like that (with half a screen filled, like in the middle of something). This was done in onCreate().

I also tried with a ContextWrapper, but I’m not sure what that is about. What should I do with the ContextWrapper? My Activity already extends a BaseActivity, I can’t extend the ContextWrapper, if that’s what is needed.

So, I’m not sure what I’m supposed to do with it.

I tried to take the code from here: https://stackoverflow.com/questions/35576383/change-language-programatically-in-android-with-country-code

But… there’s a SalonyFormatter.getInstance(wrapped).refreshResources(wrapped); line there that I’m not sure what it’s about.

Thanks!


#4

The locale is verified at register time

Users can change their desired language at any point, including after your registration process.

I tried to put recreate() after setting the locale, but the application just freezed, and stayed like that (with half a screen filled, like in the middle of something). This was done in onCreate().

That’s unexpected, but I have only used recreate() in instrumentation tests.

What should I do with the ContextWrapper?

I’m not certain. Again, I suggested researching how people are using createConfigurationContext(). Perhaps they use it directly with attachBaseContext() on Activity. Perhaps they use it with a ContextWrapper that they pass to attachBaseContext(). Perhaps nobody is using createConfigurationContext(). I have no idea. All I know is that the documentation says that updateConfiguratiion() is deprecated and createConfigurationContext() is the replacement.

But… there’s a SalonyFormatter.getInstance(wrapped).refreshResources(wrapped); line there that I’m not sure what it’s about.

Yeah, I don’t know what that is either.

Another thing you can experiment with: try manually updating something that takes a new string resource (e.g., call setText(R.string.something) on a TextView) after you set the locale. There are three main scenarios that I can see:

  1. It works, by which I mean that the TextView shows up in the proper language, whereas without the manual update it would not. In that case, the locale change is taking effect, but there is no configuration change being triggered. If so — and given that recreate() is not behaving for you — you can always manually update everything, just as if you were using android:configChanges and having to update your UI in onConfigurationChanged().

  2. It works, but only after some delay (e.g., updating the TextView using postDelayed(..., 200) works). In that case, the locale change is taking effect, but the configuration is not reflecting this change until some other work is done on the main application thread. In that case, you might try recreate() after the delay, or manually update the UI after the delay.

  3. It does not work, even after a delay. If that is the case, I would then start other tests to see if the locale change will ever take place (e.g., if I start a new activity, does it honor the locale change)? If nothing respects the change, then that device simply does not support in-process locale changes, in which case I think that you’re out of luck.


#5

These are actually very good ideas. Thanks a lot!


#6

So, I managed to solve this problem. Here’s what I did:

  1. Added this in my Application class:

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

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    LocaleManager.setLocale(this);
    }

  2. Added this in my BaseActivity class:

    @Override
    protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(CalligraphyContextWrapper.wrap(LocaleManager.setLocale(newBase)));
    }

  3. This is in the LocaleManager class:

    public static final String LANGUAGE_ENGLISH = “en”;
    private static final String LANGUAGE_KEY = “language_key”;

    public static Context setLocale(Context c) {
    return updateResources(c, getLanguage©);
    }

    public static String getLanguage(Context c) {
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences©;
    return prefs.getString(LANGUAGE_KEY, LANGUAGE_ENGLISH);
    }

    private static Context updateResources(Context context, String language) {
    Locale locale = new Locale(language);
    Locale.setDefault(locale);

    Resources res = context.getResources();
    Configuration config = new Configuration(res.getConfiguration());
    if (Build.VERSION.SDK_INT >= 17) {
    config.setLocale(locale);
    context = context.createConfigurationContext(config);
    } else {
    config.locale = locale;
    res.updateConfiguration(config, res.getDisplayMetrics());
    }
    return context;
    }

Now everything works as expected - the language is changed all over the app.

I got the solution from here: