23

I'm working on a class that is going to be used by some people from another countries. I have to localize every message, warning e.c. so that they can understand what we mean. In many cases i achieved my goal. But these property attributes like descriptions are such a pain in the ass.

Here`s what I have right now:

[Category("Editable Values"), Description("Sets the minimum select...")]
    public Ampere Simin
    {
        get
        {...}
        set
        {...}
    }

and

[Category("Editable Values"), Description(Localisation.Simin)] // "Localisation" here is the internal resource file that i wrote for messages, warnings, exceptions and -unfortunately- descriptions
        public Ampere Simin
        {
            get
            {...}
            set
            {...}
        }

That's what I'm trying to do. But it's not possible to use Localisations this way. Any Suggestions about something that I can use instead of it?

Bedir Yilmaz
  • 3,823
  • 5
  • 34
  • 54

6 Answers6

26

Subclasses:

[STAThread]
static void Main()
{   // just some example code to show it working in winforms, but
    // anything using System.ComponentModel should see the change
    Application.EnableVisualStyles();        
    Application.Run(new Form {Controls = {new PropertyGrid {Dock = DockStyle.Fill, SelectedObject = new Foo()}}});
}

class Foo
{   // assume the following literals are keys, for example to a RESX
    [LocalizedCategory("cat")]
    [LocalizedDescription("desc")]
    [LocalizedDisplayName("disp name")]
    public string Bar { get; set; }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Delegate | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter)]
class LocalizedDescriptionAttribute : DescriptionAttribute
{
    static string Localize(string key)
    {
        // TODO: lookup from resx, perhaps with cache etc
        return "Something for " + key;
    }
    public LocalizedDescriptionAttribute(string key)
        : base(Localize(key))
    {
    }
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)]
class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    static string Localize(string key)
    {
        // TODO: lookup from resx, perhaps with cache etc
        return "Something for " + key;
    }
    public LocalizedDisplayNameAttribute(string key)
        : base(Localize(key))
    {
    }
}
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Delegate | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter)]
class LocalizedCategoryAttribute : CategoryAttribute
{
    public LocalizedCategoryAttribute(string key) : base(key) { }
    protected override string  GetLocalizedString(string value)
    {
            // TODO: lookup from resx, perhaps with cache etc
        return "Something for " + value;
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I must confess that i couldn´t really understand. Is that i.e. "desc" is a key for a localized attribute? How do i access the attribute of a specified property? Could you please adapt your example to mine? – Bedir Yilmaz Sep 13 '11 at 09:38
  • @3yanlis1bos in the example I'm using the string as a key only - you would have to fetch out of RESX manually (see the generated file behind the RESX), or use reflection – Marc Gravell Sep 13 '11 at 10:29
  • Ok, i think i understood. You`ve extended the descriptionattribute and modified to suit my needs like taking localized strings as input. Is that right? then only thing that i have to do is use the new Attribute instead of old one? – Bedir Yilmaz Sep 13 '11 at 10:32
  • Tip: If you have `LocalizedDescriptionAttribute` extend `DescriptionAttribute`, you should be wary of giving anything both a attributes simultaneously, as attempts to access the `Description` may yield the `LocalizedDescription`. :::Once wasted 15 minutes Discovering this obvious in hindsight problem the hard way::: – Brian Sep 13 '11 at 11:38
11

I've combined Marc Gravell and dsmith's answers:

First: create an Attribute class named as LocalizedDescriptionAttribute

public class LocalizedDescriptionAttribute : DescriptionAttribute
{
    static string Localize(string key)
    {
        return YourResourceClassName.ResourceManager.GetString(key);
    }

    public LocalizedDescriptionAttribute(string key)
        : base(Localize(key))
    {
    }
}

Use it as LocalizedDescription (the word "Attribute" is not necessary)

public class Foo
{   
    // myString is a key that exists in your Resources file
    [LocalizedDescription("myString")]
    public string Bar { get; set; }
}
Community
  • 1
  • 1
Zanon
  • 29,231
  • 20
  • 113
  • 126
4
[Required(ErrorMessageResourceName = "LogOnModel_UserName_Required",     
      ErrorMessageResourceType = typeof(Resources.Global))]    
[Display(Name = "LogOnModel_UserName_Required",resourceType = typeof(Resources.Global))]  
public string UserName { get; set; }

see: http://geekswithblogs.net/shaunxu/archive/2010/05/06/localization-in-asp.net-mvc-ndash-3-days-investigation-1-day.aspx

Patrick
  • 2,730
  • 4
  • 33
  • 55
  • 2
    Since the OP is using `CategoryAttribute`/`DescriptionAttribute`, they are probably talking *regular* `System.ComponentModel`, not MVC - in which case this simply doesn't apply. – Marc Gravell Sep 13 '11 at 08:17
  • ah, did not know that. I mostly dev MVC and thought that the attributes would be generic (would be a great feature though). – Patrick Sep 13 '11 at 08:39
  • @Patrick I didn`t understand that either :). kinda newbie in c#. but thanks for trying anyway. was that "Resources.Global" that you access a localized resource string? If yes, how do i write something like that in a description attribute? – Bedir Yilmaz Sep 13 '11 at 10:21
3

Slight extension to Jani's answer (which has worked well for me):

string description = Strings.GetString( Key );

can be more clearly done as

ResourceManager rm = YourResourceClassName.ResourceManager;
string description = rm.GetString(Key);

It was vaguely implied, but wasn't clear what was missing from the original code. The .ResourceManager property of the resource class is auto-generated.

Then, to access it, use the extension method described by Glennular here: StackOverflow instead of hardcoding in field/class names (just use LocDescriptionAttribute instead of DescriptionAttribute).

The only other adjustment I made was to make it static based on this IComparable enum, to allow it to be used by any enum instead of being locked to a particular type; it was the best I could do given that I can't apply an interface to limit it to just the enums I've added descriptions to.

Community
  • 1
  • 1
dsmith
  • 646
  • 7
  • 14
3

Let's take this sample class:

// ------------------------------------------------------------------------
public class Customer
{
    // ----------------------------------------------------------------------
    [Category( "Editable Values" ), LocDescription( "FirstName", "Sets the first name..." )]
    public string FirstName { get; set; }

    // ----------------------------------------------------------------------
    [Category( "Editable Values" ), LocDescription(  Key = "LastName", DefaultDescription = "Sets the last name..." )]
    public string LastName { get; set; }
} // class Customer

Now you can implement a custom attribute class:

// ------------------------------------------------------------------------
public class LocDescriptionAttribute : DescriptionAttribute
{
    // ----------------------------------------------------------------------
    public LocDescriptionAttribute()
    {
    } // LocDescriptionAttribute

    // ----------------------------------------------------------------------
    public LocDescriptionAttribute( string key, string defaultDescription ) :
        base( defaultDescription )
    {
        Key = key;
        DefaultDescription = defaultDescription;
    } // LocDescriptionAttribute

    // ----------------------------------------------------------------------
    public string Key { get; set; }

    // ----------------------------------------------------------------------
    public string DefaultDescription { get; set; }

    // ----------------------------------------------------------------------
    public override string Description
    {
        get
        {
            // load from resx
            string description = Strings.GetString( Key );
            if ( string.IsNullOrEmpty( description ) )
            {
                description = DefaultDescription;
            }
            return description;
        }
    } // Description
} // class LocDescriptionAttribute

Now you have the localized description:

AttributeCollection attributes = TypeDescriptor.GetProperties( customer )[ "FirstName" ].Attributes;
DescriptionAttribute myAttribute = (DescriptionAttribute)attributes[ typeof( DescriptionAttribute ) ];
ConsoleWiterLine( myAttribute.Description );
  • I have a Resource file already. I use them for my warnings and messages too. But the problem is, you just can`t pass a localized resource to a description attribute (not possible) . that`s what i mean. – Bedir Yilmaz Sep 13 '11 at 09:43
  • I looks like the same thing that Marc did. But it`s easier to understand for me. I think i`ll try this one. – Bedir Yilmaz Sep 13 '11 at 10:55
2

It seems the CategoryAttribute has code to look for an localized string based on internal resources, butDescriptionAttribute has no localization.

From Reflector:

public string Category
{
    get
    {
        if (!this.localized)
        {
            this.localized = true;
            string localizedString = this.GetLocalizedString(this.categoryValue);
            if (localizedString != null)
            {
                this.categoryValue = localizedString;
            }
        }
        return this.categoryValue;
     }
}

I think you would to extend the attributes and create your own implementation, passing in the ResourceType. Something like this, based vaguely on DataAnnotation logic. Obviously needs a bit more clean up.

public class LocaleDescriptionAttribute : DescriptionAttribute
{
    ...

    public LocaleDescriptionAttribute(string resourceKey, Type resourceType)
        : base(resourceKey)
    {
        this.resourceType = resourceType;
    }

    public override string  Description
    {
        get 
        { 
            var description = base.Description;
            PropertyInfo property = resourceType.GetProperty(
                       description, BindingFlags.Public | BindingFlags.Static);
            if (property == null)
            {
                return description;
            }
            return property.GetValue(null, null) as string;
        }
    }    
}
TheCodeKing
  • 19,064
  • 3
  • 47
  • 70
  • Actually this Category attribute is not so different from Description in end effect. I have the localized strings in my file, but i can`t get any permission to apply them to an Attribute like applying them in an Exception. The only thing that i`m tryin to understand is how to fill i.e. the DescriptionAttribute with an internal source like you said. – Bedir Yilmaz Sep 13 '11 at 10:19