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?

Community
  • 1
  • 1
schnatterer
  • 7,525
  • 7
  • 61
  • 80
  • Hi, did you got any solution? I too have nearly same use-case. – Nitish Dec 01 '14 at 12:20
  • No, I'm still using the workaround stated in the question – schnatterer Dec 01 '14 at 21:14
  • 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. – Vincent D. Aug 12 '15 at 20:28
  • @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 user – schnatterer Aug 17 '15 at 16:49

5 Answers5

54

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

Khaled Lela
  • 7,831
  • 6
  • 45
  • 73
  • I can verify this works without changing the current locale. Solved at last! At least for API 17+ – schnatterer Jan 28 '17 at 11:52
  • @schnatterer 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);` – Khaled Lela Jan 28 '17 at 13:08
  • 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? – Ali Azaz Alam Aug 02 '19 at 05:54
  • This solution doesn't work for Galaxy Tab A (2016) with model number SM-T285 – HendraWD Oct 12 '20 at 16:52
  • https://stackoverflow.com/a/40704077/3940133 This solution worked for me – HendraWD Oct 12 '20 at 17:48
  • 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. – Dmitry K Apr 13 '21 at 12:49
  • How to use this method if the context is a Fragment? – Shubham Nanche Feb 19 '22 at 06:43
  • I tested it and when a translation doesn't exist because the language is missing, the code returns the translation from the main locale. – Alberto M Jan 20 '23 at 12:47
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)

Zbarcea Christian
  • 9,367
  • 22
  • 84
  • 137
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.

Juan Sánchez
  • 980
  • 2
  • 10
  • 26
  • 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. – schnatterer Mar 15 '14 at 15:07
  • 1
    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. – Juan Sánchez Mar 15 '14 at 17:00
  • 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 solution – schnatterer Mar 16 '14 at 15:07
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);
}
Djek-Grif
  • 1,391
  • 18
  • 18
-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);
Shatazone
  • 2,422
  • 6
  • 28
  • 38
  • 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. – schnatterer Mar 15 '14 at 15:01
  • 2
    Be careful calling `new Resources` changes the configuration of the `AssetManager`. This will cause problems later in unrelated sections of code. See this [question](http://stackoverflow.com/questions/18984397/android-is-taking-wrong-layout-on-inflating-view) and [answer](http://stackoverflow.com/a/20522668/262789) for the problems it can cause. – Jade Dec 02 '14 at 20:04