Локализация атрибута 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.Моделирование.Дизайн пространства имен и я не могу найти, какую ссылку я должен добавить для этого пространства имен.
кто-нибудь знает, есть ли более простой способ добиться локализации DisplayName в хорошем смысле ? или если есть способ использовать то, что Microsoft, кажется, использует для Visual Studio ?
10 ответов:
есть отображения атрибута из системы.ComponentModel.DataAnnotations в .NET 4. Он работает на MVC 3
PropertyGrid.[Display(ResourceType = typeof(MyResources), Name = "UserName")] public string UserName { get; set; }Это ищет ресурс с именем
UserNameв своемMyResources.файл resx.
мы делаем это для ряда атрибутов для поддержки нескольких языков. Мы применили аналогичный подход к 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"; }
вот решение, которое я получил в отдельной сборке (называемой "общей" в моем случае):
[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; } }что довольно уродливо, как я использую литеральные строки для ключа ресурса. Использование константы там означало бы изменение ресурсов.Дизайнер.cs, который, вероятно, не очень хорошая идея.
вывод: я не доволен этим, но я еще менее счастлив о Microsoft, которая не может предоставить ничего полезного для такой общей задачи.
вы можете использовать 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#>"; <# } #> } } }
Это старый вопрос, но я думаю, что это очень распространенная проблема, и вот мое решение в MVC 3.
во-первых, шаблон T4 необходим для генерации констант, чтобы избежать неприятных строк. У нас есть ярлыки файла ресурсов.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',
‘отображаемое имя’ заменяется Атрибут ‘DisplayLabel’ для того, чтобы читать с этикетки.resx файл автоматически,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; } } }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; } } }по умолчанию, имя свойства используется в качестве ключа для поиска ‘метка.resx файл, но если вы установите его через DisplayLabel, он будет использовать это вместо этого.
вы можете подкласс DisplayNameAttribute предоставить i18n, переопределив один из методов. Вот так. edit: возможно, вам придется согласиться на использование константы для ключа.
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" } } } }); } }
Я использую этот способ решения проблемы в моем случае
[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); } } }
Ну, сборка
Microsoft.VisualStudio.Modeling.Sdk.dll. который поставляется с Visual Studio SDK (с пакетом интеграции Visual Studio).но он будет использоваться в значительной степени так же, как ваш атрибут; нет никакого способа использовать сильно типы ресурсов в атрибутах просто потому, что они не являются постоянными.
приношу свои извинения 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ключ в файле ресурсов.
Comments