Lokalisierung von DisplayNameAttribute

120

Ich suche nach einer Möglichkeit, in einem PropertyGrid angezeigte Eigenschaftsnamen zu lokalisieren. Der Name der Eigenschaft kann mithilfe des Attributs DisplayNameAttribute "überschrieben" werden. Leider können Attribute keine nicht konstanten Ausdrücke haben. Daher kann ich keine stark typisierten Ressourcen wie Folgendes verwenden:

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

Ich habe mich umgesehen und einen Vorschlag gefunden, von DisplayNameAttribute zu erben, um Ressourcen verwenden zu können. Am Ende würde ich Code haben wie:

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

Ich verliere jedoch stark typisierte Ressourcenvorteile, was definitiv keine gute Sache ist. Dann bin ich auf DisplayNameResourceAttribute gestoßen , nach dem ich vielleicht suche. Aber es soll im Microsoft.VisualStudio.Modeling.Design-Namespace sein und ich kann nicht finden, welche Referenz ich für diesen Namespace hinzufügen soll.

Weiß jemand, ob es einen einfacheren Weg gibt, die Lokalisierung von DisplayName auf gute Weise zu erreichen? oder ob es eine Möglichkeit gibt, das zu verwenden, was Microsoft für Visual Studio zu verwenden scheint?

PowerKiKi
quelle
2
Was ist mit Anzeige (ResourceType = typeof (ResourceStrings), Name = "MyProperty") siehe msdn.microsoft.com/en-us/library/…
Peter
@ Peter lesen Sie den Beitrag sorgfältig, er will genau das Gegenteil, mit ResourceStrings und überprüfen Sie die Kompilierungszeit nicht fest codierte Zeichenfolgen ...
Marko

Antworten:

113

In .NET 4 gibt es das Display-Attribut von System.ComponentModel.DataAnnotations. Es funktioniert auf der MVC 3 PropertyGrid.

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

Dadurch wird eine UserNamein Ihrer MyResourcesRESX-Datei genannte Ressource nachgeschlagen.

RandomEngy
quelle
Ich habe mich umgesehen, bevor ich diese Seite gefunden habe ... das ist so ein Lebensretter. Vielen Dank! Funktioniert gut auf MVC5 für mich.
Kris
Wenn sich der Compiler beschwert typeof(MyResources), müssen Sie möglicherweise den Modifikator für den Zugriff auf Ressourcendateien auf " Öffentlich" setzen .
thatWiseGuy
80

Wir tun dies für eine Reihe von Attributen, um mehrere Sprachen zu unterstützen. Wir haben einen ähnlichen Ansatz wie Microsoft gewählt, bei dem sie ihre Basisattribute überschreiben und einen Ressourcennamen anstelle der eigentlichen Zeichenfolge übergeben. Der Ressourcenname wird dann verwendet, um in den DLL-Ressourcen nach der tatsächlichen Zeichenfolge zu suchen, die zurückgegeben werden soll.

Beispielsweise:

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

Sie können noch einen Schritt weiter gehen, wenn Sie das Attribut tatsächlich verwenden, und Ihre Ressourcennamen als Konstanten in einer statischen Klasse angeben. Auf diese Weise erhalten Sie Erklärungen wie.

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

Das Update
ResourceStrings würde ungefähr so ​​aussehen (beachten Sie, dass sich jede Zeichenfolge auf den Namen einer Ressource bezieht, die die tatsächliche Zeichenfolge angibt):

public static class ResourceStrings
{
    public const string ForegroundColorDisplayName="ForegroundColorDisplayName";
    public const string FontSizeDisplayName="FontSizeDisplayName";
}
Jeff Yates
quelle
Wenn ich diesen Ansatz versuche, wird die Fehlermeldung "Ein Attributargument muss ein konstanter Ausdruck, ein Ausdruckstyp oder ein Arrayerstellungsausdruck eines Attributparametertyps sein" angezeigt. Das Übergeben des Werts an LocalizedDisplayName als Zeichenfolge funktioniert jedoch. Ich wünschte, es wäre stark getippt.
Azure SME
1
@Andy: Die Werte in ResourceStrings müssen Konstanten sein, wie in der Antwort angegeben, keine Eigenschaften oder schreibgeschützte Werte. Sie müssen als const markiert sein und sich auf die Namen der Ressourcen beziehen, sonst wird eine Fehlermeldung angezeigt.
Jeff Yates
1
Beantwortete meine eigene Frage, war, wo Sie den Resources.ResourceManager hatten, in meinem Fall sind die Resx-Dateien öffentliche Resx generiert, so war es[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith
sagt, ich brauche eine Instanz von Resources.ResourceManager, um get string drauf
aufzurufen
1
@LTR: Kein Problem. Ich bin froh, dass Sie dem Problem auf den Grund gegangen sind. Gerne helfen wir, wenn ich kann.
Jeff Yates
41

Hier ist die Lösung, die ich in einer separaten Assembly gefunden habe (in meinem Fall "Common" genannt):

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

mit dem Code zum Nachschlagen der Ressource:

  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
  }

Typische Verwendung wäre:

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

Was ziemlich hässlich ist, wenn ich Literalzeichenfolgen als Ressourcenschlüssel verwende. Die Verwendung einer Konstanten würde bedeuten, Resources.Designer.cs zu ändern, was wahrscheinlich keine gute Idee ist.

Fazit: Ich bin damit nicht zufrieden, aber noch weniger glücklich über Microsoft, das für eine so häufige Aufgabe nichts Nützliches bieten kann.

PowerKiKi
quelle
Sehr hilfreich. Vielen Dank. Ich hoffe, dass Microsoft in Zukunft eine nette Lösung findet, die eine stark typisierte Art der Referenzierung der Ressourcen bietet.
Johnny Oshika
ya dieses String-Zeug ist wirklich scheiße :( Wenn Sie den Eigenschaftsnamen der Eigenschaft erhalten könnten, die das Attribut verwendet, könnten Sie dies in der Konvention über die Konfigurationsmethode tun, aber dies scheint nicht möglich zu sein. Aufzählungen, die Sie verwenden könnten, sind auch nicht wirklich wartbar: /
Rookian
Das ist eine gute Lösung. Ich würde die Sammlung von ResourceManagerEigenschaften einfach nicht durchlaufen . Stattdessen können Sie die Eigenschaft einfach direkt von dem im Parameter angegebenen Typ abrufen:PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer
1
Kombinieren Sie dies mit der T4-Vorlage von @ zielu1, um die Ressourcenschlüssel automatisch zu generieren, und Sie haben einen würdigen Gewinner!
David Keaveny
19

Mit dem Display- Attribut (aus System.ComponentModel.DataAnnotations) und dem Ausdruck nameof () in C # 6 erhalten Sie eine lokalisierte und stark typisierte Lösung.

[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))]
public string UserName { get; set; }
Dionoid
quelle
1
Was ist in diesem Beispiel "MyResources"? Eine stark typisierte Resx-Datei? Eine benutzerdefinierte Klasse?
Greg
14

Sie können T4 verwenden, um Konstanten zu generieren. Ich schrieb einen:

<#@ 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#>";
  <# } #>
  }
 }
}
zielu1
quelle
Wie würde die Ausgabe sein?
Irfandar
9

Dies ist eine alte Frage, aber ich denke, dies ist ein sehr häufiges Problem, und hier ist meine Lösung in MVC 3.

Erstens wird eine T4-Vorlage benötigt, um Konstanten zu generieren, um unangenehme Zeichenfolgen zu vermeiden. Wir haben eine Ressourcendatei 'Labels.resx', die alle Beschriftungszeichenfolgen enthält. Daher verwendet die T4-Vorlage die Ressourcendatei direkt.

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

Anschließend wird eine Erweiterungsmethode erstellt, um den 'Anzeigenamen' zu lokalisieren.

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

Das Attribut 'DisplayName' wird durch das Attribut 'DisplayLabel' ersetzt, um automatisch aus 'Labels.resx' zu lesen.

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

Nach all diesen Vorbereitungsarbeiten ist es Zeit, diese Standardvalidierungsattribute zu berühren. Ich verwende das Attribut 'Erforderlich' als Beispiel.

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

    }
}

Jetzt können wir diese Attribute in unserem Modell anwenden.

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

Standardmäßig wird der Eigenschaftsname als Schlüssel zum Nachschlagen von 'Label.resx' verwendet. Wenn Sie ihn jedoch über 'DisplayLabel' festlegen, wird er stattdessen verwendet.

YYFish
quelle
6

Sie können DisplayNameAttribute in die Unterklasse i18n einordnen, indem Sie eine der Methoden überschreiben. Wie so. Bearbeiten: Möglicherweise müssen Sie sich mit der Verwendung einer Konstante für den Schlüssel zufrieden geben.

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" } } } });
    }
}
Marc Gravell
quelle
2

Ich benutze diese Lösung in meinem Fall

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

Mit dem Code

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

}
HaikMnatsakanyan
quelle
1

Nun, die Versammlung ist Microsoft.VisualStudio.Modeling.Sdk.dll. Dies wird mit dem Visual Studio SDK (mit Visual Studio Integration Package) geliefert.

Aber es würde ziemlich genau so verwendet wie Ihr Attribut; Es gibt keine Möglichkeit, stark typisierte Ressourcen in Attributen zu verwenden, nur weil sie nicht konstant sind.

Konfigurator
quelle
0

Ich entschuldige mich für den VB.NET-Code, mein C # ist etwas verrostet ... Aber Sie werden auf die Idee kommen, oder?

Erstellen Sie zunächst eine neue Klasse : LocalizedPropertyDescriptor, die erbt PropertyDescriptor. Überschreiben Sie die DisplayNameEigenschaft wie folgt :

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 ist der ResourceManager der Ressourcendatei, die Ihre Übersetzungen enthält.

Implementieren Sie als Nächstes ICustomTypeDescriptorin der Klasse die lokalisierten Eigenschaften und überschreiben Sie die GetPropertiesMethode:

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

Sie können jetzt das Attribut 'DisplayName' verwenden, um einen Verweis auf einen Wert in einer Ressourcendatei zu speichern ...

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

prop_description ist der Schlüssel in der Ressourcendatei.

Vincent Van Den Berghe
quelle
Der erste Teil Ihrer Lösung ist das, was ich getan habe ... bis ich den "Was ist Some.ResourceManager?" Frage. Soll ich eine zweite Literalzeichenfolge wie "MyAssembly.Resources.Resource" angeben? viel zu gefährlich! Was den zweiten Teil (ICustomTypeDescriptor) betrifft, denke ich nicht, dass er wirklich nützlich ist
PowerKiKi
Die Lösung von Marc Gravell ist der richtige Weg, wenn Sie nichts anderes als einen übersetzten Anzeigenamen benötigen. Ich verwende den benutzerdefinierten Deskriptor auch für andere Dinge, und dies war meine Lösung. Es gibt jedoch keine Möglichkeit, dies zu tun, ohne einen Schlüssel anzugeben.
Vincent Van Den Berghe