Raro, maduro, expresivo y asombroso, JavaScript es el lenguaje de programación más utilizado en la actualidad. Siendo el lenguaje del navegador—y trabajando en el servidor con Node—JavaScript está en todas partes en las tecnologías web actuales.
Y con muchos frameworks móviles multiplataforma, envolturas de escritorio, motores de juegos e incluso frameworks de internet de las cosas (IoT), realmente es el mundo de JavaScript — nosotros solo vivimos en él.
Ahora, por supuesto, estás aquí porque quieres tomar todo eso 🔥 y aprender a localizar aplicaciones de JavaScript, preparándolas para una audiencia global. No tengas miedo: Esta guía cubrirá todo lo que necesitas saber para comenzar la localización de JavaScript en el navegador.
¡Vamos a rock && roll!
🔗 Recurso » Obtén todo el código que acompaña este artículo de nuestro repositorio de GitHub.
🗒 Nota » Internet Explorer (IE), con una participación de mercado global del 2,15%, puede considerarse un navegador obsoleto. Por brevedad, estamos omitiendo soluciones específicas de IE en esta guía. Si estás dando soporte a IE, asegúrate de verificar si las características integradas de JavaScript que cubrimos en este artículo requieren bifurcaciones (forks) o polyfills.
¿Cómo localizas una página web con JavaScript?
Si bien puede ser tentador tomar una biblioteca de internacionalización (i18n) lista para usar para tus necesidades de localización—y esa podría ser la elección correcta para tu proyecto—verás que JavaScript puro te servirá perfectamente bien para proyectos más pequeños. Crear una propia también te dará un buen recetario de técnicas de i18n que puedes usar con cualquier biblioteca que elijas.
🤿 Profundiza más » Nuestro artículo, Qué es I18n: Una definición simple de internacionalización, entra en más detalles sobre lo que son la internacionalización (i18n) y la localización (l10n).
✋🏽 Atención » Si estás construyendo una MPA (aplicación multipágina) tradicional, a menudo ocurre que gran parte de la localización se realiza en el propio servidor. Solo estamos trabajando con la localización del navegador aquí. Sin embargo, te lo ponemos fácil del lado del servidor, con un tutorial de i18n de Node y una guía de i18n de JavaScript de pila completa.
Está bien, digamos que tenemos una página que queremos localizar.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<!-- ... -->
<title>Mi Appy Apperson</title>
</head>
<body>
<div class="container">
<h1>Mi Appy Apperson</h1>
<p>¡Bienvenido a mi pequeño rincón en la web!</p>
</div>
<script src="js/scripts.js"></script>
</body>
</html>

🔗 Recurso » Puedes obtener todo el código de la aplicación que estamos construyendo en esta sección en la carpeta vanilla en nuestro repositorio de GitHub.
🔗 Recurso » Estoy usando la biblioteca Skeleton CSS en caso de que te lo preguntes.
Esto se ve bien, pero no está exactamente listo para un entorno global, ¿verdad? Todo el contenido está integrado directamente en inglés. Hagamos un poco de i18n básico.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<!-- ... -->
<title>Mi Appy Apperson</title>
</head>
<body>
<div class="container">
<h1 data-i18n-key="app-title">Mi Appy Apperson</h1>
<p data-i18n-key="lead">¡Bienvenido/a a mi pequeño rincón en la web!</p>
</div>
<script src="js/scripts.js"></script>
</body>
</html>
Nota los atributos data-i18n-key que añadimos arriba a nuestros contenedores de texto. Podemos acceder a ellos cuando se carga el documento y reemplazar su texto con traducciones. De hecho, hagamos exactamente eso.
// El idioma activo
const locale = "en";
// Podemos tener tantos idiomas aquí como queramos,
// y usar cualquier idioma o configuración regional que queramos. Tenemos el idioma inglés
// y árabe como idiomas aquí a modo de ejemplo.
const translations = {
// Traducciones al inglés
"en": {
"app-title": "Mi Appy Apperson",
"lead": "¡Bienvenido a mi pequeño rincón en la web!",
},
// Traducciones en árabe
"ar": {
"app-title": "تطبيقي المطبق",
"lead": "أهلاً بك في مكاني الصغير على النت.",
},
};
// Cuando el contenido de la página esté listo...
document.addEventListener("DOMContentLoaded", () => {
document
// Encuentra todos los elementos que tienen el atributo key
.querySelectorAll("[data-i18n-key]")
.forEach(translateElement);
});
// Reemplaza el texto interno del elemento HTML dado
// con la traducción en el idioma activo,
// correspondiente a la clave i18n de datos del elemento
function translateElement(element) {
const key = element.getAttribute("data-i18n-key");
const translation = translations[locale][key];
element.innerText = translation;
}
Ahora que ya lo tienes, cambia la segunda línea de arriba a const locale = "ar"; y recarga la página. Cuando se activa el evento DOMContentLoaded, nuestra página muestra las traducciones al árabe.

🗒 Nota » "en" y "ar" arriba son los códigos ISO 639-1 para inglés y árabe, respectivamente. Es estándar usar códigos ISO para idiomas y países al localizar.
Cargando traducciones de forma asíncrona
Hemos empezado con buen pie con nuestra solución i18n. Sin embargo, agregar idiomas y traducciones no escala bien en este momento. A medida que nuestra aplicación crece, probablemente querríamos dividir nuestras traducciones en archivos separados por idioma. El archivo de traducción correspondiente al idioma activo podría cargarse sin el coste de cargar los demás idiomas. Podemos implementar esto sin demasiado esfuerzo.
Primero, movamos nuestras traducciones fuera de nuestro script principal y a archivos JSON, uno para cada idioma que admitimos.
{
"app-title": "Mi Appy Apperson",
"lead": "¡Bienvenido a mi pequeño rincón en la web!"
}
{
"app-title": "تطبيقي المطبق",
"lead": "Bienvenido/a a mi pequeño rincón en Internet."
}
Vamos a reestructurar nuestro script para cargar los archivos JSON de forma asíncrona cuando los necesitemos.
// El idioma que nuestra aplicación muestra primero
const defaultLocale = "en";
// El idioma activo
let locale;
// Se rellena con las traducciones del idioma activo
let translations = {};
// Cuando el contenido de la página esté listo...
document.addEventListener("DOMContentLoaded", () => {
// Traducir la página al idioma predeterminado
setLocale(defaultLocale);
});
// Cargar traducciones para el idioma (o configuración regional) dado y traducir
// la página a este idioma
async function setLocale(newLocale) {
if (newLocale === locale) return;
const newTranslations =
await fetchTranslationsFor(newLocale);
locale = newLocale;
translations = newTranslations;
translatePage();
}
// Recupera el objeto JSON de traducciones para el elemento dado
// locale a través de la red
función asíncrona fetchTranslationsFor(newLocale) {
const response = await fetch(`/lang/${newLocale}.json`);
return await response.json();
}
// Reemplazar el texto interno de cada elemento que tiene
// atributo data-i18n-key con la traducción correspondiente
// a su data-i18n-key
function translatePage() {
document
.querySelectorAll("[data-i18n-key]")
.forEach(translateElement);
}
// Reemplaza el texto interno del elemento HTML dado
// con la traducción en el idioma activo,
// correspondiente a la clave data-i18n del elemento
function translateElement(element) {
const key = element.getAttribute("data-i18n-key");
const translation = translations[key];
element.innerText = translation;
}
Si recargamos nuestra página ahora, se ve exactamente como antes. Sin embargo, internamente, hemos hecho que nuestra aplicación sea mucho más escalable y mantenible.
🗒 Nota » Usamos la práctica Fetch API integrada en los navegadores modernos para obtener nuestros archivos JSON mediante la red.
Creando un selector de idioma
Nuestros usuarios aún no tienen forma de hacer uso de nuestras asombrosas habilidades asíncronas. ¿Creamos un menú desplegable para que puedan cambiar de idioma?
Agregaremos una barra de navegación y alojaremos nuestro conmutador en dicha barra.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
<title>Mi Appy Apperson</title>
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<ul class="navbar-list navbar-left">
<!-- Enlaces de navegación -->
</ul>
<div class="navbar-right">
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">Árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<h1 data-i18n-key="app-title">Mi Appy Apperson</h1>
<p data-i18n-key="lead">¡Bienvenido a mi pequeño rincón en internet!</p>
</div>
<script src="js/scripts.js"></script>
</body>
</html>
Una simple <select> bastará aquí. Podemos usar un atributo data-i18n-switcher para enganchar desde nuestro JavaScript y cargar la localización seleccionada por el usuario.
const defaultLocale = "en";
let locale;
// Cuando el contenido de la página esté listo...
document.addEventListener("DOMContentLoaded", () => {
setLocale(defaultLocale);
bindLocaleSwitcher(defaultLocale);
});
// Siempre que el usuario seleccione un nuevo idioma,
// carga las traducciones del locale y actualiza
// la página
function bindLocaleSwitcher(initialValue) {
const switcher =
document.querySelector("[data-i18n-switcher]");
switcher.value = initialValue;
switcher.onchange = (e) => {
// Establece la configuración regional en la opción seleccionada [value]
setLocale(e.target.value); // Establece el idioma según el valor seleccionado
};
}
El controlador de eventos onchange nos permite actualizar las traducciones de nuestra página según el valor de la <option> seleccionada. ¡Y ya está! Ahora los visitantes de nuestro sitio pueden seleccionar su propio idioma o configuración regional.

📣 Un saludo » a Hary Murdiono JS del Noun Project por su icono de traducción.
Detectando los idiomas preferidos del usuario desde el navegador
A veces es una buena idea intentar adivinar el idioma o la configuración regional preferida del usuario antes de darle la opción de seleccionar manualmente la que prefiera. La mayoría de las personas tienen la interfaz de usuario de su navegador configurada en su idioma preferido, a menudo el idioma del sistema operativo.
Este idioma de la interfaz de usuario del navegador se puede encontrar en el objeto navigator, concretamente en la cadena navigator.language.
El también estándar—aunque experimental mientras escribo esto—navigator.languages debería contener el idioma de la interfaz de usuario como su primera entrada, además de cualquier idioma que la persona usuaria haya configurado explícitamente en la configuración de idiomas preferidos de su navegador.
Una pequeña función que consulta navigator.languages puede ponernos en marcha con la detección del idioma del navegador.
* Recupera los idiomas preferidos del usuario desde el navegador
* @param {boolean} languageCodeOnly - cuando es verdadero, devuelve
* ["en", "fr"] en lugar de ["en-US", "fr-FR"]
* @returns array | undefined
function browserLocales(languageCodeOnly = false) {
return navigator.languages.map((locale) =>
languageCodeOnly ? locale.split("-")[0] : locale,
);
}
Ahora digamos que el usuario tiene francés (Canadá) y chino (Simplificado) en la configuración de su navegador.

En este caso, browserLocales() devolverá ["fr-CA", "zh-CN"]. Si llamamos a browserLocales(true), obtendremos ["fr", "zh"].
Ahora podemos usar esta nueva función para detectar los idiomas preferidos del usuario cuando cargamos nuestra página por primera vez.
// El idioma que nuestra aplicación muestra primero
const defaultLocale = "en";
const supportedLocales = ["en", "ar"];
document.addEventListener("DOMContentLoaded", () => {
const initialLocale =
supportedOrDefault(browserLocales(true));
setLocale(initialLocale);
bindLocaleSwitcher(initialLocale);
});
function isSupported(locale) {
devuelve true si supportedLocales.indexOf(locale) es mayor que -1;
}
// Recuperar el primer idioma que soportamos de la dada
// array, o devolver nuestro idioma por defecto
function supportedOrDefault(locales) {
return locales.find(isSupported) || defaultLocale;
}
function browserLocales(languageCodeOnly = false) {
return navigator.languages.map((locale) =>
languageCodeOnly ? locale.split("-")[0] : locale,
);
}
Ten en cuenta que hemos introducido el concepto de supportedLocales; estos son los únicos locales para los que tenemos traducciones. Con ellas, podemos recurrir a nuestro idioma predeterminado si ninguno de los idiomas preferidos del usuario está en nuestra lista de idiomas compatibles.
Nuestra aplicación ahora se traducirá al primer idioma en la lista de preferencias del usuario, con una elegante alternativa en caso necesario.
🤿 Descubre más a fondo » Cubrimos la detección de idioma tanto en el navegador como en el servidor en profundidad en Detectando la preferencia de idioma del navegador con JavaScript.
Manejo de dirección: de derecha a izquierda y lenguajes de derecha a izquierda
Árabe, hebreo, persa, urdu y otros idiomas utilizan escrituras que se escriben de derecha a izquierda. Mientras que los idiomas de izquierda a derecha (LTR) superan en número a los de derecha a izquierda (RTL), es bueno saber cómo dar soporte a estos últimos. Afortunadamente, gran parte del trabajo lo realiza el navegador aquí; solo tenemos que establecer el <html dir> atributo en nuestras páginas.
// Cargar traducciones para el idioma dado y traducir
// la página para este idioma
async function setLocale(newLocale) {
si (newLocale === locale) return;
const newTranslations = await fetchTranslationsFor(
nuevo locale,
);
locale = newLocale;
translations = newTranslations;
// Establecer el atributo <html dir>
document.documentElement.dir = dir(newLocale);
// No es necesario para el flujo de dirección, pero por si acaso…
document.documentElement.lang = newLocale;
translatePage();
}
function dir(locale) {
return locale === "ar" ? "rtl" : "ltr";
}
El <html dir> atributo puede tomar los valores "ltr" o "rtl". Proporcionamos este valor mediante una función muy simple dir() y asignamos el atributo cada vez que cambiamos de idioma.

Si abrimos las herramientas de desarrollo del navegador, podemos ver cómo se actualizan los atributos <html> al cambiar de idioma
El navegador mostrará el documento de derecha a izquierda automáticamente cuando establezcamos <html dir="rtl">. Sin embargo, cualquiera de nuestros estilos direccionales personalizados, p. ej. margin-left: 20px, requerirá algo de CSS específico para RTL, incluyendo estilos volteados. Eso generalmente no es demasiado complicado; simplemente está un poco fuera del alcance de este artículo.
🤿 Adéntrate más » Lee más sobre CSS localizado en ¿Cómo usar un archivo CSS para la localización del sitio?
¡Con nuestro nuevo código en su lugar, obtenemos una página árabe que Avicena aprobaría!

Mensajes de traducción básicos
Antes de pasar a mensajes más complejos, como los que tienen valores interpolados y plurales, vamos a repasar brevemente cómo hemos implementado los mensajes de traducción.
// En nuestra página HTML
<h1 data-i18n-key="app-title">Mi Appy Apperson</h1>
// En nuestro JavaScript
function translateElement(element) {
const key = element.getAttribute("data-i18n-key");
const translation = translations[key];
element.innerText = translation;
}
// Dado que hemos cargado traducciones en árabe desde ar.json:
translations = {
"app-title": "تطبيقي المطبق",
};
translateElement(document.querySelector("[data-i18n-key='lead']"));
// renderiza como:
<h1 data-i18n-key="app-title">تطبيقي المطبق</h1>
Ese es nuestro sistema de traducción en pocas palabras.
Interpolación
¿Qué sucede cuando tenemos valores que cambian durante la ejecución y necesitan ser insertados en nuestros mensajes? Un ejemplo común es el nombre del usuario que ha iniciado sesión actualmente. Tendremos que actualizar nuestro sistema de traducción para manejar casos como estos.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<!-- ... -->
<h1 data-i18n-key="app-title">Mi Appy Apperson</h1>
<p
data-i18n-key="lead"
data-i18n-opt='{"username": "Swoodesh"}'
>
¡Bienvenido a mi pequeño rincón en internet, {username}!
</p>
</div>
<script src="js/scripts.js"></script>
</body>
</html>
Indicamos marcadores de posición para los valores que queremos interpolar en nuestros mensajes con la sintaxis {variable}. Un nuevo atributo data-i18n-opt almacena pares clave/valor de interpolación en un objeto JSON válido.
Por supuesto, necesitaremos los marcadores de posición en nuestros archivos de idioma.
{
"lead": ¡Bienvenido a mi pequeño rincón en internet, {username}!
}
{
"lead": "¡Hola! Bienvenido a mi rinconcito en la red, {username}."
}
Ahora podemos modificar la función translateElement para manejar interpolaciones.
// ...
function translateElement(element) {
const key = element.getAttribute("data-i18n-key");
const translation = translations[key];
const options = JSON.parse(
element.getAttribute("data-i18n-opt")
);
element.innerText = options
? interpolate(translation, options)
: translation;
}
// Convertir un mensaje como "Hola, {name}" a "Hola, Chad"
// dado el objeto de interpolación {name: "Chad"}
function interpolate(message, interpolations) {
return Object.keys(interpolations).reduce(
(interpolated, key) =>
interpolated.replace(
new RegExp(`{\s*${key}\s*}`, "g"),
interpolaciones[key],
),
message,
);
}
// ...
Si detectamos un atributo data-i18n-opt en el elemento dado a translateElement(), ejecutamos su mensaje traducido a través de una nueva función interpolate() antes de actualizar el elemento. Ahora, cuando cargamos nuestra página, vemos el mensaje con el valor interpolado.


Por supuesto, tener valores estáticos en el HTML tiene un uso limitado para nosotros. Lo ideal es que podamos interpolar dinámicamente con JavaScript. Eso no es muy difícil de programar.
Traducción dinámica después de cargar la página
Vamos a extraer una función de traducción general de translateElement().
// ...
function translateElement(element) {
const key = element.getAttribute("data-i18n-key");
const options =
JSON.parse(element.getAttribute("data-i18n-opt")) || {};
element.innerText = translate(key, options);
}
function translate(key, interpolations = {}) {
return interpolate(translations[key], interpolations);
}
// ...
Simplemente hemos extraído el código que se encarga de obtener un mensaje en el idioma activo, con interpolaciones, a una nueva función translate(). Ahora podemos usar esta función para actualizar la traducción de un elemento después de cargar la página. Digamos que queremos actualizar nuestro mensaje principal después de que el usuario inicie sesión. No es gran cosa.
const element =
document.querySelector("[data-i18n-key='lead']");
// Nuestra nueva función nos está sirviendo bien aquí
element.innerText =
translate("lead", { username: "Maggie" });
// Almacenar las interpolaciones actualizadas en el documento
// en caso de que el elemento se vuelva a renderizar en el futuro
element.setAttribute(
"data-i18n-opt",
JSON.stringify({ username: "Maggie" }),
);

Ahora podemos actualizar las traducciones de los elementos en cualquier momento desde nuestro JavaScript.
Plurales
No solo necesitamos presentar diferentes mensajes basados en un contador—como “1 seguidor” o “20.000 seguidores”—los distintos idiomas tienen diferentes reglas de plural. Mientras que el inglés tiene dos formas plurales: one y other, el árabe tiene seis formas plurales, por ejemplo. Históricamente, esto significaba que implementar soporte de plurales en aplicaciones de front-end no era muy fácil. Afortunadamente, el ahora-objeto estándar Intl.PluralRules hace que manejar los plurales sea mucho más fácil.
Digamos que somos escritores prolíficos, y queremos hacer saber al mundo cuántos artículos hemos redactado de verdad.
<p
data-i18n-key="article-plural"
data-i18n-opt='{"count": 122}'
>
{count} artículos escritos y contando.
</p>
Ten en cuenta que estamos usando una convención de terminar la clave de mensaje plural con -plural. Y por supuesto, necesitamos un count entero requerido para seleccionar la forma plural correcta. Hablando de formas plurales, añadámoslas.
{
// El inglés tiene dos formas plurales
"article-plural": {
"one": "{count} artículo y contando",
"other": "{count} artículos y contando"
}
}
{
// El árabe tiene seis formas plurales
"article-plural": {
"zero": "لا توجد مقالات",
"one": "مقال {count}",
"two": "مقالان",
"few": "{count} مقالات",
"many": "{count} مقال",
"other": "{count} مقال"
}
}
Ahora actualicemos nuestra función translate para manejar mensajes plurales.
// ...
function translate(key, interpolations = {}) {
const message = translations[key];
if (key.endsWith("-plural")) {
return interpolate(
pluralFormFor(message, interpolations.count),
interpolaciones,
);
}
return interpolate(message, interpolations);
}
Dado un objeto de formularios como
{
"zero": "No hay artículos",
"one": "Un artículo",
"other": "{count} artículos"
} y una cantidad de 3, devuelve "3 artículos"
*/
function pluralFormFor(forms, count) {
const matchingForm = new Intl.PluralRules(locale).select(count);
return forms[matchingForm];
}
// ...
El truco aquí es la parte que lee new Intl.PluralRules(locale).select(...). El objeto incorporado Intl.PluralRules, dado un idioma, conoce las reglas de plural de ese idioma. Por ejemplo, pasar "ar" al constructor, luego llamar a select(5) en el objeto devuelto, devuelve "few" — la forma correcta aquí.
Así que con muy pocas líneas de código, tenemos soporte para plurales totalmente global 🙌

Formato de números
Trescientos mil euros son “€300,000.00” en inglés (Estados Unidos), “300.000,00 €” en alemán (Alemania), “€3,00,000.00” en hindi (indio)—nota las comas en ese último—y “٣٠٠٬٠٠٠٫٠٠ €” en árabe (Egipto). ¿Cómo gestionamos todos estos formatos? No te preocupes; otro objeto Intl que es parte del estándar moderno de JavaScript es justo lo que necesitabas. Intl.NumberFormat ¡al rescate!
Siendo los emprendedores que somos, imaginemos que queremos iniciar una web para seguir NFTs, con estadísticas numéricas, por supuesto.
<p
data-i18n-key="nyan-cat-price"
data-i18n-opt='{"price": {"number" : 5300}}'
>
Nyan Cat (Oficial) NFT: {price}
</p>
Nuestra data-i18n-opt identifica el valor numérico del {price} en nuestros archivos de localización. Buscaremos esa clave number cuando actualicemos nuestro código de interpolación en un momento. Primero, proporcionemos las traducciones de los mensajes.
{
"nyan-cat-price": "Nyan Cat (Oficial) NFT: {price}"
}
{
"nyan-cat-price": "نيان كات NFT: {price}"
}
OK, actualicemos nuestro JavaScript para que esto funcione.
// ...
const fullyQualifiedLocaleDefaults = {
en: "en-US",
ar: "ar-EG",
};
function interpolate(message, interpolations) {
return Object.keys(interpolations).reduce(
(interpolated, key) => {
const value = formatNumber(interpolations[key]);
return interpolated.replace(
new RegExp(`{\s*${key}\s*}`, "g"),
value,
);
},
message,
);
}
Dado un objeto de valor como
{
Número : 300.000,
"estilo": "moneda",
Moneda: EUR
} y que la configuración regional activa es "en", devuelve "€300,000.00"
function formatNumber(value) {
if (typeof value === "object" && value.number) {
const { number, ...options } = value;
return new Intl.NumberFormat(
fullyQualifiedLocaleDefaults[locale],
options,
).format(number);
} else {
return value;
}
}
Cuando interpolamos nuestros mensajes traducidos, primero pasamos el valor que vamos a intercambiar por un formateador de números, que a su vez utiliza el objeto incorporado Intl.NumberFormat. Así que, de nuevo, con muy pocas líneas de código, tenemos un formato de números localizado.


✋🏽 Heads up » Es mejor pasar una configuración regional completamente especificada, como "en-US", al constructor Intl.NumberFormat(). Si pasamos solo un código de idioma, como "en", cada navegador decidirá qué región usar para formatear sus números: Un navegador podría establecer por defecto "en-US", mientras que otro podría establecer por defecto "en-UK". Así que usamos un mapa fullyQualifiedLocaleDefaults en nuestra función formatNumber() para obtener un formato coherente en todos los navegadores.
Debido a que estamos pasando todas las opciones definidas en nuestro objeto de interpolaciones al constructor Intl.NumberFormat(), podemos hacer uso de sus múltiples opciones de formato en cualquier momento que queramos.
<p
data-i18n-key="nyan-cat-price"
data-i18n-opt='{"price": {"
Número : 5300,
"estilo": "moneda",
Moneda: EUR
}}'
Nyan Cat (Oficial): el NFT {price}
</p>


🔗 Recurso » Puede que te guste nuestra Guía concisa de localización de números.
Formato de fecha
Al igual que los números, el formato de fecha es específico de la región. El 5 de diciembre de 2021 en su forma corta se escribe como «12/5/2021» en inglés de EE. UU. y «5.12.2021» en alemán de Alemania, por ejemplo. Y, de nuevo, un práctico objeto incorporado Intl.DateTimeFormat puede encargarse de la parte más complicada cuando se trata de formatear fechas.
Digamos que queremos mostrar la fecha y hora de publicación de un artículo nuestro.
<p
data-i18n-key="publish-date"
data-i18n-opt='{"publishDate": {"
Fecha: 2021-12-05 15:29:00
}}'
Publicado el {publishDate}
</p>
La clave especial date en nuestro objeto data-i18n-opt contiene el valor de fecha y hora que queremos formatear. Como de costumbre, vamos a agregar nuestros mensajes localizados.
{
"fecha-publicación": "Publicado {publishDate}"
}
{
"fecha-publicación": "publicado {publishDate}"
}
Ahora actualicemos nuestro sistema de traducción para buscar claves date y formatear sus valores como fechas localizadas.
function interpolate(message, interpolations) {
return Object.keys(interpolations).reduce(
(interpolated, key) => {
const value = formatDate(
formatearNúmero(interpolaciones[clave]),
);
return interpolated.replace(
new RegExp(`{\s*${key}\s*}`, "g"),
Valor,
);
},
Mensaje,
);
}
Dado un objeto de valor como
{
"date": "2021-12-05 15:29:00",
"dateStyle": "long",
"timeStyle": "short"
} y que el idioma actual es "en",
devuelve "5 de diciembre de 2021 a las 3:29 PM"
function formatDate(value) {
if (typeof value === "object" && value.date) {
const { date, ...options } = value;
const parsedDate =
typeof date === "string" ? Date.parse(date) : date;
return new Intl.DateTimeFormat(
fullyQualifiedLocaleDefaults[locale],
options,
).format(parsedDate);
} else {
return value;
}
}
Después de pasar nuestro objeto de valor a nuestro formateador de números, volvemos a pasar por nuestro nuevo formateador de fechas. Las opciones de formateo de fechas se pasan al constructor de Intl.DateTimeFormat, permitiendo una bastante flexibilidad en el formateo de fechas.
✋🏽 Heads up » We use Date.parse() to make sure that our string date is converted to a Date object, otherwise Intl.DateTimeFormat will throw an error.
Así, tenemos un formato de fecha localizado 👍
<p
data-i18n-key="publish-date"
data-i18n-opt='{"publishDate": {"
"date": 2021-12-05 15:29:00
"dateStyle": "long",
"timeStyle": "short"
}}'
Publicado el {publishDate}
</p>


🤿 Profundiza más » Si buscas características de formateo de fechas más robustas, echa un vistazo a nuestro resumen, ¿Cuál es la mejor biblioteca de fechas y horas de JavaScript? Y nuestra Forma amigable para mostrar fechas en TypeScript/JavaScript te permite mostrar fechas como «hace 1 hora.»
🔗 Recurso » Si estás usando un marco declarativo como React, puedes llevar lo que construimos en esta sección más allá y Crea tu propia biblioteca de i18n en JavaScript con TypeScript.
¿Cuáles son buenas bibliotecas de JavaScript para i18n que puedo usar?
Hemos cubierto cómo crear tu propia biblioteca de i18n en JavaScript anteriormente. Sin embargo, podría tener más sentido para tu proyecto adoptar una biblioteca de i18n lista para usar. No hay escasez de opciones, y en este artículo veremos cómo usar las bibliotecas Polyglot, i18next y Globalize.
Para una selección aún más amplia, nuestros artículos populares pueden ayudarte a comenzar con buen pie:
- Las mejores bibliotecas de I18n en JavaScript
- ¿Cuál es la mejor biblioteca de fechas y horas en JavaScript?
🗒 Nota » Si por casualidad estás trabajando con gettext heredado, echa un vistazo a la biblioteca Jed.
¿Cómo localizo una página web con Polyglot?
Mantenida por Airbnb, Polyglot es una pequeña biblioteca de i18n que resuelve algunos problemas de localización que anteriormente no estaban soportados por las bibliotecas estándar de JavaScript. Lo más notable entre las características de Polyglot es su excelente manejo de los plurales. Sin embargo, como mencionamos anteriormente, el ahora incorporado Intl.PluralRules constructor es compatible con todos los navegadores modernos y resuelve de manera efectiva el problema de pluralización. Aun así, la última versión de este artículo daba un gran protagonismo a Polyglot, así que queríamos incluir esta sección en caso de que algunos de nuestros lectores aún quieran una guía de Polyglot.
🗒 Nota » A menos que tu caso de uso requiera Polyglot, consulta la alternativa biblioteca i18next en la siguiente sección antes de decidirte por una solución de localización.
Sin más preámbulos, localicemos tu pequeña aplicación de demostración con la biblioteca de localización de Airbnb.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<ul class="navbar-list navbar-start">
<li class="navbar-item">
<a href="#" data-i18n-key="home" class="navbar-link">
Inicio
</a>
</li>
<li class="navbar-item">
<a href="#" data-i18n-key="about" class="navbar-link">
Sobre
</a>
</li>
</ul>
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">Árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<h1 data-i18n-key="app-title">Con Polyglot</h1>
<p data-i18n-key="lead" data-i18n-opt='{"username": "Cadence"}'>
¡Bienvenido a mi pequeño rincón en internet, %{username}!
</p>
<p
data-i18n-key="artículos-plural"
data-i18n-opt='{"smart_count": 2}'
>
%{smart_count} artículos escritos y contando.
</p>
</div>
</body>
</html>

Vale, ¡pongámonos a localizar esta aplicación con Polyglot!
Instalación
Polyglot tiene algunas dependencias de NPM, así que necesita Node instalado localmente. Con Node en su lugar, podemos inicializar un package.json para nuestra app de demostración ejecutando lo siguiente desde la línea de comandos.
npm init -y
Para agrupar nuestras dependencias de NPM en un archivo que los navegadores puedan leer, instalaremos el empaquetador de módulos Webpack como una dependencia de desarrollo. El servidor de desarrollo de Webpack nos ayudará con la actualización en caliente del paquete en el navegador mientras desarrollamos.
npm install --save-dev webpack webpack-cli webpack-dev-server
Vale, ahora vamos a instalar a la estrella del espectáculo: Polyglot.
npm install node-polyglot
Un index.js servirá como el punto de entrada para nuestra aplicación, y podemos usarlo para hacer una prueba rápida de las instalaciones de la librería.
import Polyglot from "node-polyglot";
console.log({ Polyglot });
Un script start en nuestro package.json facilitará nuestro desarrollo al poner en marcha el servidor de desarrollo con nuestra configuración personalizada.
{
"name": "polyglot-demo",
// ...
"scripts": {
"start": "webpack-dev-server --config webpack.config.js"
},
// ...
"devDependencies": {
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0"
},
"dependencies": {
"node-polyglot": "^2.4.2"
}
}
🗒 Nota » Estamos utilizando un archivo webpack.config.js relativamente simple para agrupar nuestra aplicación y configurar el servidor de desarrollo. Échale un vistazo en nuestro repo de Git en GitHub.
Ahora vamos a insertar nuestro JS empaquetado justo antes de la etiqueta de cierre </body> en nuestro archivo public/index.html.
<script src="./bundle.js"></script>
Con esto listo, ya podemos ejecutar nuestro script start desde la línea de comandos para iniciar el servidor de desarrollo de Webpack.
npm start
Si todo va bien, nuestra aplicación debería abrirse automáticamente en el navegador. Si abres las herramientas de desarrollador de tu navegador, verás mensajes en la consola similares a los siguientes.

Traducciones básicas
Pasemos al uso básico de Polyglot. Aquí está la receta:
// 1. Importa la biblioteca
import Polyglot from "node-polyglot";
// 2. Crear una instancia
const polyglot = new Polyglot();
// 3. Agregar mensajes de traducción para el idioma activo
polyglot.extend({
"app-title": "Con Polyglot",
});
// 4. Usa los mensajes para traducir los elementos de la página
const element = document.querySelector(
"[data-i18n-key='app-title']",
);
// polyglot.t() resuelve un mensaje de traducción dado
// una clave
element.innerHTML = polyglot.t("app-title");
Para cambiar de idioma, podemos recargar la página con los mensajes del nuevo idioma.
✋🏽 Atención » Usamos innerHTML en este artículo para poner el contenido de un elemento. Ten cuidado con este atributo en producción; asegúrate de sanear cualquier HTML que inyectes usando innerHTML para evitar ataques de secuencias de comandos en sitios cruzados (XSS).
import Polyglot from "node-polyglot";
const polyglot = new Polyglot();
polyglot.extend({
"app-title": "مع بوليجلوت",
});
const element = document.querySelector(
"[data-i18n-key='app-title']",
);
element.innerHTML = polyglot.t("app-title");
Carga asíncrona de archivos de traducción
Mientras que lo anterior funciona bien para las aplicaciones más pequeñas, podríamos hacerlo un poco mejor separando nuestros mensajes de traducción en archivos JSON separados, uno por cada idioma.
{
"app-title": "Con Polyglot",
"home": "Inicio",
"about": "Sobre"
}
{
"app-title": "مع بوليجلوت",
"home": "الرئيسية",
"about": "نبذة عنا"
}
Ahora podemos configurar un idioma predeterminado para nuestra aplicación y cargar sus traducciones desde la red cuando se carga la página.
import Polyglot from "node-polyglot";
const defaultLocale = "en";
const polyglot = new Polyglot();
// Cargar mensajes de traducción desde la red
async function loadTranslations(locale) {
return await fetch(`/lang/${locale}.json`).then(
(respuesta) => respuesta.json(),
);
}
// Traduce todos los elementos de la página que tengan nuestro elemento personalizado
// atributo data-i18n-key
function traducirPagina() {
const elementosTraducibles = document.querySelectorAll(
"[data-i18n-key]",
);
elementosTraducibles.forEach((el) => {
const key = el.getAttribute("data-i18n-key");
el.innerHTML = polyglot.t(key);
});
}
// Init
(async function () {
const traducciones = await cargarTraducciones(
idioma predeterminado,
);
polyglot.extend(translations);
traducePágina();
})();
Con eso, obtenemos el siguiente renderizado en el navegador.

Nuestros elementos del menú de navegación y el título principal están traducidos a nuestro idioma predeterminado, inglés. Sin embargo, observa los errores de Polyglot en la consola, y cómo las claves faltantes (lead y article-plural) muestran el valor de las claves mismas. Añadiremos traducciones para estas claves en breve para solucionarlo.
Si cambiamos defaultLocale a "ar", obtenemos el siguiente resultado.

Selector de idioma
Nuestra aplicación ahora es más escalable porque solo se cargan las traducciones del idioma activo. Usa esto para crear un selector de idioma. Ya tenemos el HTML para el selector en nuestra aplicación:
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<!-- ... -->
<script src="./bundle.js"></script>
</body>
</html>
Conectarnos a este elemento <select> desde nuestro JavaScript nos permite agregar nuestro comportamiento para cambiar de idioma.
import Polyglot from "node-polyglot";
const defaultLocale = "en";
const polyglot = new Polyglot();
// Cargar traducciones para el idioma (o configuración regional) dado y traducir
// elementos de página para este idioma
async function loadAndTranslate(locale) {
const translations = await loadTranslations(locale);
polyglot.replace(translations);
translatePage();
}
// Siempre que el usuario cambie el idioma activo, carga
// los mensajes de este idioma en esta página
function bindLocaleSwitcher(initialValue) {
const switcher = document.querySelector(
"[data-i18n-switcher]",
);
switcher.value = initialValue;
switcher.onchange = (e) => {
loadAndTranslate(e.target.value);
};
}
// Init
.init({
loadAndTranslate(defaultLocale);
bindLocaleSwitcher(defaultLocale);
Hemos refactorizado el código que carga nuestros mensajes de traducción y traduce los elementos de nuestra página a una función reutilizable loadAndTranslate(). Una nueva función bindLocaleSwitcher() se conecta al selector de idioma <select>; utiliza loadAndTranslate() para actualizar nuestras traducciones según el idioma seleccionado por el usuario.
✋🏽 Atención » polyglot.extend() agregará mensajes de traducción a los ya cargados, así que, usamos polyglot.replace() en su lugar para asegurarnos de que solo cargamos las traducciones de la localización activa.
Esto debería poner en marcha nuestro elegante cambiador de idioma.

Interpolación
Nuestro texto principal incluye el nombre del usuario que ha iniciado sesión (ficticio, por supuesto). Este tipo de valor interpolado es manejado por Polyglot, por defecto, usando una sintaxis especial %{variable}.
🗒 Nota » Puedes cambiar los caracteres que denotan valores interpolados usando la interpolación opción pasada al constructor de Polyglot.
<!-- ... -->
<p data-i18n-key="lead" data-i18n-opt='{"username": "Cadence"}'>
¡Bienvenido/a a mi pequeño rincón en internet, %{username}!
</p>
<!-- ... -->
{
// ...
"lead": ¡Bienvenido a mi pequeño rincón en Internet, %{username}!
}
{
"lead": "أهلاً بك في مكاني الصغير على النت يا %{username}."
}
Se pueden agregar algunas líneas de código a la función translatePage() para acomodar interpolaciones.
// ...
// Traducir todos los elementos de la página que tienen un
// atributo data-i18n-key
function traducePágina() {
const elementosTraducibles = document.querySelectorAll(
"[data-i18n-key],"
);
elementosTraducibles.forEach((el) => {
const key = el.getAttribute("data-i18n-key");
// Extraer claves/valores de interpolación del HTML y
// conviértelos a JSON
const interpolations = el.getAttribute("data-i18n-opt");
const parsedInterpolations = interpolations
? JSON.parse(interpolations)
: {};
// Pasa las interpolaciones procesadas a polyglot.t().
// que maneja automáticamente las sustituciones
el.innerHTML = polyglot.t(key, parsedInterpolations);
});
}
// ...
Con el código anterior en su lugar, ahora tenemos nuestro párrafo de introducción interpolado mostrado en el idioma activo.


Plurales
Digamos que queremos mostrarte, como usuario que ha iniciado sesión, cuántos mensajes has recibido: “Tienes 1 nuevo mensaje” o “Tienes 12 nuevos mensajes”, por ejemplo. Polyglot maneja los plurales así de bien. Solo necesitamos agregar nuestros mensajes de traducción con el valor numérico interpolado especial, smart_count.
<!-- ... -->
<p
data-i18n-key="new-messages"
data-i18n-opt='{"smart_count": 12}'
Tienes %{smart_count} nuevos mensajes
</p>
<!-- ... -->
Polyglot usa smart_count para seleccionar la forma de plural correcta de un mensaje de traducción dependiendo del idioma activo. Las formas plurales se separan usando cuatro barras verticales |||| en nuestros mensajes. English tiene one y other formas de plural, y necesitamos proporcionarlas en orden:
{
"new-messages": "Tienes %{smart_count} nuevo mensaje |||| Tienes %{smart_count} nuevos mensajes"
}
El árabe tiene seis formas plurales, y las añadimos a nuestros mensajes de la misma manera.
{
"new-messages": "No tienes mensajes nuevos |||| Tienes un mensaje nuevo |||| Tienes dos mensajes nuevos |||| Tienes %{smart_count} mensajes nuevos |||| Tienes %{smart_count} mensaje nuevo |||| Tienes %{smart_count} mensaje nuevo"
}
🗒 Nota » Consulta la sección ¿Cómo localizo una página web con JavaScript? ➞ Plurals para obtener más detalles sobre las formas plurales.
Una cosa más: por defecto, Polyglot está completamente ajeno al idioma activo, por lo que no conocerá las reglas de pluralización del idioma activo a menos que especifiquemos explícitamente el idioma al cargar.
// Cargar traducciones para el idioma dado y traducir
// elementos de página para este idioma
async function loadAndTranslate(locale) {
const translations = await loadTranslations(locale);
polyglot.locale(locale);
polyglot.replace(translations);
traducePágina();
}
¡Y eso es todo! Ahora tenemos soporte avanzado de plurales en nuestra app.


🔗 Recurso » Obtén todo el código para nuestra aplicación Polyglot desde nuestro repositorio de GitHub.
🔗 Recurso » La documentación oficial de Polyglot es tan ajustada como la propia biblioteca.
¿Cómo localizo una página web con i18next?
Mientras escribo esto, i18next es una de las bibliotecas de i18n de JavaScript más populares. La biblioteca “aprende una vez, [usa] en todas partes” funciona de forma independiente y con numerosos frameworks de JavaScript. Un amplio ecosistema de plugins significa que a menudo estás a un npm install de resolver un problema común de i18n. Todo esto hace que i18next sea una recomendación fácil.
Vale, basta de cháchara. Volvamos a nuestra pequeña demostración y localicémosla con i18next.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<ul class="navbar-list navbar-start">
<li class="navbar-item">
<a href="#" data-i18n-key="home" class="navbar-link">
Inicio
</a>
</li>
<li class="navbar-item">
<a href="#" data-i18n-key="about" class="navbar-link">
Sobre
</a>
</li>
</ul>
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<h1 data-i18n-key="app-title">Con i18next</h1>
<p data-i18n-key="lead" data-i18n-opt='{"username": "Zelda"}'>
¡Bienvenido/a a mi pequeño rincón en internet, {{username}}!
</p>
<p data-i18n-key="new-messages" data-i18n-opt='{"count": 12}'>
Tienes {{count}} nuevos mensajes
</p>
</div>
<script src="./bundle.js"></script>
</body>
</html>
No hay mucho nuevo aquí. ¡Vamos a localizar!
Instalación
Usaremos Node y su gestor de paquetes NPM para instalar i18next. Primero, vamos a crear un archivo package.json para rastrear las dependencias de nuestro proyecto y los scripts de NPM, ejecutando el siguiente comando en la línea de comandos.
npm init -y
La herramienta de empaquetado Webpack nos permitirá agrupar i18next, sus complementos y nuestro JavaScript personalizado, y servirlos en un solo archivo al navegador. Instalemos Webpack, junto con su servidor de desarrollo, que tiene una útil función de recarga en caliente que facilita el desarrollo:
npm install --save-dev webpack webpack-cli webpack-dev-server
No podemos olvidar nuestra biblioteca i18n, por supuesto.
npm install i18next
Un práctico script npm start puede facilitar el arranque de nuestro servidor de desarrollo.
{
"name": "i18next-demo",
"scripts": {
"start": "webpack-dev-server --config webpack.config.js"
},
"devDependencies": {
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0"
},
"dependencies": {
"i18next": "^21.6.3"
}
}
🗒 Nota » Estamos usando un archivo webpack.config.js relativamente simple para empaquetar nuestra aplicación y configurar el servidor de desarrollo. Échale un vistazo a nuestro repositorio de Git en GitHub.
Vamos a crear un punto de entrada index.js para nuestra app y hacer una prueba rápida con i18next para asegurarnos de que esté instalado correctamente.
import i18next from "i18next";
console.log({ i18next });
Ahora, cuando ejecutas npm start desde la línea de comandos, verás que el servidor de desarrollo de Webpack se inicia y carga nuestra app en el navegador automáticamente.

Mensajes de traducción básicos
i18next es flexible en cómo acepta mensajes de traducción. Hacemos la mayor parte de nuestra configuración al inicializar la biblioteca con i18next.init(...). La configuración más básica consiste en incluir nuestros mensajes de traducción directamente bajo la opción resources.
import i18next from "i18next";
i18next.init({
// El idioma activo
lng: "en",
// Salida de consola útil habilitada durante el desarrollo
depuración: true.
// Mensajes de traducción, asociados por código de localización
Recursos:
en: {
// Por defecto, i18next espera mensajes bajo el.
// espacio de nombres "traducción"
traducción: {
"app-title": "Con Polyglot,"
Inicio: Inicio,
Acerca de: Acerca de,
},
},
ar: {
traducción: {
"app-title": "مع بوليجلوت",
home: "الرئيسية",
about: "نبذة عنا",
},
},
},
});
// Traducir elementos de la página
const translatableElements = document.querySelectorAll(
"[data-i18n-key]",
);
elementosTraducibles.forEach((el) => {
const key = el.getAttribute("data-i18n-key");
el.innerHTML = i18next.t(key);
});
Con lo anterior, no deberías ver cambios cuando tu aplicación se recarga en el navegador. Sin embargo, si cambiamos lng a "ar", vemos las siguientes traducciones en árabe.

🗒 Nota » La opción debug: true activa registros de consola muy útiles en el navegador. Nota los mensajes clave que faltan arriba, por ejemplo.
Carga asíncrona de traducciones
Siendo desarrolladores con visión de futuro, hagamos que nuestra aplicación sea más escalable dividiendo nuestras traducciones en archivos separados, uno por idioma.
{
"app-title": "Con i18next",
"home": Inicio,
"about": "Sobre"
}
{
"app-title": "مع آي أيتين نيكست",
"home": "الرئيسية",
"about": "نبذة عنا"
}
i18next es maduro y tiene muchos aspectos cubiertos; por lo que no necesitamos escribir nuestro propio código para cargar archivos de traducción desde la red. El backend HTTP oficial se integra en la biblioteca y hace todo el trabajo por nosotros. Instálalo.
npm install i18next-http-backend
Ahora podemos integrar el backend en nuestro index.js y use() mientras inicializamos i18next.
import i18next from "i18next";
import HttpApi from "i18next-http-backend";
// Hacemos la función asíncrona para poder usar 'await'
// el archivo de traducción a medida que se transmite hacia abajo
// Red
async function initI18next() {
// Usamos() el backend y esperamos a que se cargue
// la traducciones de la red
await i18next.use(HttpApi).init({
lng: "en",
depuración: true,
// Eliminar `resources` integrados
// Deshabilitar la carga de la configuración regional de desarrollo
fallbackLng: false,
// Configurar el backend HTTP
backend: {
loadPath: "/lang/{{lng}}.json",
},
});
}
// Refactorización rápida del código de traducción de la página
// a una función
function translatePageElements() {
const translatableElements = document.querySelectorAll(
"[data-i18n-key]",
);
elementosTraducibles.forEach((el) => {
const key = el.getAttribute("data-i18n-key");
el.innerHTML = i18next.t(key);
});
}
// Init
(async function () {
await initI18next();
translatePageElements();
})();
La opción backend.loadPath anula la ruta de archivo de traducción predeterminada del backend. Un marcador de posición especial {{lng}} se reemplaza con el idioma activo. Por ejemplo, cuando nuestra aplicación se carga por primera vez, el backend buscará /lang/en.json, ya que especificamos el idioma predeterminado como en anteriormente en la configuración.
Eso es todo. Nuestras traducciones ahora se cargan desde la red en lugar de estar integradas en nuestro código.
Idiomas soportados y un idioma de respaldo
A menudo queremos especificar una lista de idiomas (locales) que nuestra aplicación soporta y un idioma de respaldo cuando falta una traducción. Podemos usar las opciones de configuración supportLngs y fallbackLng de i18next, respectivamente, para conseguir esto.
async function initI18next() {
await i18next.use(HttpApi).init({
lng: "en",
debug: true,
supportedLngs: ["en", "ar"],
fallbackLng: "en",
backend: {
loadPath: "/lang/{{lng}}.json",
},
});
}
✋🏽 Atención » El idioma de respaldo se cargará siempre, independientemente del idioma activo.
Detectando automáticamente el idioma del usuario
Es común querer detectar los ajustes del navegador del usuario y usar su idioma si lo soportamos. Esto suele ser complicado, pero i18next tiene un plugin oficial que puede solucionarlo rápidamente. Empieza por instalarlo.
npm install i18next-browser-languagedetector
Al igual que el backend HTTP, importamos el plugin detector y lo usamos() al inicializarlo.
import i18next from "i18next";
import HttpApi from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
async function initI18next() {
await i18next
.use(HttpApi)
.use(LanguageDetector)
.init({
debug: true,
supportedLngs: ["en", "ar"],
fallbackLng: "en",
// Permitir que "en" se use para
// "en-US", "en-CA", etc.
nonExplicitSupportedLngs: true,
backend: {
loadPath: "/lang/{{lng}}.json",
},
});
}
Y eso es todo lo que necesitas para lograr una detección automática de idioma sólida con i18next.
🔗 Recurso » Te estarás preguntando qué criterios utiliza el detector de idioma para determinar el idioma del usuario. Cubrimos esto en detalle en nuestra Guía de localización de React con i18next .
✋🏽 Atención » El detector de idioma almacenará el idioma que detecte en el almacenamiento de idioma del navegador por defecto, y usará ese valor cuando el usuario visite nuestro sitio de nuevo.
🤿 Profundiza » Tenemos una guía dedicada a Detectar la preferencia de idioma del navegador con JavaScript, que te puede interesar.
Selector de idioma
La detección automática del idioma está bien y todo, pero a menudo necesitamos tener una interfaz para que los usuarios puedan establecer explícitamente su idioma preferido. Ya tenemos el marcado para un selector de idioma configurado.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<!-- ... -->
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">Árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<!-- ... -->
</div>
<script src="./bundle.js"></script>
</body>
</html>
Conectemos este HTML y usemos la función i18next.changeLanguage() para establecer nuestro idioma activo según la elección del usuario. Después de que se carguen los mensajes del idioma, podemos encadenar translatePageElements para volver a renderizar la página con traducciones actualizadas.
function bindLocaleSwitcher(initialValue) {
const switcher = document.querySelector(
"[data-i18n-switcher]",
);
switcher.value = initialValue;
switcher.onchange = (e) => {
i18next
.changeLanguage(e.target.value)
.then(translatePageElements);
};
}
// Init
(async function () {
await initI18next();
translatePageElements();
bindLocaleSwitcher(i18next.resolvedLanguage);
})();
✋🏽 Atención » El detector de idioma podría haber detectado un idioma que no admitimos, y ese valor existirá en i18next.language. Usamos i18next.resolvedLanguage arriba para asegurarnos de que usamos el idioma activo, soportado al inicializar nuestro selector de idioma.
¡Y voilà! Una interfaz para cambiar de idioma:

Interpolación
Por defecto, i18next utiliza una sintaxis {{variable}} para denotar valores interpolados en los mensajes de traducción.
<!-- ... -->
<p data-i18n-key="lead" data-i18n-opt='{"username": "Zelda"}'>
¡Bienvenido/a a mi pequeño rincón en la web, {{username}}!
</p>
<!-- ... -->
{
"lead": "Welcome to my little spot on the interwebs, {{username}}!" → "¡Bienvenido a mi pequeño rincón en la web, {{username}}!"
}
{
"lead": "أهلاً بك في مكاني الصغير على النت يا {{username}}."
}
Podemos extraer estos valores dinámicos de nuestros atributos HTML y alimentarlos a i18next.t(), que maneja la interpolación por nosotros.
function translatePageElements() {
const translatableElements = document.querySelectorAll(
"[data-i18n-key]",
);
translatableElements.forEach((el) => {
const key = el.getAttribute("data-i18n-key");
const interpolations = el.getAttribute("data-i18n-opt");
const parsedInterpolations = interpolations
¿? JSON.parse(interpolations)
: {};
el.innerHTML = i18next.t(key, parsedInterpolations);
});
}
Con esto, nuestros valores dinámicos se reemplazan en nuestros mensajes.


Plurales
Bajo el capó, i18next intenta usar el estándar Intl.PluralRules para gestionar los plurales. Una variable interpolada especial count determina la elección de la forma plural, dependiendo del idioma activo.
<p data-i18n-key="new-messages" data-i18n-opt='{"count": 12}'>
Tienes {{count}} nuevos mensajes
</p>
i18next utiliza la convención message_form para las claves de mensaje en plural. Por ejemplo, para manejar las formas one y other en la traducción al inglés de new-messages, podemos especificar lo siguiente.
{
"new-messages_one": Tienes {{count}} nuevo mensaje
"new-messages_other": "Tienes {{count}} nuevos mensajes"
}
El árabe tiene seis formas plurales, y podemos especificarlas de forma muy similar.
{
"new-messages_zero": "No tienes mensajes nuevos",
"new-messages_one": "لديك رسالة جديدة",
"new-messages_two": "Tienes dos mensajes nuevos",
"new-messages_few": "Tienes {{count}} mensajes nuevos",
"new-messages_many": "Tienes {{count}} mensajes nuevos",
"new-messages_other": "Tienes {{count}} mensajes nuevos"
}
Con poco esfuerzo, nuestra aplicación puede mostrar mensajes plurales.


🔗 Recurso » Obtén todo el código que hemos cubierto anteriormente de nuestro repositorio de GitHub.
¿Cómo localizo una aplicación de React, Angular o Vue?
En los últimos años, frameworks declarativos como React, Angular, Vue.js y otros han revolucionado el mundo del front-end web. Cubrimos estos frameworks en profundidad en nuestro blog. Como React es el más popular entre los grandes, proporcionaremos una guía rápida para localizar aplicaciones de React a continuación. Y para otros frameworks declarativos, consulta los siguientes artículos en profundidad.
Artículos de localización de Angular
- Traduciendo aplicaciones de Angular con el módulo I18n integrado
- ¿Cuál es la mejor biblioteca de Angular para la Internacionalización?
- Angular L10n con I18next
- Tutorial de Angular 10 sobre localización con Transloco
- Full-Stack I18n con Angular y .NET Core
Artículos de localización de Vue.js
- La Guía Definitiva de Localización de Vue 3
- Análisis en profundidad: Traducción de Vue con vue-i18next
- El único tutorial de Nuxt.js sobre I18n que vas a necesitar jamás (basado en Vue)
Artículos de localización para otros frameworks
Nos damos cuenta de que algunos de nuestros lectores pueden estar usando Svelte, Next u otro framework, así que siempre escribimos sobre las últimas novedades y lo más destacado. Aquí hay una selección de guías para localizar la aplicación que estás construyendo en tu framework favorito:
- Cómo localizar una aplicación Svelte con svelte-i18n
- Una guía paso a paso para la localización de Svelte con svelte-i18n v3
- Localizando aplicaciones de SolidJS con I18next
- Localizando Aplicaciones de Mithril
- Cómo localizar aplicaciones usando el Framework Aurelia
- Localizando aplicaciones de StimulusJS con I18next
- Full-Stack JavaScript I18n Paso a Paso (usando Next.js y Sails.js)
🗒 Nota » De manera más específica: Si vas a trabajar con números de teléfono internacionales, asegúrate de echar un vistazo a la libphonenumber library. ¡Es agnóstico respecto al framework!
¿Cómo localizo una aplicación de React con i18next?
Como prometimos, vamos a repasar rápidamente una guía para localizar nuestras aplicaciones de React con la biblioteca i18next. Hemos cubierto i18next anteriormente en este artículo, así que nos centraremos en su integración con React.
🤿 Profundiza más » Guía de localización de React con i18next es más exhaustiva que nuestro breve resumen aquí.
Primero, tomaremos nuestra querida aplicación de demostración y la descompondremos en componentes de React.
import "./App.css";
import Navbar from "./layout/Navbar";
function App() {
return (
<div className="container">
<Navbar />
<h1>React i18n</h1>
<p>
Bienvenido/a a mi pequeño rincón en internet, ¡tú!
</p>
<p>Tienes {count} nuevos mensajes</p>
</div>
);
}
export default App;
import LocaleSwitcher from "../features/LocaleSwitcher";
function Navbar() {
return (
<nav className="navbar">
<div className="container">
<ul className="navbar-list navbar-start">
<li className="navbar-item">
<a href="#" className="navbar-link">
Inicio
</a>
</li>
<li className="navbar-item">
<a href="#" className="navbar-link">
Sobre
</a>
</li>
</ul>
<div className="navbar-end">
<LocaleSwitcher />
</div>
</div>
</nav>
);
}
export default Navbar;
function LocaleSwitcher() {
return (
<>
<img
alt="Icono de traducción"
src="img/translation-icon@2x.png"
className="translation-icon"
/>
<select className="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">árabe (العربية)</option>
</select>
</>
);
}
export default LocaleSwitcher;
El selector de idioma hace poco en este momento, pero pronto lo mejoraremos. Por ahora, tenemos una buena base para localizar.

Instalación de la biblioteca
Además de i18next, vamos a usar el marco de integración oficial react-i18next , que hace que usar i18next con React sea muy sencillo. Desde la raíz del proyecto, ejecutemos lo siguiente en la línea de comandos para instalar las bibliotecas.
npm install i18next react-i18next
A continuación, inicialicemos i18next, usando() la integración de React como lo hacemos. La forma más básica de proporcionar traducciones a i18next es usando la opción resources al inicializar.
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
i18next.use(initReactI18next).init({
Recursos:
en: {
{Traducción
"app-title": "Con React",
},
},
ar: {
Traducción{
"app-title": "مع ريأكت",
},
},
},
lng: "en",
depuración: true,
interpolation: {
escapeValue: false,
},
});
export default i18next;
🗒 Nota » Establecemos interpolation.escapeValue en false para deshabilitar el escape predeterminado que i18next hace para proteger contra ataques XSS, ya que React lo hace por nosotros de todas formas.
Traigamos nuestro módulo a nuestro index.js raíz para que podamos inicializar i18next cuando nuestra aplicación se cargue.
import React from "react";
import ReactDOM from "react-dom";
import "./lib/skeleton/normalize.css";
import "./lib/skeleton/skeleton.css";
import "./services/i18n";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root"),
);
// ...
Traducciones básicas
La función de traducción i18next.t() familiar se puede usar en nuestros componentes de React. Solo necesitamos importar el hook de React useTranslation para que t esté disponible.
import { useTranslation } from "react-i18next";
import Navbar from "./layout/Navbar";
import "./App.css";
function App() {
const { t } = useTranslation();
return (
<div className="container">
<Navbar />
<h1>{t("app-title")}</h1>
// ...
</div>
);
}
export default App;
Cuando nuestra app se recarga, todo debería verse igual. Sin embargo, si cambiamos el valor de lng a "ar" en nuestro archivo inicializador src/services/i18n.js, deberíamos ver el título de nuestra aplicación localizado al árabe.

Carga asíncrona de archivos de traducción
Hagamos que nuestra aplicación sea más escalable dividiendo nuestras traducciones en archivos por idioma. El práctico y oficial complemento i18next-http-backend hace que esto sea un trabajo rápido para nosotros. Instálalo.
npm install i18next-http-backend
El backend buscará archivos en /locales/{{lng}}/{{ns}}.json por defecto, donde {{lng}} se resuelve en la localización activa, y {{ns}} se resuelve en el espacio de nombres activo. Dado que el espacio de nombres por defecto es translation, podemos poner nuestros mensajes de traducción en inglés en public/locales/en/translation.json.
{
"app-title": "Con React",
"home": Inicio,
"about": "Sobre"
}
Nuestro archivo en árabe sigue la misma convención:
{
"app-title": "مع ريأكت",
"home": "الرئيسية",
"about": "نبذة عنا"
}
Ahora solo tenemos que importar el backend y usar() al inicializar i18next. También deberíamos eliminar nuestras traducciones en línea bajo la clave resources, ya que ahora el plugin cargará nuestros mensajes de traducción desde la red.
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import HttpApi from "i18next-http-backend";
i18next
.use(initReactI18next)
.use(HttpApi)
.init({
// Eliminar `resources`
lng: "en",
debug: true,
interpolation: {
escapeValue: false,
},
});
export default i18next;
Cuando nuestra aplicación se recargue, deberíamos ver exactamente el mismo renderizado localizado. Por supuesto, las traducciones de la localización activa ahora están fluyendo por la red, por lo que nuestra aplicación es más ligera de carga y más fácil de escalar y mantener.

Selector de idioma
Construir una interfaz para cambiar de idioma es pan comido con React e i18next. Podemos actualizar nuestro componente LocaleSwitcher, controlando el <select> en su interior para cambiar el idioma activo al que el usuario elija.
import { useTranslation } from "react-i18next";
function LocaleSwitcher() {
const { i18n } = useTranslation();
return (
<>
<img
src="img/translation-icon@2x.png"
alt="Icono de traducción"
className="translation-icon"
/>
<select
className="locale-switcher"
value={i18n.language}
onChange={(e) =>
i18n.changeLanguage(e.target.value)
}
>
<option value="en">Inglés</option>
<option value="ar">Árabe (العربية)</option>
</select>
</>
);
}
export default LocaleSwitcher;
La integración de i18next React garantiza que las traducciones se vuelvan a renderizar cuando se cambia el idioma activo.

🔗 Recurso » Obtén el código de todo lo que hemos construido anteriormente desde nuestro repositorio de GitHub.
Artículos de localización de React
Nos encanta escribir sobre React en nuestro blog, así que estamos felices de ofrecerte una selección de nuestros análisis en profundidad y tutoriales basados en React, todos centrados en la localización:
- Una guía para la localización de React con i18next
- Introducción a la internacionalización en JavaScript con i18next y Moment.js
- Tutorial de React Redux: Internacionalización con react-i18n-redux
- Construye tu propia solución i18n con React y Redux
- Localizando Aplicaciones de JavaScript y React con LinguiJS
- Renderizado Localizado del Lado del Servidor con React
- Localización de aplicaciones Meteor impulsadas por React
- Una Guía Completa para la Localización de React Native
- Todo lo que necesitas saber sobre i18n con Gatsby (basado en React)
- JavaScript I18n de Full Stack paso a paso (usando Next.js, que está basado en React)
¿Cómo localizas una página web con jQuery e i18next?
Aunque no está tan de moda como hace algunos años, jQuery sigue siendo una de las bibliotecas de JavaScript más populares actualmente en uso. Encontrarás que localizar aplicaciones jQuery es bastante fácil con la biblioteca i18next. Un plugin oficial de i18next para jQuery requiere muy poco trabajo para configurarse, así que vamos a usarlo para traducir nuestra fiable aplicación de demostración.
🗒 Nota » Hemos cubierto i18next en más detalle anteriormente en este artículo, así que nos centraremos en la integración de jQuery aquí.
Si has estado siguiendo la lectura, la siguiente aplicación de ejemplo te resultará familiar. Sirve como una buena base para nuestro trabajo de localización.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<ul class="navbar-list navbar-start">
<li class="navbar-item">
<a href="#" data-i18n="home" class="navbar-link">
Inicio
</a>
</li>
<li class="navbar-item">
<a href="#" data-i18n="about" class="navbar-link">
Sobre
</a>
</li>
</ul>
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<h1 data-i18n="app-title">jQuery i18n</h1>
<p data-i18n="lead" data-i18n-options='{"username": "Jackie"}'>
¡Bienvenido/a a mi pequeño rincón en internet, {{username}}!
</p>
<p data-i18n="new-messages" data-i18n-options='{"count": 3}'>
Tienes {{count}} nuevos mensajes
</p>
</div>
</body>
</html>
🗒 Nota » Por defecto, el complemento jQuery de i18next utiliza data-i18n (no nuestro anterior data-i18n-key) para sus claves de traducción. Este se puede cambiar en las opciones del plugin.

¿Es hora de localizar? ¡Vamos!
Instalación
La forma más sencilla de instalar i18next y su plugin jQuery es descargar sus archivos de distribución minificados e incluirlos en nuestro HTML. Puedes obtener los archivos en las siguientes ubicaciones.
🔗 Recurso » Consulta la documentación oficial del plugin jQuery de i18next en GitHub.
Después de descargar los archivos anteriores, podemos colocarlos en un directorio js/lib en nuestro proyecto y cargarlos en nuestra página HTML principal.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
</div>
<script src="./js/lib/jquery-3.6.0.min.js"></script>
<script src="./js/lib/i18next.min.js"></script>
<script src="./js/lib/jquery-i18next.min.js"></script>
<!-- Nuestro JavaScript personalizado, en un momento... -->
<script src="./js/scripts.js"></script>
</body>
</html>
Traducciones básicas
Con nuestras bibliotecas instaladas, ahora podemos escribir algo de código de configuración para hacer que el trabajo de localización básico funcione.
// Inicializar i18next
i18next.init({
lng: "es", // Idioma inicial
debug: true, // Proporciona mensajes útiles en la consola
resources: { // Traducciones
en: {
translation: {
"app-title": "jQuery + i18next",
},
},
ar: {
translation: {
"app-title": "جي كويري + آي إيتين نيكست",
},
},
},
});
// Inicializar el plugin jQuery i18next
jqueryI18next.init(i18next, $);
// Traducir elementos de la página
$("body").localize();
Hemos cubierto i18next.init(...) anteriormente en este artículo. Nota que aquí también tenemos una llamada a jQueryI18next.init(...). En su forma más básica, la función init del plugin jQuery i18next toma la instancia activa de i18next, así como una referencia al objeto jQuery, $.
Cuando se inicializa el plugin, agrega una función localize() a jQuery. Llamar a $(selector).localize() hace que el plugin localice todos los elementos bajo la jerarquía seleccionada. Para cada elemento, si se encuentra un atributo data-i18n, su traducción correspondiente, en el idioma activo, se sustituye.
Por ejemplo:
// En nuestro JavaScript
i18next.init({
lng: "en",
resources: {
en: {
translation: {
"app-title": "jQuery + i18next",
},
},
ar: {
translation: {
"app-title": "جي كويري + آي إيتين نيكست",
},
},
},
});
jqueryI18next.init(i18next, $);
$("#main-title").localize();
// En nuestro HTML
<h1 id="main-title" data-i18n="app-title"></h1>
// Renderiza como:
<h1 id="main-title" data-i18n="app-title">jQuery + i18next</h1>
Sencillo y agradable 😊

Y si cambiamos nuestro idioma inicial a árabe cambiando lng a "ar", obtendremos un título en árabe.

Carga asíncrona de archivos de traducción
¿Qué tal si dividimos nuestros archivos de traducción en archivos separados, uno por idioma? Sé que te lo preguntas. No te preocupes, el plugin oficial de backend HTTP de i18next nos respalda.
Para instalar el plugin, podemos obtener el script de distribución minificado de GitHub y colocarlo en nuestro directorio js/lib. Por supuesto, también querremos incluirlo en nuestro HTML.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<!-- ... -->
</div>
<script src="./js/lib/jquery-3.6.0.min.js"></script>
<script src="./js/lib/i18next.min.js"></script>
<script src="./js/lib/i18nextHttpBackend.min.js"></script>
<script src="./js/lib/jquery-i18next.min.js"></script>
<script src="./js/scripts.js"></script>
</body>
</html>
Ahora podemos hacer que nuestra aplicación sea más escalable moviendo nuestras traducciones a archivos JSON separados.
{
"app-title": "Con jQuery + i18next",
"home": Inicio,
"about": "Acerca de"
}
{
"app-title": "مع جي كويري و آي إيتين نيكست",
"home": "الرئيسية",
"about": "نبذة عنا"
}
Tendremos que rehacer nuestro código de configuración para usar() el plugin HTTP al inicializar i18next. También querremos esperar a que se descargue el archivo de traducción de nuestro idioma inicial antes de intentar traducir los elementos de nuestra página.
// Esperar a que las traducciones lleguen a través de la red
// antes de inicializar el plugin de jQuery
async function initI18n() {
// Usar el plugin de backend HTTP para descargar traducciones
await i18next.use(i18nextHttpBackend).init({
lng: "en",
// Eliminar la opción `resources`, ya que nuestras traducciones
// ahora están en archivos JSON
});
jqueryI18next.init(i18next, $);
}
// Refactorizar a función
function translatePage() {
$("body").localize();
}
// Init
(async function () {
// Espera a que i18next se inicialice antes
// traduciendo los elementos de la página
await initI18n();
translatePage();
})();
Si recargamos nuestra aplicación, no notamos ninguna diferencia en la salida. Sin embargo, si miras más de cerca la pestaña Red de las herramientas de desarrollo, verás una solución más mantenible para los archivos de traducción: «descárgalo cuando lo necesites».

Selector de idioma
Es posible que hayas notado que tenemos algo de HTML que parece una interfaz de cambio de idioma en la demo.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<!-- ... -->
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">Árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<!-- ... -->
</div>
<!-- ... -->
</body>
</html>
Conectemos el elemento <select> de arriba para que nuestra aplicación cambie sus traducciones para que coincidan con el idioma seleccionado por el usuario.
// ...
function bindLocaleSwitcher() {
const $switcher = $("[data-i18n-switcher]");
// Valor inicial
$switcher.val(i18next.language);
$switcher.on("change", async function () {
// Cambiar el idioma activo hará que su
// para que las traducciones se carguen desde la red, así que
// esperamos esa carga antes de actualizar
// elementos de la página
await i18next.changeLanguage($switcher.val());
translatePage();
});
}
(async function () {
await initI18n();
translatePage();
bindLocaleSwitcher();
})();
Así de fácil, tenemos un selector de idioma funcional.

Interpolación
Manejar valores dinámicos en nuestros mensajes de traducción viene de serie con i18next. Solo necesitamos proporcionar un mapa JSON data-i18n-options en el elemento en el que tenemos interpolaciones.
<p data-i18n="lead" data-i18n-options='{"username": "Jackie"}'>
¡Bienvenid@ a mi pequeño rincón en internet, {{username}}!
</p>
<!-- ... -->
El plugin de jQuery de i18next no busca data-i18n-options por defecto; tenemos que usar su useOptionsAttr opción de configuración para habilitar la interpolación automática.
async function initI18n() {
await i18next.use(i18nextHttpBackend).init({ lng: "en" });
jqueryI18next.init(i18next, $, { useOptionsAttr: true });
}
// ...
Por supuesto, asegurémonos de que tenemos los {{variable}} marcadores de posición que i18next espera en nuestros mensajes de traducción.
{
"app-title": "Con jQuery + i18next",
"home": "Inicio",
"about": "Acerca de",
"lead": "¡Bienvenido a mi pequeño rincón en internet, {{username}}!"
}
{
"app-title": "مع جي كويري و آي إيتين نيكست",
"home": "الرئيسية",
"about": "نبذة عنا",
"lead": "أهلاً بك في مكاني الصغير على النت يا {{username}}."
}
¡Allá vamos! Interpolaciones interpoladas.


Plurales
Usamos el mismo atributo data-i18n-options para proporcionar una variable numérica especial count cuando tenemos mensajes en plural.
🗒 Nota » Consulta la sección anterior i18next ➞ Plurales para obtener más detalles sobre cómo la biblioteca gestiona los plurales.
<!-- ... -->
<p data-i18n="new-messages" data-i18n-options='{"count": 3}'>
Tienes {{count}} nuevos mensajes
</p>
<!-- ... -->
Usamos una convención de key_form al especificar nuestros mensajes en plural. El inglés tiene dos formas plurales, one y other.
{
// ...
"new-messages_one": "Tienes {{count}} nuevo mensaje",
"new-messages_other": "Tienes {{count}} nuevos mensajes"
}
El árabe tiene seis formas plurales, y se gestionan automáticamente en i18next.
{
// ...
"new-messages_zero": "لا توجد لديك رسائل جديدة",
"new-messages_one": "لديك رسالة جديدة",
"new-messages_two": "لديك رسالتان جداد",
"new-messages_few": "لديك {{count}} رسائل جديدة",
"new-messages_many": "لديك {{count}} رسالة جديدة",
"new-messages_other": "لديك {{count}} رسالة جديدة"
}
Así que sin código adicional, nuestra aplicación tiene un soporte avanzado para plurales.


🔗 Recurso » Obtén el código completo en funcionamiento de la app de arriba desde nuestro repositorio de GitHub.
🔗 Recurso » Si buscas una alternativa a i18next, consulta la Guía avanzada de jQuery i18n, que usa la biblioteca jQuery.i18n.
¿Cómo puedo localizar una página web utilizando el formato ICU con Globalize?
Si deseas la cobertura de localización casi exhaustiva de los Componentes Internacionales para Unicode (ICU) y el Repositorio de Datos de Locales Comunes (CLDR) en tu aplicación JavaScript, la biblioteca Globalize seguramente cumplirá con creces su función. Vamos a repasar cómo instalar Globalize y localizar nuestra humilde aplicación de demostración usándola.
🔗 Recurso » La Guía imprescindible del Formato de Mensaje ICU cubre qué es ICU y CLDR con más detalle.
🗒 Nota » A diferencia de muchas otras bibliotecas que cubrimos en este artículo, Globalize sí es compatible con Internet Explorer 9+.
¿Qué demostración? dices. Nuestro confiable one-pager, por supuesto.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<ul class="navbar-list navbar-start">
<li class="navbar-item">
<a href="#" data-i18n-key="home" class="navbar-link">
Inicio
</a>
</li>
<li class="navbar-item">
<a href="#" data-i18n-key="about" class="navbar-link">
Sobre
</a>
</li>
</ul>
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">árabe (العربية)</option>
</select>
</div>
</div>
</nav>
<h1 data-i18n-key="app-title">Con Globalize</h1>
<p data-i18n-key="lead" data-i18n-opt='{"username": "Stella"}'>
¡Bienvenido a mi pequeño rincón en los interwebs, {username}!
</p>
<p data-i18n-key="new-messages" data-i18n-opt='{"count": 100}'>
Tienes # nuevos mensajes
</p>
</div>
</body>
</html>

Empecemos a localizar.
Instalación
Globalize es muy modular, por lo que puede ser un poco engorroso instalarlo con JavaScript puro. Aún así, es una receta relativamente simple.
- Descarga la última versión de Globalize
- Descarga la última versión del traverser CLDR (cldr.js)
- Descarga la última versión de datos JSON de CLDR—asegúrate de descargar la variante
-fullde la lista de lanzamientos para seguir aquí
Esto debería darnos tres archivos ZIP. Descomprimamos, renombremos los directorios de nivel superior descomprimidos y los movamos a nuestro directorio de proyecto. He colocado el mío en un directorio /lib en mi proyecto, así que mi proyecto ahora se ve así:
. ├── css/ ├── img/ ├── lib/ │ ├── cldr/ << renombrado de cldr-x.x.x │ ├── cldr-json/ << renombrado desde cldr-x.x.x-json-full │ └── globalize/ << anteriormente llamado globalize.x.x └── index.html
🔗 Recurso » La documentación oficial repasa diferentes formas de instalar Globalize.
Usando la herramienta de requisitos para determinar los scripts necesarios
Siempre necesitamos algunas partes de Globalize y cldr.js; otras dependerán de las funcionalidades de localización de nuestra aplicación. Una herramienta útil, So What’cha Want, nos permite saber qué archivos incluir en nuestro proyecto dependiendo de las características seleccionadas. Podemos comenzar deseleccionando todas las características excepto message.

Nota las dos listas de archivos cerca de la parte inferior de la página. La lista de la izquierda indica qué archivos incluir de Globalize y cldr.js; podemos usar la etiqueta <script> para estos.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<!-- ... -->
</div>
<!-- Requisitos de Globalize -->
<script src="./lib/cldr/dist/cldr.js"></script>
<script src="./lib/cldr/dist/cldr/event.js"></script>
<script src="./lib/cldr/dist/cldr/supplemental.js"></script>
<script src="./lib/globalize/dist/globalize.js"></script>
<script src="./lib/globalize/dist/globalize/message.js"></script>
<!-- Nuestro punto de entrada de la aplicación -->
<script src="./index.js"></script>
</body>
</html>
La lista en la esquina inferior derecha de So What’cha Want tiene datos JSON de CLDR que necesitamos. Este JSON lo necesitaremos obtener en nuestro código y pasarlo a Globalize mediante su función load().
// Agregaremos más a esta lista a medida que avancemos
const supplementals = ["likelySubtags"];
async function loadIntoGlobalize(featureUrls) {
await Promise.all(
featureUrls.map((url) => fetchJson(url))
).then((downloaded) =>
downloaded.forEach((feature) => Globalize.load(feature))
);
}
function supplementalUrlsFor(options) {
return options.map(
(feature) =>
`/lib/cldr-json/cldr-core/supplemental/${feature}.json`
);
}
async function fetchJson(url) {
const response = await fetch(url);
return await response.json();
}
(async function () {
// Cargar requisitos suplementarios
await loadIntoGlobalize(
supplementalUrlsFor(supplementals)
);
})();
Eso es todo por la configuración. Bueno, no es la biblioteca más fácil de instalar, pero para un proyecto que necesita una localización infalible, vale la pena el esfuerzo.
Traducciones básicas
Usar Globalize para traducir elementos de la página es un proceso sencillo de tres pasos.
// ...
(async function () {
await loadIntoGlobalize(
supplementalUrlsFor(supplementals)
);
// 1. Cargar mensajes de traducción
Globalize.loadMessages({
en: {
"app-title": "¡Hola Globalize!",
},
ar: {
"app-title": "أهلاً جلوبالايز",
},
});
// 2. Establece el idioma predeterminado
Globalize.locale("en");
// 3. Usa formatMessage() para obtener la traducción por clave
document.querySelector(
"[data-i18n-key='app-title']"
).textContent = Globalize.formatMessage("app-title");
})();
🔗 Recurso » La documentación oficial de Globalize tiene una práctica sección de la API que enumera las funciones disponibles.
Disco. Nuestro título se muestra utilizando las traducciones de nuestro idioma predeterminado.

Si cambiamos la llamada anterior para que diga Globalize.locale("ar"), veremos el título de nuestra aplicación en árabe.

Manejo de errores por mensajes faltantes
Generalicemos el código que traduce los elementos de la página escribiendo una translatePageElements() función. A diferencia de otras bibliotecas de i18n, Globalize lanzará un error y se detendrá si encuentra un mensaje que falta para una clave dada en formatMessage(). Nada que un poco de try/catch no pueda suavizar.
function translatePageElements() {
const elements = document.querySelectorAll(
"[data-i18n-key]"
);
elements.forEach((element) => {
const key = element.getAttribute("data-i18n-key");
try {
element.innerHTML = Globalize.formatMessage(key);
catch (error) {
if (error.code === "E_MISSING_MESSAGE") {
// Mostrar advertencias en la consola sobre mensajes faltantes
// en lugar de detenerse por completo.
console.warn(error.message);
// Mostrar el valor de la clave en la página para el mensaje faltante
element.innerHTML = key;
} else {
console.error(error);
}
}
});
}
(async function () {
Globalize.loadMessages(/* ... */);
Globalize.locale("en");
translatePageElements();
})();
Menos «fallo en caso de mensaje faltante», más «mostrar la clave y su valor y mostrar una advertencia en la consola».

Carga asíncrona de archivos de traducción
Como hemos hecho con otras soluciones en este artículo, dividamos nuestros mensajes de traducción en archivos JSON por cada localización para escalabilidad y mantenimiento.
{
// Globalize espera que el código de idioma sea la clave principal
"en": {
"app-title": "Con Globalize",
"home": Inicio,
"about": Sobre
}
}
{
"ar": {
"app-title": "مع جلوبالايز",
"home": "الرئيسية",
"about": "نبذة عنا"
}
}
Una función reutilizable setLocale() puede cargar nuestro archivo de traducción, configurar Globalize y renderizar de nuevo los elementos de la página.
const defaultLocale = "en";
async function setLocale(locale) {
const mensajes = await fetchJson(`/lang/${locale}.json`);
Globalize.loadMessages(mensajes);
Globalize.locale(locale);
translatePageElements();
}
(async function () {
await loadIntoGlobalize(
supplementalUrlsFor(suplementos)
);
setLocale(defaultLocale);
})();
Carga de archivo de traducción asíncrona: listo.
Selector de idioma
Podemos usar la función setLocale() que acabamos de escribir para que nuestra interfaz de usuario de cambio de idioma funcione. Puede que recuerdes que nuestra aplicación de demostración ya tiene algo de HTML para el selector.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<nav class="navbar">
<div class="container">
<div class="navbar-end">
<img src="img/translation-icon@2x.png" class="translation-icon" />
<select data-i18n-switcher class="locale-switcher">
<option value="en">Inglés</option>
<option value="ar">árabe (العربية)</option>
</select>
</div>
</div>
</nav>
</div>
<script src="./index.js"></script>
</body>
</html>
Algo de JavaScripting hará que el interruptor cambie.
const defaultLocale = "en";
function bindLocaleSwitcher() {
const switcher = document.querySelector(
"[data-i18n-switcher]"
);
// Globalize.locale() devuelve la configuración regional activa
switcher.value = Globalize.locale().locale;
switcher.onchange = (e) => {
setLocale(e.target.value); // Establece el idioma según el valor seleccionado
};
}
(async function () {
await loadIntoGlobalize(
supplementalUrlsFor(supplementals)
);
await setLocale(defaultLocale);
bindLocaleSwitcher(defaultLocale);
})();
Y con eso, nuestros usuarios pueden elegir su idioma preferido.

Mostrando los nombres de los idiomas en el idioma activo
Una de las características más poderosas de usar una biblioteca ICU como Globalize es el acceso a una variedad de datos de localización CLDR inmensamente rica. Por ejemplo, podemos mostrar los idiomas en nuestro selector de idiomas en el idioma activo—el inglés sería “الإنجليزية” en árabe, por ejemplo. Necesitaríamos obtener los datos principales de CLDR y luego actualizar nuestro select > option texto.
const defaultLocale = "ar";
const mains = {
localenames: ["languages"],
};
const supplementals = "likelySubtags"];
async function setLocale(locale) {
const mensajes = await fetchJson(`/lang/${locale}.json`);
Globalize.loadMessages(mensajes);
await loadIntoGlobalize(mainUrlsFor(mains, locale));
Globalize.locale(locale);
translatePageElements();
setLocaleSwitcherDisplayNames();
}
// Opciones dadas = {
// nombresLocales: ["idiomas"],
// fechas: ["ca-generic", "ca-gregorian"],
// y, locale = "en", devuelve un array como
// [
// "/lib/cldr-json/cldr-localenames-full/main/en/languages.json",
// "/lib/cldr-json/cldr-dates-full/main/en/ca-generic.json",
// "/lib/cldr-json/cldr-dates-full/main/en/ca-gregorian.json"
// ]
function mainUrlsFor(opciones, idioma) {
const resultado = [];
Object.keys(opciones).forEach((clave) => {
opciones[clave].forEach((colección) => {
resultado.push(
`/lib/cldr-json/cldr-${key}-full/main/${locale}/${collection}.json`
);
});
});
return resultado;
}
function setLocaleSwitcherDisplayNames() {
const options = document.querySelectorAll(
"[data-i18n-switcher] opción"
);
options.forEach((option) => {
const localeCode = option.value;
// Obtener datos principales de CLDR por ruta
option.textContent = Globalize.cldr.main(
`localeDisplayNames/languages/${localeCode}`
);
});
}
Podemos usar nuestro código principal de carga de JSON siempre que queramos cargar los datos principales de CLDR en JSON. De lo contrario, solo un poco de manipulación del DOM cuando se elige un nuevo idioma nos da nombres de idiomas localizados (¡tan meta!).

🔗 Recurso » La documentación oficial de cldr.js explica cómo recuperar datos JSON de CLDR con más detalle.
Interpolación
El manejo de valores dinámicos en nuestros mensajes se gestiona mediante el formato de mensaje ICU mediante la sintaxis {variable}.
{
"en": {
"lead": "¡Bienvenido/a a mi pequeño rincón en la web, {username}!"
}
}
{
"ar": {
"lead": "أهلاً بك في مكاني الصغير على النت يا {username}."
}
}
Podemos proporcionar pares de sustitución clave/valor en nuestro HTML.
<p data-i18n-key="lead" data-i18n-opt='{"username": "Stella"}'>
¡Bienvenido/a a mi pequeño rincón en la web, {username}!
</p>
<!-- ... -->
Y ahora solo necesitamos leer esos pares clave/valor y alimentarlos a Globalize.formatMessage().
function translatePageElements() {
const elements = document.querySelectorAll(
"[data-i18n-key]"
);
elements.forEach((element) => {
const key = element.getAttribute("data-i18n-key");
const interpolaciones =
element.getAttribute("data-i18n-opt");
const parsedInterpolations = interpolaciones
? JSON.parse(interpolations)
: {};
try {
element.innerHTML = Globalize.formatMessage(
key,
parsedInterpolations
);
} catch (error) {
if (error.code === "E_MISSING_MESSAGE") {
console.warn(error.message);
} else {
console.error(error);
}
}
});
}
No hay problema.


Plurales
El manejo de plurales en el formato ICU es insuperable y cubre formas plurales complejas como las del ruso o el árabe. Tenemos que configurar la función de plural antes de poder usarla.
Añadir requisitos de plural
Vayamos a So What’cha Want para averiguar qué necesitaremos incluir si habilitamos plural.

No está tan mal: primero necesitaremos agregar una etiqueta <script> para la función.
<!DOCTYPE html>
<html lang="es">
<head>
<!-- ... -->
</head>
<body>
<div class="container">
<!-- ... -->
</div>
<script src="./lib/cldr/dist/cldr.js"></script>
<script src="./lib/cldr/dist/cldr/event.js"></script>
<script src="./lib/cldr/dist/cldr/supplemental.js"></script>
<script src="./lib/globalize/dist/globalize.js"></script>
<script src="./lib/globalize/dist/globalize/message.js"></script>
<script src="./lib/globalize/dist/globalize/plural.js"></script>
<script src="./index.js"></script>
</body>
</html>
También necesitaremos el JSON suplementario de plurales, y probablemente el de ordinales si queremos cubrir formatos como «3.º» y «4.º» en nuestros mensajes. Solo necesitaremos agregar los requisitos a nuestro arreglo de configuración supplementals. Nuestra aplicación ya está configurada para cargar el JSON correspondiente al cargar.
const defaultLocale = "ar"; const supplementals = [ "likelySubtags", "plurals", "ordinals", ]; // ...
Ahora podemos agregar nuestros mensajes plurales. La sintaxis plural de ICU es en gran medida intuitiva: una variable count determina la forma plural elegida, y un símbolo especial # se reemplaza con el valor count al mostrarse.
{
"en": {
// ...
"new-messages": [
// Podemos llamar a `count` como queramos, siempre que
// coincide con la clave en nuestra llamada a formatMessage()
"Tienes {count, plural,
one {# new message}",
other {# new messages}",
"}"
]
}
}
🗒 Nota » Globalize nos permite dividir mensajes multilínea utilizando arrays.
Las seis formas plurales en árabe se gestionan mediante el formato ICU.
{
"ar": {
// ...
"new-messages": [
"{count, plural, ",
zero {لا توجد لديك رسائل جديدة}",
one {لديك رسالة جديدة}",
two {لديك رسالتان جداد}",
few {لديك # رسائل جديدة}",
many {لديك # رسالة جديدة}",
other {لديك # رسالة جديدة}",
"}"
]
}
}
🤿 Profundiza » Cubrimos los plurales de ICU con más detalle en La Guía Perdida del Formato de Mensaje ICU.
Por supuesto, necesitamos asegurarnos de que count se suministre a Globalize.formatMessage().
<!-- ... -->
<p data-i18n-key="new-messages" data-i18n-opt='{"count": 110}'>
Tienes # nuevos mensajes
</p>
<!-- ... -->
Y eso es básicamente todo sobre los plurales.


🗒 Nota » La implementación de ICU de Globalize abarca los formatos completos de fechas y números. Echa un vistazo a la documentación oficial para más información.
🔗 Recurso » Te mostramos más opciones de instalación de Globalize y casos de uso en JS I18n con Globalize.js.
Conclusión de nuestra guía de localización de JavaScript
Y con esto terminamos por aquí. Esperamos que hayas disfrutado de esta incursión en algunas de las soluciones de localización de JavaScript más populares.
🔗 Recurso » Si trabajas con Ruby on Rails, te puede interesar Cómo localizar JavaScript en aplicaciones Rails.
Y si buscas llevar tu proceso de localización al siguiente nivel, echa un vistazo a Phrase. Phrase soporta el formato ICU, todos los demás formatos de traducción que hemos cubierto aquí y muchos más. Con su CLI y la sincronización con Bitbucket, GitHub y GitLab, tu i18n puede estar en piloto automático. La consola web de Phrase, totalmente equipada, con aprendizaje automático y sugerencias inteligentes, es un placer de usar para los traductores. Una vez que las traducciones estén listas, se pueden sincronizar automáticamente con tu proyecto. Lo configuras y te olvidas, permitiéndote centrarte en el código que amas.
Consulta todas las características de Phrase para desarrolladores y comprueba tú mismo cómo puede agilizar tus flujos de trabajo de localización de software.

