枚举本地化
你如何本地化一个ListBoxFor
枚举可以有多个选项?
例如,包含angular色的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
。 但有没有办法做到这一点,多select列表?
[编辑]
这就是我想如何使用它,现在它是如何工作的,但不会用另一种语言翻译:
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");
注意:我已经将angular色的angular色重新命名为RoleType。
您可以实现描述属性。
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");
我通过创build一个在我看来使用的EnumExtension来解决这个问题。 此扩展查找名为“EnumResources.resx”的资源文件,并通过以下命名约定{EnumType的名称} _ {传入的枚举值}查找资源。 如果资源键缺失,将显示封装在双括号[[EnumValue]]中的资源的值。 这种方式很容易在您的视图中find“未翻译”的枚举。 此外,这有助于提醒您是否忘记在重命名之后更新资源文件等。
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; } }
资源文件如下所示:
用法:
<div>@ContractStatus.Created.GetDisplayName()</div>
有一种方法使用属性来指定一个string来显示它们时用于枚举,但是当你必须处理本地化时,我们发现它太烦琐。
所以我们通常对需要本地化的枚举做的是编写一个扩展类,它提供了一个方法来获得翻译后的名字。 你可以使用一个从通常资源返回string的开关。 这样,就像为其他string一样,通过资源为枚举提供翻译后的string。
例如:
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
的接口的一部分。 当然,你也可以为这个类添加任何其他有用的扩展枚举。
[编辑]
如果你想处理多个标志设置(“pipe理员和主持人和网站pipe理员”),那么你需要做一些不同的事情:
[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());
资源文件
资源文件是你国际化你的string的方式。 有关如何使用它们的更多信息,请参阅:
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
你可以使用你的枚举值来做你想要的。
public enum Roles { Administrator = 0, Moderator = 1 , Webmaster = 2, Guest = 3 , Etc.... = 4 }
当您想要获得列表框中选定的枚举时,您检索列表框项目,然后检索关联的枚举编号。
然后你会将它转换为这样的枚举项
Roles myrol = (Roles) i
( i
为这个例子关联的int val)
将枚举项目转换为整数和整数值返回枚举项目
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() ) ;
当你从哈希表中select一个项目时,将其转换回Enum项目并在需要的地方使用。 您可以使用相同的方式来填充数据表/combobox,下拉列表等
翻译/本地化枚举的一个问题是,你不仅需要翻译它们来显示,而且还要将翻译parsing回枚举值。 下面的C#文件包含了我如何克服枚举的双向转换问题。 请原谅那些过度的评论,但是我的创作却相当冗长。
// // 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. Eg, /// /// <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 } }
这不需要每个枚举值之前的属性,但需要在资源中的已翻译的枚举string的名称属性被格式化,使得它是枚举types名称和枚举值的串联。 有关更多信息,请参阅LocalEnumDescription方法上面的注释。 另外,它通过映射它们的翻译来保留这些枚举的翻译(向前和向后),使得在每次遇到枚举值时我们不需要search翻译。
希望很容易理解和使用。
还有一种可能性是通过Enum类创build一个全局显示名称存储作为类扩展:
// 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(); } }
可能的用途非常简单,例如:
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]"
波纹pipe的扩展方法对我很有用。
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(); } }
我holpe帮助。
@ eluxen的答案适用于某些便携式(PCL)库(特别是Profile47
),原始解决scheme无法正常工作。 解决了两个问题: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; } } }
有关用法,请参阅原始答案 。 如果您没有遇到任何问题,我仍然会使用原来的答案,因为它不包含通过reflection的解决方法
答案与答案一样,但没有代码分析警告
[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(); } } }
上面的解决scheme使用LocalizedDescriptionAttribute从程序的当前语言中读取。 完美的客户端,而不是如此灵活的服务器端,当你想通过语言参数。
所以我通过添加另一种方法来扩展eluxen解决scheme与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来避免使用属性中的任何string。
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");
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()
希望这会有助于某人