martes, 3 de octubre de 2017

ASP.NET CORE–Localización de aplicaciones

Una de las mayores ventajas que nos proporciona la plataforma ASP.NET Core es la facilidad con la que podemos acceder a ‘las tripas’ de las llamadas que se realizan, así como el soporte por defecto de inyección de dependencias.
Combinando estas y otras características, tenemos una sustancial mejora a la hora de gestionar comportamientos como el del multiidioma, localización y globalización de aplicaciones.

Introducción

La localización en ASP.NET Core es bastante similar a como funciona en ASP.NET 4.X. Por defecto definimos una serie de archivos de recursos .resx, uno para cada cultura que soportamos. Entonces podemos referenciar los recursos por clave y, dependiendo de la cultura actual, se obtiene el valor apropiado del archivo de recursos que corresponda.
Mientras el concepto de un archivo .resx por cultura se mantiene en ASP.NET Core, la manera en la que los recursos se utilizan ha cambiado significativamente. En la versión anterior, cuando añadíamos un archivo .resx a nuestra solución, se generaba automáticamente un archivo “designer” que proporcionaba acceso a los recursos de una manera directa (static strongly typed access) mediante llamadas del tipo “Resources.MyTitleString”.
En ASP.NET Core, los recursos se acceden a través de dos abstracciones, IStringLocalizer y IStringLocalizer<T>, que se ‘preparan’ cuando son necesarias mediante la “inyección de dependencias”. Estos interfaces proporcionan un indexador que permite acceder a los recursos por clave. Si no existe un recurso para la clave especificada, el sistema utiliza la propia clave como valor del recurso.
La idea detrás de esta nueva aproximación es permitir que la aplicación se diseñe desde el principio para ser “localizable” sin que sea necesario crear todos los archivos de recursos al inicio. Así podemos simplemente utilizar los valores por defecto como claves y añadir los distintos archivos de recursos a posteriori.
Indudablemente se trata de una opción muy interesante, pero lógicamente tiene un problema relativamente serio, ya que al no tener acceso ‘tipado’ a los recursos se puede dar el caso de que sin querer cambiemos uno de esos literales / claves y entonces perdemos el vínculo con su recurso. Entiendo que es muy probable que al final cojamos costumbre de trabajar de esta manera, aunque me da la sensación de que también es muy posible que tengamos ‘dificultades’ para detectar en un momento dado qué tenemos traducido y qué no… Lo mejor entiendo que será aprovechar las ventajas de la nueva gestión de la localización, pero trabajando desde el principio con los archivos de recursos necesarios en la aplicación.

Al turrón

Todo esto es muy bonito (o lo parece) pero, ¿cómo se hace?

Añadir localización a una aplicación

El primer paso es añadir los servicios de localización a nuestra aplicación. En este caso, como estamos trabajando con una aplicación MVC, configuraremos también localización para las Vistas e incluso para los “DataAnnotations”. En ASP.NET Core añadimos estos servicios en el método “ConfigureServices” de la clase “Startup
image
En la captura anterior podemos observar cómo añadimos los servicios de localización para los tres elementos que hemos comentado; como punto interesante indicar que al especificar un “ResourcesPath” le estamos diciendo al motor dónde se ubicarán los archivos de recursos (en este caso en una carpeta “Resources”.
Al añadir estos servicios tenemos la posibilidad de inyectar las abstracciones que hemos comentado en la parte de la introducción, así como una especialización de ellas que permite trabajar con vistas “IViewLocalizer”.
Con estas instrucciones ya podríamos utilizar localización en nuestra aplicación, pero el sistema nos queda cojo ya que no hemos hecho nada para que las peticiones a la aplicación utilicen la cultura…
Para manipular estas peticiones configuraremos el middleware “RequestLocalizationMiddleware”. Este middleware utiliza una serie de proveedores para determinar la cultura actual, estos proveedores de definen en el método “ConfigureServices” de la clase “Startup
image
En la captura anterior tenemos un ejemplo de cómo generar una lista con las culturas que ‘soporta’ la aplicación y aplicar una cultura por defecto. Una vez aplicada esta configuración tenemos que añadir el middleware de localización, que se hace en el método “Configure” de la clase “Startup”.
image
Lo que hacemos en este método es obtener el servicio que hemos registrado anteriormente y lo utilizamos para indicar que lo queremos utilizar para la localización de las peticiones.

Utilizar localización en controladores y servicios

Cuando necesitemos acceder a texto localizado en servicios o controladores, lo primero que tendremos que hacer es inyectar In “IStringLocalizer<T>” en la clase y utilizar el indexador proporcionado para obtener el valor del texto:
image
Como ya hemos explicado, en el constructor del controlador inyectamos la dependencia para el “localizer” y en la “acción” Index accedemos al recurso “Título de la aplicación en castellano”. Si ahora accedemos a la página de inicio de la aplicación, veremos que aparece el texto que hemos utilizado como ‘índice’ (como hemos comentado antes, si no existe una entrada con la clave indicada, dicha clave se utiliza como texto ‘de vuelta’:
image
Ahora sería buen momento para añadir el archivo de recursos para un idioma adicional… Si partimos del hecho de que el idioma por defecto de nuestra aplicación será el castellano y viendo cómo funciona la localización en ASP.NET Core, podríamos asumir que NO necesitamos archivo de recursos para castellano, por lo que directamente crearemos el archivo de recursos para el resto de culturas configuradas, por ejemplo como se ve en las capturas anteriores, inglés.
En la parte de configuración hemos indicado que los archivos de recursos van en una carpeta “Resources”, que ASP.NET Core intenta encontrar en el raíz de nuestra aplicación, con lo que tendremos que crear dicha carpeta. a continuación, el sistema de ‘localización’ buscará el archivo de recursos correspondiente a la clase en la que estamos (HomeController), para ello por convención buscará una carpeta “Controllers” y dentro de ella el archivo de recursos (HomeController.en.resx), o también puede buscar un archivo de recurso con un nombre según este patrón: Controllers.HomeController.en.resx. Yo recomiendo la primera estructura de carpetas, lo que nos lleva a esta captura:
image
En esta carpeta crearemos el archivo de recursos (HomeController.en.resx) y en él añadiremos la entrada correspondiente a la clave que hemos utilizado anteriormente:
image
Como veréis, Visual Studio se “queja” de la clave que hemos utilizado, incluso es posible que aparezca un error en el panel de “Error List” pero por lo visto se trata de un problema del propio Visual Studio y no afecta a la compilación del proyecto. Sí, es un inconveniente…
image

Utilizar localización en Vistas

En el caso de las vistas, en lugar de ‘inyectar’ un “IStringLocalizer<T>” haremos lo propio con un “IViewLocalizer”. Este componente utiliza un patrón idéntico al que hemos visto en el caso anterior para encontrar los archivos de recursos, pero utilizando la estructura de vistas existente. por ejemplo para crear un archivo de recursos para una vista “Index.cshtml” que está en una carpeta “Home” tendríamos la siguiente estructura de carpetas:
image
Dentro de esta carpeta crearemos un archivo de recursos con el mismo nombre que la vista, como por ejemplo Index.en.resx:
image
La inyección del “IViewLocalizer” se realiza en la vista con la instrucción @inject”:
image
Como podemos ver en la captura, necesitamos importar el namespace para el interfaz, y con la instrucción @inject” generamos una propiedad “Localizer” mediante la cual accedemos al recurso.
image

Utilizar localización en ViewModels

Otro de los puntos que normalmente queremos localizar serán literales de posibles mensajes de error o advertencia que mostramos en las clases del “ViewModel”, por ejemplo a través de atributos de “DataAnnotation”. El mecanismo es análogo a los que hemos explicado anteriormente, el archivo de recursos estará en una subcarpeta de este estilo: “Resources/ViewModels/HomeViewModel.en.resx” y el uso en atributos iría así:
image
En el ejemplo vemos qué claves queremos localizar, el sistema los mapeará de manera automática.
image

Permitir a los usuarios elegir una cultura

Hasta ahora hemos estado hablando de localizar una aplicación ASP.NET Core, pero todo esto no sirve de nada si no disponemos de un mecanismo que permita al usuario cambiar el idioma de la aplicación… Hombre, sí que sirve si lo que queremos es basarnos en el idioma del navegador, pero ya que tenemos una aplicación multiidioma, pues vamos a aprovechar, ¿no?
El middleware que hemos configurado para activar la localización (recuerda, el RequestLocalizationMiddleware) ofrece un mecanismo extensible de proveedores de ‘cultura’ y además viene con tres proveedores por defecto:
  • QueryStringRequestCultureProvider: Especificar la cultura en la QueryString, por ejemplo ?culture=en
  • AcceptLanguageHeaderRequestCultureProvider: Especificar la cultura en el encabezado de la petición (mediante Accept-Language)
  • CookieRequestCultureProvider: Especificar la cultura mediante una Cookie.
En esta entrada vamos a probar el tercer proveedor, ya que parece que tiene más sentido utilizar cookies para este tema, ¿verdad?
Para permitir al usuario cambiar de idioma vamos a crear el equivalente a un control de usuario ascx en ASP.NET Core (o así lo entiendo yo) así que utilizaremos una vista parcial, _SelectLanguagePartial.cshtml en la carpeta Shared de las vistas:
image
En la captura anterior hay mucha tela que cortar, pero por intentar resumir un poco los pasos, tendríamos:
  1. Inyectamos el objeto para la localización (IViewLocalizer)
  2. Inyectamos el objeto donde hemos almacenado las opciones de la localización (IOptions<RequestLocalizationOptions>)
  3. Obtenemos la cultura de la petición, así como las distintas culturas que tenemos disponibles, estas últimas en una lista de objetos SelectListItem para poder utilizarlas en un combo con el que el usuario podrá cambiar el idioma de la aplicación (toda la sección HTML de la parte inferior)
Una vez diseñada la vista parcial tenemos que añadirla en las páginas deseadas (normalmente querremos el cambio de idioma en todas las páginas, así que podemos utilizar la vista _Layout.cshtml), para añadirla utilizaremos el método HTML.PartialAsync:
image
Para terminar, necesitamos implementar el código de controlador que realizará el cambio de idioma, acción “SetLanguage” del controlador “HomeController” como se puede ver en la captura del control de cambio de idioma.
image
En este código podemos ver cómo añadimos la cookie por defecto para el idioma en la colección de la respuesta y como (en este ejemplo) establecer un año de validez a la cookie.
¿Cómo queda el control de cambio de usuario?
Pues en esta ocasión quedaría así:
image
Si cambiamos el idioma en el combo y hacemos clic en “Guardar”, podemos ver cómo los literales se cogen de los distintos archivos de recursos.

Conclusiones

A ver, es mucho más sencillo ‘localizar’ aplicaciones en ASP.NET Core, pero, no por ser más sencillo estamos ‘aprueba’ de errores, algunos de los más típicos:
  1. Clave del recurso incorrecta (o modificada): Como este sistema no requiere que los archivos de recursos estén generados, y no tenemos tipado fuerte, se puede dar el caso de que al corregir, por ejemplo, una falta ortográfica en uno de los textos, se nos olvide modificar el texto también en los archivos de recursos… Resultado, no hay traducción.
  2. Ruta de los archivos de recursos: Por convención, el acceso a los archivos de recursos se realiza en función del Namespace de las clases donde vamos a utilizarlos, es un poco raro, pero si en algún momento veis que no está cogiendo traducciones, revisad por favor la ruta del archivo de recursos (y por supuesto como he puesto en el punto anterior, el nombre de la clave)
Creo que todavía tiene margen de mejora, pero lo cierto es que olvidarnos de establecer el CurrentCulture, la Cookie y demás, resulta ummm, cómodo.

Nota final

Si alguien se lo está preguntando, no he hecho ninguna prueba con el comportamiento de este mecanismo con controles tipo “Kendo”… No me chilléis, por favor.

1 comentario:

Unknown dijo...

Amigo, buen día, muchísimas gracias por tomarse la molestia de guiarnos con este espectacular tutorial, lo seguí al pie de la letra y me ha funcionado muy bien.

Quería preguntarte si sabes el motivo por el cuál en desarrollo, esto funciona correctamente, pero cuando publico la aplicación en IIS, no me toma ni el lenguaje del navegador por defecto ni tampoco lo cambia con el botón para tal fin.

Sí pudieras ayudarme con esto, estaré muy agradecido.

Saludos,

Octavio Peñuela