El desarrollo de software de la presente generación, debe estar preparado para trascender el territorio nacional y posicionarse en mercados internacionales, como respuesta a esta recurrente identificación de oportunidades de crecimiento externas, es preciso establecer estrategias a nivel técnico, que permitan la construcción de aplicaciones concebidas para interactuar con usuarios extranjeros, extendiendo su impacto, superando las barreras del idioma y generando valor. De acuerdo con esto, en el presente artículo se describe una estrategia para la internacionalización de cualquier aplicación construida sobre, Net Core y AWS, en lo que concierne a la gestión del idioma del contenido estático devuelto por el backend. Aspectos como el manejo de la moneda, formato de fechas y convenciones de medida no son contemplados en este artículo, serán tratados en trabajos futuros.
El contenido estático de la aplicación a la que se refiere este artículo corresponde al contenido de tipo texto; el cual incluye mensajes de confirmación, mensajes de error, encabezados de archivos PDF o de hojas de cálculo. El contenido es estático, en la medida en que es definido por la lógica del negocio o por el equipo de desarrollo del software, y permanece inmutable como respuesta a una situación concreta. Por ejemplo, un mensaje de error para una actualización fallida de inventario, será siempre la misma y estará definida en su estructura e idioma por el negocio.
Desde una perspectiva de backend, los contenidos estáticos suelen ser textos de confirmación, error, encabezados de informes o reportes y cualquier otro contenido que permanece inmutable, lo que facilita la traducción, ya que siempre será la misma. Sin embargo, el reto consiste en traducir una gran cantidad de texto en múltiples idiomas, con tiempos de respuesta eficientes y con una administración sencilla.
Como respuesta a la problemática de la traducción de contenido estático desde el backend, se centralizaron en una sola tabla de DynamoDB, cada uno de los mensajes, encabezados, confirmaciones exitosas y mensajes de error, esto permitió una administración de las traducciones más eficiente y la inclusión de nuevos idiomas de una manera sencilla, evitando la necesidad de volver a desplegar los microservicios para actualizar los mensajes y sus traducciones.
El mecanismo de detección del idioma de respuesta, utilizó un parámetro query string obligatorio en la configuración de Amazon API Gateway, para indicar la cultura de origen de la petición, de tal manera que las soluciones desarrolladas en .NET Core, utilicen dicha información para determinar el idioma de destino para la traducción.
La figura 1, representa la arquitectura de la estrategia de traducción, los clientes realizan peticiones a la aplicación a través de solicitudes controladas por el API Gateway de Amazon, el cual debe estar configurado para exigir la presencia de un query string que determine la cultura en la cual se esperan las respuestas. Las peticiones pasan a AWS Lambda o a Contenedores alojados en Amazon ECS, bien sea a través de instancias de EC2 o AWS Fargate, en los cuales se almacenan y ejecutan cada uno de los microservicios de la aplicación. Finalmente, cada proyecto realiza solicitudes a una base de datos en DynamoDB, en la cual se determina una tabla diseñada especialmente para la administración de los mensajes estáticos en múltiples idiomas de la aplicación.
Supóngase una tabla en DynamoDB, llamada TermTranslation, dicha tabla deberá tener como clave de partición un campo Module, el cual indicará que microservicio de la aplicación pertenece el mensaje que allí se almacenará, esto permitirá que eventualmente cada solución consulte sólo aquellos mensajes que corresponden a su propia lógica de negocio. Por ejemplo, una solución que se encargue de la gestión de Inventario, deberá consultar en DynamoDB la tabla TermTranslation, únicamente con la partition key asignada para dicha solución estrategia que aprovecha las consultas tipo query de DynamoDB, en donde se debe establecer la partition key sobre la cual se realizará la búsqueda, lo que evita que se hagan sobre toda la tabla.
La clave de ordenación estará compuesta por un campo Label, el cual consiste en concatenar la cultura y el nombre del mensaje de la siguiente manera, Cultura#NombreMensaje, esto permite realizar consultas más sofisticadas dentro de una misma partition key.
La tabla 1 proporciona un ejemplo de la estructura mencionada.
La configuración para una aplicación web ASP.NET Core y una aplicación de tipo AWS Lambda es diferente, estas variaciones existen debido a que en la aplicación de tipo AWS Lambda, no se reconoce de forma automática la cultura en el momento en que llegan las peticiones, lo que implica una variación en la forma de identificar la cultura. A continuación se proporcionan las configuraciones necesarias para cada caso.
El primer paso para configurar el entorno de desarrollo, consiste en construir una clase DefinitionCulture, dicha clase será la encargada de definir las culturas soportadas por la aplicación y devolverlas cuando sea requerido, la clase también tiene la labor de definir el formato para la separación de decimales, separador de miles y formato de fechas de acuerdo con la cultura identificada.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class DefinitionCulture { protected DefinitionCulture() { } private static List<CultureInfo> supportedCultures = new List<CultureInfo> { new CultureInfo("es-CO"), new CultureInfo("en-US"), new CultureInfo("fr-FR") }; public static CultureInfo GetCulture(string culture) { var supportCulture = supportedCultures.Find(c => c.Name.Equals(culture)); return supportCulture ?? supportedCultures.First(); } public static List<CultureInfo> DefineCulture() { foreach (var culture in supportedCultures) { culture.NumberFormat.CurrencyDecimalSeparator = ","; culture.NumberFormat.CurrencyGroupSeparator = "."; culture.NumberFormat.NumberDecimalSeparator = ","; culture.NumberFormat.NumberGroupSeparator = "."; culture.NumberFormat.PercentDecimalSeparator = ","; culture.NumberFormat.PercentGroupSeparator = "."; } return supportedCultures; } } |
Como se puede observar en el código anterior, entre las líneas 7 y 9, se genera una lista de objetos CultureInfo, para establecer las culturas español de Colombia, inglés de Estados Unidos y francés de Francia, en esta lista se establecen las culturas permitidas por la aplicación y por lo tanto, también los idiomas soportados. El método DefineCulture tiene como función establecer el comportamiento de cada cultura respecto al formato de separador de miles, decimales, configuración de formato de fecha y de moneda.
Una vez establecida la clase DefinitionCulture, es preciso elaborar un método void Configure(IApplicationBuilder app) en la clase Startup.cs del proyecto de tipo web application, dicho método utiliza la interfaz IApplicationBuilder, que proporciona los mecanismos para configurar la canalización de solicitudes de una aplicación [1] y utilizar su método UseRequestLocalization(RequestLocalizationOptions options) el cual agrega un RequestLocalizationMiddleware, para configurar automáticamente la información de la cultura para las solicitudes HttpRequest, basadas en la información proporcionada por el cliente [2,3]. El siguiente fragmento de código genera las culturas soportadas utilizando la clase DefinitionCulture, establece la cultura default de la aplicación y define las opciones requeridas para la detección automática de las culturas en las peticiones.
1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
2 {
3 var supportedCultures = DefinitionCulture.DefineCulture();
4
5 var options = new RequestLocalizationOptions
6 {
7 DefaultRequestCulture = new RequestCulture(supportedCultures.First()),
8 SupportedCultures = supportedCultures,
9 SupportedUICultures = supportedCultures,
10 };
11
12 System.Threading.Thread.CurrentThread.CurrentCulture = supportedCultures.First();
13
14 app.UseRequestLocalization(options);
15 }
Posterior a la configuración de la clase Startup, es necesario establecer un método que permita obtener el listado completo de mensajes de traducción de la tabla TermTranslation, esta alternativa consiste en realizar la consulta a la tabla de mensajes por cada petición, sin embargo esto ralentiza considerablemente la respuesta para agilizar los tiempos, se sugiere realizar la consulta una sola vez, almacenando la lista de traducciones correspondientes al módulo actual (microservicio) junto con las traducciones transversales a todos los módulos de la aplicación en una clase InstanceTermsTranslation, con esta estrategia, la lista de mensajes se mantiene en memoria durante la ejecución de la solución. A continuación los pasos requeridos para obtener el listado de traducciones por módulo:
1 public class TermTranslationRepository
2 {
3 public List<TermTranslation> GetTranslations()
4 {
5 if (InstanceTermsTranslation.instance == null)
6 {
7 InstanceTermsTranslation.instance = GetAllTermsByModule();
8 }
9 return InstanceTermsTranslation.instance;
10 }
11 }
La lógica del método GetTranslations, garantiza que la petición a DynamoDB se realice una única vez y estos datos se mantengan en memoria durante el tiempo de ejecución del microservicios.
Como resultado de un adecuado diseño de la solución, se evidencia la escalabilidad de propuesta debido a la manera modular en que se estructura la tabla de dynamo, lo que favorece una adecuada segmentación de las traducciones por microservicio y por idioma. Por otro lado, la centralización de los mensajes de traducción para contenido estático, permite además una administración sencilla desde base de datos, que no requiere intervención en el código, exceptuando el caso de agregar un nuevo idioma, en donde se requiere agregar el idioma en los permitidos. Sin embargo, dicha excepción es fácilmente parametrizable, por lo cual se deja al lector la implementación de la oportunidad de mejora.