34

At one point in one of my Android apps I need to load strings of a specific language. For example:

 values:    <string name="txt_help">Help</string>
 values-de: <string name="txt_help">Hilfe</string>
 values-fr: <string name="txt_help">Aider</string>

Now I need the default (values) text.

Is there a way to load the key "txt_help" with a given language "en"? I can't find a method where I can set a locale as an additional parameter.

Or can I create a new ResourceManager with a given locale and read the String through this resource object?

Many thanks in advance. hjw

inazaruk
  • 74,247
  • 24
  • 188
  • 156
Harald Wilhelm
  • 6,656
  • 11
  • 67
  • 85
  • Maybe this helps you: http://developer.android.com/resources/tutorials/localization/index.html – Aman Alam Mar 09 '11 at 11:24
  • 1
    Thanks, but this is not what I'm looking for. Our apps are localized already but at one single part we want to read the strings from the default strings.xml even if there are localized strings available. – Harald Wilhelm Mar 10 '11 at 16:03
  • 1
    Have you tried creating a new [`Resources`](http://developer.android.com/reference/android/content/res/Resources.html) object providing it a [`Configuration`](http://developer.android.com/reference/android/content/res/Configuration.html) with the [`locale` field](http://developer.android.com/reference/android/content/res/Configuration.html#locale) set to the Locale that you want? – kabuko Jun 17 '11 at 00:01
  • Can you please, show us your manifest. In particular the uses-sdk / target-sdk part. I got the same issue with running on 4.x. – Loda Mar 26 '14 at 11:25
  • Bug Report: Please START IT. https://code.google.com/p/android/issues/detail?id=67672 – Loda Mar 26 '14 at 11:29

2 Answers2

47

Looking in the documentation, this looks promising:

Resources standardResources = context.getResources();
AssetManager assets = standardResources.getAssets();
DisplayMetrics metrics = standardResources.getDisplayMetrics();
Configuration config = new Configuration(standardResources.getConfiguration());
config.locale = Locale.US;
Resources defaultResources = new Resources(assets, metrics, config);

In other words, create a new Resources object configured to a standard locale.

Anomie
  • 92,546
  • 13
  • 126
  • 145
  • 7
    Very close... what's weird is that the `Resources defaultResources = new Resources(assets, metrics, config);` line seems to change something so that after that's called, `getResources()` returns the new locale that I custom set here... any clue why that would happen? I guess I can always just re-set config.locale back to the original; but I don't understand how creating a new Resources object could cause my standard getResources() to change. – GendoIkari Jun 17 '11 at 12:53
  • 4
    @GendoIkari: It looks like you're right. The problem is that a Resources object internally pushes its configuration (including the locale) down to the underlying AssetManager object, so it's the AssetManager underlying both Resources objects that is getting changed. Unfortunately, there doesn't seem to be any documented way to clone or create a new AssetManager, just to retrieve the existing AssetManager. So it looks like we're stuck with changing the configuration before fetching the resource and then changing it back after. – Anomie Jun 17 '11 at 14:42
  • 2
    Interesting... thanks. I messed with the code a little so that now I just call the `new Resources(assets, metrics, config);` constructor; not need to even set it to a variable. I just set the config; call the constructor; get my strings; then set the config back and call the constructor again. It works. Thanks again! – GendoIkari Jun 17 '11 at 14:44
  • @GendoIkari: For that matter, you can just call `updateConfiguration` on the existing Resources object. – Anomie Jun 17 '11 at 16:59
  • @Anomie: You'd think so... just tested it; and got some extremely weird behavior. When I use `new Resources(assets, metrics, config);` to set the config; it works. When I use `res.updateConfiguration(config, metrics);` the app freezes... doesn't crash; just goes dim; after which I can hit "back" but then all the displays are drawn incorrectly. – GendoIkari Jun 17 '11 at 18:15
  • This is inside a bindView() method. I noticed that with `new Resources(assets, metrics, config);`, bindView is called 5 times for each row in my list (2 rows). However, with `res.updateConfiguration(config, metrics);`, bindView is first called 3 times for each row in the list; and then it is called 11 more times for just the first row. Never seen anything like it.... but the constructor solution works fine. – GendoIkari Jun 17 '11 at 18:17
  • `new Resources()` has been deprecated as of API 25. Instead, `context.createConfigurationContext(config)` should be called – Felix Jassler Nov 23 '22 at 13:41
2

From my own question and taking reference from this answer and this answer, I came up with the following custom class solution:

package com.my.package.localisation;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;

import java.util.Formatter;
import java.util.Locale;

/**
 * Class to manage fetching {@link Resources} for a specific {@link Locale}. API levels less
 * than {@link Build.VERSION_CODES#JELLY_BEAN_MR1} require an ugly implementation.
 * <p/>
 * Subclass extends {@link Resources} in case of further functionality requirements.
 */
public class MyResources {

    private final Context mContext;
    private final AssetManager assetManager;
    private final DisplayMetrics metrics;
    private final Configuration configuration;
    private final Locale targetLocale;
    private final Locale defaultLocale;

    public MyResources(@NonNull final Context mContext, @NonNull final Locale defaultLocale,
                         @NonNull final Locale targetLocale) {

        this.mContext = mContext;
        final Resources resources = this.mContext.getResources();
        this.assetManager = resources.getAssets();
        this.metrics = resources.getDisplayMetrics();
        this.configuration = new Configuration(resources.getConfiguration());
        this.targetLocale = targetLocale;
        this.defaultLocale = defaultLocale;
    }

    public String[] getStringArray(final int resourceId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(targetLocale);
            return mContext.createConfigurationContext(configuration).getResources().getStringArray(resourceId);
        } else {
            configuration.locale = targetLocale;
            final String[] resourceArray = new ResourceManager(assetManager, metrics, configuration).getStringArray(resourceId);
            configuration.locale = defaultLocale; // reset
            new ResourceManager(assetManager, metrics, configuration); // reset
            return resourceArray;
        }
    }

    public String getString(final int resourceId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(targetLocale);
            return mContext.createConfigurationContext(configuration).getResources().getString(resourceId);
        } else {
            configuration.locale = targetLocale;
            final String resource = new ResourceManager(assetManager, metrics, configuration).getString(resourceId);
            configuration.locale = defaultLocale; // reset
            new ResourceManager(assetManager, metrics, configuration); // reset
            return resource;
        }
    }

    private final class ResourceManager extends Resources {
        public ResourceManager(final AssetManager assets, final DisplayMetrics metrics, final Configuration config) {
            super(assets, metrics, config);
        }

        /**
         * Return the string array associated with a particular resource ID.
         *
         * @param id The desired resource identifier, as generated by the aapt
         *           tool. This integer encodes the package, type, and resource
         *           entry. The value 0 is an invalid identifier.
         * @return The string array associated with the resource.
         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
         */
        @Override
        public String[] getStringArray(final int id) throws NotFoundException {
            return super.getStringArray(id);
        }

        /**
         * Return the string value associated with a particular resource ID,
         * substituting the format arguments as defined in {@link Formatter}
         * and {@link String#format}. It will be stripped of any styled text
         * information.
         * {@more}
         *
         * @param id         The desired resource identifier, as generated by the aapt
         *                   tool. This integer encodes the package, type, and resource
         *                   entry. The value 0 is an invalid identifier.
         * @param formatArgs The format arguments that will be used for substitution.
         * @return String The string data associated with the resource,
         * stripped of styled text information.
         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
         */
        @NonNull
        @Override
        public String getString(final int id, final Object... formatArgs) throws NotFoundException {
            return super.getString(id, formatArgs);
        }

        /**
         * Return the string value associated with a particular resource ID.  It
         * will be stripped of any styled text information.
         * {@more}
         *
         * @param id The desired resource identifier, as generated by the aapt
         *           tool. This integer encodes the package, type, and resource
         *           entry. The value 0 is an invalid identifier.
         * @return String The string data associated with the resource,
         * stripped of styled text information.
         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
         */
        @NonNull
        @Override
        public String getString(final int id) throws NotFoundException {
            return super.getString(id);
        }
    }
}
Community
  • 1
  • 1
brandall
  • 6,094
  • 4
  • 49
  • 103