Enum localization
Asked Answered
P

17

66

How do you localize enums for a ListBoxFor where multiple options are possible?

For example an enum that contains roles:

public enum RoleType
{
    [Display(Description = "Administrator", ResourceType = typeof(Resource))]
    Administrator = 1,
    [Display(Description = "Moderator", ResourceType = typeof(Resource))]
    Moderator = 2,
    [Display(Description = "Webmaster", ResourceType = typeof(Resource))]
    Webmaster = 3,
    [Display(Description = "Guest", ResourceType = typeof(Resource))]
    Guest = 4,
    Etc.... = 5,
}

I have seen this done with dropdownlist/selectlists. But is there a way to do this for a multi select list?

[EDIT]

This is how I'd like to use it, which is how it works now but doesn't get translated in a different language:

var roles = from role r in Enum.GetValues(typeof(RoleType))
            select new
            {
               Id = (int)Enum.Parse(typeof(RoleType), r.ToString()),
               Name = r.ToString()
            };

searchModel.roles = new MultiSelectList(roles, "Id", "Name");

Note: i have renamed the enum from Role to RoleType.

Philipines answered 29/6, 2013 at 13:53 Comment(4)
You mean using values that can be combined with bitwise operations like this?Instrumental
@ChrisSinclair, no i mean for translation in the view. Enum descriptions are saved in a resource file.Philipines
Possible duplicate of How to use localization in C#Supertax
For Wpf implementation check this one https://mcmap.net/q/297385/-linking-enum-value-with-localized-string-resourceGonocyte
A
87

You can implement a description attribute.

public class LocalizedDescriptionAttribute : DescriptionAttribute
{
     private readonly string _resourceKey;
    private readonly ResourceManager _resource;
    public LocalizedDescriptionAttribute(string resourceKey, Type resourceType)
    {
        _resource = new ResourceManager(resourceType);
        _resourceKey = resourceKey;
    }

    public override string Description
    {
        get
        {
            string displayName = _resource.GetString(_resourceKey);

            return string.IsNullOrEmpty(displayName)
                ? string.Format("[[{0}]]", _resourceKey)
                : displayName;
        }
    }
}

public static class EnumExtensions
{
    public static string GetDescription(this Enum enumValue) 
    {
        FieldInfo fi = enumValue.GetType().GetField(enumValue.ToString());

        DescriptionAttribute[] attributes =
            (DescriptionAttribute[])fi.GetCustomAttributes(
            typeof(DescriptionAttribute),
            false);

        if (attributes != null &&
            attributes.Length > 0)
            return attributes[0].Description;
        else
            return enumValue.ToString();
    }
}

Define it like this:

public enum Roles
{
    [LocalizedDescription("Administrator", typeof(Resource))]
    Administrator,
...
}

And use it like this:

var roles = from RoleType role in Enum.GetValues(typeof(RoleType))
                    select new
                    {
                        Id = (int)role,
                        Name = role.GetDescription()
                    };
 searchModel.roles = new MultiSelectList(roles, "Id", "Name");
Ada answered 29/6, 2013 at 14:22 Comment(13)
Hi, this looks intresting. How would I get all the enums for a listbox? And does it automatically pick the right language with this line <globalization culture="nl" uiCulture="nl" /> defined in the webconfig?Philipines
your code has a type in "LocalizedDescriptionAttributre". I fixed it and it seems to work with the language files. I'm just missing the numbers behind the roles. Those numbers are needed as Id's for the join table. How do i get the numbers of the selected roles when the page posts back to the controller?Philipines
Just as you've mentioned in your question. I've edited my answer, you can see the example code under "And use it like this"Ada
Yes, the implementation uses the ResourceManager.GetString method which returns the value according to the current UI cultureAda
@eluxen: What is Resource in [LocalizedDescription("Administrator", typeof(Resource))] ?Footcandle
I am getting this error Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "Resources.Admin.resources" was correctly embedded or linked into assembly "Auction" at compile time, or that all the satellite assemblies required are loadable and fully signed.Footcandle
'Resource' is the a .resx file that is probably missing from your projectAda
misspelling 'LocalizedDescriptionAttribute'Rudder
@Jitendra I changed to _resource = Language.Resource.ResourceManager; and it started working for meSeldun
I addressed the problem reported by @Jitendra Pancholi in a separate answer: https://mcmap.net/q/295075/-enum-localizationDiscombobulate
could i change it to generic ? i have some enum type, but i cant find out how to change it generic type when call. i stuck at. <code>public static MvcHtmlString EnumToJson<T>(this HtmlHelper helper) where T : struct, IConvertible { var values = from T e in Enum.GetValues(typeof(T)) select String.Format(@"{{""Value"": {0:d}, ""Text"": ""{1}""}}", e, e.ToString()); return MvcHtmlString.Create("[" + String.Join(",", values.ToArray()) + "]"); }</code> i wanna change e.toString() to e.GetDescription() like example.thanks !Hazeghi
Thanks that work brilliantly, just one question, is it possible to string.format or string interpolation on the returned value?Whitefaced
Don't think this works anymore because attributes[0].Description nowadays returns just the literal value like "Administrator" and not the translated value. You will need to use GetDescription instead. Please see my answer for a more straightforward implementation.Adiana
C
37

I solved the issue by creating a EnumExtension which I use in my view. This extension looks for a resource file called "EnumResources.resx" and looks up the resource by the following naming convention {Name of EnumType}_{Value of enum passed in}. If the resource key is missing it will display the value of the resource encapsulated within double brackets [[EnumValue]]. This way its easy to find a "untranslated" Enum in your view. Also this helps reminding you if you forgot to update the resource file after a rename or such.

public static class EnumExtensions
{
    public static string GetDisplayName(this Enum e)
    {
        var rm = new ResourceManager(typeof (EnumResources));
        var resourceDisplayName = rm.GetString(e.GetType().Name + "_" + e);

        return string.IsNullOrWhiteSpace(resourceDisplayName) ? string.Format("[[{0}]]", e) : resourceDisplayName;
    }
}

The resource file looks like this: Resource file

Usage:

<div>@ContractStatus.Created.GetDisplayName()</div>
Congest answered 19/3, 2014 at 12:57 Comment(6)
Looks great and simple, I like it. One question though, are all the enum's placed in one resource file? Is it possible to have multiple files too?Philipines
@Philipines you might be able to set up your ResourceManager using this: msdn.microsoft.com/en-us/library/yfsz7ac5(v=vs.110).aspx I have not tried it yet tho.Congest
@Philipines I tried it and i got it to work. You can replace the creation of the ResourceManager with something like this: var rm = new ResourceManager("MyAppNamespace.Web.Resources.EnumResources", typeof(EnumResources).Assembly); You do not need to use EnumResources in the typeof() statement, just use any class located in the same assembly as your resource files. Then all you have to do is resolve which resource file you want to use and set the first argument to that specific resource. Maybe you can use e.GetType().Name in someway?Congest
thanks for your convention over configuration based answer. Just one comment, you need to pass culture you want to match against here: var resourceDisplayName = rm.GetString(e.GetType().Name + "_" + e, EnumResources.Culture);Colucci
@Congest But when you get localized string and you want to convert it back to enum, you have to reverse this and traverse again all localizations resx.Einkorn
@mike00, yes that is correct, but I'm struggling to figure out when this would be a real scenario. Say for example if you have some kind of drop down you would still want to send the enum value back to the server instead of the translated display value of said enum!Congest
A
18

UPDATE 2019

Nowadays its just plain easy, setup your enum:

public enum ContactOptionType
{
    [Display(Description = "ContactOption1", ResourceType = typeof(Globalization.Contact))]
    Demo = 1,

    [Display(Description = "ContactOption2", ResourceType = typeof(Globalization.Contact))]
    Callback = 2,

    [Display(Description = "ContactOption3", ResourceType = typeof(Globalization.Contact))]
    Quotation = 3,

    [Display(Description = "ContactOption4", ResourceType = typeof(Globalization.Contact))]
    Other = 4
}

Each enum value gets a Display attribute with a Description value which is an entry in a resource assembly class called Globalization.Contact. This resource assembly (project) contains various translations for the different contact option types (Demo, Callback, Quotation, Other). It contains files like these: contact.nl.resx (for the Netherlands) and contact.resx (The default which is en-US) in which the different enum values have their localized values (translations).

Now in a static enum helper class we have this method:

public static string GetDisplayDescription(this Enum enumValue)
{
    return enumValue.GetType().GetMember(enumValue.ToString())
        .FirstOrDefault()?
        .GetCustomAttribute<DisplayAttribute>()
        .GetDescription() ?? "unknown";
}

This will get the value for the Description property of the Display attribute. Which will be the translated value if and only if the CurrentUICulture is set. This "glues" everything together.

Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");

or

Thread.CurrentThread.CurrentUICulture = new CultureInfo("nl-NL");

Now in a simple Unit Test (XUnit) we can see if it works as expected / desired / designed:

[Theory]
[InlineData("nl-NL", "Terugbelverzoek")]
[InlineData("en-US", "Callback")]
public void TestTranslationOfDescriptionAttribute(string culture, string expectedValue)
{
    // Arrange
    CultureInfo cultureInfo = new CultureInfo(culture);
    Thread.CurrentThread.CurrentCulture = cultureInfo;
    Thread.CurrentThread.CurrentUICulture = cultureInfo;

    ContactOptionType contactOptionType = ContactOptionType.Callback;

    // Act
    string description = contactOptionType.GetDisplayDescription();

    // Assert
    Assert.Equal(expectedValue, description);
}

The above will succeed effortlessly ✅🙋‍♂️

So this solution doesn't use a "complex" LocalizedAttribute anymore only a simple helper that gets the (translated) value of the Description property of the Display attribute. The presence of a ResourceType value in the Display attribute in combination with setting the CurrentUICulture does the trick.

Adiana answered 5/9, 2019 at 10:1 Comment(2)
For anyone getting errors like Custom tool PublicResXFileCodeGenerator failed to produce an output for input file: try to set only the main resource file like contact.resx to public code generation. Leave the others on no code generation.Paul
I don't know why I couldn't get this to work, but used a more pragmatic (and smaller) solution now without attributes: Just added a Enum extension to return the respective resx value (given the Enum name (not Display attribute) is the key in the resx file: static ResourceManager rM = appRes.ResourceManager; public static string GetDisplayName(this Enum enumValue) { return rM.GetString(enumValue.ToString()) ?? enumValue.ToString(); }Xenomorphic
K
14

There is a way of using attributes to specify a string to use for enums when displaying them, but we found it way too fiddly when you had to handle localization.

So what we usually do for enums that need to be localized is to write an extension class that provides a method to obtain the translated name. You can just use a switch that returns strings from the usual resources. That way, you provide translated strings for enums via the resources just like you do for other strings.

For example:

public enum Role
{
    Administrator,
    Moderator,
    Webmaster,
    Guest
}

public static class RoleExt
{
    public static string AsDisplayString(this Role role)
    {
        switch (role)
        {
            case Role.Administrator: return Resources.RoleAdministrator;
            case Role.Moderator:     return Resources.RoleModerator;
            case Role.Webmaster:     return Resources.RoleWebmaster;
            case Role.Guest:         return Resources.RoleGuest;

            default: throw new ArgumentOutOfRangeException("role");
        }
    }
}

Which you can use like this:

var role = Role.Administrator;
Console.WriteLine(role.AsDisplayString());

If you keep the RoleExt class implementation next to the enum Role implementation it will effectively become part of the interface for Role. Of course you could also add to this class any other useful extensions for the enum .

[EDIT]

If you want to handle multiple flags settings ("Administrator AND Moderator AND Webmaster") then you need to do things a little differently:

[Flags]
public enum Roles
{
    None          = 0,
    Administrator = 1,
    Moderator     = 2,
    Webmaster     = 4,
    Guest         = 8
}

public static class RolesExt
{
    public static string AsDisplayString(this Roles roles)
    {
        if (roles == 0)
            return Resources.RoleNone;

        var result = new StringBuilder();

        if ((roles & Roles.Administrator) != 0)
            result.Append(Resources.RoleAdministrator + " ");

        if ((roles & Roles.Moderator) != 0)
            result.Append(Resources.RoleModerator + " ");

        if ((roles & Roles.Webmaster) != 0)
            result.Append(Resources.RoleWebmaster + " ");

        if ((roles & Roles.Guest) != 0)
            result.Append(Resources.RoleGuest + " ");

        return result.ToString().TrimEnd();
    }
}

Which you might use like this:

Roles roles = Roles.Administrator | Roles.Guest | Roles.Moderator;
Console.WriteLine(roles.AsDisplayString());

Resource Files

Resource files are the way that you internationalize your strings. For more information on how to use them, see here:

http://msdn.microsoft.com/en-us/library/vstudio/aa992030%28v=vs.100%29.aspx http://msdn.microsoft.com/en-us/library/vstudio/756hydy4%28v=vs.100%29.aspx

Kinna answered 29/6, 2013 at 14:28 Comment(5)
How exactly does this work with resource files? And i need it to use it in a listbox where multiple selections are possible.Philipines
@Philipines It works just like all other strings in resource files - how are you handling all your other translated strings? I assume you're using the built-in resource file handling, with resource files named for the language that they are for. If you want to allow multiple selections, then that is handled by making the enum values powers of two so you can OR them together. I'll update the code to show how to create a string for multiple values.Kinna
I handle the other strings with an extension called modelmetadata which can be found here: github.com/Code52/internationalization-mvc4/wiki/… But it seems that this doesnt work for enums.Philipines
@Philipines But you must have the strings themselves in resource files, yes?Kinna
yes I have Key/String in the resource files. Right now, just 2 languages One is called Language.resx and the other one is called Language.nl.resx. I edited my questionPhilipines
D
2

A version of @eluxen's answer working for certain portable (PCL) libraries (specifically for Profile47) where original solution won't work. Two problems were addressed: DescriptionAttribute is not available in portable libraries, and problem reported by @Jitendra Pancholi with "Could not find any resources" error is solved

public class LocalizedDescriptionAttribute : Attribute
{
    private readonly string _resourceKey;
    private readonly Type _resourceType;
    public LocalizedDescriptionAttribute(string resourceKey, Type resourceType)
    {
        _resourceType = resourceType;
        _resourceKey = resourceKey;
    }

    public string Description
    {
        get
        {
            string displayName = String.Empty;
            ResourceManager resMan = _resourceType.GetProperty(
                @"ResourceManager", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(null, null) as ResourceManager;
            CultureInfo culture = _resourceType.GetProperty(
                    @"Culture", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).GetValue(null, null) as CultureInfo;

            if (resMan != null)
            {
                displayName = resMan.GetString(_resourceKey, culture);
            }

            var ret = string.IsNullOrEmpty(displayName) ? string.Format("[[{0}]]", _resourceKey) : displayName;
            return ret;
        }
    }
}

For usage see the original answer. And if you are not encountering any problems, I would still use the original answer because it does not contain workarounds through reflection

Discombobulate answered 22/3, 2016 at 18:21 Comment(0)
E
1

You can use your enum value to do what you want.

public enum Roles
{
    Administrator = 0,
    Moderator = 1 ,
    Webmaster  = 2,
    Guest = 3 ,
    Etc.... = 4 
}

When you want to get the selected enum on the listbox, you retrieve the listbox item and then you retrieve the associated enum number.

Then you will convert that to an enum item like this

Roles myrol = (Roles) i

(i is associated int vale for this example)

Converting enum item to Integer and integer value back to enum item

 Enum Item to Integer----- 
    int i =  (int)Roles.Admin ;
    Integer to enum Itenm
    Roles r = (Roles)i ; 

     //Getting the name of the enum
     string role =  Role.Admin.ToString() 

IF you are adding to a Hashtable then you can do it this way

Hashtable h = new Hashtable() ; 
h.Items.Add((int)Roles.Admin , Roles.Admin.ToStrinng() ) ;
h.Items.Add((int)Roles.Local , Roles.Local.ToStrinng() ) ; 

when you pick an item from the hashtable , convert it back to Enum item and use it where you want. You can use the same way to populate Datatables / Comboboxes , dropdown lists and so on

Earthaearthborn answered 29/6, 2013 at 14:4 Comment(7)
how will that translate the enum for example to the language Dutch?Philipines
What you want to do is , to Translate enum item ba another type and vice versa, Right ? Enum iem to > Integer Integer > enum itemEarthaearthborn
No, something like this: ruijarimba.wordpress.com/2012/02/17/…Philipines
You want to add items to a Listbox , and then you need to pick up correct enum item or items when you select listbox items , right ?Earthaearthborn
Yea, where multiple options are selectable. A user can have multiple roles.Philipines
Enable multiselect in your Listbox , and the retrive accociated enum numbers of each selected item , then you can use all selected roles in your codeEarthaearthborn
The link i pasted shows that the enums are decorated with an attribute. Which in turn looks for the key in the resource file for the correct translation. How does what you're saying translate the enums in a different language? Aren't attributes needed? And isn't it true that enums by default don't 'read' their attributes?Philipines
M
1

The extension method of bellow it's worked to me.

    public static string GetDisplayValue(this Enum value)
    {
        try
        {
            var fieldInfo = value.GetType().GetField(value.ToString());
            var descriptionAttributes = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[];

            if (descriptionAttributes == null || descriptionAttributes.Length == 0) return value.ToString();

            if (descriptionAttributes[0].ResourceType != null)
            {
                var resource = descriptionAttributes[0].ResourceType.GetProperty("ResourceManager").GetValue(null) as ResourceManager;
                return resource.GetString(descriptionAttributes[0].Name);
            }
            else
            {
                return descriptionAttributes[0].Name;
            }
        }
        catch
        {
            return value.ToString();
        }
    }

I holpe helps.

Mandie answered 27/1, 2016 at 16:48 Comment(0)
P
1

One of the problems with translating/localizing enums is that not only do you have to translate them to show, but also parse the translations back to the enum value. The following C# file contains how I overcame the issues of two way translations of enums. Pardon the excessive comments, but I do get rather verbose on my creations.

//
// EnumExtensions.cs  
//
using System;
using System.Collections.Generic;

namespace EnumExtensionsLibrary
{
    /// <summary>
    /// The functions in this class use the localized strings in the resources
    /// to translate the enum value when output to the UI and reverse--
    /// translate when receiving input from the UI to store as the actual
    /// enum value.
    /// </summary>
    /// 
    /// <Note>
    /// Some of the exported functions assume that the ParserEnumLocalizationHolder
    /// and ToStringEnumLocalizationHolder dictionaries (maps) may contain the enum 
    /// types since they callthe Initialize methods with the input type before executing.
    /// </Note>
    public static class EnumExtensions
    {
        #region Exported methods
        /// <summary>
        /// Save resource from calling project so that we can look up enums as needed.
        /// </summary>
        /// <param name="resourceManager">Where we fish the translated strings from</param>
        /// <remarks>
        /// We do not have access to all of the resources from the other projects directly,
        /// so they must be loaded from the code from within the project.
        /// </remarks>
        public static void RegisterResource(System.Resources.ResourceManager resourceManager)
        {
            if (!MapOfResourceManagers.Contains(resourceManager))
                MapOfResourceManagers.Add(resourceManager);
        }

        /// <summary>
        /// Parses the localized string value of the enum by mapping it 
        /// to the saved enum value
        /// </summary>
        /// <remarks>
        /// In some cases, string for enums in the applications may not be translated to the
        /// localized version (usually when the program presets parameters).  If the enumMap
        /// doesn't contain the value string, we call to Enum.Parse() to handle the conversion
        /// or throw an exception.
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <exception cref="ArgumentNullException"> enumType or value is null.</exception>
        /// <exception cref="ArgumentException"> enumType is not an Enum. value is either an 
        /// empty string or only contains white space, value is a name, but not one of the 
        /// named constants defined for the enumeration.</exception>
        /// <exception cref="ArgumentNullException">enumType or value is null.</exception>
        /// <returns>
        /// The enum value that matched the input string if found.  If not found, we call 
        /// Enum.Parse to handle the value.
        /// </returns>
        public static T ParseEnum<T>(this string value) where T : struct
        {
            ParserInitialize(typeof(T));
            var enumMap = ParserEnumLocalizationHolder[typeof(T)];
            if (enumMap.ContainsKey(value))
                return (T) enumMap[value];
            return (T)Enum.Parse(typeof(T), value); 
        }

        /// <summary>
        /// Parses the localized string value of the enum by mapping it 
        /// to the saved enum value.  
        /// </summary>
        /// <remarks>
        /// In some cases, string for enums in the applications may not be translated to the
        /// localized version (usually when the program presets parameters).  If the enumMap
        /// doesn't contain the value string, we call to Enum.TryParse() to handle the 
        /// conversion. and return.
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="result"></param>
        /// <returns>
        /// Returns true if the enum mapping contains the localized string value and the data 
        /// in the returned result parameter will be a valid value of that enum type. if the
        /// string value is not mapped, then calls Enum.TryParse to handle the conversion and 
        /// return result.
        /// </returns>
        public static bool TryParseEnum<T>(this string value, out T result) where T : struct
        {
            ParserInitialize(typeof(T));
            var enumMap = ParserEnumLocalizationHolder[typeof(T)];
            if (!enumMap.ContainsKey(value))
                return Enum.TryParse(value, out result);
            result = (T)enumMap[value];
            return true;
        }

        /// <summary>
        /// Converts the enum value to a localized string.
        /// </summary>
        /// <typeparam name="T">must be an enum to work</typeparam>
        /// <param name="value">is an enum</param>
        /// <returns>
        /// The localized string equivalent of the input enum value
        /// </returns>
        public static string EnumToString<T>(this T value) where T : struct
        {
            ToStringInitialize(typeof(T));
            var toStringMap = ToStringEnumLocalizationHolder[typeof(T)];
            return toStringMap.ContainsKey(value) ? toStringMap[value] : value.ToString();

            //return EnumDescription(value);
        }

        /// <summary>
        /// Gathers all of the localized translations for each 
        /// value of the input enum type into an array
        /// </summary>
        /// <remarks>
        /// The return array from Type.GetEnumValues(), the array elements are sorted by 
        /// the binary values (that is, the unsigned values) of the enumeration constants.
        /// </remarks>
        /// <param name="enumType"></param>
        /// <exception cref="ArgumentException"> The current type is not an enumeration.</exception>
        /// <returns>
        /// A string array with the localized strings representing
        /// each of the values of the input enumType.
        /// </returns>
        public static string[] AllDescription(this Type enumType)
        {
            ToStringInitialize(enumType);
            var descriptions = new List<string>();
            var values = enumType.GetEnumValues();
            var toStringMap = ToStringEnumLocalizationHolder[enumType];
            foreach (var value in values)
            {
                descriptions.Add(toStringMap.ContainsKey(value) ? toStringMap[value] : value.ToString());
            }
            return descriptions.ToArray();
        }
        #endregion

        #region Helper methods
        /// <summary>
        /// Translates an enum value into its localized string equivalent
        /// </summary>
        /// <remarks>
        /// This assumes that the "name" for the localized string in the 
        /// resources will look like "enum-type-name""value".  For example,  
        /// if I have an enum setup as:
        /// 
        ///     enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
        /// 
        /// the value "Sun" in the enum must have the name: "DaysSun"
        /// in the resources. The localized (translated) string will
        /// be in the value field.  E.g.,
        ///
        ///  <data name="DaysSun" xml:space="preserve">
        /// <value>Sunday</value>
        ///  </data>    
        /// 
        /// 2nd note: there may be multiple resources to pull from.
        /// Will look in each resource until we find a match or 
        /// return null.
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumType">the enum type</param>
        /// <param name="value">the specific enum value</param>
        /// <returns>
        /// If the enum value is found in the resources, then return 
        /// that string.  If not, then return null. 
        /// </returns>
        private static string LocalEnumDescription<T>(Type enumType, T value)
        {
            foreach (var resourceManager in MapOfResourceManagers)
            {
                // The following live line uses string interpolation to perform:
                //var rk = string.Format("{0}{1}", enumType.Name, value);
                var rk = $"{enumType.Name}{value}";

                // Given the above string formatting/interpolation, neither the enum.Name 
                // nor the value will have a '.' so we do not have to remove it.
                var result = resourceManager.GetString(rk);
                if (!string.IsNullOrEmpty(result))
                    return result;
            }
            return null;
        }

        /// <summary>
        /// Initializes the mapping of the enum type to its mapping of localized strings to 
        /// the enum's values.
        /// </summary>
        /// <remarks>
        /// The reason for each enum type to have a mapping from the localized string back 
        /// to its values is for ParseEnum and TryParseEnum to quickly return a value rather
        /// than doing a lengthy loop comparing each value in the resources.
        /// 
        /// Also, we only map the corresponding resource string if it exists in the resources.
        /// If not in the resources, then we call the Enum methods Parse() and TryParse() to
        /// figure the results and throw the appropriate exception as needed.
        /// </remarks>
        /// 
        /// <param name="enumType"></param>
        private static void ParserInitialize(Type enumType)
        {
            if (!ParserEnumLocalizationHolder.ContainsKey(enumType))
            {
                var values = enumType.GetEnumValues();  // See remark for AllDescription().
                var enumMap = new Dictionary<string, object>();
                foreach (var value in values)
                {
                    var description = LocalEnumDescription(enumType, value);
                    if (description != null)
                        enumMap[description] = value;
                }
                ParserEnumLocalizationHolder[enumType] = enumMap;
            }
        }

        /// <summary>
        /// Initializes the mapping of the enum type to its mapping of the enum's values
        /// to their localized strings.
        /// </summary>
        /// <remarks>
        /// The reason for each enum type to have a mapping from the localized string to its
        /// values is for AllDescription and EnumToString to quickly return a value rather 
        /// than doing a lengthy loop runing through each of the resources.
        /// 
        /// Also, we only map the corresponding resource string if it exists in the resources.
        /// See the EnumDescription method for more information.
        /// </remarks>
        /// 
        /// <param name="enumType"></param>
        private static void ToStringInitialize(Type enumType)
        {
            if (!ToStringEnumLocalizationHolder.ContainsKey(enumType))
            {
                var values = enumType.GetEnumValues();  // See remark for AllDescription().
                var enumMap = new Dictionary<object, string>();
                foreach (var value in values)
                {
                    var description = LocalEnumDescription(enumType, value);
                    if (description != null)
                        enumMap[value] = description;
                }
                ToStringEnumLocalizationHolder[enumType] = enumMap;
            }
        }
        #endregion

        #region Data
        private static readonly List<System.Resources.ResourceManager> MapOfResourceManagers =
            new List<System.Resources.ResourceManager>();
        private static readonly Dictionary<Type, Dictionary<string, object>> ParserEnumLocalizationHolder =
            new Dictionary<Type, Dictionary<string, object>>();
        private static readonly Dictionary<Type, Dictionary<object, string>> ToStringEnumLocalizationHolder =
            new Dictionary<Type, Dictionary<object, string>>();
        #endregion
    }
}

This doesn't require an attribute ahead of each enum value but does require that the name attribute of your translated enum string in the resources is formatted such that it is a concatenation of the enum-type-name and enum value. See the comment above the LocalEnumDescription method for more information. In addition, it preserves the translations of the enums (both forwards and backwards) by mapping their translations such that we don't need to search for the translation each time we encounter an enum value.

Hopefully, it is easy enough to understand and use.

Principate answered 26/8, 2016 at 22:26 Comment(1)
could you provide snippet code how to use it? it look great but i dont know how to use it !Hazeghi
F
1

You could use Lexical.Localization¹ which allows embedding default value and culture specific values into the code, and be expanded in external localization files (like .json, .resx or .ini) for futher cultures.

namespace Library
{
    enum Permissions
    {
        Create = 1,
        Drop = 2,
        Modify = 4,
        GrantCreate = 8,
        GrantDrop = 16,
        GrantModify = 32
    }
}

In Program code:

// Load localization.ini
LineRoot.Builder.AddLocalizationFile("localization.ini").Build();
// Create key for enum
ILine key = LineRoot.Global.Assembly("ConsoleApp4").Type<Permissions>().Format("{0}");
// Print 
Console.WriteLine(key.Value(Permissions.Create | Permissions.Drop));
Console.WriteLine(key.Value(Permissions.Create | Permissions.Drop).Culture("en"));
Console.WriteLine(key.Value(Permissions.Create | Permissions.Drop).Culture("fi"));

localization.ini:

[Assembly:ConsoleApp4:Type:Library.Permissions:Culture:fi]
Key:Create = Luonti
Key:Drop = Poisto
Key:Modify = Muutos
Key:GrantCreate = Luonnin myöntö
Key:GrantDrop = Poiston myöntö
Key:GrantModify = Muutoksen myöntö

[Assembly:ConsoleApp4:Type:Library.Permissions:Culture:en]
Key:Create = Create
Key:Drop = Drop
Key:Modify = Modify
Key:GrantCreate = Grant Create
Key:GrantDrop = Grant Drop
Key:GrantModify = Grant Modify 

¹

Fenwick answered 10/6, 2019 at 16:46 Comment(0)
C
0

Yet another possibility is to create a global display name storage as class extension over Enum class:

// Enum display names
public static class EnumDisplayNames {
  // Display name storage
  private static Dictionary<Type, Dictionary<Enum, String>> s_Names = 
    new Dictionary<Type, Dictionary<Enum, String>>();

  // Display name for the single enum's option
  private static String CoreAsDisplayName(Enum value) {
    Dictionary<Enum, String> dict = null;

    if (s_Names.TryGetValue(value.GetType(), out dict)) {
      String result = null;

      if (dict.TryGetValue(value, out result))
        return result;
      else
        return Enum.GetName(value.GetType(), value);
    }
    else
      return Enum.GetName(value.GetType(), value);
  }

  // Register new display name
  public static void RegisterDisplayName(this Enum value, String name) {
    Dictionary<Enum, String> dict = null;

    if (!s_Names.TryGetValue(value.GetType(), out dict)) {
      dict = new Dictionary<Enum, String>();

      s_Names.Add(value.GetType(), dict);
    }

    if (dict.ContainsKey(value))
      dict[value] = name;
    else
      dict.Add(value, name);
  }

  // Get display name
  public static String AsDisplayName(this Enum value) {
    Type tp = value.GetType();

    // If enum hasn't Flags attribute, just put vaue's name 
    if (Object.ReferenceEquals(null, Attribute.GetCustomAttribute(tp, typeof(FlagsAttribute))))
      return CoreAsDisplayName(value);

    // If enum has Flags attribute, enumerate all set options
    Array items = Enum.GetValues(tp);

    StringBuilder Sb = new StringBuilder();

    foreach (var it in items) {
      Enum item = (Enum) it;

      if (Object.Equals(item, Enum.ToObject(tp, 0)))
        continue;

      if (value.HasFlag(item)) {
        if (Sb.Length > 0)
          Sb.Append(", ");
        Sb.Append(CoreAsDisplayName(item));
      }
    }

    Sb.Insert(0, '[');

    Sb.Append(']');

    return Sb.ToString();
  }
}

Possible use is very simple, for instance:

  public enum TestEnum {
    None,
    One,
    Two,
    Three
  }

  [Flags]
  public enum TestOptions {
    None = 0,
    One = 1,
    Two = 2,
    Three = 4
  }

  ...

  // Let them be in German (for demonstration only)... 
  TestEnum.None.RegisterDisplayName("Nichts");
  TestEnum.One.RegisterDisplayName("Eins");
  TestEnum.Two.RegisterDisplayName("Zwei");
  TestEnum.Three.RegisterDisplayName("Drei");

  // Usually, you obtain display names from resources:
  // TestEnum.None.RegisterDisplayName(Resources.NoneName);
  // ...

  TestOptions.None.RegisterDisplayName("-");
  TestOptions.One.RegisterDisplayName("bit 0 set");
  TestOptions.Two.RegisterDisplayName("bit 1 set");
  TestOptions.Three.RegisterDisplayName("bit 2 set");
  TestOptions.Four.RegisterDisplayName("bit 3 set");

  ...

  TestEnum v = TestEnum.Two;
  String result = v.AsDisplayName(); // <- "Zwei"

  TestOptions o = TestOptions.One | TestOptions.Three | TestOptions.Four;
  String result2 = o.AsDisplayName(); // <- "[bit 0 set, bit 2 set, bit 3 set]"
Countryandwestern answered 29/6, 2013 at 18:2 Comment(2)
So this loads all languages in a variable and then picks it up depending on the language set. Am i right?Philipines
Not actually. This loads all enums (not languages) that have display names into one variable.Countryandwestern
E
0
public enum RoleEnum
{
    Administrator = 4,
    Official = 1,
    Trader = 3,
    HeadOfOffice = 2
}
public static class RoleEnumExtension
{
    private static readonly ResourceManager Resource =
        new ResourceManager("Project.CommonResource", typeof(CommonResource).Assembly);

    public static string Display(this RoleEnum role)
    {
        return Resource.GetString("RoleType_" + role);
    }
}

You can use this as

RoleEnum.Administrator.Display()

Hope this will help to someone

Etymon answered 4/3, 2015 at 10:3 Comment(0)
S
0

Same answer as accepted answer but without code analysis warnings

 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments", Justification ="Resourcetype is only needed to instantiate Resource manager, and ResourceManager is exposed")]
[AttributeUsage(AttributeTargets.All)]
public sealed class LocalizedDescriptionAttribute 
    : DescriptionAttribute
{
    private readonly string _resourceKey;
    private readonly ResourceManager _resourceManager;

    public LocalizedDescriptionAttribute(string resourceKey, Type resourceType)
    {
        _resourceManager = new ResourceManager(resourceType);
        _resourceKey = resourceKey;
    }

    public string ResourceKey
    {
        get { return _resourceKey; }
    }

    public ResourceManager ResourceManager
    {
        get { return _resourceManager; }
    }

    public override string Description
    {
        get
        {
            string displayName = _resourceManager.GetString(_resourceKey);
            return string.IsNullOrEmpty(displayName)? string.Format(CultureInfo.CurrentUICulture ,"[[{0}]]", _resourceKey) : displayName;
        }
    }
}

public static class EnumExtensions
{
    public static string GetDescription(this Enum enumValue)
    {
        if (enumValue == null)
        {
            throw new ArgumentNullException("enumValue");
        }
        FieldInfo fi = enumValue.GetType().GetField(enumValue.ToString());

        DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

        if (attributes != null && attributes.Length > 0)
        {
            return attributes[0].Description;
        }
        else
        {
            return enumValue.ToString();
        }
    }
}
Scurrility answered 4/7, 2016 at 11:27 Comment(0)
G
0

The solutions above with the LocalizedDescriptionAttribute read it from the current language of the program. Perfect for a client, not so flexible for a server side when you want to pass the language as parameter.

So I extended eluxen solution with the LocalizedDescriptionAttribute by adding another method:

    /// <summary>
    /// Gets the description, stored in this attribute, reading from the resource using the cultureInfo defined by the language!
    /// </summary>
    /// <param name="language">The language.</param>
    /// <returns>Description for the given language if found; the default Description or ressourceKey otherwise</returns>
    public string GetDescription(string language)
    {
        return resource.GetStringFromResourceForLanguage(resourceKey, language, Description);
    }

    public static string GetStringFromResourceForLanguage(this ResourceManager resourceManager, string resourceKey, string language, string defaultValue = null)
    {
        if (string.IsNullOrEmpty(defaultValue))
            defaultValue = resourceKey;
        try
        {
            CultureInfo culture = CultureInfo.GetCultureInfo(language);
            string displayName = resourceManager.GetString(resourceKey, culture);
            return !string.IsNullOrEmpty(displayName) ? displayName : defaultValue;
        }
        catch // any, not only CultureNotFoundException
        {
            return defaultValue;
        }
    }

With the GetDescription extension also with the language parameter:

            bool hasLanguage = !string.IsNullOrEmpty(language);
            if (hasLanguage)
            {
                var attribute = (LocalizedDescriptionAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(LocalizedDescriptionAttribute));
                if (attribute != null)
                {
                    description = attribute.GetDescription(language);
                }
                else
                    hasLanguage = false;
            }
            if (!hasLanguage)
            {
                var attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute));
                if (attribute != null)
                {
                    description = attribute.Description;
                }
            }

And eventually, I prefer to avoid any string in the attribue usage, using nameof.

public enum QualityPersonInfo
{
    Ok = 0,
    [LocalizedDescription(nameof(QualityStrings.P1), typeof(QualityStrings))]
    Duplicate = 1,
    [LocalizedDescription(nameof(QualityStrings.P2), typeof(QualityStrings))]
    IdMissing = 2,
}

which I use in my code as follow:

 QualityPersonInfo status = QualityPersonInfo.IdMissing; 
 var description = status.GetDescription("ch-DE");
Gleeful answered 16/3, 2017 at 16:12 Comment(0)
N
0

I use this currently, hope it helps!:

    /// <summary>
    ///  Retrieves a translated value from an enumerated list.
    /// </summary>
    /// <param name="value">Enum</param>
    /// <param name="resource">string</param>
    /// <returns>string</returns>
    protected string GetTranslatedEnum(Enum value, string resource)
    {
        string path = String.Format("Resources.{0}", resource);

        ResourceManager resources = new ResourceManager(path, global::System.Reflection.Assembly.Load("App_GlobalResources"));

        if (resources != null) {
            return resources.GetString(value.ToString());
        } else {
            return value.ToString();
        }
    }

Created a .resx file named "App_GlobalResources\ProductNames.resx".

Usage:

// Convert the ProductId integer on the item to its Enum equivalent.
Products product = (Products) item.ProductId;

string productName = this.GetTranslatedEnum(product, "ProductNames");
Nailbrush answered 26/6, 2018 at 19:45 Comment(0)
R
0

I have used the accepted answer but changed it little bit. It is shorter and doesn't have custom class.

This is my enum. All items have DisplayAttribute

public enum OvertimeRequestedProvisionFor
{
    [Display(ResourceType = typeof(Localization), Name = LocalizationKeys.General_Fee)]
    Fee = 1,

    [Display(ResourceType = typeof(Localization), Name = LocalizationKeys.General_Permit)]
    Permit = 2,
}

And this is the extension method

public static string GetDisplayName(this Enum enumValue)
{
    var fi = enumValue.GetType().GetField(enumValue.ToString());

    var attributes = (DisplayAttribute[])fi.GetCustomAttributes(typeof(DisplayAttribute), false);

    return attributes != null && attributes.Length > 0
            ? attributes[0].GetName()
            : enumValue.ToString();
}

Now all have to do:

var localization = OvertimeRequestedProvisionFor.Fee.GetDisplayName();
Rating answered 17/6, 2020 at 20:50 Comment(0)
H
0

isnt the answer to the question but if someone looking for get Enum by resource value (already traslated)...

    public static T GetEnumIntByResource<T>(string valueResource)
    {
        var enumType = typeof(T);
        var enumRet = enumType;

    if (enumType == typeof(Enum))
            throw new ArgumentException("typeof(TEnum) == System.Enum", "T");

        if (!(enumType.IsEnum))
            throw new ArgumentException(String.Format("typeof({0}).IsEnum == false", enumType), "T");

        var list = Enum.GetValues(enumType).OfType<T>().ToList();
            
        foreach(T value in list)
        {
            string traduccion = GetDisplayDescription(value as Enum);
            if(traduccion == valueResource)
            {
                return value;
            }
        }
        return default(T);
    }
Hollerman answered 4/5, 2022 at 14:52 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Corves
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewMarlinemarlinespike
L
0

I think the resource file solution is very useful. But for those who don't want to deal with resource files (for example just 2 different languages used in the project), I implemented a solution only using an extension method and standard Description attributes. Description of enums are written by seperating localized descriptions with a special character and the description is tokenized using the split method in the extension method so the appropriate value of the resulting array is returned.

Example enum;

public enum Organs
{
    [Description("Dirsek#Elbow")]
    Dirsek = 1,
    [Description("El#Hand")]
    El = 2,
    [Description("Kol#Arm")]        
    Kol = 3
}

Extension method with a boolean parameter;

public static string GetEnumDescription(this Enum e, bool isTurkish)
{
    var index = 0;
    if (!isTurkish)
        index = 1;
    var descriptionAttribute = e.GetType().GetMember(e.ToString())[0]
        .GetCustomAttributes(typeof(DescriptionAttribute), inherit: false)[0]
                as DescriptionAttribute;

    return descriptionAttribute.Description.Split("#")[index];
}

Call;

((Organs)item.Organ).GetEnumDescription(IsTurkish)
Lytta answered 22/11, 2022 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.