Bản địa hóa DisplayNameAttribute


120

Tôi đang tìm cách bản địa hóa các tên thuộc tính được hiển thị trong PropertyGrid. Tên của thuộc tính có thể bị "ghi đè" bằng cách sử dụng thuộc tính DisplayNameAttribute. Thật không may, các thuộc tính không thể có các biểu thức không phải là hằng số. Vì vậy, tôi không thể sử dụng các tài nguyên được đánh máy mạnh như:

class Foo
{
   [DisplayAttribute(Resources.MyPropertyNameLocalized)]  // do not compile
   string MyProperty {get; set;}
}

Tôi đã xem xét xung quanh và tìm thấy một số gợi ý để kế thừa từ DisplayNameAttribute để có thể sử dụng tài nguyên. Tôi sẽ kết thúc với mã như:

class Foo
{
   [MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed
   string MyProperty {get; set;}
}

Tuy nhiên, tôi bị mất các lợi ích tài nguyên được đánh máy mạnh mẽ, điều đó chắc chắn không phải là một điều tốt. Sau đó, tôi bắt gặp DisplayNameResourceAttribute , đây có thể là thứ tôi đang tìm kiếm. Nhưng nó phải nằm trong không gian tên Microsoft.VisualStudio.Modeling.Design và tôi không thể tìm thấy tham chiếu nào mà tôi phải thêm vào không gian tên này.

Có ai biết có cách nào dễ dàng hơn để bản địa hóa DisplayName theo cách tốt không? hoặc nếu có cách nào để sử dụng những gì Microsoft dường như đang sử dụng cho Visual Studio?


2
Điều gì về Hiển thị (ResourceType = typeof (ResourceStrings), Name = "MyProperty") xem msdn.microsoft.com/en-us/library/…
Peter

@Peter đọc kỹ bài đăng, anh ấy muốn hoàn toàn ngược lại, sử dụng ResourceStrings và kiểm tra thời gian biên dịch chứ không phải các chuỗi được mã hóa cứng ...
Marko

Câu trả lời:


113

thuộc tính Display từ System.ComponentModel.DataAnnotations trong .NET 4. Nó hoạt động trên MVC 3 PropertyGrid.

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

Thao tác này sẽ tìm kiếm một tài nguyên có tên UserNametrong MyResourcestệp .resx của bạn .


Tôi đã xem qua tất cả trước khi tìm thấy trang này ... đây là một cứu cánh. Cảm ơn! Hoạt động tốt trên MVC5 đối với tôi.
Kris

Nếu trình biên dịch đang phàn nàn về điều đó typeof(MyResources), bạn có thể cần đặt công cụ sửa đổi quyền truy cập tệp tài nguyên của mình thành Công khai .
thatWiseGuy

80

Chúng tôi đang thực hiện việc này cho một số thuộc tính để hỗ trợ nhiều ngôn ngữ. Chúng tôi đã thực hiện một cách tiếp cận tương tự đối với Microsoft, trong đó họ ghi đè các thuộc tính cơ sở của mình và chuyển một tên tài nguyên thay vì chuỗi thực tế. Tên tài nguyên sau đó được sử dụng để thực hiện tra cứu trong tài nguyên DLL cho chuỗi thực tế trả về.

Ví dụ:

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

Bạn có thể thực hiện bước này xa hơn khi thực sự sử dụng thuộc tính và chỉ định tên tài nguyên của bạn dưới dạng hằng số trong một lớp tĩnh. Bằng cách đó, bạn sẽ nhận được các khai báo như.

[LocalizedDisplayName(ResourceStrings.MyPropertyName)]
public string MyProperty
{
  get
  {
    ...
  }
}

Cập nhật
ResourceStrings sẽ trông giống như (lưu ý, mỗi chuỗi sẽ tham chiếu đến tên của một tài nguyên chỉ định chuỗi thực):

public static class ResourceStrings
{
    public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
    public const string FontSizeDisplayName="FontSizeDisplayName";
}

Khi tôi thử cách tiếp cận này, tôi nhận được thông báo lỗi cho biết "Đối số thuộc tính phải là biểu thức hằng, biểu thức typeof hoặc biểu thức tạo mảng của kiểu tham số thuộc tính". Tuy nhiên, việc chuyển giá trị cho LocalizedDisplayName dưới dạng một chuỗi hoạt động. Ước gì nó sẽ được đánh máy mạnh mẽ.
Azure SME

1
@Andy: Các giá trị trong ResourceStrings phải là hằng số, như được chỉ ra trong câu trả lời, không phải thuộc tính hoặc giá trị chỉ đọc. Chúng phải được đánh dấu là const và tham chiếu đến tên của các tài nguyên, nếu không bạn sẽ gặp lỗi.
Jeff Yates

1
Đã trả lời câu hỏi của riêng tôi, là về nơi bạn có Resources.ResourceManager, trong trường hợp của tôi các file resx là resx nào tạo nên nó[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith

nói rằng tôi cần một phiên bản của Resources.ResourceManager để gọi lấy chuỗi trên đó
topwik

1
@LTR: Không sao. Tôi rất vui vì bạn đã đến tận cùng của vấn đề. Rất vui được giúp đỡ, nếu tôi có thể.
Jeff Yates

41

Đây là giải pháp tôi đã kết thúc trong một hội đồng riêng biệt (được gọi là "Chung" trong trường hợp của tôi):

   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
   public class DisplayNameLocalizedAttribute : DisplayNameAttribute
   {
      public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey)
         : base(Utils.LookupResource(resourceManagerProvider, resourceKey))
      {
      }
   }

với mã để tra cứu tài nguyên:

  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
  }

Cách sử dụng điển hình sẽ là:

class Foo
{
      [Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"),
      Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")]
      public DateTime CreationDate
      {
         get;
         set;
      }
}

Điều gì là xấu xí khi tôi sử dụng chuỗi ký tự cho khóa tài nguyên. Sử dụng một hằng số có nghĩa là phải sửa đổi Resources.Designer.cs, điều này có lẽ không phải là một ý kiến ​​hay.

Kết luận: Tôi không hài lòng với điều đó, nhưng tôi thậm chí còn ít hài lòng hơn về việc Microsoft không thể cung cấp bất cứ thứ gì hữu ích cho một nhiệm vụ phổ biến như vậy.


Rất hữu ích. Cảm ơn. Trong tương lai, tôi hy vọng Microsoft sẽ đưa ra một giải pháp hay, cung cấp cách thức tham chiếu tài nguyên được đánh máy mạnh mẽ.
Johnny Oshika

ya nội dung chuỗi này thực sự rất khó :( Nếu bạn có thể lấy tên thuộc tính của thuộc tính sử dụng thuộc tính, bạn có thể làm điều đó theo quy ước theo cách cấu hình, nhưng điều này có vẻ không khả thi. Quan tâm đến "mạnh mẽ" enumerations, bạn có thể sử dụng, cũng không phải là thực sự duy trì: /
Rookian

Đó là một giải pháp tốt. Tôi sẽ không lặp lại qua bộ sưu tập các ResourceManagerthuộc tính. Thay vào đó bạn chỉ có thể có được tài sản trực tiếp từ các loại được cung cấp trong tham số:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer

1
Kết hợp điều này với mẫu T4 của @ zielu1 để tự động tạo khóa tài nguyên và bạn có một người chiến thắng xứng đáng!
David Keaveny

19

Sử dụng thuộc tính Display (từ System.ComponentModel.DataAnnotations) và biểu thức nameof () trong C # 6, bạn sẽ nhận được một giải pháp được bản địa hóa và đánh máy mạnh.

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

1
Trong ví dụ này, "MyResources" là gì? Một tệp resx được đánh máy mạnh? Một lớp học tùy chỉnh?
Greg

14

Bạn có thể sử dụng T4 để tạo hằng số. Tôi đã viết một:

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

Đầu ra sẽ như thế nào?
irfandar

9

Đây là một câu hỏi cũ, nhưng tôi nghĩ rằng đây là một vấn đề rất phổ biến, và đây là giải pháp của tôi trong MVC 3.

Đầu tiên, một mẫu T4 là cần thiết để tạo các hằng số để tránh các chuỗi khó chịu. Chúng tôi có một tệp tài nguyên 'Labels.resx' chứa tất cả các chuỗi nhãn. Do đó, mẫu T4 sử dụng trực tiếp tệp tài nguyên,

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

Sau đó, một phương thức tiện ích mở rộng được tạo để bản địa hóa 'Tên hiển thị',

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

Thuộc tính 'DisplayName' được thay thế bằng thuộc tính 'DisplayLabel' để tự động đọc từ '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;
            }
        }
    }
}

Sau tất cả những công việc chuẩn bị đó, đã đến lúc chạm vào các thuộc tính xác thực mặc định đó. Tôi đang sử dụng thuộc tính 'Bắt ​​buộc' làm ví dụ,

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

    }
}

Bây giờ, chúng tôi có thể áp dụng các thuộc tính đó trong mô hình của mình,

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

Theo mặc định, tên thuộc tính được sử dụng làm khóa để tra cứu 'Label.resx', nhưng nếu bạn đặt nó thông qua 'DisplayLabel', nó sẽ sử dụng nó thay thế.


6

Bạn có thể phân lớp DisplayNameAttribute để cung cấp i18n, bằng cách ghi đè một trong các phương thức. Như vậy. chỉnh sửa: Bạn có thể phải giải quyết việc sử dụng một hằng số cho khóa.

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

Tôi sử dụng cách này để giải quyết trong trường hợp của tôi

[LocalizedDisplayName("Age", NameResourceType = typeof(RegistrationResources))]
 public bool Age { get; set; }

Với mã

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

Vâng, hội đồng là Microsoft.VisualStudio.Modeling.Sdk.dll. đi kèm với Visual Studio SDK (Với Gói tích hợp Visual Studio).

Nhưng nó sẽ được sử dụng theo cách khá giống với thuộc tính của bạn; không có cách nào để sử dụng các loại tài nguyên mạnh mẽ trong các thuộc tính chỉ vì chúng không phải là hằng số.


0

Tôi xin lỗi vì mã VB.NET, C # của tôi hơi bị lỗi ... Nhưng bạn sẽ hiểu, phải không?

Trước hết, hãy tạo một lớp mới : LocalizedPropertyDescriptor, kế thừa PropertyDescriptor. Ghi đè thuộc DisplayNametính như thế này:

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 là ResourceManager của tệp tài nguyên chứa các bản dịch của bạn.

Tiếp theo, triển khai ICustomTypeDescriptortrong lớp với các thuộc tính được bản địa hóa và ghi đè GetPropertiesphương thức:

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

Bây giờ, bạn có thể sử dụng thuộc tính 'DisplayName` để lưu trữ tham chiếu đến một giá trị trong tệp tài nguyên ...

<DisplayName("prop_description")> _
Public Property Description() As String

prop_description là khóa trong tệp tài nguyên.


Phần đầu tiên của giải pháp của bạn là những gì tôi đã làm ... cho đến khi tôi phải giải quyết "Some.ResourceManager là gì?" câu hỏi. Tôi có phải cung cấp một chuỗi chữ thứ hai chẳng hạn như "MyAssembly.Resources.Resource" không? quá nguy hiểm! Đối với phần thứ hai (ICustomTypeDescriptor) Tôi không nghĩ rằng nó thực sự hữu ích
PowerKiKi

Giải pháp của Marc Gravell là cách để đi nếu bạn không cần bất kỳ thứ gì khác ngoài Tên hiển thị đã dịch - Tôi cũng sử dụng bộ mô tả tùy chỉnh cho những thứ khác và đây là giải pháp của tôi. Tuy nhiên, không có cách nào để làm điều này mà không cung cấp một số loại chìa khóa.
Vincent Van Den Berghe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.