Más localización a base de atributos

El localizar una aplicación con los tiempos que corren es necesidad imperiosa. Hacerlo de forma elegante (el único modo en el que tú entiendes las cosas, ¡truhán!) puede ser algo menos que strightforward. Una forma que dista de ser mi preferida (me consta que es mejorable), es haciendo uso de atributos.

Como hace poco me tocó vérmelas con la PropertyGrid del famoso y altamente recomendable WPF Extended Toolkit, he tenido que ponerme a fuego lento con atributos que realmente funcionan, aunque tengo que confesar que no les he prestado demasiado tiempo a dominarlos a la perfección (maldito tiempo, siempre acuciando).

Pues aquí los dejo, sin más dilación. Su uso es bien sencillo.

PropertyGrid usa propiedades de objetos por reflexión. Esas propiedades tienen los nombres y descripciones como Dios las trajo al mundo, es decir, en su mayoría en inglés. Y para qué nos vamos a engañar, ¡muchos programamos todo en inglés! Así es que, sin toquetear demasiado el código y aplicando atributos (attributed programming model, que me pone verraco), he forzado la maquinaria.

Todo se basa en la LocalizableString (un invento que he sacado de las tripas de .NET)

// Type: System.ComponentModel.DataAnnotations.LocalizableString
// Assembly: System.ComponentModel.DataAnnotations, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.ComponentModel.DataAnnotations.dll

using System;
using System.Globalization;
using System.Reflection;
using System.Runtime;
using WPF.Common.Properties;

namespace WPF.Common.Attributes
{
    internal class LocalizableString
    {
        private readonly string propertyName;
        private string propertyValue;
        private Type resourceType;
        private Func<string> cachedResult;

        public string Value
        {
            [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
            get
            {
                return this.propertyValue;
            }
            set
            {
                if (!(this.propertyValue != value))
                    return;
                this.ClearCache();
                this.propertyValue = value;
            }
        }

        public Type ResourceType
        {
            [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
            get
            {
                return this.resourceType;
            }
            set
            {
                if (!(this.resourceType != value))
                    return;
                this.ClearCache();
                this.resourceType = value;
            }
        }

        [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
        public LocalizableString(string propertyName)
        {
            this.propertyName = propertyName;
        }

        public string GetLocalizableValue()
        {
            if (this.cachedResult == null)
            {
                if (this.propertyValue == null || this.resourceType == (Type)null)
                {
                    this.cachedResult = (Func<string>)(() => this.propertyValue);
                }
                else
                {
                    PropertyInfo property = this.resourceType.GetProperty(this.propertyValue);
                    bool flag = false;
                    if (!this.resourceType.IsVisible || property == (PropertyInfo)null || property.PropertyType != typeof(string))
                    {
                        flag = true;
                    }
                    else
                    {
                        MethodInfo getMethod = property.GetGetMethod();
                        if (getMethod == (MethodInfo)null || !getMethod.IsPublic || !getMethod.IsStatic)
                            flag = true;
                    }
                    if (flag)
                    {
                        string exceptionMessage = string.Format(CultureInfo.CurrentCulture, Resources.LocalizableString_LocalizationFailed, (object)this.propertyName, (object)this.resourceType.FullName, (object)this.propertyValue);
                        this.cachedResult = (Func<string>)(() =>
                        {
                            throw new InvalidOperationException(string.Format("{0}: {1}", exceptionMessage, propertyValue));
                        });
                    }
                    else
                        this.cachedResult = (Func<string>)(() => (string)property.GetValue((object)null, (object[])null));
                }
            }
            return this.cachedResult();
        }

        private void ClearCache()
        {
            this.cachedResult = (Func<string>)null;
        }
    }
}

Ahora los atributos mágicos:

Para categoría, CategoryAttribute.

using System;
using System.ComponentModel;

namespace WPF.Common.Attributes
{
    [AttributeUsage(AttributeTargets.All)]
    public sealed class LocalizedCategoryAttribute : CategoryAttribute
    {
        private Type resourceType;
        private readonly LocalizableString category = new LocalizableString("Category");

        protected override string GetLocalizedString(string value)
        {
            return category.GetLocalizableValue();
        }

        public string Name
        {
            get { return category.Value; }
            set
            {
                if (category.Value == value)
                    return;
                category.Value = value;
            }
        }

        public Type ResourceType
        {
            get { return resourceType; }
            set
            {
                if (resourceType == value)
                    return;

                resourceType = value;
                category.ResourceType = value;
            }
        } 
    }
}

Y para la descripción, DescriptionAttribute.

using System;
using System.ComponentModel;

namespace WPF.Common.Attributes
{
    [AttributeUsage(AttributeTargets.All)]
    public sealed class LocalizedDescriptionAttribute : DescriptionAttribute
    {
        private Type resourceType;
        private readonly LocalizableString description = new LocalizableString("Description");

        public string Name
        {
            get { return description.Value; }
            set
            {
                if (description.Value == value)
                    return;
                description.Value=value;
            }
        }

        public override string Description
        {
            get { return description.GetLocalizableValue(); }
        }

        public Type ResourceType {
            get { return resourceType; }
            set
            {
                if (resourceType == value)
                    return;

                resourceType = value;
                description.ResourceType = value;
            }
        } 
            
    }
}

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s