Localization of DisplayNameAttribute
There is the Display attribute from System.ComponentModel.DataAnnotations in .NET 4. It works on the MVC 3 PropertyGrid
.
[Display(ResourceType = typeof(MyResources), Name = "UserName")]
public string UserName { get; set; }
This looks up a resource named UserName
in your MyResources
.resx file.
We are doing this for a number of attributes in order to support multiple language. We have taken a similar approach to Microsoft, where they override their base attributes and pass a resource name rather than the actual string. The resource name is then used to perform a lookup in the DLL resources for the actual string to return.
For example:
class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private readonly string resourceName;
public LocalizedDisplayNameAttribute(string resourceName)
: base()
{
this.resourceName = resourceName;
}
public override string DisplayName
{
get
{
return Resources.ResourceManager.GetString(this.resourceName);
}
}
}
You can take this a step further when actually using the attribute, and specify your resource names as constants in a static class. That way, you get declarations like.
[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
get
{
...
}
}
UpdateResourceStrings
would look something like (note, each string would refer to the name of a resource that specifies the actual string):
public static class ResourceStrings
{
public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
public const string FontSizeDisplayName="FontSizeDisplayName";
}
Here is the solution I ended up with in a separate assembly (called "Common" in my case):
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
: base(Utils.LookupResource(resourceManagerProvider, resourceKey))
{
}
}
with the code to look up the resource:
internal static string LookupResource(Type resourceManagerProvider, string resourceKey)
{
foreach (PropertyInfo staticProperty in resourceManagerProvider.GetProperties(BindingFlags.Static | BindingFlags.NonPublic))
{
if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager))
{
System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null);
return resourceManager.GetString(resourceKey);
}
}
return resourceKey; // Fallback with the key name
}
Typical usage would be:
class Foo
{
[Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
public DateTime CreationDate
{
get;
set;
}
}
What is pretty much ugly as I use literal strings for resource key. Using a constant there would mean to modify Resources.Designer.cs which is probably not a good idea.
Conclusion: I am not happy with that, but I am even less happy about Microsoft who can't provide anything useful for such a common task.
Using the Display attribute (from System.ComponentModel.DataAnnotations) and the nameof() expression in C# 6, you'll get a localized and strongly typed solution.
[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))]
public string UserName { get; set; }
You could use T4 to generate constants. I wrote one:
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.XPath" #>
using System;
using System.ComponentModel;
namespace Bear.Client
{
/// <summary>
/// Localized display name attribute
/// </summary>
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
readonly string _resourceName;
/// <summary>
/// Initializes a new instance of the <see cref="LocalizedDisplayNameAttribute"/> class.
/// </summary>
/// <param name="resourceName">Name of the resource.</param>
public LocalizedDisplayNameAttribute(string resourceName)
: base()
{
_resourceName = resourceName;
}
/// <summary>
/// Gets the display name for a property, event, or public void method that takes no arguments stored in this attribute.
/// </summary>
/// <value></value>
/// <returns>
/// The display name.
/// </returns>
public override String DisplayName
{
get
{
return Resources.ResourceManager.GetString(this._resourceName);
}
}
}
partial class Constants
{
public partial class Resources
{
<#
var reader = XmlReader.Create(Host.ResolvePath("resources.resx"));
var document = new XPathDocument(reader);
var navigator = document.CreateNavigator();
var dataNav = navigator.Select("/root/data");
foreach (XPathNavigator item in dataNav)
{
var name = item.GetAttribute("name", String.Empty);
#>
public const String <#= name#> = "<#= name#>";
<# } #>
}
}
}