Multi-idioma de los recursos (Parte 2)

telefono celular con informacion en la pantalla

Foto de Bastian Riccardi en Unsplash

Tal como se explicó en la primera parte de la gestión de literales, vamos a comentar cómo aplicar o dar formato a un dato cuando el lugar, tipo y patrón viene dado en un valor del recurso visto previamente aplicando a cada una una estrategia concreta.

Te preguntarás ¿y por qué? bueno, pueden darse circunstancias o casos en los que por necesidades no pueda aplicarse directamente un pipe o donde el lugar del dato a formatear no se sabe o se desconoce el tipo de dato que es. Por ejemplo, se pueden almacenar fórmulas o textos que donde dentro hay partes donde se tienen que sustituir por un importe.

El ejemplo que voy a presentar sería más o menos de la siguiente forma:
“El día X pagué Y en concepto de Z”.

En el caso más fácil, si el texto fuera parte del html del componente, podríamos verlo de la siguiente forma:

<p>El día {{ dia | date }} pagué {{ importe | currency }}  en concepto de {{ concepto }}</p>

No obstante, el texto está en una base de datos con lo que deberemos saber en donde se encuentra cada dato, su tipo y cómo queremos formatearlo quedando de la siguiente forma en nuestro json de recursos:

{ "key": "EXAMPLE", "value": "El día {0:d[f:dd/MM/yyyy]} pagué {1:c} en concepto de {2}" }

Descifrando el texto

Tendrá los siguientes requisitos:

  1. Se enmarcará entre llaves “{“ y “}” para saber que ahí va un valor (una substitución)
  2. Se indicará la posición/índice del array que contiene los valores a colocar seguido de dos puntos “:” (obligatorio si no es tipo string)
  3. Se pondrá el tipo de dato que tiene que ser (en caso de no poner nada, por defecto se selecciona string “s”):
    1. d: Fecha
    2. c: Moneda
    3. n: Número
    4. k: Key de otro recurso
    5. s: Otro string
  4. Formato (opcional) entre corchetes “[“,”]”. Según el tipo de dato puede necesitar un formato específico.

El array de que contiene los valores según el ejemplo visto puede ser el siguiente:

[new Date(2019, 0, 1), 45.5, “inscripción”]

De esta manera, utilizando el pipe visto en la primera parte, en el html quedaría de la siguiente forma:

{{ 'EXAMPLE' | translate: values }}

Y values, definido en un getter del componente:

public get values(): Array<any> {
   return [new Date(2019, 0, 1), 45.5, 'inscripción'];
 }

En una primera aproximación será un array de tipo “any”, porque es una lista de objetos varios. El tipo a dar lo sabe el valor de la key a sustituir.

La construcción de la sustitución se realizará en el pipe utilizando el patrón strategy. Cada estrategia corresponderá a un tipo de sustitución según el tipo (fecha, número, moneda, …).

Este sería el código del pipe:

@Pipe({
 name: 'translate'
})
export class TranslatePipe implements PipeTransform {
 private _strategies: Array<strategies.ITranslateStrategy> = [];
 
 public constructor(
   private _resourcesService: ResourcesService,
   private _stringStrategy: strategies.StringStrategy,
   private _resourceKeyStrategy: strategies.ResourceKeyStrategy,
   private _dateStrategy: strategies.DateStrategy,
   private _currencyStrategy: strategies.CurrencyStrategy,
   private _numberStrategy: strategies.NumberStrategy
 ) {
   this._strategies = [
     this._stringStrategy,
     this._resourceKeyStrategy,
     this._dateStrategy,
     this._currencyStrategy,
     this._numberStrategy
   ];
 }
 
 public transform(value: string, ...args: any[]): string {
   const resource: ITextResource = this._resourcesService.get(value);
   return resource.notFound ? value : this.format(resource.value, args[0]);
 }
 
 private format(value: string, args: any[]) {
   if (value) {
     (args || []).forEach((arg: any, index: number) => {
       const strategyFound: strategies.ITranslateStrategy = this._strategies.find(
         (strategy: strategies.ITranslateStrategy) => {
           return strategy.canApply(index, value, arg);
         }
       );
 
       if (strategyFound) {
         value = strategyFound.apply(index, value, arg);
       }
     });
   }
 
   return value;
 }
}

Se cargan las estrategias, y en el transform, se aplica la estrategia (apply) si es que se puede aplicar (canApply). 

Cada estrategia deberá implementar una interfaz e implementar su lógica.

export interface ITranslateStrategy {
 canApply(index: number, text: string, value: any): boolean;
 apply(index: number, text: string, value: any): string;
}

Por ejemplo, la estrategia de la sustitución de un campo numérico sería:

import { Injectable } from '@angular/core';
import { ITranslateStrategy } from './iTranslateStrategy';
import { DecimalPipe } from '@angular/common';
 
@Injectable()
export class NumberStrategy implements ITranslateStrategy {
 public constructor(private _decimalPipe: DecimalPipe) {}
 
 private getRegExp = (index: number) => new RegExp(`{${index}:n}`, 'gm');
 private isNumber = (value: any) => typeof value === 'number';
 
 public canApply(index: number, text: string, value: any): boolean {
   const regNumber = this.getRegExp(index);
   return regNumber.test(text);
 }
 
 public apply(index: number, text: string, value: any): string {
   if (!this.isNumber(value)) {
     console.log(`TranslatePipe: value at position ${index} is not a number`);
     return text;
   }
   const regNumber = this.getRegExp(index);
   return text.replace(regNumber, this._decimalPipe.transform(value));
 }
}

Puedes descargar el código del artículo completo (dos partes) en GitHub. Verás la implementación de las siguientes estrategias: Currency, Date, Number, ResourceKey y String.

Se pueden ir añadiendo tantas estrategias como desees. Por ejemplo, se puede crear una estrategia donde se tenga que rellenar un texto con tantos ‘0’ hasta una longitud dada o bien realizar el cálculo de alguna fórmula, etc.

Entradas relacionadas

Utilidades npm: skott, visualiza las dependencias

por César Marín
2 años atrás

Creación de objetos en TypeScript

por César Marín
5 años atrás

Comparar dos objetos en typescript con JSON patch

por César Marín
5 años atrás
Salir de la versión móvil