How do you localize enums for a ListBoxFor
where multiple options are possible?
For example an enum
that contains roles:
pu
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
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.
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");
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 ✅
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:
Usage:
<div>@ContractStatus.Created.GetDisplayName()</div>
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();
}
}
}