DisplayNameAttribute的本地化

Pow*_*iKi 119 c# attributes localization visual-studio-2008

我正在寻找一种方法来本地化PropertyGrid中显示的属性名称.可以使用DisplayNameAttribute属性"覆盖"属性的名称.不幸的是,属性不能有非常量表达式.所以我不能使用强类型资源,例如:

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

我浏览了一下,发现了一些从DisplayNameAttribute继承的建议,以便能够使用资源.我最终会得到如下代码:

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

但是我失去了强大的类型资源优势,这绝对不是一件好事.然后我遇到了DisplayNameResourceAttribute,这可能是我正在寻找的.但它应该在Microsoft.VisualStudio.Modeling.Design命名空间中,我找不到我应该为此命名空间添加的引用.

有人知道是否有更好的方法以一种好的方式实现DisplayName本地化?或者是否有使用Microsoft似乎用于Visual Studio的方法?

Ran*_*ngy 111

.NET 4中的System.ComponentModel.DataAnnotations 有Display属性.它适用于MVC 3 PropertyGrid.

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

这将查找.resx文件中指定UserNameMyResources资源.

  • 如果编译器抱怨“typeof(MyResources)”,您可能需要将资源文件访问修饰符设置为 **Public**。 (2认同)

Jef*_*tes 79

我们这样做是为了支持多种语言的许多属性.我们采用了类似于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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

实际使用属性时,可以更进一步,并将资源名称指定为静态类中的常量.这样,你得到的声明就像.

[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
  get
  {
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

更新
ResourceStrings看起来像(注意,每个字符串将引用指定实际字符串的资源的名称):

public static class ResourceStrings
{
    public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
    public const string FontSizeDisplayName="FontSizeDisplayName";
}
Run Code Online (Sandbox Code Playgroud)


Pow*_*iKi 41

这是我在一个单独的程序集中结束的解决方案(在我的例子中称为"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))
      {
      }
   }
Run Code Online (Sandbox Code Playgroud)

使用代码查找资源:

  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
  }
Run Code Online (Sandbox Code Playgroud)

典型用法是:

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

什么是非常难看,因为我使用文字字符串作为资源键.使用常量意味着修改Resources.Designer.cs,这可能不是一个好主意.

结论:我对此并不满意,但我对于那些无法为这样一个共同任务提供任何有用的东西感到高兴.


dio*_*oid 19

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

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

  • 在这个例子中,什么是“MyResources”?强类型的 resx 文件?自定义类? (2认同)

zie*_*lu1 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#>";
  <# } #>
  }
 }
}
Run Code Online (Sandbox Code Playgroud)


YYF*_*ish 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#>";                    
<#
           }    
#>
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,创建一个扩展方法来本地化'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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

'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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

完成所有准备工作后,有时间触摸那些默认验证属性.我使用'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());
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

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

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,属性名称用作查找"Label.resx"的键,但如果通过"DisplayLabel"设置它,则会使用该名称.


Mar*_*ell 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" } } } });
    }
}
Run Code Online (Sandbox Code Playgroud)