50

I have an enumeration where I need to display the values as localized strings. My current approach has been this:

public enum MyEnum {
    VALUE1(R.string.VALUE1),
    VALUE2(R.string.VALUE2),
    .
    .
    VALUE10(R.string.VALUE10);

    private int mResId = -1;

    private MuEnum(int resId) {
        mResId = resId;
    }

    public String toLocalizedString(Resources r) {
        if (-1 != mResId) return (r.getString(mResId));
        return (this.toString());
    }
}

Is there any easier way to to do this? I'd love it if I could somehow lookup the resource based on the enumeration value name (i.e 'VALUE1').

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="VALUE1"/>My string</string>
    <string name="VALUE2"/>My string 2</string>
    .
    .
    <string name="VALUE10"/>My string 3</string>
</resources>

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

EDIT: Just for future reference, this is the solution that worked best for me:

public enum MyEnum {
    VALUE1, 
    VALUE2, 
    . 
    . 
    VALUE10; 

    /**
     * Returns a localized label used to represent this enumeration value.  If no label
     * has been defined, then this defaults to the result of {@link Enum#name()}.  
     * 
     * <p>The name of the string resource for the label must match the name of the enumeration
     * value.  For example, for enum value 'ENUM1' the resource would be defined as 'R.string.ENUM1'.
     * 
     * @param context   the context that the string resource of the label is in.
     * @return      a localized label for the enum value or the result of name()
     */
    public String getLabel(Context context) {
        Resources res = context.getResources();
        int resId = res.getIdentifier(this.name(), "string", context.getPackageName());
        if (0 != resId) {
            return (res.getString(resId));
        }
        return (name());
    }
}
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
jsmith
  • 4,847
  • 2
  • 32
  • 39
  • Look this post, there is much simpler solution for this question: http://stackoverflow.com/a/29625457/1891118 – Oleksii K. Apr 14 '15 at 10:53

6 Answers6

29

You can certainly look up a resource by its name using Resources.getIdentifier(). For instance, with the string resources you posted as an example, you can do this from an activity:

Resources res = getResources();
MyEnum e = MyEnum.VALUE1;
String localized = res.getString(res.getIdentifier(e.name(), "string", getPackageName()));

From a View, you'd have to change the last argument to getContext().getPackageName()

Dheeraj Vepakomma
  • 26,870
  • 17
  • 81
  • 104
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • Is there any reason why you wouldn't just directly inject the name of the package the enum is in instead of 'getPackageName()'? I ask because my enum is in a library shared by multiple projects. – jsmith Mar 16 '12 at 18:15
  • 1
    @jsmith - That's precisely why you would not want to inject the package name directly. The package name that goes there is the name declared in the manifest, not the package that the enum is in. – Ted Hopp Mar 16 '12 at 18:17
  • I looked at the generated packages within the apps that use the library, and I think I see what you mean. But, I'd love an explanation of how packaging works with inlcuded libraries. Can you reference me to documentation on this? – jsmith Mar 16 '12 at 18:24
  • @jsmith Sure. Read about library projects [here](http://developer.android.com/guide/developing/projects/index.html#LibraryProjects). Besides the [documentation for the AndroidManifest.xml](http://developer.android.com/guide/topics/manifest/manifest-intro.html) file, the blog post [Things That Cannot Change](http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html) is well worth reading. – Ted Hopp Mar 16 '12 at 18:28
  • Instead of `getPackageName()` you can also use `BuildConfig.APPLICATION_ID` and save yourself the context trouble. – Gerhard Burger Dec 30 '14 at 10:09
  • @GerhardBurger - `BuildConfig.APPLICATION_ID` is only available for Gradle/Android Studio builds. It is not defined for other builds. – Ted Hopp Dec 30 '14 at 17:19
  • Good to know! But Android Studio seems the way to go for future projects ;) – Gerhard Burger Dec 30 '14 at 17:32
  • @GerhardBurger - Perhaps so. I still use ADT/Eclipse. But I suspect that `BuildConfig.APPLICATION_ID` is wrong even for Gradle builds. If the code in question is part of a library, the application ID depends on what project uses the library and is not known at compile time. I suspect it will use the library's declared application name, and the wrong name will be compiled in-line into the generated class file. – Ted Hopp Dec 30 '14 at 18:01
  • @SiddarthG - Check for typos, including case. Make sure the resource you are trying to find is packaged in your apk. (You can check the field names in the compiler-generated `R.java` file for all the resource names packaged in your app.) – Ted Hopp Mar 24 '20 at 12:36
10

I think what you have tried is good except you dont need to pass the resources argument to the enum every time you want to translate the enum.


Use the link to subclass the Application Class, then follow this approach.

Better Solution

import android.app.Application;

public enum MyEnum {

    VALUE1(R.string.VALUE1),
    VALUE2(R.string.VALUE2),
    .
    .
    VALUE10(R.string.VALUE10);

    private int resourceId;

    private MyEnum(int id)  {
        resourceId = id;
    }


    @Override
    public String toString() {

        return MyApplication.getApplicationContext().getString(resourceId);

    }

}

Then calling MyEnum.VALUEx will always give you the translated enum value, but be careful this might not be what you want always e.g you may have a raw query like this:

select * from Customer where userStatus = MyEnum.VALUEx.toString();

This may break your app, if you are storing the enum values as VALUE1, VALUE2... in db, so remember to use this MyEnum.VALUEx.name() when you dont want to use the translated value of your MyEnum.

select * from Customer where userStatus = MyEnum.VALUEx.name();
Community
  • 1
  • 1
dsharew
  • 10,377
  • 6
  • 49
  • 75
  • 1
    What is "MyApplication" ? If I take my launcher activity which extends AppCompatActivity, I get following error: Non-static method "getApplicationCOntext() – lidox Sep 12 '16 at 08:10
5

Use static Application is always a bad practice, because not only it breaks Instant Run, but also this is against the decoupling principle of programming, thus makes modularization difficult to implement. Not to mention Android actually supports multiple Applications in a single process.

For this reason, I'd suggest define an inner class for the enum to be created in runtime, whenever locale might be changed.

enum Example {
    A(R.string.label_a),
    B(R.string.label_b);

    Example(@StringRes int label) { mLabel = label; }
    private @StringRes int mLabel;

    class Entry {
        private Context mContext;
        Entry(final Context context) { mContext = context; }
        @Override public String toString() { return mContext.getString(mLabel); }
    }
}

Then, build Example.Entry instance or array of Example.Entry to represent the localized version of the original enum.

Example.A.new Entry(context);

Arrays.stream(Example.values()).map(item -> item.new Entry(context)).toArray(Example.Entry[]::new)
Oasis Feng
  • 7,490
  • 3
  • 31
  • 44
4
enum class MeasurementEnum(var position: Int, @StringRes
                    var userRedableStringRes: Int) {
    False(0, R.string.boolean_false),
    True(1,R.string.boolean_true)
}
Damian JK
  • 479
  • 1
  • 3
  • 9
  • 2
    While this command may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value – LordWilmore May 13 '19 at 20:59
2

If you subclass your application class, you can have it as singleton ( see:http://androidcookbook.com/Recipe.seam?recipeId=1218 ) Once you got your singleton instance , you can use it in toLocalizedString() to get resource object and get rid of parameter:

 public String getString() {
    return YourApp.getInstance().getResources().getString(resId);
}

voila - now you have clean looking interface.

Konstantin Pribluda
  • 12,329
  • 1
  • 30
  • 35
0

First create new class:

import android.app.Application;

public class MyApplication extends Application {

    private static MyApplication singleton;

    public static MyApplication getInstance(){
        return singleton;
    }
    @Override
    public void onCreate() {

        super.onCreate();
        singleton = this;
    }
}

Now add the reference to your application class in AndroidManifest.xml:

<application ... android:name="com.yourPackageName.application.MyApplication  ">

Next create your enum. Example enum for gender:

public enum Gender {
MALE(0, R.string.male),
FEMALE(1, R.string.female);

private Integer resourceId;
private Integer index;

private static final Map<Integer, Gender> lookupIndex = new HashMap<Integer, Gender>();
private static final Map<Integer, Gender> lookupResourceId = new HashMap<Integer, Gender>();
private static final Map<String, Gender> lookupTranslation = new HashMap<String, Gender>();

static {
    for (Gender g : values()) {
        lookupIndex.put(g.getIndex(), g);
        lookupResourceId.put(g.getResourceId(), g);
        lookupTranslation.put(g.toString(), g);
    }
}

private Gender(Integer index, Integer displayText) {
    this.resourceId = displayText;
    this.index = index;
}

public Integer getIndex() {
    return this.index;
}

public Integer getResourceId() {
    return this.resourceId;
}

public static Gender findByIndex(Integer index) {
    return lookupIndex.get(index);
}

public static Gender findByResourceId(Integer id) {
    return lookupResourceId.get(id);
}

public static Gender findByTranslationText(String text) {
    return lookupTranslation.get(text);
}

@Override
public String toString() {
    return MyApplication.getInstance().getResources().getString(this.resourceId);
}}

Now you can use the requested lookup pattern:

// by index
Gender male = Gender.findByIndex(0);

// by translation
String femaleTranslated = context.getResources().getString(R.string.female);
Gender gender = Gender.findByTranslationText(femaleTranslated);

// by id
Gender gender = Gender.findByResourceId(R.string.female);

Speacial thanks goes to Ahmet Yüksektepe

lidox
  • 1,901
  • 3
  • 21
  • 40