Локалізація 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.