HEADER_lecciones_de_software

Implementación de las herramientas AWS y .Net Core, para la traducción web

por Esteban Vallejo, el 26 de abril de 2021

 

implementacion de las herramientas aws y .net core

Introducción


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.

Estrategia de traducción de contenido estático

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.

Arquitectura de la estrategia de 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.

1, estrategia para la traduccion de aplicaciones web

Estructura de la tabla de traducciones en DynamoDB

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.

2, estrategia para la traducción de una aplicacion web

Configuraciones de la solución en .NET Core

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.

Configuración para aplicación web ASP.NET Core

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:

  • Se establece una clase InstanceTermsTranslation, que contiene una propiedad estática de tipo List<TermTranslation>.

1 class InstanceTermsTranslation
2 {
3 public static List<TermTranslation> Instance;
4 }


  • Se construye una clase TermTranslationRepository, en la cual se realizan las consultas a DynamoDB para obtener las traducciones:

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.

  • Se construyen los métodos que permiten realizar peticiones a tabla TermTranslation, utilizando consultas de tipo Query de DynamoDB, la Partition Key, corresponde al nombre del módulo que se quiere consultar, por lo tanto, entre las líneas 12 y 13 se indican los nombres de los módulos de interés; "Inventory" y "Tranversal", que corresponden respectivamente al módulo actual y a las traducciones transversales a la aplicación.

1 public class TermTranslationRepository
2 {
3 (...)
4 public List<TermTranslation> GetAllTermsByModule()
5 {
6 try
7 {
8 List<TermTranslation> termTranslations = new
9 List<TermTranslation>();
10
11 List<string> modules = new List<string>
12 { "Inventory", "Transversal" };
13 QueryFilter queryFilter = new QueryFilter();
14
15 foreach (var module in modules)
16 {
17 queryFilter.AddCondition("Module",QueryOperator.Equal,module);
18 List<TermTranslation> termsTranslationResult =
19 GetTermTranslationsByFilters(queryFilter);
20
21 if (termsTranslationResult != null && termsTranslationResult.Any())
22 {
23 termTranslations.AddRange(termsTranslationResult);
24 }
25 }
26
27 if (termTranslations.Any())
28 {
29 return termTranslations;
30 }
31
32 return new List<TermTranslation>();;
33 }
34 catch (Exception ex)
35 {
36 throw (new Exception("Message"));
37 }
38 }
39
40 public List<TermTranslation> GetTermTranslationsByFilters(QueryFilter queryFilter)
41 {
42 List<TermTranslation> partialResultDynamo = new
43 List<TermTranslation>();
44 List<TermTranslation> totalResultDynamo = new List<TermTranslation>();
45
46 using (DynamoDBContext context = GetContext())
47 {
48 var table = context.GetTargetTable<TermTranslation>();
49 Search search = table.Query(new QueryOperationConfig
50 {
51 Filter = queryFilter
52 });
53
54 do
55 {
56 partialResultDynamo.Clear();
57 List<Document> items = search.GetNextSetAsync().GetAwaiter().GetResult();
58 partialResultDynamo = context.FromDocuments< TermTranslation>(items).ToList();
59 if (partialResultDynamo.Count > 0)
60 {
61 totalResultDynamo.AddRange(partialResultDynamo);
62 }
63 } while (!search.IsDone);
64 return totalResultDynamo;
65 }
66 }
67
68 private DynamoDBContext GetContext()
69 {
70 string prefix = "Dev-";
71 AmazonDynamoDBClient Client = new AmazonDynamoDBClient();
72 AWSConfigsDynamoDB.Context.TableNamePrefix = prefix;
73 return new DynamoDBContext(Client);
74 }
75
76 public string RefreshTranslations()
77 {
78 try
79 {
80 InstanceTermsTranslation.instance = GetAllTermsByModule();
81 return "OK";
82 }
83 catch (Exception)
84 {
85 return "FAIL";
86 }
87 }
88 }

El método GetAllTermsByModule, realiza una consulta de tipo Query a la tabla TermTranslation, por cada uno de los módulos especificados en la lista declarada entre las líneas 11 y 12. El método GetTermTranslationsByFilters, utiliza las clases proporcionadas por la biblioteca AWSSDK.DynamoDBv2 [4], que se puede obtener a través del Nuget Package Manager de .NET.

Resulta importante advertir, que la propuesta de un objeto estático, con la lista de mensajes implica que no se actualiza con los últimos cambios que pudieran realizarse a la tabla TermTranslation, razón por la cual requiere de un método que le permita refrescar la información de la tabla de traducciones, por tal motivo, a partir de la la línea 76 se elabora el método "RefreshTranslations".



  • Suponiendo que se tiene una clase InventoryDomainService (en donde se realiza la lógica de negocio) en la cual se utiliza una clase TranslationDomainService (en donde se centraliza toda lógica que concierne a traducción) se construye un método "GetTranslationTerm(string label)" en la clase TranslationDomainService, donde label contiene el nombre asignado al mensaje. Por ejemplo: label = "Msg_UpdateInventorySuccess".

    1 public string GetTranslationTerm(string label)
    2 {
    3 try
    4 {
    5 string culture = Thread.CurrentThread.CurrentCulture.ToString();
    6
    7 List<TermTranslation> termTranslations = termTranslationRepository.GetTranslations();
    8
    9 List<TermTranslation> termList = termTranslations.
    10 Where(t => t.Label == $"{culture}#{label}").ToList();
    11 string translationTerm = null;
    12
    13 if (termList.Count > 0)
    14 {
    15 translationTerm = termTranslations.
    16 Where(t => t.Label == $"{culture}#{label}").ToList()[0].Message;
    17 }
    18 else
    19 {
    20 translationTerm = termTranslations
    21 .Where(t => t.Label == $"es-CO#{label}").ToList()[0].Message;
    22 }
    23 return translationTerm;
    24 }
    25 catch (Exception ex)
    26 {
    27 Console.Write($"Error: {ex.InnerException}");
    28 }
    29 return label;
    30 }

    La línea 8, utiliza una instancia de la Clase TermTranslationRepository, para obtener las traducciones correspondientes al módulo de Inventario, la línea 11 por su parte, utiliza la cultura actual para conformar la estructura definida para el campo Label, de la tabla 1, si no se encuentra el idioma correspondiente a la cultura actual, se devuelve el mensaje en español.

    Configuración para aplicación de tipo AWS Lambda


    Dado que una aplicación de tipo AWS Lambda en .Net Core, no tiene la capacidad para identificar el idioma de manera automática a partir del parámetro query string, el procedimiento para extraer esta información debe realizarse manualmente. Así pues, se requiere elaborar un método adicional que extraiga del objeto APIGatewayProxyRequest los QueryStringParameters, localice el parámetro "culture" y le asigne dicha cultura al hilo actual. El siguiente código permite llevar a cabo dicho procedimiento:

    1 private void DefineCulture(APIGatewayProxyRequest request)
    2 {
    3 string culture = request.QueryStringParameters != null &&
    4 request.QueryStringParameters.Keys.Contains("culture")
    5 ? request.QueryStringParameters["culture"]
    6 : "es-CO";
    7
    8 Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(culture);
    9 }

    Finalmente, DefineCulture debe ser llamado en la clase Function.cs de la lambda, antes de que se invoque cualquier otro método que requiera traducción.

    Configuración de AWS Api Gateway

    Tanto en el menú de "solicitud del método" (figura 2) como en el de la "solicitud de integración" (figura 3) se deben configurar los parámetros de consulta de URL con el campo culture, el cual debe ser obligatorio, este campo de tipo query string, se utiliza con el fin de que al realizar una petición a la url correspondiente al endpoint configurado, se envíe la cultura seleccionada hacia el backend, mediante este mecanismo es posible configurar las soluciones en backend para determinar el idioma de las respuestas. Esta configuración se debe hacer para cada método GET, POST, PATCH, PUT o DELETE que esté establecido en el API Gateway.

    A continuación se presentan dos ejemplos de peticiones para actualizar el stock de una farmacia, que utilizan la cultura para determinar el idioma en el cual los mensajes de error o confirmación deben devolverse en español, inglés o francés respectivamente:


  • http://localhost:25416/api/inventory/UpdateStock/?culture=es-CO
  • http://localhost:25416/api/inventory/UpdateStock/?culture=en-US
  • http://localhost:25416/api/inventory/UpdateStock/?culture=fr-FR

    3, Estrategia para la traducción de una aplicacion  web

4, estraategia para la traduccion de una aplicacion traducción web

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.

Nuevo llamado a la acción

Temas:Desarrollo de Software

Lecciones Pragma

Lecciones en Academia Pragma

Aquí encontrarás tutoriales técnicos para que apliques en temas de desarrollo de software, cloud, calidad en software y aplicaciones móviles. 

También puedes visitar nuestro Blog con contenido actual sobre Transformación Digital, Marketing, Conocimiento de Usuario y más. 

Blog

Suscríbete a la academia

Descarga la Guía para trabajar con ambientes IBM Websphere Portal