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
Peter

@Peterは投稿を注意深く読み、ResourceStringsとコンパイル時間チェックを使用して、ハードコードされた文字列ではなく、正反対を求めています...
Marko

回答:


113

.NET 4にはSystem.ComponentModel.DataAnnotations のDisplay属性があります。これはMVC 3で動作しPropertyGridます。

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

これはUserNameMyResources.resxファイルで指定されたリソースを検索します。


私はこのページを見つける前に一通り見ました...これはそのような命の恩人です。ありがとう!MVC5でうまく動作します。
クリス

コンパイラがについて不平を言っている場合は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としてマークされ、リソースの名前を参照する必要があります。そうしないと、エラーが発生します。
ジェフイェイツ

1
私自身の質問に答えました、Resources.ResourceManagerの場所についてでした。私の場合、resxファイルはpublic resxで生成されたものでした[MyNamespace].[MyResourceFile].ResourceManager.GetString("MyString");
Tristan Warner-Smith

get stringを呼び出すためにResources.ResourceManagerのインスタンスが必要だと言っています
topwik

1
@LTR:問題ありません。問題の底に着いてよかったです。できればお手伝いさせていただきます。
ジェフイェーツ2012

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にはさらに不満があります。


非常に便利。ありがとう。将来的には、マイクロソフトがリソースを参照するための厳密に型指定された方法を提供する優れたソリューションを考え出すことを期待しています。
ジョニー・オシカ

yaこの文字列の要素は本当に大変です:(属性を使用するプロパティのプロパティ名を取得できれば、コンベンショナルオーバーコンフィギュレーションの方法でそれを行うことができますが、これは可能ではないようです。使用できる列挙型も実際には保守可能ではありません:/
Rookian

それは良い解決策です。ResourceManagerプロパティのコレクションを繰り返し処理しないだけです。:代わりに、単にパラメータに指定されたタイプから直接プロパティを取得することができますPropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
Maksymilian Majer

1
これを@ zielu1のT4テンプレートと組み合わせてリソースキーを自動生成すると、価値のある勝者がいます!
David Keaveny、2011

19

C#6でDisplay属性(System.ComponentModel.DataAnnotationsから)とnameof()式を使用すると、ローカライズされた厳密に型指定されたソリューションが得られます。

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

1
この例では、「MyResources」とは何ですか?強く型付けされたresxファイル?カスタムクラス?
グレッグ

14

T4を使用して定数を生成できます。私は1つ書いた:

<#@ 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 2017年

9

これは古い質問ですが、これは非常に一般的な問題だと思います。MVC3での私の解決策を次に示します。

まず、厄介な文字列を回避するために定数を生成するには、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;
        }
    }
}

「Label.resx」から自動的に読み取るために、「DisplayName」属性は「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

メソッドの1つをオーバーライドすることにより、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 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」などの2番目のリテラル文字列を指定することになっていますか?危険すぎる!2番目の部分(ICustomTypeDescriptor)については、実際には役に立たないと思います
PowerKiKi

Marc Gravellのソリューションは、翻訳されたDisplayName以外のものが必要ない場合の解決策です-私は他のものにもカスタム記述子を使用しており、これが私のソリューションでした。ただし、何らかのキーを提供せずにこれを行う方法はありません。
Vincent Van Den Berghe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.