Android: How to get string in specific locale WITHOUT changing the current locale
Asked Answered
S

5

51

Use Case: Logging error messages as displayed to the user.

However you don't want to have messages in your log that depend on the locale of the user's device. On the other hand you don't want to change the locale of the user's device just for your (technical) logging. Can this be achieved? I found a couple of potential solutions here on stackoverflow:

However, they all result in changing the locale of my device (until the next configuration change).

Anyway, my current workaround is like so:

public String getStringInDefaultLocale(int resId) {
    Resources currentResources = getResources();
    AssetManager assets = currentResources.getAssets();
    DisplayMetrics metrics = currentResources.getDisplayMetrics();
    Configuration config = new Configuration(
            currentResources.getConfiguration());
    config.locale = DEFAULT_LOCALE;
    /*
     * Note: This (temporiarily) changes the devices locale! TODO find a
     * better way to get the string in the specific locale
     */
    Resources defaultLocaleResources = new Resources(assets, metrics,
            config);
    String string = defaultLocaleResources.getString(resId);
    // Restore device-specific locale
    new Resources(assets, metrics, currentResources.getConfiguration());
    return string;
}

To be honest, I don't like this approach at all. It's not efficient and - thinking about concurrency and all that - it might result in some view in the "wrong" locale.

So - Any ideas? Maybe this could be achieved using ResourceBundles, just like in standard Java?

Shanna answered 21/7, 2013 at 10:50 Comment(4)
Hi, did you got any solution? I too have nearly same use-case.Anesthesiology
No, I'm still using the workaround stated in the questionShanna
why you don't use a specific english based string for your logging purpose. R.string.dev_error_clicking_button_b. If you don't translate it to other languages (and it don't need to) you will get this one by default.Finder
@VincentD. thanks for your idea, but how do know the strings are not translated? I'm looking for a generic mechanism to log everything that was in some way displayed to the userShanna
B
55

You can use this for API +17

@NonNull
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static String getStringByLocal(Activity context, int id, String locale) {
    Configuration configuration = new Configuration(context.getResources().getConfiguration());
    configuration.setLocale(new Locale(locale));
    return context.createConfigurationContext(configuration).getResources().getString(id);
}

Update (1) : How to support old versions.

@NonNull
public static String getStringByLocal(Activity context, int resId, String locale) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
        return getStringByLocalPlus17(context, resId, locale);
    else
        return getStringByLocalBefore17(context, resId, locale);
}

@NonNull
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static String getStringByLocalPlus17(Activity context, int resId, String locale) {
    Configuration configuration = new Configuration(context.getResources().getConfiguration());
    configuration.setLocale(new Locale(locale));
    return context.createConfigurationContext(configuration).getResources().getString(resId);
}

private static String getStringByLocalBefore17(Context context,int resId, String language) {
    Resources currentResources = context.getResources();
    AssetManager assets = currentResources.getAssets();
    DisplayMetrics metrics = currentResources.getDisplayMetrics();
    Configuration config = new Configuration(currentResources.getConfiguration());
    Locale locale = new Locale(language);
    Locale.setDefault(locale);
    config.locale = locale;
/*
 * Note: This (temporarily) changes the devices locale! TODO find a
 * better way to get the string in the specific locale
 */
    Resources defaultLocaleResources = new Resources(assets, metrics, config);
    String string = defaultLocaleResources.getString(resId);
    // Restore device-specific locale
    new Resources(assets, metrics, currentResources.getConfiguration());
    return string;
}

Update (2): Check this article

Bucky answered 28/1, 2017 at 8:23 Comment(8)
I can verify this works without changing the current locale. Solved at last! At least for API 17+ 😉Shanna
@Shanna You can sure check build version to support old version if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return getStringByLocal(context, resId, locale); else return getStringByLocalBefore17(context, resId, locale);Bucky
by this method I need to set text to all textviews manually. Is there any possibility to not go on this hectic work of setting text?Marque
This solution doesn't work for Galaxy Tab A (2016) with model number SM-T285Bilinear
https://mcmap.net/q/66642/-android-context-getresources-updateconfiguration-deprecated This solution worked for meBilinear
I'd prefer to pass a Locale object instead of a String as an argument, because Locale has predefined constants for popular locales (e.g. Locale.US) and also convenient constructors that help you avoid mistakes in the specification.Bordeaux
How to use this method if the context is a Fragment?Clansman
I tested it and when a translation doesn't exist because the language is missing, the code returns the translation from the main locale.Stormie
S
6

Thanks to @Khaled Lela answer, I've made a Kotlin extension:

fun Context.getStringByLocale(@StringRes stringRes: Int, locale: Locale, vararg formatArgs: Any): String {
    val configuration = Configuration(resources.configuration)
    configuration.setLocale(locale)
    return createConfigurationContext(configuration).resources.getString(stringRes, *formatArgs)
}

And I'm using directly within an Activity, Fragment or Context related classes, just simply calling:

getStringByLocale(R.string.my_text, Locale.UK) or with arguments:

getStringByLocale(R.string.my_other_text, Locale.RU, arg1, arg2, arg3)

Syngamy answered 26/6, 2020 at 13:55 Comment(0)
P
4

You can save all the strings of the default locale in a Map inside a global class, like Application that is executed when launching the app:

public class DualLocaleApplication extends Application {

    private static Map<Integer, String> defaultLocaleString;

    public void onCreate() {
        super.onCreate();
        Resources currentResources = getResources();
        AssetManager assets = currentResources.getAssets();
        DisplayMetrics metrics = currentResources.getDisplayMetrics();
        Configuration config = new Configuration(
                currentResources.getConfiguration());
        config.locale = Locale.ENGLISH;
        new Resources(assets, metrics, config);
        defaultLocaleString = new HashMap<Integer, String>();
        Class<?> stringResources = R.string.class;
        for (Field field : stringResources.getFields()) {
            String packageName = getPackageName();
            int resId = getResources().getIdentifier(field.getName(), "string", packageName);
            defaultLocaleString.put(resId, getString(resId));
        }
        // Restore device-specific locale
        new Resources(assets, metrics, currentResources.getConfiguration());
    }

    public static String getStringInDefaultLocale(int resId) {
        return defaultLocaleString.get(resId);
    }

}

This solution is not optimal, but you won't have concurrency problems.

Pourpoint answered 15/10, 2013 at 1:53 Comment(3)
Thanks for proposing another work around! It's a trade-off: No concurrency issues vs higher memory usage and slower startup of the app. However, for my specific use case I don't need the resources most of the time (only to log errors/exceptions). So creating a map and keeping it in memory at each start seems like a bit too much effort here.Shanna
It will depend on how many string resources you are actually using for those error/exceptions. If the subset is small, maybe you can live with it. Another approach (work around approach :D) would be temporary saving these resources in a tmp file, and accessing them on demand. No extra memory usage with this approach, and it can fit your needs, I guess.Billionaire
You really are creative when it comes to finding workarounds! +1 for that ;) Thanks for your efforts - however, I'm still hoping someone turns up with a non-workaround solutionShanna
I
0

For api +17 we could use this:

public static String getDefaultString(Context context, @StringRes int stringId){
    Resources resources = context.getResources();
    Configuration configuration = new Configuration(resources.getConfiguration());
    Locale defaultLocale = new Locale("en");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        LocaleList localeList = new LocaleList(defaultLocale);
        configuration.setLocales(localeList);
        return context.createConfigurationContext(configuration).getString(stringId);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
        configuration.setLocale(defaultLocale);
        return context.createConfigurationContext(configuration).getString(stringId);
    }
    return context.getString(stringId);
}
Interlocution answered 15/12, 2017 at 13:53 Comment(0)
G
-2

Try this:

  Configuration conf = getResources().getConfiguration();
  conf.locale = new Locale("ar"); // locale I used here is Arabic

  Resources resources = new Resources(getAssets(), getResources().getDisplayMetrics(), conf);
  /* get localized string */
  String appName = resources.getString(R.string.app_name);
Galatea answered 14/3, 2014 at 16:21 Comment(2)
Am I missing something or is there no difference between your approach and the one in the question? If you execute new Resources(getAssets(), getResources().getDisplayMetrics(), getResources().getConfiguration()).getString(R.string.app_name); the string will still be in Arabic, even if your device's language was different before! I'm looking for a solution for the following use case: Your device is in lets say Chinese but the log should print the default (e.g. English) message.Shanna
Be careful calling new Resources changes the configuration of the AssetManager. This will cause problems later in unrelated sections of code. See this question and answer for the problems it can cause.Hayley

© 2022 - 2024 — McMap. All rights reserved.