การแปลเป็นภาษาท้องถิ่นของ DisplayNameAttribute


120

ฉันกำลังมองหาวิธีการแปลชื่อคุณสมบัติที่แสดงใน PropertyGrid ชื่อของคุณสมบัติอาจเป็น "overriden" โดยใช้แอตทริบิวต์ 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 namespace และฉันไม่พบข้อมูลอ้างอิงที่ฉันควรจะเพิ่มสำหรับเนมสเปซนี้

มีใครรู้บ้างว่ามีวิธีที่ง่ายกว่าในการปรับให้เข้ากับ DisplayName ด้วยวิธีที่ดีกว่านี้หรือไม่ หรือมีวิธีใช้สิ่งที่ Microsoft ดูเหมือนจะใช้กับ Visual Studio หรือไม่?


2
สิ่งที่เกี่ยวกับ Display (ResourceType = typeof (ResourceStrings), Name = "MyProperty") โปรดดูmsdn.microsoft.com/en-us/library/…
ปีเตอร์

@Peter อ่านโพสต์อย่างระมัดระวังเขาต้องการสิ่งที่ตรงกันข้ามโดยใช้ ResourceStrings และรวบรวมการตรวจสอบเวลาไม่ใช่สตริงที่เข้ารหัสยาก ...
Marko

คำตอบ:


113

มีเป็นคุณลักษณะแสดงจาก System.ComponentModel.DataAnnotations ใน .NET 4 มันทำงานบน MVC PropertyGrid3

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

สิ่งนี้ค้นหาทรัพยากรที่มีชื่อUserNameในMyResourcesไฟล์. resx ของคุณ


ฉันมองไปทั่วก่อนจะพบหน้านี้ ... นี่คือตัวช่วยชีวิต ขอบคุณ! ใช้ได้ดีกับ MVC5 สำหรับฉัน
Kris

ถ้าคอมไพเลอร์จะบ่นเกี่ยวกับ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 และอ้างถึงชื่อของทรัพยากรมิฉะนั้นคุณจะได้รับข้อผิดพลาด
Jeff Yates

1
ตอบคำถามของฉันเองว่าคุณมีทรัพยากรที่ไหน ResourceManager ในกรณีของฉันไฟล์ resx เป็น resx สาธารณะที่สร้างขึ้นดังนั้นจึงเป็น[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith

บอกว่าฉันต้องการอินสแตนซ์ของ Resources.ResourceManager เพื่อที่จะเรียกรับสตริง
topwik

1
@LTR: ไม่มีปัญหา ฉันดีใจที่คุณเข้าใจประเด็นนี้ ยินดีให้ความช่วยเหลือถ้าทำได้
Jeff Yates

41

นี่คือวิธีแก้ปัญหาที่ฉันพบในชุดประกอบแยกต่างหาก (เรียกว่า "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 ที่ไม่สามารถให้อะไรที่เป็นประโยชน์สำหรับงานทั่วไปเช่นนี้ได้


มีประโยชน์มาก. ขอบคุณ ในอนาคตฉันหวังว่า Microsoft จะมีโซลูชันที่ดีซึ่งนำเสนอวิธีการอ้างอิงทรัพยากรที่พิมพ์ผิด
Johnny Oshika

ใช่แล้วสตริงนี้ยากมาก :( หากคุณสามารถรับชื่อคุณสมบัติของคุณสมบัติที่ใช้แอตทริบิวต์คุณสามารถทำได้ในรูปแบบมากกว่าวิธีการกำหนดค่า แต่ดูเหมือนจะไม่สามารถทำได้การดูแล "อย่างยิ่ง tpyed" การแจงนับที่คุณสามารถใช้ได้ก็ไม่สามารถบำรุงรักษาได้จริง: /
Rookian

นั่นเป็นทางออกที่ดี ฉันจะไม่ทำซ้ำผ่านการรวบรวมResourceManagerคุณสมบัติ แต่คุณสามารถรับคุณสมบัติโดยตรงจากประเภทที่ระบุไว้ในพารามิเตอร์:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer

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 ที่พิมพ์ยาก? คลาสที่กำหนดเอง?
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" ถูกแทนที่ด้วยแอตทริบิวต์ "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ดีประกอบเป็น ซึ่งมาพร้อมกับ Visual Studio SDK (With Visual Studio Integration Package)

แต่จะใช้ในลักษณะเดียวกับแอตทริบิวต์ของคุณ ไม่มีวิธีใดที่จะใช้ทรัพยากรประเภทที่รุนแรงในคุณลักษณะเพียงเพราะไม่คงที่


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

โซลูชันของ Marc Gravell เป็นวิธีที่จะไปหากคุณไม่ต้องการสิ่งอื่นใดนอกจาก DisplayName ที่แปลแล้ว - ฉันใช้ตัวบอกที่กำหนดเองสำหรับสิ่งอื่น ๆ ด้วยและนี่คือวิธีแก้ปัญหาของฉัน ไม่มีวิธีใดที่จะทำสิ่งนี้ได้โดยไม่ต้องจัดหาคีย์บางประเภท
Vincent Van Den Berghe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.