Enum localization

后端 未结 15 593
慢半拍i
慢半拍i 2020-11-30 17:56

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

For example an enum that contains roles:

pu         


        
相关标签:
15条回答
  • 2020-11-30 18:08

    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");
    
    0 讨论(0)
  • 2020-11-30 18:08

    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();
    
    0 讨论(0)
  • 2020-11-30 18:09

    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]"
    
    0 讨论(0)
  • 2020-11-30 18:11
    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

    0 讨论(0)
  • 2020-11-30 18:12

    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.

    0 讨论(0)
  • 2020-11-30 18:15

    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

    0 讨论(0)
提交回复
热议问题