Intl: la API de localización nativa de JavaScript
José M. Alarcón 🌐
Posted on August 27, 2019
Artículo publicado originalmente en JASoft.org
Como seguramente ya tengas claro, la traducción y la localización son conceptos relacionados pero muy distintos.
Para traducir tu aplicación basada en Web mucho me temo que no te queda más remedio que usar archivos de lenguaje de algún tipo y alguna biblioteca especializada. Sin embargo para la localización , es decir, la adaptación de la aplicación a las particularidades de cada idioma, todo lo que necesitas viene incluido con tu navegador.
El objeto Intl
Mucha gente no lo sabe, pero JavaScript dispone de un objeto global específico para ayudarnos con la localización de aplicaciones a otros idiomas y culturas: Intl
:
Podemos usar sus diferentes objetos asociados, mostrados en la figura anterior, para averiguar mucha información sobre localización en cualquier idioma.
Vamos a verlos...
Intl.Collator: para comparar cadenas de texto
El objeto Collator
sirve para hacer comparaciones de cadenas teniendo en cuenta las particularidades locales.
Raramente se utiliza ya que no suele ser necesario, gracias a que la clase string
tiene un método específico para llevar a cabo este tipo de comparaciones: localeCompare()
.
Solo lo utilizaremos si tenemos que realizar muchísimas comparaciones en un bucle o algo así (algo muy poco habitual), ya que nos daría más rendimiento. En el resto de los casos puedes hacer caso omiso de él.
Intl.DateTimeFormat: para dar formato a fechas y horas
Como su propio nombre sugiere, nos ayuda a dar formato a las fechas y las horas según las particularidades de cada país.
Como todos los objetos de Intl
se instancia pasándole como argumento una cadena de texto en formato IETF BCP 47, que suena muy complicado pero en general no es más que el nombre abreviado internacional del idioma (es
, en
, it
...) para idiomas genéricos, o lo anterior seguido de un guión y la abreviatura del país/cultura en mayúscula (es-ES
, es-AR
, en-US
, en-UK
...). Como ves, muy fácil.
Así que, por ejemplo, para obtener una fecha bien formateada en varios idiomas sólo tenemos que hacer esto:
var fecha = new Date(2019, 6, 30, 16, 30, 0);
var dtfEs = new Intl.DateTimeFormat('es-ES');
var dtfEnUs = new Intl.DateTimeFormat('en-US');
var dtfArMa = new Intl.DateTimeFormat('ar-MA');
console.log(dtfEs.format(fecha));
console.log(dtfEnUs.format(fecha));
console.log(dtfArMa.format(fecha));
que nos devolverá por consola esa fecha (29 de julio de 2019, fíjate en que los meses se numeran desde el 0) en español, inglés americano y árabe de Marruecos (que tienen un formato bien complicado):
Fíjate en que no nos devuelve la hora, ni tampoco hemos podido controlar el formato exacto de cada componente que queremos obtener. Eso lo controlaremos gracias a las opciones del constructor , que he omitido en el fragmento anterior.
Todos los objetos de Intl
tienen un segundo argumento opcional para las opciones (valga la redundancia). En el caso de DateTimeFormat
tiene un montón de propiedades posibles que no voy a detallar porque las tienes en la MDN. Pero vamos a ver un ejemplo de cómo usarlos:
var fecha = new Date(2019, 6, 30, 16, 30, 0);
var opciones = {
weekday: 'long',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'long'
};
var dtfEs = new Intl.DateTimeFormat('es-ES', opciones);
var dtfEnUs = new Intl.DateTimeFormat('en-US', opciones);
var dtfArMa = new Intl.DateTimeFormat('ar-MA', opciones);
console.log(dtfEs.format(fecha));
console.log(dtfEnUs.format(fecha));
console.log(dtfArMa.format(fecha));
con el resultado siguiente:
Fíjate en que este código es exactamente igual al anterior solo que le hemos pasado como segundo argumento del constructor un objeto con las opciones pertinentes. Al haber especificado el formato en el que nos interesaba cada componente de la fecha, incluyendo las horas (para que las muestre), lo ha transformado adecuadamente y con las palabras apropiadas en cada idioma, e incluso escrito de derecha a izquierda en el caso del árabe de Marruecos.
Si queremos podemos utilizar el método formatToParts()
para obtener cada una de las partes de la fecha, de modo que podamos utilizarlas en cualquier formato personalizado si lo necesitásemos (aunque no te lo recomiendo, pues para eso tienes las facilidades que da el objeto, sin recurrir a formatos propios):
y podemos, en cada idioma, obtener exactamente cada parte de la cadena final, en función de las opciones que hayamos elegido.
Intl.RelativeTimeFormat: para facilitar la lectura de intervalos de tiempo relativos
Otra necesidad muy común en la mayor parte de las aplicaciones es la de expresar intervalos de tiempo relativos a la fecha actual. Por ejemplo, si tenemos un listado de tareas, en la columna de la fecha de vencimiento podemos poner tal cual la fecha o bien ser mucho más amigables para el usuario y escribir cosas como "Vence en 3 días" o "Caducada hace 5 horas"...
Esto es mucho más complicado de hacer de lo que parece de una manera consistente, y si además debemos hacerlo en varios idiomas no te quiero ni contar. Por suerte Intl
nos ofrece también funcionalidad apropiada para lograrlo de manera sencilla.
Al igual que antes, lo único que tenemos que hacer es instanciar la clase pasándole el identificador del idioma a utilizar para la localización:
var rtf = new Intl.RelativeTimeFormat('es-ES');
Ahora podemos obtener los intervalos apropiados en ese idioma llamando al método format()
, y pasándole el número de intervalos y el tipo de intervalo, que es una cadena en ingles. Por ejemplo:
rtf.format(1, 'day') //dentro de 1 día
rtf.format(-3, 'day') //hace 3 días
rtf.format(0, 'day') //dentro de 0 días
rtf.format(5, 'hour') //dentro de 5 horas
Esto es genial y ahorra muchas KB de bibliotecas JavaScript que ya no tendremos que descargarnos.
Ademñas, en el constructor podemos establecer algunas opciones para especificar cómo queremos que generen esos intervalos. Por ejemplo, a mi no me gusta el estilo por defecto que tienen, usando siempre números, así que lo puedo cambiar estableciendo la propiedad numeric
como 'auto'
:
var rtf = new Intl.RelativeTimeFormat('es-ES', { numeric: 'auto' });
y así conseguir que, por ejemplo, si es algo de hace un día ponga "ayer" y si es en un día obtenga "mañana", haciéndolo aún más natural:
Como vemos, de gran utilidad.
Como antes, también existe el método formatToParts()
para obtener una matriz con cada uno de los fragmentos del formato por separado.
Intl.NumberFormat: para dar formato a números y dinero
Seguimos con necesidades habituales de localización, en este caso con los números. Como sabes, cada idioma tiene formatos diferentes para muchas cosas con los números y las cantidades monetarias. Por ejemplo, en España los separadores de mil son puntos y el decimal es una coma, y la moneda se pone trás la cantidad. Sin embargo en EEUU es justo al revés: los miles se separan con comas, los decimales con puntos y la moneda va delante de la cantidad.
¿Cómo gestionamos esto de manera sencilla para cualquier idioma del planeta? Antes era complicadísimo. Ahora es muy sencillo gracias a Intl.NumberFormat
.
Como todos los anteriores se instancia pasándole una cadena con el idioma (si no ponemos nada se usará el idioma del sistema operativo):
var nfEs = new Intl.NumberFormat('es-ES');
var nfEn = new Intl.NumberFormat('en-EU');
var nfFr = new Intl.NumberFormat('fr');
console.log(nfEs.format(123456.78));
console.log(nfEn.format(123456.78));
console.log(nfFr.format(123456.78));
y como podemos comprobar genera los separadores en el formato adecuado a cada caso:
Fíjate en cómo los franceses utilizan com separador de miles un espacio, por ejemplo.
En cuanto a las opciones podemos establecer incluso el sistema de numeración que no tiene por qué sel arábigo, el tipo de moneda si va a ser una cantidad de dinero, y también la forma de nombrar las monedas, entre otras muchas opciones. La más importante es style
que nos permite seleccionar si queremos mostrar decimales ('decimal'
, valor por defecto), monedas ('currency'
) o porcentajes ('percent'
).
Por ejemplo, para mostrar una cantidad en euros o dólares escribiríamos:
var nfEs = new Intl.NumberFormat('es-ES', {style: 'currency', currency: 'EUR'});
var nfEn = new Intl.NumberFormat('en-EU', {style: 'currency', currency: 'USD'});
var nfFr = new Intl.NumberFormat('fr', {style: 'currency', currency: 'EUR', currencyDisplay: 'name'});
console.log(nfEs.format(123456.78));
console.log(nfEn.format(123456.78));
console.log(nfFr.format(123456.78));
Fíjate en cómo adapta perfectamente el formato a cada idioma y cómo además usa el símbolo o el nombre según las opciones indicadas:
Intl.ListFormat: par dar formato a listas
Otra necesidad clásica en las aplicaicones: partir de una lista o array de elementos y generar una lista legible para cada idioma.
Por ejemplo, si tenemos esta matriz, que generalmente en una aplicación la habremos obtenido de un servicio remoto:
var beatles = ['John', 'Paul', 'George', 'Ringo'];
y queremos meterlos en una lista amigable para el usuario para formar la frase: 'Los Beatles eran John, Paul, George y Ringo'
. Algo tan simple como esto requiere bastante trabajo si queremos adaptarlo a diversos idiomas. No todos usan las comas para separar y desde luego el último elemento no tiene que ser un "y" tampoco.
Con Intl.ListFormat
la cosa es muy sencilla:
var beatles = ['John', 'Paul', 'George', 'Ringo'];
var lfEs = new Intl.ListFormat('es-ES');
var lfDe = new Intl.ListFormat('de-DE');
console.log(lfEs.format(beatles));
console.log(lfDe.format(beatles));
Como vemos nos devuelve la lista formateada para cada localización, incluyendo en este caso la palabra "y" en el idioma correspondiente:
Por supuesto no siempre querremos que la lista sea inclusiva, sino que a veces podemos necesitar que sea una lista de opciones y que ese "y" se convierta en un "o", por ejemplo. Para cambiar este comportamiento en las opciones del constructor tenemos la propiedad type
que puede tomar los valores:
-
'conjunction'
, para listas de tipo "y" -
'disjunction'
para listas de tipo "o" -
'unit'
si la lista es de unidades de medida, que se suelen poner en forma de lista de modo diferente.
Así, con la lista anterior podemos poner esto:
var beatles = ['John', 'Paul', 'George', 'Ringo'];
var lfEs = new Intl.ListFormat('es-ES', {type:'disjunction'});
var lfDe = new Intl.ListFormat('de-DE', {type:'disjunction'});
console.log(lfEs.format(beatles));
console.log(lfDe.format(beatles));
para tenerla de tipo "o":
Si fuesen unidades, por ejemplo la longitud de una viga en una aplicación de construcción pondríamos:
var medidas = ['3 metros', '12 centímetros'];
var lfEs = new Intl.ListFormat('es-ES', {type:'unit'});
var lfDe = new Intl.ListFormat('de-DE', {type:'unit'});
console.log(lfEs.format(medidas));
console.log(lfDe.format(medidas));
Fíjate en un detalle importante: aunque la localización ha funcionado perfectamente porque las listas tienen el formato adecuado para cada idioma, la traducción está mal ya que en alemán sigue poniendo las medidas en español. Obviamente esto no es responsabilidad de
Intl
ya que se trata de traducción y es responsabilidad de la aplicación. Antes de crear la lista de cadenas deberemos asegurarnos de que las medidas están en el idioma apropiado.
Hay algunos parámetros más para las opciones del constructor, pero lo importante es lo que hemos visto.
Intl.PluralRules: para pluralización
Esta ya es una característica avanzada. Al contrario que las otras claes que hemos visto no está pensada para pasarle una cadena y que nos las devuelva en plural, sino que es a más bajo nivel. Lo que hace es facilitarnos la forma de plural que corresponde a cada número que se le pase a su método select()
.
Por ejemplo, en español, inglés u otros idiomas occidentales una viga mide 1 metro (singular), 3 metros (plural) o, curiosamente, 0 metros (plural aunque sea cero). Sin embargo en árabe tiene otras acepciones para ciertos números.
Si lo probamos con la clase PluralRules
:
var prEs = new Intl.PluralRules('es-ES');
var prMa = new Intl.PluralRules('ar-MA');
console.log('ESPAÑOL:');
console.log(prEs.select(0));
console.log(prEs.select(1));
console.log(prEs.select(3));
console.log(prEs.select(0.5));
console.log('ÁRABE:');
console.log(prMa.select(0));
console.log(prMa.select(1));
console.log(prMa.select(3));
console.log(prMa.select(0.5));
veremos lo siguiente:
Como puedes observar, para los idiomas occidentales generalmente hay dos posibilidades: 'one'
(singular) o 'other'
(plural), y con eso podemos decidir si se le pone una "s" al final o no.
Nota : lo anterior es una simplificación enorme ya que en español es mucho más complicado que eso. A veces el plural lleva "s" (gato, gatos), otras veces lleva "es" (flor, flores) y otras veces no lleva nada (¿cuál es el plural de "virus" en español? Pues "virus" también porque es invariable en plural, al contrario que en inglés, que es "viruses"). En ese sentido otros idiomas como el inglés son mucho más sencillos.
Pero en otros idiomas la cosa es mucho más complicada, como puedes comprobar con el árabe.
Por lo tanto, aunque está bien disponer de esta funcionalidad para algunas aplicaciones muy concretas, no nos va a servir de gran ayuda a la hora de generar plurales "serios", así que generalmente no lo vas a utilizar.
Soporte
El soporte actual de navegadores es ya universal desde hace años, por lo que no deberías tener problemas para usarla. La excepción, como casi siempre, es Internet Explorer, pero incluso éste tiene soporte para la mayor parte de las clases en su versión 11. En esta tabla de MDN tienes un buen resumen detallado del soporte específico por clase y navegador.
También tienes un polyfill que puedes utilizar si fuese necesario en estos navegadores antiguos, aunque no es tan potente.
En resumen
Para casi todas las tareas comunes relacionadas con la localización de aplicaciones, JavaScript nos proporciona ayuda integrada y no vamos a necesitar utilizar bibliotecas externas que añaden complejidad, peso y que además con toda seguridad no serán tan buenas como el sistema operativo para estos menesteres. Dado que la API de internacionalización de JavaScript, a través del objeto global Intl
, utiliza por debajo los servicios del sistema operativo para conseguirlo, podemos garantizar resultados rápidos y correctos.
Deberíamos acostumbrarnos a usa esta API ya que nos ayudará a conectar mejor con nuestros usuarios y a hacer las aplicaciones más amigables.
Por cierto, si te ha gustado este artículo, te encantará lo que puedes aprender con mi curso de JavaScript avanzado en campusMVP. Anímate a aprender JavaScript en serio y dejar de "tocar de oído" 😊 Además tendrás vídeos prácticos, prácticas sugeridas, evaluaciones, referencias cruzadas, hitos de aprendizaje.... y tendrás contacto directo conmigo y con el fenómeno Eduard Tomàs para contestar todas tus dudas y seguir tu progreso.
¡Espero que te sea útil!
Artículo publicado originalmente en JASoft.org
Posted on August 27, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.