Локализация 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, который может быть тем, что я ищу. Но он должен быть в пространстве имен Microsoft.VisualStudio.Modeling.Design, и я не могу найти ссылку, которую я должен добавить для этого пространства имен.

Кто-нибудь знает, есть ли более простой способ добиться локализации DisplayName хорошим способом? или если есть способ использовать то, что Microsoft, кажется, использует для Visual Studio?

10.12.2008 15:25:21
А как насчет Display (ResourceType = typeof (ResourceStrings), Name = "MyProperty"), см. Msdn.microsoft.com/en-us/library/…
Peter 14.12.2011 13:46:59
@ Питер внимательно прочитал пост, он хочет прямо противоположного, используя ResourceStrings и проверяет время компиляции, а не жестко закодированные строки ...
Marko 20.12.2013 17:13:14
10 ОТВЕТОВ

Ну, сборка есть Microsoft.VisualStudio.Modeling.Sdk.dll. который поставляется с Visual Studio SDK (с пакетом интеграции Visual Studio).

Но он будет использоваться почти так же, как ваш атрибут; невозможно строго использовать ресурсы типов в атрибутах просто потому, что они не являются постоянными.

1
10.12.2008 15:36:03

Вы можете создать подкласс 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" } } } });
    }
}
6
10.12.2008 15:50:56

Я прошу прощения за код VB.NET, мой C # немного ржавый ... Но вы поймете идею, верно?

Прежде всего, создайте новый класс:, LocalizedPropertyDescriptorкоторый наследует PropertyDescriptor. Переопределите DisplayNameсвойство следующим образом:

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 является ключом в файле ресурсов.

0
10.12.2008 15:47:06
Первая часть вашего решения - это то, что я сделал ... пока мне не пришлось решать "что такое Some.ResourceManager?" вопрос. Я должен дать вторую буквальную строку, такую ​​как «MyAssembly.Resources.Resource»? слишком опасно! Что касается второй части (ICustomTypeDescriptor), я не думаю, что это на самом деле полезно
PowerKiKi 10.12.2008 16:25:04
Решение Марка Гравелла - это путь, если вам не нужно ничего, кроме переведенного DisplayName - я использую пользовательский дескриптор и для других вещей, и это было мое решение. Однако нет способа сделать это без предоставления какого-либо ключа.
Vincent Van Den Berghe 10.12.2008 16:56:50

Мы делаем это для ряда атрибутов, чтобы поддерживать несколько языков. Мы применили аналогичный подход к 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";
}
80
11.06.2010 09:42:07
Когда я пытаюсь использовать этот подход, я получаю сообщение об ошибке, в котором говорится: «Аргумент атрибута должен быть выражением константы, выражением typeof или выражением создания массива типа параметра атрибута». Однако передача значения LocalizedDisplayName в виде строки работает. Хотелось бы, чтобы это было сильно напечатано.
Andrés Nava - Azure 25.02.2010 09:11:38
@Andy: значения в ResourceStrings должны быть константами, как указано в ответе, а не свойствами или значениями только для чтения. Они должны быть помечены как const и ссылаться на имена ресурсов, в противном случае вы получите ошибку.
Jeff Yates 1.03.2010 16:05:56
Ответил на мой собственный вопрос, о том, где у вас был Resources.ResourceManager, в моем случае файлы resx - это сгенерированные публичные resx, так оно и было[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith 17.08.2010 11:37:56
говорит, что мне нужен экземпляр Resources.ResourceManager, чтобы вызвать для него строку get
topwik 2.12.2011 20:06:56
@LTR: нет проблем. Я рад, что вы дошли до сути вопроса. Рад помочь, если смогу.
Jeff Yates 24.07.2012 14:34:19
РЕШЕНИЕ

Вот решение, которое я выбрал в отдельной сборке (в моем случае это называется «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.Designer.cs, что, вероятно, не очень хорошая идея.

Вывод: меня это не устраивает, но я еще менее рад тому, что Microsoft не может предоставить ничего полезного для решения такой распространенной задачи.

41
11.12.2008 13:37:57
Очень полезный. Спасибо. Я надеюсь, что в будущем Microsoft предложит хорошее решение, которое предлагает строго типизированный способ ссылки на ресурсы.
Johnny Oshika 19.10.2009 05:50:32
ya эта строковая штука действительно отстойная :( Если бы вы могли получить имя свойства свойства, которое использует атрибут, вы могли бы сделать это в соответствии с соглашением, а не способом конфигурации, но это не представляется возможным. Уход за «сильно tpyed» Перечисления, которые вы могли бы использовать, также не совсем поддерживаемы: /
Rookian 30.06.2010 09:18:08
Это хорошее решение. Я просто не стал бы перебирать коллекцию ResourceManagerсвойств. Вместо этого вы можете просто получить свойство напрямую из типа, указанного в параметре:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer 27.10.2010 08:26:18
Добавьте к этому шаблон T4 @ zielu1 для автоматической генерации ключей ресурсов, и вы получите достойного победителя!
David Keaveny 1.07.2011 06:04:03

Вы можете использовать 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#>";
  <# } #>
  }
 }
}
14
22.07.2010 16:32:24
Каким будет результат?
irfandar 7.10.2017 20:16:58

Существует атрибут Display из System.ComponentModel.DataAnnotations в .NET 4. Он работает на MVC 3 PropertyGrid.

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

Это ищет ресурс, названный UserNameв вашем MyResources.resx файле.

113
27.03.2014 23:08:25
Я просмотрел всю страницу, прежде чем нашел эту страницу ... это такой спасатель жизни. Спасибо! Хорошо работает на MVC5 для меня.
Kris 20.04.2018 14:42:57
Если компилятор жалуется typeof(MyResources), вам может потребоваться установить для модификатора доступа к файлу ресурса значение Public .
thatWiseGuy 2.02.2019 15:33:46

Это старый вопрос, но я думаю, что это очень распространенная проблема, и вот мое решение в 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», он будет использовать его вместо этого.

9
4.08.2011 15:45:19

Я использую этот способ решить в моем случае

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

}
2
12.01.2015 15:04:04

Используя атрибут Display (из System.ComponentModel.DataAnnotations) и выражение nameof () в C # 6, вы получите локализованное и строго типизированное решение.

[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))]
public string UserName { get; set; }
19
24.09.2016 19:36:46
В этом примере, что такое «MyResources»? Строго типизированный файл resx? Пользовательский класс?
Greg 16.05.2019 17:33:10