Подтвердить что ты не робот

Локализация Enum

Как вы локализуете перечисления для ListBoxFor, где возможны несколько вариантов?

Например, enum, который содержит роли:

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,
}

Я видел это с помощью dropdownlist/selectlists. Но есть ли способ сделать это для нескольких избранных списков?

[EDIT]

Вот как я хотел бы использовать его, вот как он работает сейчас, но не переводится на другой язык:

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");

Примечание. Я переименовал enum из Role в RoleType.

4b9b3361

Ответ 1

Вы можете реализовать атрибут описания.

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();
    }
}

Определите это следующим образом:

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

И используйте его следующим образом:

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");

Ответ 2

Я решил проблему, создав EnumExtension, который я использую на мой взгляд. Это расширение ищет файл ресурсов с именем "EnumResources.resx" и ищет ресурс по следующему соглашению об именах {Name of EnumType} _ {Значение перечислимого числа, переданного в}. Если ключ ресурса отсутствует, будет отображаться значение ресурса, заключенного в двойные скобки [[EnumValue]]. Таким образом, легко найти "нетранслируемое" перечисление на ваш взгляд. Также это поможет вам вспомнить, если вы забыли обновить файл ресурсов после переименования или такого.

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;
    }
}

Файл ресурсов выглядит следующим образом: Resource file

Использование:

<div>@ContractStatus.Created.GetDisplayName()</div>

Ответ 3

Существует способ использования атрибутов для указания строки, используемой для перечислений при их отображении, но мы обнаружили, что это слишком сложно, когда вам приходилось обрабатывать локализацию.

Итак, что мы обычно делаем для перечислений, которые должны быть локализованы, это написать класс расширения, который предоставляет метод для получения переведенного имени. Вы можете просто использовать переключатель, который возвращает строки из обычных ресурсов. Таким образом, вы предоставляете переведенные строки для перечислений через ресурсы, как и для других строк.

Например:

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");
        }
    }
}

Что вы можете использовать следующим образом:

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

Если вы сохраните реализацию класса RoleExt рядом с реализацией enum Role, она станет фактически частью интерфейса для Role. Конечно, вы также можете добавить к этому классу любые другие полезные расширения для перечисления.

[EDIT]

Если вы хотите обрабатывать несколько параметров флажка ( "Администратор и модератор и веб-мастер" ), вам нужно сделать что-то по-другому:

[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();
    }
}

Что вы можете использовать следующим образом:

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

Файлы ресурсов

Файлы ресурсов - это то, как вы интернационализируете свои строки. Подробнее о том, как их использовать, см. Здесь:

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

Ответ 4

Вы можете использовать значение enum для выполнения того, что хотите.

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

Если вы хотите получить выбранное перечисление в списке, вы извлекаете элемент списка и затем получаете связанный номер перечисления.

Затем вы преобразуете это в элемент перечисления, подобный этому

Roles myrol = (Roles) i

(i ассоциирован с int vale для этого примера)

Преобразование элемента перечисления в целое и целочисленное значение обратно в элемент перечисления

 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() 

ЕСЛИ вы добавляете в Hashtable, тогда вы можете сделать это таким образом

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

когда вы выбираете элемент из хеш-таблицы, конвертируйте его обратно в элемент Enum и используйте его там, где хотите. Вы можете использовать тот же способ для заполнения Datatables/Comboboxes, выпадающих списков и т.д.

Ответ 5

Метод расширения, который он работал, работал со мной.

    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();
        }
    }

Я хочу помочь.

Ответ 6

Версия @eluxen answer работает для некоторых переносимых (PCL) библиотек (специально для Profile47), где оригинальное решение не будет работать. Были рассмотрены две проблемы: DescriptionAttribute недоступен в портативных библиотеках, и проблема, о которой сообщает @Jitendra Pancholi с ошибкой "Не удалось найти какие-либо ресурсы", решается

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;
        }
    }
}

Для использования см. исходный ответ. И если вы не сталкиваетесь с какими-либо проблемами, я все равно буду использовать оригинальный ответ, потому что он не содержит обходных решений через отражение

Ответ 7

Одна из проблем с переводом/локализацией перечислений состоит в том, что вам нужно не только перевести их для показа, но и проанализировать переводы обратно на значение перечисления. Следующий файл С# содержит то, как я преодолел проблемы двухсторонних переводов перечислений. Простите чрезмерные комментарии, но я действительно довольно подробно расскажу о своих творениях.

//
// 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 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 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
    }
}

Это не требует атрибута перед каждым значением перечисления, но требует, чтобы атрибут имени вашей переведенной строки перечисления в ресурсах был отформатирован таким образом, что он является конкатенацией имени перечисления и имени перечисления. См. Комментарий выше метода LocalEnumDescription для получения дополнительной информации. Кроме того, он сохраняет переводы перечислений (как вперед, так и назад) путем сопоставления их переводов таким образом, что нам не нужно искать перевод каждый раз, когда мы сталкиваемся с значением перечисления.

Надеюсь, это легко понять и использовать.

Ответ 8

Еще одна возможность заключается в создании глобального хранилища отображаемых имен в качестве расширения класса над классом Enum:

// 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 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 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();
  }
}

Возможное использование очень просто, например:

  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]"

Ответ 9

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);
    }
}

Вы можете использовать это как

RoleEnum.Administrator.Display()

Надеюсь, это поможет кому-то.

Ответ 10

Тот же ответ, что и принятый ответ, но без предупреждений анализа кода

 [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();
        }
    }
}

Ответ 11

Вышеупомянутые решения с LocalizedDescriptionAttribute читают его с текущего языка программы. Идеально подходит для клиента, не столь гибкого для серверной части, когда вы хотите передать язык в качестве параметра.

Итак, я расширил решение eluxen с помощью LocalizedDescriptionAttribute, добавив другой метод:

    /// <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;
        }
    }

С расширением GetDescription также с параметром языка:

            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;
                }
            }

И в конце концов, я предпочитаю избегать любой строки в использовании атрибута, используя nameof.

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

который я использую в своем коде следующим образом:

 QualityPersonInfo status = QualityPersonInfo.IdMissing; 
 var description = status.GetDescription("ch-DE");

Ответ 12

Я использую это в настоящее время, надеюсь, это поможет!

    /// <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();
        }
    }

Создан файл .resx с именем " App_GlobalResources\ProductNames.resx ".

Использование:

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

string productName = this.GetTranslatedEnum(product, "ProductNames");

Ответ 13

Вы можете использовать Lexical.Localization ¹, который позволяет встраивать значения по умолчанию и значения, специфичные для культуры, в код и расширяться во внешних файлах локализации (таких как .json,.resx или .ini) для более поздних культур.

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

В программном коде:

// 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 

¹ (я хранитель этой библиотеки)

Ответ 14

ОБНОВЛЕНИЕ 2019

В настоящее время просто просто, настройте перечисление:

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
}

Каждое значение перечисления получает Display attribute со значением Description, которое является записью в классе сборки ресурсов с именем Globalization.Contact. Эта сборка ресурсов (проект) содержит различные переводы для различных типов опций контакта (демонстрация, обратный вызов, предложение, другое). Он содержит файлы, подобные этим: contact.nl.resx (для Нидерландов) и contact.resx (по умолчанию это en-US), в которых различные значения enum имеют свои локализованные значения (переводы).

Теперь в статическом вспомогательном классе enum у нас есть этот метод:

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

Это позволит получить значение для свойства Description атрибута Display. Который будет переведенным значением тогда и только тогда, когда установлен CurrentUICulture. Это "склеивает" все вместе.

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

или

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

Теперь в простом модульном тесте (XUnit) мы можем увидеть, работает ли он должным образом/желаемый/разработанный:

[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);
}

Все вышеперечисленное пройдет без усилий ✅🙋‍♂️

Таким образом, это решение больше не использует "сложный" LocalizedAttribute только простой помощник, который получает (переведенное) значение свойства Description атрибута Display. Наличие значения ResourceType в атрибуте Display в сочетании с настройкой CurrentUICulture решает проблему.