Como hacer una aplicación MVC multilenguaje en .NET

Posted on Posted in .Net, MVC

Uno de los muchos beneficios de las aplicaciones web es que pueden ser usadas desde otro país sin ningún problema, ya que no dependen de ser instaladas en la computadora, con una simple consulta en el navegador es suficiente para que el usuario empiece a interactuar con el sistema; el problema viene cuando el usuario no entiende la interfaz porque no sabe el idioma en el que esta el proyecto, así que la solución es hacer nuestra aplicación MVC multilenguaje.

Creando los recursos

Realmente es mas simple de lo que uno creería, lo primero que haremos es agregar la carpeta App_GlobalResources al proyecto, para esto, tenemos que dar click derecho en el proyecto > Add > Add ASP.NET FolderApp_GlobalResources

Agregar folder App_GlobalResources
Agregar folder App_GlobalResources

Después necesitaremos agregar los recursos, que son nuestros archivos que contienen los textos necesarios para que la aplicación pueda mostrar un lenguaje u otro dependiendo del usuario, esto lo hacemos dando click derecho en el folder recién creado, y luego en Add > Resources File

Agregar archivo de recursos
Agregar archivo de recursos

Normalmente yo le pongo Strings.resx como nombre al archivo de recursos que creamos, y crearemos otro igual, llamado Strings.en.resx, el primero (que es el archivo de recursos que se usara por default) tendra los textos en español y el segundo en ingles.

Lo siguiente que necesitamos hacer es modificar las propiedades de los recursos creados, de tal forma que queden así:

Propiedades de los archivos de recursos
Propiedades de los archivos de recursos

Donde YourNamespace es justamente el Namespace (root/principal de preferencia) de tu apicacion MVC multilenguaje.

Ahora, hay que agregar los textos a los archivos de recursos, hay dos columnas principales, el Name que es el nombre de la propiedad y el Value, que es el texto asociado a la propiedad, en este caso podemos ver un ejemplo mas claro de como agregaremos el texto de prueba:

Agregando texto a los recursos
Agregando texto a los recursos

Configurando las rutas

La forma en la que el lenguaje (cultura) va a ser establecido es por medio de la ruta de la URL, porque? Por que es un excelente lugar para establecerla, ya que si la estableciéramos en otro lado, habría procesos que ya habrían corrido sin tomar en cuenta la cultura como en las validaciones de los Data Annotations del modelo. Y de esta forma, nos aseguramos que desde que se valida la ruta, se guarda la cultura deseada.

Lo siguiente sera configurar nuestro archivo RouteConfig.cs en la carpeta App_Start con el siguiente código

namespace YourNamespace
{
    class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.IgnoreRoute("Content/Dashboard-template.html");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            //  Multicurtural routing
            foreach (Route r in routes)
            {
                if (!(r.RouteHandler is SingleCultureMvcRouteHandler))
                {
                    r.RouteHandler = new MultiCultureMvcRouteHandler();
                    r.Url = "{culture}/" + r.Url;
                    //Adding default culture
                    if (r.Defaults == null)
                        r.Defaults = new RouteValueDictionary();

                    r.Defaults.Add("culture", CulturesAvailables.es.ToString());

                    //Adding constraint for culture param
                    if (r.Constraints == null)
                        r.Constraints = new RouteValueDictionary();

                    r.Constraints.Add("culture", new CultureConstraint(
                        CulturesAvailables.es.ToString(),
                        CulturesAvailables.en.ToString()));
                }
            }
            routes.MapRoute(
                name: "SingleCulture",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }).RouteHandler = new SingleCultureMvcRouteHandler();
        }
    }

    public class SingleCultureMvcRouteHandler : MvcRouteHandler { }

    public class MultiCultureMvcRouteHandler : MvcRouteHandler
    {
        protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            var culture = requestContext.RouteData.Values["culture"].ToString();
            var ci = new CultureInfo(culture);
            Thread.CurrentThread.CurrentUICulture = ci;
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
            return base.GetHttpHandler(requestContext);
        }
    }

    public class CultureConstraint : IRouteConstraint
    {
        private string[] _values;
        public CultureConstraint(params string[] values)
        {
            this._values = values;
        }
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            // Get the value called "parameterName" from the
            // RouteValueDictionary called "value"
            string value = values[parameterName].ToString();
            // Return true is the list of allowed values contains
            // this value.
            return _values.Contains(value);
        }
    }


    public enum CulturesAvailables
    {
        es = 1,
        en = 2
    }
}

No explicare toooodo el código, porque pueden debugearlo y saber que es lo que pasa paso a paso, pero si daré unos detalles importantes del mismo:

  • Agregamos/mapeamos otra ruta aparte de la ruta por default, la cual incluye la cultura en la URL, específicamente antes del controlador, de tal forma que quedaría así:
    http://misitio.com/cultura/controlador/accion
  • Si la ruta no tiene el segmento de la cultura, entonces se establecerá la cultura por defecto, es decir, los textos serán del archivo Strings.resx
  • Puedes establecer una cultura mas especifica, por ejemplo: es-MX en lugar de es, y en-US en lugar de en. Esto ayudaría con algunas cosas especificas de cada cultura, como fechas o formatos de números. en el caso de querer especificar en-US en lugar de solamente en, tienes que modificar:
    • El nombre del recurso (archivo .resx) Strings.en-US.resx
    • En CulturesAvailables de en = 2 en_US = 2
    • Agregar .Replace(“_”, “-“) a requestContext.RouteData.Values[“culture”].ToString()
    • Agregar .Replace(“_”, “-“) CulturesAvailables.en_US.ToString()

Usar los archivos de recursos

Ya que tenemos los archivos creados con el texto que necesitamos, y la ruta configurada, solo queda usar los recursos en nuestra aplicación, en el controlador Home, acción Index (Home/Index) viene un código HTML por default, que es el que se genera al crear nuestra aplicacion MVC multilenguaje.

Para usar los recursos es necesario llamar a la Clase Strings (como el nombre de nuestros archivos) y a la propiedad que queramos, por ejemplo en este caso seria Strings.QueBuenoVerteDeNuevo y si lo vas a usar en HTML entonces solamente agregarle un @ antes de Strings.

De tal forma que si modificamos el codigo generado de esta forma

<div class="jumbotron">
    <h1>ASP.NET <small>@Strings.QueBuenoVerteDeNuevo</small></h1>
    <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
    <p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
</div>

y ejecutamos nuestra aplicacion MVC multilenguaje con la ruta http://localhost/es/home/indexhttp://localhost/home/index veremos esto:

Aplicacion MVC multilenguaje en español
Aplicacion MVC multilenguaje en español

y si la corremos en la dirección http://localhost/en/home/index veremos esto

App multilenguaje en ingles
App multilenguaje en ingles

Si definimos el la cultura como en-US en lugar de en, la ruta debera de ser http://localhost/en-US/home/index, lo mismo para es-MX.

Aplicacion MVC multilenguaje: Usando Data Annotations

Una de las ventajas de hacer la aplicacion MVC multilenguaje es que los Data Annotations pueden soportar multilenguaje tambien, de la siguiente forma:

[Display(Name = "MiPropiedad", ResourceType = typeof(Strings))]
[Required(ErrorMessageResourceName = "CampoRequerido", ErrorMessageResourceType = typeof(Strings))]
[MaxLength(10, ErrorMessageResourceName = "LongitudMaxima", ErrorMessageResourceType = typeof(Strings))]

Pare que funcione tendrías que crear los registros MiPropiedad, CampoRequerido y LongitudMaxima en los archivos .resx, si quieres saber el texto que deberían tener, puedes ver el post que hice acerca del funcionamiento de los Data Annotations.

Debo de decir que para hacer multilenguaje la aplicación, me base en este blog post de Mohammad Habibzadeh y en esta respuesta de Stack Overflow de marapet.