DisplayNameAttribute Yerelleştirmesi


120

PropertyGrid'de görüntülenen özellik adlarını yerelleştirmenin bir yolunu arıyorum. Özelliğin adı, DisplayNameAttribute özniteliği kullanılarak "geçersiz kılınabilir". Ne yazık ki özniteliklerin sabit olmayan ifadeleri olamaz. Bu nedenle, güçlü yazılmış kaynakları kullanamıyorum, örneğin:

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

Etrafa bir göz attım ve kaynağı kullanabilmek için DisplayNameAttribute'tan devralmak için bazı öneriler buldum. Şunun gibi bir kodla sonuçlanırdım:

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

Bununla birlikte, kesinlikle iyi bir şey olmayan güçlü yazılmış kaynak avantajlarını kaybediyorum. Sonra aradığım şey olan DisplayNameResourceAttribute ile karşılaştım . Ancak Microsoft.VisualStudio.Modeling.Design ad alanında olması gerekiyor ve bu ad alanı için eklemem gereken referansı bulamıyorum.

DisplayName yerelleştirmesini iyi bir şekilde gerçekleştirmenin daha kolay bir yolu olup olmadığını bilen var mı? ya da Microsoft'un Visual Studio için kullandığı bir şeyi kullanmanın bir yolu varsa?


2
Display hakkında ne (KaynakTürü = typeof (ResourceStrings), Ad = "MyProperty") bkz msdn.microsoft.com/en-us/library/...
Peter

@Peter gönderiyi dikkatlice okudu, ResourceStrings kullanarak tam tersini istiyor ve kodlanmış dizeleri değil derleme zamanı kontrolü ...
Marko

Yanıtlar:


113

NET 4'te System.ComponentModel.DataAnnotations'dan Display özniteliği vardır PropertyGrid. MVC 3 üzerinde çalışır .

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

Bu UserName, MyResources.resx dosyanızda adlı bir kaynağı arar .


Bu sayfayı bulmadan önce her yere baktım ... bu tam bir hayat kurtarıcı. Teşekkürler! MVC5'te benim için iyi çalışıyor.
Kris

Derleyici şikayet ediyorsa, typeof(MyResources)kaynak dosyası erişim değiştiricinizi Genel olarak ayarlamanız gerekebilir .
thatWiseGuy

80

Bunu, birden çok dili desteklemek için bir dizi özellik için yapıyoruz. Microsoft'a da benzer bir yaklaşım uyguladık, burada temel niteliklerini geçersiz kılıyorlar ve gerçek dizeden ziyade bir kaynak adı geçiyorlar. Kaynak adı daha sonra döndürülecek gerçek dizenin DLL kaynaklarında bir arama gerçekleştirmek için kullanılır.

Örneğin:

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

Özniteliği gerçekten kullanırken bunu bir adım öteye taşıyabilir ve kaynak adlarınızı statik bir sınıfta sabitler olarak belirtebilirsiniz. Bu şekilde, gibi beyanlar alırsınız.

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

Güncelleme
ResourceStrings şunun gibi görünecektir (not, her dizge gerçek dizeyi belirten bir kaynağın adına başvurur):

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

Bu yaklaşımı denediğimde, "Bir öznitelik bağımsız değişkeni sabit bir ifade, typeof ifadesi veya bir öznitelik parametresi türünün dizi oluşturma ifadesi olmalıdır" şeklinde bir hata mesajı alıyorum. Ancak, değeri LocalizedDisplayName'e bir dize olarak geçirmek çalışır. Güçlü bir şekilde yazılmış olmasını isterdim.
Azure SME

1
@Andy: ResourceStrings'deki değerler, özelliklerde veya salt okunur değerler değil, yanıtta belirtildiği gibi sabit olmalıdır. Sabit olarak işaretlenmeleri ve kaynakların adlarına atıfta bulunmaları gerekir, aksi takdirde bir hata alırsınız.
Jeff Yates

1
Kendi sorumu yanıtladım, Kaynakların nerede bulunduğuyla ilgiliydi. ResourceManager, benim durumumda resx dosyaları halka açık resx oluşturuldu, bu yüzden[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith

üzerinde get stringi çağırmak için Resources.ResourceManager örneğine ihtiyacım olduğunu söylüyor
topwik

1
@LTR: Sorun değil. Sorunun altına inmenize sevindim. Mümkünse yardımcı olmaktan mutluluk duyarım.
Jeff Yates

41

İşte ayrı bir derlemede bulduğum çözüm (benim durumumda "Ortak" olarak adlandırılır):

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

kaynağı aramak için kodla:

  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
  }

Tipik kullanım şöyle olacaktır:

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

Kaynak anahtarı için değişmez dizeler kullandığım için oldukça çirkin olan şey. Bir sabit kullanmak, Resources.Designer.cs'yi değiştirmek anlamına gelir ki bu muhtemelen iyi bir fikir değildir.

Sonuç: Bundan memnun değilim, ancak bu kadar yaygın bir görev için yararlı hiçbir şey sağlayamayan Microsoft'tan daha da az mutluyum.


Çok kullanışlı. Teşekkürler. Gelecekte, Microsoft'un kaynaklara atıfta bulunmak için güçlü bir şekilde yazılmış bir yol sunan güzel bir çözüm bulacağını umuyorum.
Johnny Oshika

ya bu dizge gerçekten çok berbat :( Eğer özniteliği kullanan mülkün özellik adını alabiliyorsanız, bunu yapılandırma yolu yerine konvansiyonda yapabilirdiniz, ancak bu mümkün görünmüyor. Kullanabileceğiniz numaralandırmalar da gerçekten sürdürülemez: /
Rookian

Bu iyi bir çözüm. Sadece ResourceManagermülk koleksiyonunu yinelemem . Bunun yerine, mülkü doğrudan parametrede sağlanan türden alabilirsiniz:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer

1
Kaynak anahtarlarını otomatik olarak oluşturmak için bunu @ zielu1'in T4 şablonuyla birleştirin ve değerli bir kazananınız olsun!
David Keaveny

19

C # 6'daki Display özniteliğini (System.ComponentModel.DataAnnotations'dan) ve nameof () ifadesini kullanarak, yerelleştirilmiş ve kesin olarak yazılmış bir çözüm elde edersiniz.

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

1
Bu örnekte, "Kaynaklarım" nedir? Kesin yazılmış bir resx dosyası mı? Özel bir sınıf mı?
Greg

14

Sabitleri oluşturmak için T4'ü kullanabilirsiniz. Bir tane yazdım:

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

Çıktı nasıl olur?
irfandar

9

Bu eski bir soru, ancak bunun çok yaygın bir sorun olduğunu düşünüyorum ve işte MVC 3'teki çözümüm.

İlk olarak, kötü dizelerden kaçınmak için sabitler oluşturmak için bir T4 şablonuna ihtiyaç vardır. Tüm etiket dizelerini tutan 'Labels.resx' kaynak dosyamız var. Bu nedenle T4 şablonu kaynak dosyasını doğrudan kullanır,

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

Ardından, 'DisplayName'i yerelleştirmek için bir uzantı yöntemi oluşturulur,

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

"Labels.resx" ten otomatik olarak okunabilmesi için "DisplayName" özniteliğinin yerini "DisplayLabel" özniteliği almıştır,

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

Tüm bu hazırlık çalışmalarından sonra, bu varsayılan doğrulama özelliklerine dokunma zamanı. Örnek olarak "Gerekli" özelliğini kullanıyorum,

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

    }
}

Şimdi bu özellikleri modelimize uygulayabiliriz,

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

Varsayılan olarak, özellik adı 'Label.resx'i aramak için anahtar olarak kullanılır, ancak bunu' DisplayLabel 'üzerinden ayarlarsanız, bunun yerine onu kullanır.


6

Yöntemlerden birini geçersiz kılarak i18n sağlamak için DisplayNameAttribute öğesini alt sınıflandırabilirsiniz. Öyle gibi. düzenleme: Anahtar için bir sabit kullanmak zorunda kalabilirsiniz.

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

Benim durumumda bu şekilde çözüyorum

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

Kod ile

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

Meclis öyle Microsoft.VisualStudio.Modeling.Sdk.dll. Visual Studio SDK (Visual Studio Entegrasyon Paketi ile) ile birlikte gelir.

Ancak, özniteliğinizle hemen hemen aynı şekilde kullanılır; Sabit olmadıkları için özniteliklerde güçlü tür kaynakları kullanmanın bir yolu yoktur.


0

VB.NET kodu için özür dilerim, benim C # kodum biraz paslanmış ... Ama fikri anlayacaksınız, değil mi?

Her şeyden önce, LocalizedPropertyDescriptormiras alan yeni bir sınıf oluşturun PropertyDescriptor. DisplayNameÖzelliği şu şekilde geçersiz kılın :

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 çevirilerinizi içeren kaynak dosyanın ResourceManager'ıdır.

Ardından, ICustomTypeDescriptoryerelleştirilmiş özelliklerle sınıfta uygulayın ve GetPropertiesyöntemi geçersiz kılın :

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

Artık bir kaynak dosyasındaki bir değere referans depolamak için "DisplayName" özelliğini kullanabilirsiniz ...

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

prop_description kaynak dosyasındaki anahtardır.


Çözümünüzün ilk kısmı yaptığım şeydi ... ta ki "Some.ResourceManager nedir?" soru. "MyAssembly.Resources.Resource" gibi ikinci bir hazır bilgi dizisi vermem gerekiyor mu? çok tehlikeli! İkinci bölüme gelince (ICustomTypeDescriptor) gerçekten yararlı olduğunu düşünmüyorum
PowerKiKi

Marc Gravell'in çözümü, çevrilmiş bir DisplayName'den başka bir şeye ihtiyacınız yoksa gitmenin yoludur - diğer şeyler için de özel tanımlayıcıyı kullanıyorum ve bu benim çözümümdü. Yine de bir çeşit anahtar sağlamadan bunu yapmanın bir yolu yok.
Vincent Van Den Berghe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.