Quel est le meilleur moyen de localiser les descriptions d’énumération dans .net?
(Voir Ajout de descriptions aux constantes d’énumération pour un exemple de description d’énumération)
Idéalement, j'aimerais utiliser quelque chose qui utilise ResourceManager et des fichiers de ressources afin que cela corresponde à la manière dont les autres zones de l'application sont localisées.
C’est ce que j’ai fini par comprendre: je n’ai pas compris l’intérêt d’ajouter une classe d’attributs personnalisée pour détenir une clé de ressource, puis de rechercher dans les fichiers de ressources. Pourquoi ne pas simplement utiliser l’énum typename + valeur comme clé de ressource?
using System;
using System.Resources;
using System.Reflection;
public class MyClass
{
enum SomeEnum {Small,Large};
private ResourceManager _resources = new ResourceManager("MyClass.myResources",
System.Reflection.Assembly.GetExecutingAssembly());
public string EnumDescription(Enum enumerator)
{
string rk = String.Format("{0}.{1}",enumerator.GetType(),enumerator);
string localizedDescription = _resources.GetString(rk);
if (localizedDescription == null)
{
// A localized string was not found so you can either just return
// the enums value - most likely readable and a good fallback.
return enumerator.ToString();
// Or you can return the full resourceKey which will be helpful when
// editing the resource files(e.g. MyClass+SomeEnum.Small)
// return resourceKey;
}
else
return localizedDescription;
}
void SomeRoutine()
{
// Looks in resource file for a string matching the key
// "MyClass+SomeEnum.Large"
string s1 = EnumDescription(SomeEnum.Large);
}
}
Ma solution, utilisant l'attribut native decription:
public class LocalizedEnumAttribute : DescriptionAttribute
{
private PropertyInfo _nameProperty;
private Type _resourceType;
public LocalizedEnumAttribute(string displayNameKey)
: base(displayNameKey)
{
}
public Type NameResourceType
{
get
{
return _resourceType;
}
set
{
_resourceType = value;
_nameProperty = _resourceType.GetProperty(this.Description, BindingFlags.Static | BindingFlags.Public);
}
}
public override string Description
{
get
{
//check if nameProperty is null and return original display name value
if (_nameProperty == null)
{
return base.Description;
}
return (string)_nameProperty.GetValue(_nameProperty.DeclaringType, null);
}
}
}
public static class EnumExtender
{
public static string GetLocalizedDescription(this Enum @enum)
{
if (@enum == null)
return null;
string description = @enum.ToString();
FieldInfo fieldInfo = @enum.GetType().GetField(description);
DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return attributes[0].Description;
return description;
}
}
La déclaration Enum
public enum MyEnum
{
[LocalizedEnum("ResourceName", NameResourceType = typeof(ResourceType))]
Test = 0
}
Ensuite, appelez MyEnumInstance.GetLocalizedDescription()
il existe une solution simple: utilise l'attribut LocalizedDescription pour transmettre une clé de ressource.
[Serializable]
public class LocalizableDescriptionAttribute:DescriptionAttribute
{
public LocalizableDescriptionAttribute(string resourceKey)
:base(Resources.ResourceManager.GetString(resourceKey))
{ }
}
Une façon de le faire, une fois, a été d’ajouter une méthode extention dans le même espace de noms qu’enum, qui renvoyait une chaîne. Dans mon cas, il s'agissait simplement d'un code, mais cela ne poserait aucun problème de les obtenir à partir d'un fichier de ressources.
public static string Describe(this SomeEnum e)
{
switch(e)
{
SomeEnum.A:
return "Some text from resourcefile";
SomeEnum.B:
return "Some other text from resourcefile";
...:
return ...;
}
}
Peut-être pas une solution extrêmement lisse ou élégante, mais cela fonctionne =)
Remplacez la méthode de @ nairik par la suivante pour ajouter la prise en charge des indicateurs enum.
public static string GetLocalizedDescription(this Enum @enum)
{
if ( @enum == null )
return null;
StringBuilder sbRet = new StringBuilder();
string description = @enum.ToString();
var fields = description.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach ( var field in fields )
{
FieldInfo fieldInfo = @enum.GetType().GetField(field);
DescriptionAttribute[] attributes = ( DescriptionAttribute[] )fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ( attributes.Any() )
sbRet.AppendFormat("{0}, ", attributes[0].Description);
else
sbRet.AppendFormat("{0}, ", field);
}
if ( sbRet.Length > 2 )
sbRet.Remove(sbRet.Length - 2, 2);
return sbRet.ToString();
}
et remplacez NameResourceType dans l'attribut:
public Type NameResourceType
{
get
{
return _resourceType;
}
set
{
_resourceType = value;
_nameProperty = _resourceType.GetProperty(base.Description, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
}
}
Voir mon exemple de tableau dans cette question:
Localisation/I18n des données de base de données dans LINQ to SQL
La table des types d'état correspond aux valeurs d'énumération. L'avantage réel ici est que vous pouvez avoir une localisation dans vos rapports et dans vos applications, et spécifier des identifiants externes pour une intégration avec des tiers qui ne veulent pas de vos valeurs internes, etc. Cela sépare la description enum de sa valeur.
Vous ne pouvez pas appliquer plusieurs System.ComponentModel.DescriptionAttribute (cette option est donc désactivée).
Donc, ajoutez un niveau d'indirection, la description contient un nom de ressource, puis utilisez le support de localisation dans les ressources. Clairement, les utilisateurs de l'énumération devront appeler votre méthode d'assistance pour le faire.