Formatear cualquier divisa con JavaScript y CurrencyJs

Averigua cómo formatear cualquier divisa del mundo con JavaScript y CurrencyJs, mejorando así la localización de tu aplicación web.

Como es habitual por aquí, esta publicación va acompañada de un ejercicio práctico. Haz click en la siguiente imagen para ver el resultado terminado.

Ver ejercicio terminado en una ventana nueva.

Si deseas saltarte la teoría, e ir directamente al tutorial del ejercicio, haz click aquí.

Desarrollar aplicaciones web que sean funcionales para cualquier persona del mundo, sin duda, conlleva muchas consideraciones técnicas.

Ofrecer el contenido en múltiples idiomas sería una de las más evidentes. 

Pero también lo es manejar fechas y horas locales, tal y como ya vimos en la publicación de hace unos meses. 

Manejar fechas con DayJs

En el artículo de hoy, nos enfrentaremos a un problema más, derivado de las diferencias entre países del globo.

Me refiero, en concreto, al formato en el que se presentan los importes, en las diferentes monedas de curso legal.

No te preocupes, en seguida entenderás a qué me refiero.

En países donde se usa el Euro como moneda de intercambio, es habitual representar los importes siguiendo la siguiente convención:

  • El símbolo € se incluye a la derecha, justo después de indicar el importe, y con un espacio. 
  • Para la separación de las centenas, se utiliza el punto «.»
  • Para indicar la parte decimal, se usa la coma «,» como separador.

No obstante, en Estados Unidos, donde el dólar es la moneda oficial, las normas de formato son totalmente distintas. 

De hecho son justo a la inversa de las del Euro. 

  • El símbolo $ se ubica al inicio, justo antes del importe, sin ningún espacio. 
  • El separador numérico principal es la coma «,». 
  • Los decimales se representan con el punto «.».

Como ya imaginarás, no tener en cuenta esto puede provocar confusión entre los usuarios, e incluso serios problemas de usabilidad.

Ejemplo de valor formateado para dos divisas
Ejemplo de valor formateado para dos divisas

Por eso en la publicación de hoy veremos cómo la librería CurrencyJs nos puede ayudar a formatear correctamente un importe, para cualquier divisa del mundo.

Una librería para presentar valores en formatos de moneda local

CurrencyJs es una librería JavaScript que permite convertir cualquier valor numérico a una cadena de texto con un determinado formato de divisa.

El repositorio de ésta herramienta ha acumulado más de 2.7k estrellas desde su creación, hace ya más de 5 años.

Su creador, Jason Wilson (a.k.a scurker) mantiene el proyecto de forma activa, conjuntamente con un equipo de 19 personas.

Uno de los factores diferenciales de «currency.js» es su reducido peso, solo ocupa alrededor de 1kb. 

A pesar de su ligero peso, tiene totalmente en cuenta los problemas intrínsecos del lenguaje JavaScript, a la hora de manipular valores numéricos con decimales

De este modo no tienes que preocuparte de casos como los siguientes. 

2.51 + .01; // 2.5199999999999996
currency(2.51).add(.01);      // 2.52
2.52 - .01; // 2.5100000000000002
currency(2.52).subtract(.01); // 2.51 

Ejemplo extraído de la documentación oficial.

Además, la librería cuenta con las siguientes características.

  • No requiere de otras dependencias
  • Es immutable
  • Admite múltiples opciones de formato
  • Acepta cualquier tipo de formato de moneda como valor de entrada.

Como es habitual, este recurso se puede instalar con el comando NPM

npm install --save currency.js

Su API es extremadamente sencilla. El módulo «currency.js» expone una simple función con el mismo nombre. 

import currency from "currency.js";

Dicha función acepta dos parámetros de entrada.

El primero es el importe, que como dije, puede ser en forma de valor numérico, o como un texto ya formateado. 

El segundo, es un objeto JavaScript de configuración para definir los criterios de formato.

Entre las opciones de parametrización del objeto, existen las siguientes:

  • symbol: propiedad para asignar el símbolo propio de la moneda. 
  • separator: carácter para separar los grupos de tres cifras del valor. Normalmente un punto o una coma. 
  • decimal: carácter para separar los números decimales del resto. Normalmente un punto o una coma. 
  • precision: Valor numérico para acotar la cantidad de decimales a mostrar. Por defecto 2.
  • pattern: patrón de texto compuesto por los elementos «!» y «#». Sirve para definir en qué posición debe ir el símbolo («!») respecto al importe («#»), así como añadir un espacio entre estos, o no.

Estos son los parámetros más útiles, pero por supuesto, no he listado todos los que la API pone a disposición del desarrollador. 

Si quieres estudiar el resto, te dejo el enlace a la documentación oficial. 

Documentación oficial de la librería

Tras llamar a la función «currency» pasando los dos argumentos, ésta devolverá un objeto con el que trabajar.

const formattedValue = currency(
  4801.45,
  {
  symbol: "€",
  pattern: "# !",
  separator: ".",
  decimal: ",",
  }
);
formattedValue.format() //"4.801,45 €"

El objeto «formattedValue» es una instancia generada por la librería. A través de éste objeto, podemos ejecutar métodos como «formattedValue.format()» para obtener el valor en el formato indicador.

Por supuesto, la función «format» no es la única que ofrece el objeto creado por «currency.js».

Otros métodos destacables són:

  • add(cantidad): Permite sumar una cantidad al importe, sin tener que generar de nuevo el objeto.
  • subtract(cantidad): Similar a «add» però restando en vez de incrementar.
  • multiply(cantidad) y devide(cantidad): Igual que los anteriores, pero para operaciones de multiplicación y división
  • distribute(número): Devuelve un array de cantidades repartidas equitativamente, la suma de las cuales corresponde al importe. 
  • format(): Tal como vimos en el ejemplo, devuelve el importe formateado.

De nuevo, estos son solo algunos de los métodos más destacables, en la documentación oficial encontrarás el resto.

Crear un formateador de divisas online

A continuación pondremos en práctica la librería. Realizaremos un sencillo ejercicio, donde un usuario podrá introducir un importe, y seleccionar un país para obtener el formato en la moneda local.

Por ejemplo: Input: 1390,25 —> USA —> output: $1,390.25

Antes de ponernos manos a la obra, te dejo el enlace al repositorio del ejercicio terminado, de este modo, podrás tenerlo de referencia por si algún punto no está del todo claro.

Enlace al repositorio del ejercicio terminado

La idea es mostrar a los usuarios un conjunto de banderas del mundo. Estas actuarán de botones, para seleccionar el país del que se desea obtener el formato de moneda local.

Por ese motivo, vamos a instalar un recurso adicional.

La librería “flag-icons” nos proveerá de imágenes en formato SVG de todas las banderas del mundo.

Instalamos “flag-icons” con el comando que sigue:

npm i flag-icons

Ahora sí, vamos a empezar a programar nuestro formateador de divisas.

Empezamos generando una estructura HTML base, compuesta por los siguientes elementos.

Una cabecera con el título del programa:

<div class="header">
  <h1>Divisa<span class="font-light">Format</span></h1>
</div>

Un contenedor principal, con la clase “main-container” que abarca tres secciones.

Un contenedor con el “input” para introducir el valor:

<div class="input-container">
  <label for="amount-input">Importe (sin formato)</label>
  <input
    id="amount-input"
    type="number"
    placeholder="por ejemplo 1300.75"
    min="0"
    step="0.01"
    value="1300.75"
  />
</div>

Un listado de botones con las banderas de distintas regiones del planeta.

<h2>Selecciona un país</h2>
<div class="flags-container">
  <a href="#" class="flag-btn selected" data-region="eu"
    ><span class="fi fi-eu fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="us"
    ><span class="fi fi-us fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="jp"
    ><span class="fi fi-jp fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="kr"
    ><span class="fi fi-kr fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="il"
    ><span class="fi fi-il fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="cn"
    ><span class="fi fi-cn fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="ma"
    ><span class="fi fi-ma fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="ph"
    ><span class="fi fi-ph fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="pl"
    ><span class="fi fi-pl fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="sa"
    ><span class="fi fi-sa fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="ch"
    ><span class="fi fi-ch fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="tw"
    ><span class="fi fi-tw fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="th"
    ><span class="fi fi-th fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="tr"
    ><span class="fi fi-tr fis"></span
  ></a>
  <a href="#" class="flag-btn" data-region="gb"
    ><span class="fi fi-gb fis"></span
  ></a>
</div>

Aunque aún no hemos importado la dependencia “flag-icons”, ten en cuenta que a través de las clases “fi fi-eu fis” declaramos qué bandera deseamos mostrar en cada “span”. También destacar que cada elemento «span» contiene un atributo data-region con el código del país, más tarde nos será útil para identificar el formato escogido por el usuario.

Por último, añadimos contenedores para mostrar el resultado, una vez “currency.js” haga su trabajo de conversión de formato.

<h2>Resultado:</h2>
<div id="amount-formatted">1.300,75 €</div>

Al inicio del archivo “style.sass” importamos, por un lado, una fuente Google Fonts, y por el otro, los estilos de la librería «flag-icons». 

@import "flag-icons/sass/flag-icons.scss"
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200;600;800&display=swap')

También debes tener en cuenta que los estilos de flag-icons buscan los archivos de imágenes de las banderas en el directorio público del servidor. 

Eso significa que debes poner una copia del directorio «node_modules/flag-icons/flags» en tu directorio público.

El resto de estilos CSS del archivo styles.sass definen colores, espacios y demás propiedades. Te dejo el código a continuación para que lo estudies detenidamente. 

*
  box-sizing: border-box
body
  margin: 0
  font-family: 'Manrope', sans-serif
  background-color: #fafafa
h1
  margin: 0
  color: #fff
  text-align: center
  padding: 40px 0 80px 0
  font-size: 10vw
  font-size: clamp(30px, 10vw, 70px)
  font-weight: 800
  .font-light
    font-weight: 100
h2
  font-weight: 100
  color: #363636
  text-align: center
.header
  background: rgb(31,230,97)
  background: linear-gradient(21deg, rgba(31,230,97,1) 0%, rgba(57,203,233,1) 100%)
.main-container
  position: relative
  padding: 100px 10px 30px 10px
  .input-container
    position: absolute
    transform: translateX(-50%)
    top: -50px
    left: 50%
    max-width: 500px
    display: block
    width: 100%
    background-color: #fafafa
    padding: 20px 30px
    border-top-left-radius: 10px
    border-top-right-radius: 10px
    label
      font-weight: 100
      display: block
      margin-bottom: 10px
    input
      display: block
      width: 100%
      box-sizing: border-box
      padding: 10px
      font-size: 2rem
      font-family: 'Manrope', sans-serif
      border: solid 1px #cfcfcf
      border-radius: 5px
      &:focus-visible
        outline-color: rgb(31,230,97)
.flags-container
  display: flex
  justify-content: center
  align-items: center
  flex-wrap: wrap
  .flag-btn
    text-decoration: none
    display: block
    margin: 0 5px 5px 5px
    &.selected
      .fi
        border: solid 5px #25e080
        box-shadow: 0 0 10px rgb(31,230,97)
    .fi
      box-shadow: 0 2px 3px rgba(0,0,0,.2)
      display: block
      width: 100px
      height: 100px
      overflow: hidden
      border-radius: 20px
      pointer-events: none
#amount-formatted
  text-align: center
  font-size: clamp(30px, 10vw, 70px)
  background: rgb(31,230,97)
  background: linear-gradient(21deg, rgba(31,230,97,1) 0%, rgba(57,203,233,1) 100%)
  max-width: 500px
  color: #fff
  border-radius: 40px
  box-shadow: 0 3px 5px rgba(0,0,0,.2) inset
  margin: 0 auto

Si aún no lo has hecho, instala la librería currency.js con el comando. 

npm install --save currency.js

Vamos a programar el comportamiento con JavaScript

Crea un archivo nuevo llamado «main.js». Al inicio importa los estilos y la dependencia que acabamos de instalar. 

import "./style.sass";
import currency from "currency.js";

Seguidamente, seleccionamos un conjunto de elementos del DOM necesarios para la interacción del usuario, y los guardamos en variables para su posterior uso.

const amountInput = document.querySelector("#amount-input");
const flagsContainer = document.querySelector(".flags-container");
const resultContainer = document.querySelector("#amount-formatted");

Generamos una variable adicional llamada “amount”, en ella guardaremos el código del país seleccionado, y el importe.

const amount = {
  regionSelectedCode: "eu",
  value: Number(amountInput.value) || 0,
};

Añadimos un “listener” para detectar cuando el usuario modifica la cantidad a través del “slider”. En la función de retorno, editamos el nuevo valor en “amount”, y ejecutamos la función “​​formatValue” que declararemos más adelante.

amountInput.addEventListener("input", (e) => {
  e.preventDefault();
  console.log(e.target.value);
  if (!e.target.value) {
    e.target.value = 0;
  }
  amount.value = Number(e.target.value);
  formatValue(amount);
});

Por otra parte, si el usuario hace click en el contenedor de banderas, comprobamos si ha seleccionado una de ellas. De nuevo, actualizamos el objeto “amount” con el código extraído del “dataset”, y llamamos a “formatValue” con el parámetro actualizado

flagsContainer.addEventListener("click", (e) => {
  if (!e.target.classList.contains("flag-btn")) {
    return;
  }
  e.preventDefault();
  flagsContainer.querySelectorAll(".flag-btn").forEach((el) => {
    console.log(el);
    el.classList.remove("selected");
  });
  e.target.classList.add("selected");
  amount.regionSelectedCode = e.target.dataset.region;
  formatValue(amount);
});

Finalizamos el script principal declarando la función “​​formatValue”. En ella recibiremos el objeto “amount” y, en función de la propiedad “regionSelectedCode”, pasaremos un objeto de configuración u otro a la librería currency.js”

const formatValue = (formatData) => {
  let formatOptions = {};
  switch (formatData.regionSelectedCode) {
    case "us":
      formatOptions = {
        symbol: "$",
        pattern: "!#",
        separator: ",",
        decimal: ".",
      };
      break;
    case "jp":
      formatOptions = {
        symbol: "¥",
        pattern: "! #",
        separator: ",",
        decimal: ".",
      };
      break;
    case "cn":
      formatOptions = {
        symbol: "¥",
        pattern: "! #",
        separator: ",",
        decimal: ".",
      };
      break;
    case "kr":
      formatOptions = {
        symbol: "₩",
        pattern: "! #",
        separator: ",",
        decimal: ".",
      };
      break;
    case "ma":
      formatOptions = {
        symbol: ".د.م.",
        pattern: "# !",
        separator: ",",
        decimal: ".",
      };
      break;
    case "ph":
      formatOptions = {
        symbol: "₱",
        pattern: "! #",
        separator: ",",
        decimal: ".",
      };
      break;
    case "pl":
      formatOptions = {
        symbol: "zł",
        pattern: "# !",
        separator: ".",
        decimal: ",",
      };
      break;
    case "sa":
      formatOptions = {
        symbol: "﷼",
        pattern: "# !",
        separator: ",",
        decimal: ".",
      };
      break;
    case "ch":
      formatOptions = {
        symbol: "fr.",
        pattern: "! #",
        separator: ".",
        decimal: ",",
      };
      break;
    case "tw":
      formatOptions = {
        symbol: "元",
        pattern: "! #",
        separator: ",",
        decimal: ".",
      };
      break;
    case "th":
      formatOptions = {
        symbol: "฿",
        pattern: "# !",
        separator: ",",
        decimal: ".",
      };
      break;
    case "tr":
      formatOptions = {
        symbol: "₺",
        pattern: "# !",
        separator: ",",
        decimal: ".",
      };
      break;
    case "il":
      formatOptions = {
        symbol: "₪",
        pattern: "! #",
        separator: ".",
        decimal: ",",
      };
      break;
    case "gb":
      formatOptions = {
        symbol: "£",
        pattern: "!#",
        separator: ",",
        decimal: ".",
      };
      break;
    case "eu":
    default:
      formatOptions = {
        symbol: "€",
        pattern: "# !",
        separator: ".",
        decimal: ",",
      };
      break;
  }
  const formattedValue = currency(formatData.value, formatOptions);
  resultContainer.innerHTML = formattedValue.format();
};

Más material para ampliar tus conocimientos.

Este ha sido un rápido vistazo a la biblioteca JavaScript “currency.js”, espero que te haya servido para añadir una nueva herramienta entre tus recursos de desarrollador.

A continuación te dejo un listado de enlaces a material adicional, por si te interesa seguir aprendiendo.

¡Hasta la próxima!

Deja un comentario