DisplayNameAttribute的本地化


120

我正在寻找一种本地化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,这可能是我想要的。但这应该在Microsoft.VisualStudio.Modeling.Design命名空间中,而我找不到应该为该命名空间添加的引用。

有人知道是否有一种简便的方法可以很好地实现DisplayName本地化?或者是否可以使用Microsoft似乎在Visual Studio中使用的方式?


2
关于Display(ResourceType = typeof(ResourceStrings),Name =“ MyProperty”)的说明,请参见msdn.microsoft.com/zh-cn/library/…–
Peter

@Peter仔细阅读了这篇文章,他希望使用ResourceStrings和编译时检查完全相反,而不是硬编码的字符串...
Marko 2013年

Answers:


113

.NET 4中有System.ComponentModel.DataAnnotations 的Display属性。它在MVC 3上有效PropertyGrid

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

这会UserName在您的MyResources.resx文件中查找命名的资源。


在找到此页面之前,我四处张望...这真是个救命稻草。谢谢!对我来说,在MVC5上效果很好。
克里斯(Kris)

如果编译器在抱怨typeof(MyResources),则可能需要将资源文件访问修饰符设置为Public
thatWiseGuy

80

我们正在对许多属性执行此操作,以支持多种语言。我们对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";
}

当我尝试这种方法时,会收到一条错误消息,提示“属性参数必须是属性参数类型的常量表达式,typeof表达式或数组创建表达式”。但是,将值作为字符串传递给LocalizedDisplayName是可行的。希望它将被强类型输入。
Azure SME

1
@Andy:ResourceStrings中的值必须是常量,如答案中所示,而不是属性或只读值。必须将它们标记为const并引用资源的名称,否则会出现错误。
杰夫·耶茨

1
回答了我自己的问题,关于Resources.ResourceManager在哪里,在我的情况下,resx文件是公共resx生成的,所以它是[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith,2010年

说我需要一个Resources.ResourceManager实例才能对其调用调用
topwik 2011年

1
@LTR:没问题。我很高兴您能深入浅出。如果可以的话,很乐意提供帮助。
杰夫·耶茨

41

这是我最终在单独的程序集中找到的解决方案(在我的情况下称为“通用”):

   [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.Designer.cs,这可能不是一个好主意。

结论:我对此感到不满意,但是对于Microsoft无法提供用于执行此常见任务的任何有用信息的情况,我甚至不满意。


很有用。谢谢。将来,我希望Microsoft提出一个不错的解决方案,该解决方案提供引用资源的强类型方法。
约翰尼·奥希卡

是的,这串东西真是太难了:(如果您可以获取使用该属性的属性的属性名称,则可以按照约定通过配置方式来完成,但这似乎是不可能的。枚举(您可以使用)也并非真正可维护:/
Rookian 2010年

那是一个很好的解决方案。我只是不会遍历ResourceManager属性集合。相反,您可以直接从参数中提供的类型中直接获取属性:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer,2010年

1
将此与@ zielu1的T4模板结合使用以自动生成资源密钥,您将是一个值得的赢家!
David Keaveny

19

使用C#6中的Display属性(来自System.ComponentModel.DataAnnotations)和nameof()表达式,您将获得本地化和强类型化的解决方案。

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

1
在此示例中,“ MyResources”是什么?强类型的resx文件?自定义类?
格雷格

14

您可以使用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#>";
  <# } #>
  }
 }
}

输出是什么样的?
irfandar

9

这是一个老问题,但是我认为这是一个非常常见的问题,这是我在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;
        }
    }
}

为了自动读取“ Labels.resx”,“ DisplayName”属性被“ DisplayLabel”属性取代,

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

完成所有这些准备工作之后,就该触摸这些默认验证属性了。我以“ Required”属性为例,

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”进行设置,它将使用该名称。


6

您可以通过重写方法之一来为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" } } } });
    }
}

2

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

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

}

1

好,大会是Microsoft.VisualStudio.Modeling.Sdk.dll。随Visual Studio SDK(带有Visual Studio集成包)一起提供。

但是,它的使用方式几乎与属性相同;仅仅因为它们不是常量,就无法在属性中使用强类型资源。


0

我为VB.NET代码表示歉意,我的C#有点生锈...但是您会明白的,对吧?

首先,创建一个新类:LocalizedPropertyDescriptor,该类继承PropertyDescriptorDisplayName像这样覆盖属性:

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 是资源文件中的密钥。


解决方案的第一部分是我所做的...直到必须解决“什么是Some.ResourceManager吗?” 题。我是不是应该给一个第二个文本字符串,如“MyAssembly.Resources.Resource”?太危险了!至于第二部分(ICustomTypeDescriptor),我认为它实际上没有用
PowerKiKi

如果您只需要翻译后的DisplayName,Marc Gravell的解决方案就是解决之道-我也将自定义描述符用于其他内容,这就是我的解决方案。但是,没有提供某种密钥就无法做到这一点。
文森特·范·登·伯格
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.