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
Display (ResourceType = typeof (ResourceStrings), Name = "MyProperty")는 msdn.microsoft.com/en-us/library/…를
Peter

주의 깊게 게시물을 읽을 @ 피터, 그는 ... 정반대 사용 ResourceStrings 및 컴파일 시간 체크하지 하드 코드 된 문자열을 원한다
마르코

답변:


113

.NET 4에는 System.ComponentModel.DataAnnotations 의 Display 속성이 있습니다 PropertyGrid. MVC 3에서 작동합니다 .

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

라는 이름의 자원까지이 모양 UserName당신에 MyResources된 .resx 파일.


나는이 페이지를 찾기 전에 모든 것을 훑어 보았다. 이것은 생명의 은인이다. 감사! 나를 위해 MVC5에서 잘 작동합니다.
Kris

컴파일러가 불만을 제기하는 경우 typeof(MyResources)리소스 파일 액세스 수정자를 Public 으로 설정해야 할 수 있습니다 .
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로 표시되고 리소스 이름을 참조해야합니다. 그렇지 않으면 오류가 발생합니다.
Jeff Yates

1
내 자신의 질문에 대답, 내 경우에 RESX 파일이 그것이 있도록 공공 RESX가 발생하면 Resources.ResourceManager했다 장소에 대한했다[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
트리스탄 워너 - 스미스

내가 그것에 문자열 가져 오기를 호출하려면 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가 리소스를 참조하는 강력한 형식의 방법을 제공하는 멋진 솔루션을 제공하기를 바랍니다.
Johnny Oshika

ya this string stuffs really hard :( 만약 당신이 속성을 사용하는 속성의 속성 이름을 얻을 수 있다면, 당신은 컨피규레이션 방식을 통해 그것을 할 수 있지만 이것은 불가능한 것 같습니다. "강하게 tpyed"를 돌보는 것 사용할 수있는 열거
형도

그것은 좋은 해결책입니다. ResourceManager속성 컬렉션을 반복하지 않을 것 입니다. 대신 당신은 단순히 매개 변수에 제공된 유형에서 직접 속성을 얻을 수 있습니다PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian 마예

1
이를 @ zielu1의 T4 템플릿과 결합하여 리소스 키를 자동 생성하면 합당한 승자가됩니다!
David Keaveny

19

은 Using 디스플레이 (System.ComponentModel.DataAnnotations에서) 속성과 nameof () C # 6의 발현을, 당신은 지역화 및 강력한 형식의 솔루션을 얻을 수 있습니다.

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

1
이 예에서 "MyResources"는 무엇입니까? 강력한 형식의 resx 파일? 커스텀 클래스?
Greg

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

출력은 어떻게됩니까?
irfandar

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'속성은 'Labels.resx'에서 자동으로 읽기 위해 'DisplayLabel'속성으로 대체됩니다.

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. Visual Studio SDK (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 리소스 파일의 키입니다.


솔루션의 첫 번째 부분은 "What is Some.ResourceManager?"를 해결해야 할 때까지 제가 한 일입니다. 질문. "MyAssembly.Resources.Resource"와 같은 두 번째 리터럴 문자열 을 제공해야 합니까? 너무 위험 해! 두 번째 부분 (은 ICustomTypeDescriptor)에 관해서는 나는 실제로 유용한 생각하지 않습니다
PowerKiKi

Marc Gravell의 솔루션은 번역 된 DisplayName 외에 다른 것이 필요하지 않은 경우 사용할 수있는 방법입니다. 다른 항목에도 사용자 정의 설명자를 사용하고 이것이 제 솔루션이었습니다. 그러나 일종의 키를 제공하지 않고는이를 수행 할 수 없습니다.
Vincent Van Den Berghe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.