DisplayNameAttribute 的本地化

我正在寻找一种方法来本地化在 PropertyGrid 中显示的属性名称。使用 DisplayNameAttribute 属性可以“重写”属性的名称。遗憾的是,属性不能有非常量表达式。因此,我不能使用强类型资源,例如:

class Foo
{
[DisplayAttribute(Resources.MyPropertyNameLocalized)]  // do not compile
string MyProperty {get; set;}
}

我四处看了看,发现了一些建议,可以从 DisplayNameAttribute 继承来使用资源。我最终会得到这样的代码:

class Foo
{
[MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed
string MyProperty {get; set;}
}

然而,我失去了强类型资源的好处,这绝对不是一件好事。然后我发现了 DisplayNameResourceAttribute这可能就是我要找的。但它应该在微软。VisualStudio.模特。设计名称空间,但我找不到应该为这个名称空间添加什么引用。

有人知道是否有一种更简单的方法可以很好地实现 DisplayName 本地化吗?或者是否有办法使用微软似乎正在使用的 Visual Studio?

93951 次浏览

这个程序集是 Microsoft.VisualStudio.Modeling.Sdk.dll,它与 VisualStudioSDK (VisualStudioIntegrationPackage)一起提供。

但是它的使用方式与属性非常相似; 不能仅仅因为属性不是常量就在属性中使用强类型资源。

可以通过重写其中一个方法来子类 DisplayNameAttribute 以提供 i18n。像这样。你可能不得不满足于使用一个常量作为密钥。

using System;
using System.ComponentModel;
using System.Windows.Forms;


class Foo {
[MyDisplayName("bar")] // perhaps use a constant: SomeType.SomeResName
public string Bar {get; set; }
}


public class MyDisplayNameAttribute : DisplayNameAttribute {
public MyDisplayNameAttribute(string key) : base(Lookup(key)) {}


static string Lookup(string key) {
try {
// get from your resx or whatever
return "le bar";
} catch {
return key; // fallback
}
}
}


class Program {
[STAThread]
static void Main() {
Application.Run(new Form { Controls = {
new PropertyGrid { SelectedObject =
new Foo { Bar = "abc" } } } });
}
}

我为 VB.NET 代码道歉,我的 C # 有点生疏... 但是你会明白的,对吗?

首先,创建一个新类: LocalizedPropertyDescriptor,它继承 PropertyDescriptor:

Public Overrides ReadOnly Property DisplayName() As String
Get
Dim BaseValue As String = MyBase.DisplayName
Dim Translated As String = Some.ResourceManager.GetString(BaseValue)
If String.IsNullOrEmpty(Translated) Then
Return MyBase.DisplayName
Else
Return Translated
End If
End Get
End Property

Some.ResourceManager是包含您的翻译的资源文件的 ResourceManager。

接下来,使用本地化属性在类中实现 ICustomTypeDescriptor,并重写 GetProperties方法:

Public Function GetProperties() As PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
Dim baseProps As PropertyDescriptorCollection = TypeDescriptor.GetProperties(Me, True)
Dim LocalizedProps As PropertyDescriptorCollection = New PropertyDescriptorCollection(Nothing)


Dim oProp As PropertyDescriptor
For Each oProp In baseProps
LocalizedProps.Add(New LocalizedPropertyDescriptor(oProp))
Next
Return LocalizedProps
End Function

现在可以使用“ DisplayName”属性在资源文件中存储对值的引用..。

<DisplayName("prop_description")> _
Public Property Description() As String

prop_description是资源文件中的键。

为了支持多种语言,我们对许多属性进行了这样的处理。我们对 Microsoft 采用了类似的方法,它们覆盖基本属性并传递资源名,而不是实际的字符串。然后使用资源名称在 DLL 资源中查找要返回的实际字符串。

例如:

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

在实际使用该属性时,您可以更进一步,将资源名称指定为静态类中的常量。这样的话,你就会得到。

[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
get
{
...
}
}

更新
ResourceStrings大致如下(注意,每个字符串都指代指定实际字符串的资源的名称) :

public static class ResourceStrings
{
public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
public const string FontSizeDisplayName="FontSizeDisplayName";
}

下面是我在一个单独的程序集中得到的解决方案(在我的例子中称为“ Common”) :

   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
public class DisplayNameLocalizedAttribute : DisplayNameAttribute
{
public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
: base(Utils.LookupResource(resourceManagerProvider, resourceKey))
{
}
}

使用查找资源的代码:

  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
}

典型的用法是:

class Foo
{
[Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
public DateTime CreationDate
{
get;
set;
}
}

当我使用字符串作为资源键时,这是非常丑陋的。在那里使用一个常量将意味着修改 Resources。Cs,这可能不是一个好主意。

结论: 我对此并不满意,但我对微软更不满意,因为它不能为这样一个常见的任务提供任何有用的东西。

你可以用 T4来生成常量,我写了一个:

<#@ 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#>";
<# } #>
}
}
}

在.NET4中有来自 System.Component 模型的 显示属性数据注释。它在 MVC3PropertyGrid上工作。

[Display(ResourceType = typeof(MyResources), Name = "UserName")]
public string UserName { get; set; }

这将在 MyResources.resx文件中查找名为 UserName的资源。

这是一个老问题,但我认为这是一个非常普遍的问题,这是我在 MVC 3中的解决方案。

首先,需要一个 T4模板来生成常量以避免讨厌的字符串。我们有一个包含所有标签字符串的资源文件‘ Labels.resx’。因此 T4模板直接使用资源文件,

<#@ template debug="True" hostspecific="True" language="C#" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="C:\Project\trunk\Resources\bin\Development\Resources.dll" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Resources" #>
<#
var resourceStrings = new List<string>();
var manager = Resources.Labels.ResourceManager;


IDictionaryEnumerator enumerator = manager.GetResourceSet(CultureInfo.CurrentCulture,  true, true)
.GetEnumerator();
while (enumerator.MoveNext())
{
resourceStrings.Add(enumerator.Key.ToString());
}
#>


// This file is generated automatically. Do NOT modify any content inside.


namespace Lib.Const{
public static class LabelNames{
<#
foreach (String label in resourceStrings){
#>
public const string <#=label#> =     "<#=label#>";
<#
}
#>
}
}

然后,创建一个扩展方法来本地化“ DisplayName”,

using System.ComponentModel.DataAnnotations;
using Resources;


namespace Web.Extensions.ValidationAttributes
{
public static class ValidationAttributeHelper
{
public static ValidationContext LocalizeDisplayName(this ValidationContext    context)
{
context.DisplayName = Labels.ResourceManager.GetString(context.DisplayName) ?? context.DisplayName;
return context;
}
}
}

将“ DisplayName”属性替换为“ DisplayLabel”属性,以便自动从“ Labels.resx”中读取,

namespace Web.Extensions.ValidationAttributes
{


public class DisplayLabelAttribute :System.ComponentModel.DisplayNameAttribute
{
private readonly string _propertyLabel;


public DisplayLabelAttribute(string propertyLabel)
{
_propertyLabel = propertyLabel;
}


public override string DisplayName
{
get
{
return _propertyLabel;
}
}
}
}

在所有这些准备工作之后,是时候触摸这些默认验证属性了。我使用“必需”属性作为例子,

using System.ComponentModel.DataAnnotations;
using Resources;


namespace Web.Extensions.ValidationAttributes
{
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
public RequiredAttribute()
{
ErrorMessageResourceType = typeof (Errors);
ErrorMessageResourceName = "Required";
}


protected override ValidationResult IsValid(object value, ValidationContext  validationContext)
{
return base.IsValid(value, validationContext.LocalizeDisplayName());
}


}
}

现在,我们可以在模型中应用这些属性,

using Web.Extensions.ValidationAttributes;


namespace Web.Areas.Foo.Models
{
public class Person
{
[DisplayLabel(Lib.Const.LabelNames.HowOldAreYou)]
public int Age { get; set; }


[Required]
public string Name { get; set; }
}
}

默认情况下,属性名用作查找“ Label.resx”的键,但是如果通过“ DisplayLabel”设置它,它将使用该键。

我用这种方法解决我的情况

[LocalizedDisplayName("Age", NameResourceType = typeof(RegistrationResources))]
public bool Age { get; set; }

用密码

public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
private PropertyInfo _nameProperty;
private Type _resourceType;




public LocalizedDisplayNameAttribute(string displayNameKey)
: base(displayNameKey)
{


}


public Type NameResourceType
{
get
{
return _resourceType;
}
set
{
_resourceType = value;
_nameProperty = _resourceType.GetProperty(base.DisplayName, BindingFlags.Static | BindingFlags.Public);
}
}


public override string DisplayName
{
get
{
if (_nameProperty == null)
{
return base.DisplayName;
}


return (string)_nameProperty.GetValue(_nameProperty.DeclaringType, null);
}
}


}

使用 展示属性(来自 System。组件模型。和 C # 6中的 名称()表达式,您将得到一个本地化的强类型解决方案。

[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))]
public string UserName { get; set; }

显示名称:

    public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public string ResourceKey { get; }
public string BaseName { get; set; }
public Type ResourceType { get; set; }


public LocalizedDisplayNameAttribute(string resourceKey)
{
ResourceKey = resourceKey;
}


public override string DisplayName
{
get
{
var baseName = BaseName;
var assembly = ResourceType?.Assembly ?? Assembly.GetEntryAssembly();


if (baseName.IsNullOrEmpty())
{
// ReSharper disable once PossibleNullReferenceException
baseName = $"{(ResourceType != null ? ResourceType.Namespace : assembly.GetName().Name)}.Resources";
}


// ReSharper disable once AssignNullToNotNullAttribute
var res = new ResourceManager(baseName, assembly);


var str = res.GetString(ResourceKey);


return string.IsNullOrEmpty(str)
? $"[[{ResourceKey}]]"
: str;
}
}
}

描述:

public sealed class LocalizedDescriptionAttribute : DescriptionAttribute
{
public string ResourceKey { get; }
public string BaseName { get; set; }
public Type ResourceType { get; set; }


public LocalizedDescriptionAttribute(string resourceKey)
{
ResourceKey = resourceKey;
}


public override string Description
{
get
{
var baseName = BaseName;
var assembly = ResourceType?.Assembly ?? Assembly.GetEntryAssembly();


if (baseName.IsNullOrEmpty())
{
// ReSharper disable once PossibleNullReferenceException
baseName = $"{(ResourceType != null ? ResourceType.Namespace : assembly.GetName().Name)}.Resources";
}


// ReSharper disable once AssignNullToNotNullAttribute
var res = new ResourceManager(baseName, assembly);
var str = res.GetString(ResourceKey);
                

return string.IsNullOrEmpty(str)
? $"[[{ResourceKey}]]"
: str;
}
}
}

类别(PropertyGrid) :

    public sealed class LocalizedCategoryAttribute : CategoryAttribute
{
public string ResourceKey { get; }
public string BaseName { get; set; }
public Type ResourceType { get; set; }


public LocalizedCategoryAttribute(string resourceKey)
: base(resourceKey)
{
ResourceKey = resourceKey;
}


protected override string GetLocalizedString(string resourceKey)
{
var baseName = BaseName;
var assembly = ResourceType?.Assembly ?? Assembly.GetEntryAssembly();


if (baseName.IsNullOrEmpty())
{
// ReSharper disable once PossibleNullReferenceException
baseName = $"{(ResourceType != null ? ResourceType.Namespace : assembly.GetName().Name)}.Resources";
}


// ReSharper disable once AssignNullToNotNullAttribute
var res = new ResourceManager(baseName, assembly);
var str = res.GetString(resourceKey);


return string.IsNullOrEmpty(str)
? $"[[{ResourceKey}]]"
: str;
}
}

例如: [LocalizedDisplayName("ResourceKey", ResourceType = typeof(RE))]

其中“ RE”位于包含“ Resources.de.resx”或“ Resources.en.resx”等资源文件的程序集中。

使用枚举和属性。

干杯