Локалізація 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
Що про дисплей (ResourceType = typeof (ResourceStrings), Name = "MyProperty") див. Msdn.microsoft.com/en-us/library/…
Петро,

@Peter уважно читає публікацію, він хоче з точністю до навпаки, використовуючи ResourceStrings і збираючи час, перевіряти не жорсткі кодовані рядки ...
Marko

Відповіді:


113

Є атрибут Display від System.ComponentModel.DataAnnotations в .NET 4. Він працює на MVC 3 PropertyGrid.

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

Це шукає ресурс, названий UserNameу вашому MyResourcesфайлі .resx.


Я все переглянув, перш ніж знайти цю сторінку ... це така рятівниця життя. Дякую! Добре працює на MVC5 для мене.
Кріс

Якщо компілятор скаржиться на typeof(MyResources)вас, можливо, вам доведеться встановити модифікатор доступу до ресурсного файла на Загальнодоступний .
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

каже, що мені потрібен екземпляр Resources.ResourceManager для того, щоб зателефонувати отримати на нього рядок
topwik

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 придумає приємне рішення, яке пропонує чітко набраний спосіб посилання на ресурси.
Джонні Ошика

ya цей рядковий матеріал смокче дуже важко :( Якщо ви могли отримати назву властивості властивості, яка використовує атрибут, ви можете це зробити за умовами конфігураційного способу, але це, здається, неможливо. Перерахування, які ви могли б використати, теж не є
реальними для

Це хороше рішення. Я б просто не повторював колекцію ResourceManagerвластивостей. Натомість ви можете просто отримати майно безпосередньо з типу, передбаченого параметром:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Максиміліан Маєр

1
Поєднайте це за допомогою шаблону T4 @ zielu1, щоб автоматично генерувати ключі ресурсів, і у вас є гідний переможець!
David Keaveny

19

Використовуючи атрибут Display (від System.ComponentModel.DataAnnotations) та вираз nameof () у C # 6, ви отримаєте локалізоване та сильно набране рішення.

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

Яким буде вихід?
ірфандар

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

Атрибут "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", він використовуватиме його замість цього.


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. який поставляється з SDK Visual Studio (із пакетом інтеграції Visual Studio).

Але він би використовувався майже так само, як і ваш атрибут; немає способу використовувати сильно типові ресурси в атрибутах просто тому, що вони не є постійними.


0

Прошу вибачення за код 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 є ключем у файлі ресурсу.


Перша частина вашого рішення - це те, що я зробив ... поки мені не довелося вирішити "що таке Some.ResourceManager?" питання. Чи повинен я дати другий буквальний рядок, такий як "MyAssembly.Resources.Resource"? шлях занадто небезпечний! Що стосується другої частини (ICustomTypeDescriptor), я не думаю, що це насправді корисно
PowerKiKi

Рішення Марка Гравелла - це шлях, якщо вам не потрібно нічого, крім перекладеного DisplayName - я використовую спеціальний дескриптор і для інших матеріалів, і це було моє рішення. Неможливо зробити це, не вводячи якийсь ключ.
Вінсент Ван Ден Берге
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.