Локализация 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?
11 ответов
Вот решение, которое я нашел в отдельной сборке (в моем случае это называется "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 не может предоставить ничего полезного для решения такой распространенной задачи.
Существует атрибут Display из System.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";
}
Вы можете использовать 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 необходим для генерации констант, чтобы избежать неприятных строк. У нас есть файл ресурсов '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", он будет использовать его вместо этого.
Вы можете создать подкласс 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" } } } });
}
}
Я использую этот способ решить в моем случае
[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);
}
}
}
Показать имя:
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
public string ResourceKey { get; }
public string BaseName { get; set; }
public Type ResourceType { get; set; }
public LocalizedDisplayNameAttribute(string resourceKey)
{
ResourceKey = resourceKey;
}
public override string DisplayName
{
get
{
var baseName = BaseName;
var assembly = ResourceType?.Assembly ?? Assembly.GetEntryAssembly();
if (baseName.IsNullOrEmpty())
{
// ReSharper disable once PossibleNullReferenceException
baseName = $"{(ResourceType != null ? ResourceType.Namespace : assembly.GetName().Name)}.Resources";
}
// ReSharper disable once AssignNullToNotNullAttribute
var res = new ResourceManager(baseName, assembly);
var str = res.GetString(ResourceKey);
return string.IsNullOrEmpty(str)
? $"[[{ResourceKey}]]"
: str;
}
}
}
Описание:
public sealed class LocalizedDescriptionAttribute : DescriptionAttribute
{
public string ResourceKey { get; }
public string BaseName { get; set; }
public Type ResourceType { get; set; }
public LocalizedDescriptionAttribute(string resourceKey)
{
ResourceKey = resourceKey;
}
public override string Description
{
get
{
var baseName = BaseName;
var assembly = ResourceType?.Assembly ?? Assembly.GetEntryAssembly();
if (baseName.IsNullOrEmpty())
{
// ReSharper disable once PossibleNullReferenceException
baseName = $"{(ResourceType != null ? ResourceType.Namespace : assembly.GetName().Name)}.Resources";
}
// ReSharper disable once AssignNullToNotNullAttribute
var res = new ResourceManager(baseName, assembly);
var str = res.GetString(ResourceKey);
return string.IsNullOrEmpty(str)
? $"[[{ResourceKey}]]"
: str;
}
}
}
Категории (PropertyGrid):
public sealed class LocalizedCategoryAttribute : CategoryAttribute
{
public string ResourceKey { get; }
public string BaseName { get; set; }
public Type ResourceType { get; set; }
public LocalizedCategoryAttribute(string resourceKey)
: base(resourceKey)
{
ResourceKey = resourceKey;
}
protected override string GetLocalizedString(string resourceKey)
{
var baseName = BaseName;
var assembly = ResourceType?.Assembly ?? Assembly.GetEntryAssembly();
if (baseName.IsNullOrEmpty())
{
// ReSharper disable once PossibleNullReferenceException
baseName = $"{(ResourceType != null ? ResourceType.Namespace : assembly.GetName().Name)}.Resources";
}
// ReSharper disable once AssignNullToNotNullAttribute
var res = new ResourceManager(baseName, assembly);
var str = res.GetString(resourceKey);
return string.IsNullOrEmpty(str)
? $"[[{ResourceKey}]]"
: str;
}
}
Пример:
[LocalizedDisplayName("ResourceKey", ResourceType = typeof(RE))]
Где «RE» живет в сборке, содержащей ваши файлы ресурсов, такие как «Resources.de.resx» или «Resources.en.resx».
Работает с перечислениями и свойствами.
Ваше здоровье
Ну и сборка есть 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
является ключом в файле ресурсов.