{"id":133327,"date":"2022-09-14T07:00:43","date_gmt":"2022-09-14T05:00:43","guid":{"rendered":"https:\/\/phrase.com\/blog\/posts\/la-guia-definitiva-para-la-localizacion-de-javascript\/"},"modified":"2026-02-26T16:51:13","modified_gmt":"2026-02-26T15:51:13","slug":"step-step-guide-javascript-localization","status":"publish","type":"post","link":"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/","title":{"rendered":"La gu\u00eda definitiva para la localizaci\u00f3n de JavaScript"},"content":{"rendered":"<p>Extra\u00f1o, veterano, expresivo y asombroso, JavaScript es el <a href=\"https:\/\/insights.stackoverflow.com\/survey\/2020#technology-programming-scripting-and-markup-languages-professional-developers\">lenguaje de programaci\u00f3n m\u00e1s utilizado en la actualidad<\/a>. Al ser el lenguaje de los navegadores, y tambi\u00e9n de los servidores con <a href=\"https:\/\/nodejs.org\/en\/\">Node<\/a>, JavaScript est\u00e1 en todas partes en las tecnolog\u00edas web actuales.<br \/>\nY con muchos frameworks m\u00f3viles multiplataforma, envolturas de escritorio, motores de juegos e incluso frameworks de internet de las cosas (IoT), podr\u00edamos decir que el mundo es de JavaScript&#8230; nosotros solo vivimos en \u00e9l.<br \/>\nPor supuesto, si est\u00e1s aqu\u00ed es porque quieres sacar partido de todo ese \ud83d\udd25 y aprender a localizar aplicaciones de JavaScript, prepar\u00e1ndolas para una audiencia global. Pues no temas: Esta gu\u00eda cubrir\u00e1 todo lo que necesitas saber para comenzar la localizaci\u00f3n de JavaScript en navegadores.<br \/>\n\u00a1D\u00e9mosle <code><\/code> ca\u00f1a!<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Obt\u00e9n todo el c\u00f3digo que acompa\u00f1a este art\u00edculo de nuestro <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\">repositorio de GitHub<\/a>.<\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Internet Explorer (IE), con una <a href=\"https:\/\/kinsta.com\/browser-market-share\/\">participaci\u00f3n de mercado global del 2,15 %<\/a>, puede considerarse un navegador obsoleto. Por brevedad, estamos omitiendo soluciones espec\u00edficas para IE en esta gu\u00eda. Si <em>est\u00e1s<\/em> dando soporte a IE, aseg\u00farate de verificar si las caracter\u00edsticas integradas de JavaScript que cubrimos en este art\u00edculo requieren bifurcaciones (forks) o polyfills.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_81 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Alternar tabla de contenidos\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfcomo-es-la-localizacion-de-javascript-para-paginas-web\" >\u00bfC\u00f3mo es la localizaci\u00f3n de JavaScript para p\u00e1ginas web?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#cargando-traducciones-de-forma-asincrona\" >Cargando traducciones de forma as\u00edncrona<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#creando-un-selector-de-idioma\" >Creando un selector de idioma<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#detectar-los-idiomas-preferidos-del-usuario-desde-el-navegador\" >Detectar los idiomas preferidos del usuario desde el navegador<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#manejo-de-direccion-de-derecha-a-izquierda-y-lenguajes-de-derecha-a-izquierda\" >Manejo de direcci\u00f3n: de derecha a izquierda y lenguajes de derecha a izquierda<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#mensajes-de-traduccion-basicos\" >Mensajes de traducci\u00f3n b\u00e1sicos<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#interpolacion\" >Interpolaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#traduccion-dinamica-despues-de-cargar-la-pagina\" >Traducci\u00f3n din\u00e1mica despu\u00e9s de cargar la p\u00e1gina<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#plurales\" >Plurales<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#formato-de-numeros\" >Formato de n\u00fameros<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#formato-de-fecha\" >Formato de fecha<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfque-bibliotecas-de-internacionalizacion-de-javascript-me-recomendais\" >\u00bfQu\u00e9 bibliotecas de internacionalizaci\u00f3n de JavaScript me recomend\u00e1is?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfcomo-localizo-una-pagina-web-con-polyglot\" >\u00bfC\u00f3mo localizo una p\u00e1gina web con Polyglot?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#instalacion\" >Instalaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#traducciones-basicas\" >Traducciones b\u00e1sicas<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#carga-asincrona-de-archivos-de-traduccion\" >Carga as\u00edncrona de archivos de traducci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-17\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#selector-de-idioma\" >Selector de idioma<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-18\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#interpolacion-2\" >Interpolaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-19\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#plurales-2\" >Plurales<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-20\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#how-do-i-localize-a-web-page-with-i18next\" >How do I localize a web page with i18next?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-21\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#instalacion-2\" >Instalaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-22\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#mensajes-de-traduccion-basicos-2\" >Mensajes de traducci\u00f3n b\u00e1sicos<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-23\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#carga-asincrona-de-traducciones\" >Carga as\u00edncrona de traducciones<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-24\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#idiomas-soportados-y-un-idioma-de-respaldo\" >Idiomas soportados y un idioma de respaldo<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-25\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#detectando-automaticamente-el-idioma-del-usuario\" >Detectando autom\u00e1ticamente el idioma del usuario<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-26\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#selector-de-idioma-2\" >Selector de idioma<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-27\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#interpolacion-3\" >Interpolaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-28\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#plurales-3\" >Plurales<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-29\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfcomo-localizo-una-aplicacion-de-react-angular-o-vue\" >\u00bfC\u00f3mo localizo una aplicaci\u00f3n de React, Angular o Vue?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-30\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#articulos-de-localizacion-de-angular\" >Art\u00edculos de localizaci\u00f3n de Angular<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-31\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#articulos-de-localizacion-de-vuejs\" >Art\u00edculos de localizaci\u00f3n de Vue.js<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-32\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#articulos-de-localizacion-para-otros-frameworks\" >Art\u00edculos de localizaci\u00f3n para otros frameworks<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-33\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfcomo-localizo-una-aplicacion-de-react-con-i18next\" >\u00bfC\u00f3mo localizo una aplicaci\u00f3n de React con i18next?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-34\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#instalacion-de-la-biblioteca\" >Instalaci\u00f3n de la biblioteca<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-35\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#traducciones-basicas-2\" >Traducciones b\u00e1sicas<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-36\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#carga-asincrona-de-archivos-de-traduccion-2\" >Carga as\u00edncrona de archivos de traducci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-37\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#selector-de-idioma-3\" >Selector de idioma<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-38\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#articulos-de-localizacion-de-react\" >Art\u00edculos de localizaci\u00f3n de React<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-39\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfcomo-localizas-una-pagina-web-con-jquery-e-i18next\" >\u00bfC\u00f3mo localizas una p\u00e1gina web con jQuery e i18next?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-40\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#instalacion-3\" >Instalaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-41\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#traducciones-basicas-3\" >Traducciones b\u00e1sicas<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-42\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#carga-asincrona-de-archivos-de-traduccion-3\" >Carga as\u00edncrona de archivos de traducci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-43\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#selector-de-idioma-4\" >Selector de idioma<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-44\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#interpolacion-4\" >Interpolaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-45\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#plurales-4\" >Plurales<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-46\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#%c2%bfcomo-puedo-localizar-una-pagina-web-utilizando-el-formato-icu-con-globalize\" >\u00bfC\u00f3mo puedo localizar una p\u00e1gina web utilizando el formato ICU con Globalize?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-47\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#instalacion-4\" >Instalaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-48\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#traducciones-basicas-4\" >Traducciones b\u00e1sicas<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-49\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#manejo-de-errores-por-mensajes-faltantes\" >Manejo de errores por mensajes faltantes<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-50\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#carga-asincrona-de-archivos-de-traduccion-4\" >Carga as\u00edncrona de archivos de traducci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-51\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#selector-de-idioma-5\" >Selector de idioma<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-52\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#interpolacion-5\" >Interpolaci\u00f3n<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-53\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#plurales-5\" >Plurales<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-54\" href=\"https:\/\/phrase.com\/es\/blog\/posts\/step-step-guide-javascript-localization\/#conclusion-de-nuestra-guia-de-localizacion-de-javascript\" >Conclusi\u00f3n de nuestra gu\u00eda de localizaci\u00f3n de JavaScript<\/a><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfcomo-es-la-localizacion-de-javascript-para-paginas-web\"><\/span>\u00bfC\u00f3mo es la localizaci\u00f3n de JavaScript para p\u00e1ginas web?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Si bien puede ser tentador tomar una biblioteca de internacionalizaci\u00f3n (i18n) lista para usar para tus necesidades de localizaci\u00f3n (y hay casos en los que esa podr\u00eda ser la elecci\u00f3n correcta para tu proyecto) ver\u00e1s que JavaScript puro te servir\u00e1 perfectamente para proyectos m\u00e1s peque\u00f1os. Crear una propia tambi\u00e9n te dar\u00e1 un buen recetario de t\u00e9cnicas de internacionalizaci\u00f3n que puedes usar con <em>cualquier<\/em> biblioteca que elijas.<\/p>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em><i> <\/i>Nuestro art\u00edculo, <a href=\"https:\/\/phrase.com\/blog\/posts\/i18n-a-simple-definition\/\">What Is I18n: A Simple Definition of Internationalization<\/a>, entra en m\u00e1s detalles sobre lo que son la internacionalizaci\u00f3n (i18n) y la localizaci\u00f3n (l10n).<\/p>\n<p>\u270b\ud83c\udffd <em>Atenci\u00f3n \u00bb<\/em> Si est\u00e1s construyendo una MPA (aplicaci\u00f3n multip\u00e1gina) tradicional, a menudo ocurre que gran parte de la localizaci\u00f3n se realiza en el propio servidor. En nuestro ejemplo solo estamos trabajando con la localizaci\u00f3n del navegador. Sin embargo, te lo ponemos f\u00e1cil del lado del servidor, con un <a href=\"https:\/\/phrase.com\/blog\/posts\/nodejs-tutorial-on-creating-multilingual-web-app\/\">tutorial de internacionalizaci\u00f3n de Node<\/a> y una <a href=\"https:\/\/phrase.com\/blog\/posts\/full-stack-javascript-i18n\/\">gu\u00eda de internacionalizaci\u00f3n full-Stack de JavaScript<\/a>.<\/p>\n<p>Bien, pues digamos que tenemos una p\u00e1gina que queremos localizar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"html\" data-enlighter-group=\"e17c768d-5eb9-41e9-a82c-c8758e6dff71\" data-enlighter-title=\"index.html\" data-enlighter-linenumbers=\"false\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\"&gt;\n  &lt;!-- ... --&gt;\n  &lt;title&gt;My Appy Apperson&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;h1&gt;My Appy Apperson&lt;\/h1&gt;\n    &lt;p&gt;Welcome to my little spot on the interwebs!&lt;\/p&gt;\n  &lt;\/div&gt;\n  &lt;script src=\"js\/scripts.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15564 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-en.png\" alt=\"Versi\u00f3n en ingl\u00e9s de una aplicaci\u00f3n de demostraci\u00f3n de JavaScript | Phrase\" width=\"998\" height=\"316\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-en.png 998w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-en-300x95.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-en-768x243.png 768w\" sizes=\"auto, (max-width: 998px) 100vw, 998px\" \/><\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Puedes obtener todo el c\u00f3digo de la aplicaci\u00f3n que estamos construyendo en esta secci\u00f3n en la <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/tree\/main\/vanilla\">carpeta de Vanilla en nuestro repositorio de GitHub<\/a>.<br \/>\n\ud83d\udd17 <em>Recurso \u00bb<\/em> Estoy usando la biblioteca <a href=\"http:\/\/getskeleton.com\/\">Skeleton CSS<\/a>, por si te lo preguntabas.<\/p>\n<p>Tiene buena pinta, pero no est\u00e1 exactamente listo para un entorno global, \u00bfverdad? Todo el contenido est\u00e1 codificado directamente en ingl\u00e9s. Hagamos un poco de internacionalizaci\u00f3n b\u00e1sica.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-highlight=\"10,11\" data-enlighter-group=\"9ae201e0-a675-4f5e-8ebb-1c941d5f6f4b\" data-enlighter-title=\"index.html\" data-enlighter-linenumbers=\"false\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\"&gt;\n  &lt;!-- ... --&gt;\n  &lt;title&gt;My Appy Apperson&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;h1 data-i18n-key=\"app-title\"&gt;My Appy Apperson&lt;\/h1&gt;\n    &lt;p data-i18n-key=\"lead\"&gt;Welcome to my little spot on the interwebs!&lt;\/p&gt;\n  &lt;\/div&gt;\n  &lt;script src=\"js\/scripts.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Observa los <code>atributos data-i18n-key<\/code> que a\u00f1adimos 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.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"eb3e63cb-cd04-47cf-a52d-41474533e624\" data-enlighter-title=\"\/js\/scripts.js\">\/\/ El idioma activo\nconst locale = \"en\";\n\/\/ Podemos tener tantos idiomas aqu\u00ed como queramos,\n\/\/ y usar cualquier idioma o configuraci\u00f3n regional que queramos. Tenemos el idioma ingl\u00e9s\n\/\/ y \u00e1rabe aqu\u00ed a modo de ejemplo.\nconst translations = {\n  \/\/ Traducciones al ingl\u00e9s\n  \"en\": {\n    \"app-title\": \"My Appy Apperson\",\n    \"lead\": \"Welcome to my little spot on the interwebs!\",\n  },\n  \/\/ Traducciones en \u00e1rabe\n  \"ar\": {\n    \"app-title\": \"\u062a\u0637\u0628\u064a\u0642\u064a \u0627\u0644\u0645\u0637\u0628\u0642\",\n    \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a.\",\n  },\n};\n\/\/ Cuando el contenido de la p\u00e1gina est\u00e9 listo...\ndocument.addEventListener(\"DOMContentLoaded\", () =&gt; {\n  document\n    \/\/ Encuentra todos los elementos que tienen el atributo key\n    .querySelectorAll(\"[data-i18n-key]\")\n    .forEach(translateElement);\n});\n\/\/ Reemplaza el texto interno del elemento HTML dado\n\/\/ con la traducci\u00f3n en el idioma activo,\n\/\/ correspondiente a la clave de internacionalizaci\u00f3n de datos del elemento\nfunction translateElement(element) {\n  const key = element.getAttribute(\"data-i18n-key\");\n  const translation = translations[locale][key];\n  element.innerText = translation;\n}\n<\/pre>\n<p>Ahora que ya lo tienes, cambia la segunda l\u00ednea de arriba a <code>const locale = \"ar\";<\/code> y vuelve a cargar la p\u00e1gina. Cuando se activa el <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/DOMContentLoaded_event\">evento DOMContentLoaded<\/a>, nuestra p\u00e1gina muestra las traducciones al \u00e1rabe.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15566 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-ar.png\" alt=\"Versi\u00f3n \u00e1rabe de una aplicaci\u00f3n de demostraci\u00f3n de JavaScript | Phrase\" width=\"572\" height=\"272\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-ar.png 572w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/vanilla-ar-300x143.png 300w\" sizes=\"auto, (max-width: 572px) 100vw, 572px\" \/><\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> los c\u00f3digos <code>\"en\"<\/code> y <code>\"ar\"<\/code> de arriba son los <a href=\"https:\/\/www.loc.gov\/standards\/iso639-2\/php\/code_list.php\">c\u00f3digos ISO 639-1<\/a> para ingl\u00e9s y \u00e1rabe, respectivamente. Es est\u00e1ndar usar c\u00f3digos ISO para <a href=\"https:\/\/unicode-org.github.io\/icu\/userguide\/locale\/#language-code\">idiomas<\/a> y <a href=\"https:\/\/unicode-org.github.io\/icu\/userguide\/locale\/#country-code\">pa\u00edses<\/a> al localizar.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"cargando-traducciones-de-forma-asincrona\"><\/span>Cargando traducciones de forma as\u00edncrona<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Hemos empezado con buen pie con nuestra soluci\u00f3n de internacionalizaci\u00f3n. Sin embargo, agregar idiomas y traducciones no escala bien en este momento. A medida que nuestra aplicaci\u00f3n crezca, probablemente nos interese dividir nuestras traducciones en archivos separados por idioma. El archivo de traducci\u00f3n correspondiente al idioma activo podr\u00eda cargarse sin el coste de cargar los dem\u00e1s idiomas. Podemos implementar esto sin demasiado esfuerzo.<br \/>\nPrimero, saquemos nuestras traducciones de nuestro script principal y mov\u00e1moslas a archivos JSON, uno para cada idioma que admitimos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"6063d19b-b975-4367-8a9f-2be85e5d7770\" data-enlighter-title=\"\/lang\/en.json\">{\n  \"app-title\": \"My Appy Apperson\",\n  \"lead\": \"Welcome to my little spot on the interwebs!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"c6570b0b-a294-414a-b00a-5ab4e1103c72\" data-enlighter-title=\"\/lang\/ar.json\">{\n  \"app-title\": \"\u062a\u0637\u0628\u064a\u0642\u064a \u0627\u0644\u0645\u0637\u0628\u0642\",\n  \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a.\"\n}\n<\/pre>\n<p>Vamos a reestructurar nuestro script para cargar los archivos JSON de forma as\u00edncrona cuando los necesitemos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"eecf242f-182d-40ed-bc04-f77f3246859a\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"2,5,8,13,18,19,20,21,22,23,24,25,26,27,28,32,33,34,35,40-44,51\">\/\/ El idioma que nuestra aplicaci\u00f3n muestra primero\nconst defaultLocale = \"en\";\n\/\/ El idioma activo\nlet locale;\n\/\/ Se rellena con las traducciones del idioma activo\nlet translations = {};\n\/\/ Cuando el contenido de la p\u00e1gina est\u00e9 listo...\ndocument.addEventListener(\"DOMContentLoaded\", () =&gt; {\n  \/\/ Traducir la p\u00e1gina al idioma predeterminado\n  setLocale(defaultLocale);\n});\n\/\/ Cargar traducciones para el idioma (o configuraci\u00f3n regional) dado y traducir\n\/\/ la p\u00e1gina a este idioma\nasync function setLocale(newLocale) {\n  if (newLocale === locale) return;\n  const newTranslations =\n    await fetchTranslationsFor(newLocale);\n  locale = newLocale;\n  translations = newTranslations;\n  translatePage();\n}\n\/\/ Recupera el objeto JSON de traducciones para el\n\/\/ idioma dado por la red\nasync function fetchTranslationsFor(newLocale) {\n  const response = await fetch(`\/lang\/${newLocale}.json`);\n  return await response.json();\n}\n\/\/ Reemplazar el texto interno de cada elemento que tiene\n\/\/ atributo data-i18n-key con la traducci\u00f3n correspondiente\n\/\/ a su data-i18n-key\nfunction translatePage() {\n  document\n    .querySelectorAll(\"[data-i18n-key]\")\n    .forEach(translateElement);\n}\n\/\/ Reemplaza el texto interno del elemento HTML dado\n\/\/ con la traducci\u00f3n en el idioma activo,\n\/\/ correspondiente a la clave de internacionalizaci\u00f3n de datos del elemento\nfunction translateElement(element) {\n  const key = element.getAttribute(\"data-i18n-key\");\n  const translation = translations[key];\n  element.innerText = translation;\n}\n<\/pre>\n<p>Si recargamos nuestra p\u00e1gina ahora, se ve exactamente como antes. Sin embargo, internamente, hemos hecho que nuestra aplicaci\u00f3n sea mucho m\u00e1s f\u00e1cil de escalar y mantener.<\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Usamos la pr\u00e1ctica <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Fetch_API\">Fetch API<\/a> integrada en los navegadores modernos para obtener nuestros archivos JSON a trav\u00e9s de la red.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"creando-un-selector-de-idioma\"><\/span>Creando un selector de idioma<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Nuestros usuarios a\u00fan no tienen forma de hacer uso de nuestras asombrosas habilidades as\u00edncronas. \u00bfCreamos un men\u00fa desplegable para que puedan cambiar de idioma?<br \/>\nAgregaremos una barra de navegaci\u00f3n y alojaremos nuestro selector en dicha barra.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-highlight=\"9,18,19,20,21,24\" data-enlighter-group=\"ffdcbc91-e87f-487d-b549-2a88f94eb91a\" data-enlighter-title=\"index.html\" data-enlighter-linenumbers=\"false\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n  &lt;title&gt;My Appy Apperson&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;ul class=\"navbar-list navbar-left\"&gt;\n          &lt;!-- Nav links --&gt;\n        &lt;\/ul&gt;\n        &lt;div class=\"navbar-right\"&gt;\n          &lt;!-- ... --&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;h1 data-i18n-key=\"app-title\"&gt;My Appy Apperson&lt;\/h1&gt;\n    &lt;p data-i18n-key=\"lead\"&gt;Welcome to my little spot on the interwebs!&lt;\/p&gt;\n  &lt;\/div&gt;\n  &lt;script src=\"js\/scripts.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Una simple <code>&lt;select&gt;<\/code> bastar\u00e1 aqu\u00ed. Podemos usar un atributo <code>data-i18n-switcher<\/code> para comunicarnos con nuestro JavaScript y cargar la localizaci\u00f3n seleccionada por el usuario.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f468dc96-a9e9-47c3-84a4-e59ac95a6d68\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"10,18,19,20,21,22,23,24,25,26,27,28\">const defaultLocale = \"en\";\nlet locale;\n\/\/ ...\n\/\/ Cuando el contenido de la p\u00e1gina est\u00e9 listo...\ndocument.addEventListener(\"DOMContentLoaded\", () =&gt; {\n  setLocale(defaultLocale);\n  bindLocaleSwitcher(defaultLocale);\n});\n\/\/ ...\n\/\/ Siempre que el usuario seleccione un nuevo idioma,\n\/\/ carga las traducciones del idioma y actualiza\n\/\/ la p\u00e1gina\nfunction bindLocaleSwitcher(initialValue) {\n  const switcher =\n    document.querySelector(\"[data-i18n-switcher]\");\n  switcher.value = initialValue;\n  switcher.onchange = (e) =&gt; {\n    \/\/ Establece la configuraci\u00f3n regional en la opci\u00f3n seleccionada [value]\n    setLocale(e.target.value);\n  };\n}\n<\/pre>\n<p>El controlador de eventos <code>onchange<\/code> nos permite actualizar las traducciones de nuestra p\u00e1gina seg\u00fan el valor de la <code>&lt;option&gt;<\/code> seleccionada. \u00a1Y ya est\u00e1! Ahora los visitantes de nuestro sitio pueden seleccionar su propio idioma o configuraci\u00f3n regional.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15567 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/locale-switcher.gif\" alt=\"App de demostraci\u00f3n con controlador del evento onchange | Phrase\" width=\"600\" height=\"204\" \/><\/p>\n<p>\ud83d\udce3 <em>Agradecimientos \u00bb<\/em> A <a href=\"https:\/\/thenounproject.com\/search\/?q=translation&amp;i=4380109%0A\">Hary Murdiono JS del Noun Project<\/a> por su icono de <em>traducci\u00f3n<\/em>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"detectar-los-idiomas-preferidos-del-usuario-desde-el-navegador\"><\/span>Detectar los idiomas preferidos del usuario desde el navegador<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A veces es una buena idea intentar adivinar el idioma o la configuraci\u00f3n regional preferida del usuario antes de darle la opci\u00f3n de seleccionar manualmente la que prefiera. La mayor\u00eda de las personas tienen la interfaz de usuario de su navegador configurada en su idioma preferido, que a menudo es el idioma del sistema operativo.<br \/>\nEste idioma de la interfaz de usuario del navegador se puede encontrar en el objeto <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Navigator\/language\">navigator<\/a>, concretamente en la cadena <code>navigator.language<\/code>.<br \/>\nLa matriz tambi\u00e9n est\u00e1ndar (aunque experimental mientras escribo esto) <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Navigator\/languages\">navigator.languages<\/a> deber\u00eda contener el idioma de la interfaz de usuario como su primera entrada, adem\u00e1s de cualquier idioma que la persona usuaria haya configurado expl\u00edcitamente en la configuraci\u00f3n de idiomas preferidos de su navegador.<br \/>\nUna peque\u00f1a funci\u00f3n que consulta <code>navigator.languages<\/code> nos puede permitir arrancar con la detecci\u00f3n del idioma del navegador.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"905d80b4-e5a7-4a8e-ba58-54f9ba5f8ee9\" data-enlighter-title=\"\/js\/scripts.js\">\/**\n * Recupera los idiomas preferidos del usuario desde el navegador\n *\n * @param {boolean} languageCodeOnly - cuando es verdadero, devuelve\n * [\"en\", \"fr\"]\u00a0en lugar de [\"en-US\", \"fr-FR\"]\n * @returns array | undefined\n *\/\nfunction browserLocales(languageCodeOnly = false) {\n  return navigator.languages.map((locale) =&gt;\n    languageCodeOnly ? locale.split(\"-\")[0] : locale,\n  );\n}\n<\/pre>\n<p>Pongamos ahora que el usuario tiene franc\u00e9s (Canad\u00e1) y chino (simplificado) en la configuraci\u00f3n de su navegador.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15568 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/browser-locale-prefs-1024x605.png\" alt=\"Configuraci\u00f3n de idioma del navegador | Phrase\" width=\"1024\" height=\"605\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/browser-locale-prefs-1024x605.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/browser-locale-prefs-300x177.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/browser-locale-prefs-768x454.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/browser-locale-prefs.png 1438w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><br \/>\nEn este caso, <code>browserLocales()<\/code> devolver\u00e1 <code>[\"fr-CA\", \"zh-CN\"]<\/code>. Si llamamos a <code>browserLocales(true)<\/code>, obtendremos <code>[\"fr\", \"zh\"]<\/code>.<br \/>\nAhora podemos usar esta nueva funci\u00f3n para detectar los idiomas preferidos del usuario cuando carguemos nuestra p\u00e1gina por primera vez.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"72b255d6-6feb-43d4-8437-db90b0dbc34e\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"4,9,10,12,14,19,20,21,25,26,27\">\/\/ El idioma que nuestra aplicaci\u00f3n muestra primero\nconst defaultLocale = \"en\";\nconst supportedLocales = [\"en\", \"ar\"];\n\/\/ ...\ndocument.addEventListener(\"DOMContentLoaded\", () =&gt; {\n  const initialLocale =\n    supportedOrDefault(browserLocales(true));\n  setLocale(initialLocale);\n  bindLocaleSwitcher(initialLocale);\n});\n\/\/ ...\nfunction isSupported(locale) {\n  return supportedLocales.indexOf(locale) &gt; -1;\n}\n\/\/ Recuperar el primer idioma que soportamos de la\n\/\/ matriz indicada, o devolver nuestro idioma por defecto\nfunction supportedOrDefault(locales) {\n  return locales.find(isSupported) || defaultLocale;\n}\n\/\/ ...\nfunction browserLocales(languageCodeOnly = false) {\n  return navigator.languages.map((locale) =&gt;\n    languageCodeOnly ? locale.split(\"-\")[0] : locale,\n  );\n}\n<\/pre>\n<p>Ten en cuenta que hemos introducido el concepto de <code>supportedLocales<\/code>; estos son los \u00fanicos idiomas para los que tenemos traducciones. Con ellos, podemos recurrir a nuestro idioma predeterminado si ninguno de los idiomas preferidos del usuario est\u00e1 en nuestra lista de idiomas compatibles.<br \/>\nNuestra aplicaci\u00f3n ahora se traducir\u00e1 al primer idioma en la lista de preferencias del usuario, con una elegante alternativa en caso necesario.<\/p>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em> Cubrimos la detecci\u00f3n de idioma tanto en el navegador como en el servidor en profundidad en <a href=\"https:\/\/phrase.com\/es\/blog\/posts\/detecting-a-users-locale\/\">Detectando la preferencia de idioma del navegador con JavaScript<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"manejo-de-direccion-de-derecha-a-izquierda-y-lenguajes-de-derecha-a-izquierda\"><\/span>Manejo de direcci\u00f3n: de derecha a izquierda y lenguajes de derecha a izquierda<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\u00c1rabe, hebreo, persa, urdu y otros idiomas <a href=\"https:\/\/www.worldatlas.com\/articles\/which-languages-are-written-from-right-to-left.html\">utilizan escrituras que se escriben de derecha a izquierda<\/a>. Mientras que los idiomas de izquierda a derecha (LTR) superan en n\u00famero a los de derecha a izquierda (RTL), es bueno saber c\u00f3mo dar soporte a estos \u00faltimos. Afortunadamente, gran parte del trabajo lo realiza el navegador aqu\u00ed; solo tenemos que establecer el <code>&lt;html dir&gt;<\/code> atributo en nuestras p\u00e1ginas.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"6a86b7f1-65ce-4b15-891b-0425324892e3\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"16,26,27,28\">\/\/ ...\n\/\/ Cargar traducciones para el idioma (o configuraci\u00f3n regional) dado y traducir\n\/\/ la p\u00e1gina a este idioma\nasync function setLocale(newLocale) {\n  if (newLocale === locale) return;\n  const newTranslations = await fetchTranslationsFor(\n    newLocale,\n  );\n  locale = newLocale;\n  translations = newTranslations;\n  \/\/ Establecer el atributo &lt;html dir&gt;\n  document.documentElement.dir = dir(newLocale);\n  \/\/ No es necesario para el flujo de direcci\u00f3n, pero por si acaso\u2026\n  document.documentElement.lang = newLocale;\n  translatePage();\n}\n\/\/ ...\nfunction dir(locale) {\n  return locale === \"ar\" ? \"rtl\" : \"ltr\";\n}\n\/\/ ...\n<\/pre>\n<p>El <code>&lt;html dir&gt;<\/code> atributo puede tomar los valores <code>\"ltr\"<\/code> o <code>\"rtl\"<\/code>. Proporcionamos este valor mediante una funci\u00f3n muy simple <code>dir()<\/code> y asignamos el atributo cada vez que cambiamos de idioma.<\/p>\n<p style=\"text-align: center\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15569 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dom-lang-dir.png\" alt=\"Atributos HTML de las herramientas de desarrollo del navegador | Phrase\" width=\"322\" height=\"208\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dom-lang-dir.png 322w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dom-lang-dir-300x194.png 300w\" sizes=\"auto, (max-width: 322px) 100vw, 322px\" \/><br \/>\n<em>Si abrimos las herramientas de desarrollo del navegador, podemos ver c\u00f3mo se actualizan los atributos <code>&lt;html&gt;<\/code> al cambiar de idioma<\/em><\/p>\n<p>El navegador mostrar\u00e1 el documento de derecha a izquierda autom\u00e1ticamente cuando establezcamos <code>&lt;html dir=\"rtl\"&gt;<\/code>. Sin embargo, cualquiera de nuestros estilos direccionales personalizados, p. ej. <code>margin-left: 20px<\/code>, requerir\u00e1 algo de CSS espec\u00edfico para RTL, incluyendo estilos volteados. Eso generalmente no es demasiado complicado; simplemente est\u00e1 un poco fuera del alcance de este art\u00edculo.<\/p>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em> Lee m\u00e1s sobre CSS localizado en el art\u00edculo sobre <a href=\"https:\/\/phrase.com\/blog\/posts\/how-do-i-use-a-css-file-for-site-localization\/\">c\u00f3mo usar un archivo CSS para la localizaci\u00f3n de sitios web<\/a>.<\/p>\n<p>\u00a1Con nuestro nuevo c\u00f3digo en su lugar, obtenemos una p\u00e1gina \u00e1rabe que Avicena aprobar\u00eda!<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15570 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/ar-rtl-1024x348.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n con alineaci\u00f3n a la derecha de texto \u00e1rabe | Phrase\" width=\"1024\" height=\"348\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/ar-rtl-1024x348.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/ar-rtl-300x102.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/ar-rtl-768x261.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/ar-rtl.png 1330w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"mensajes-de-traduccion-basicos\"><\/span>Mensajes de traducci\u00f3n b\u00e1sicos<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Antes de pasar a mensajes m\u00e1s complejos, como los que tienen valores interpolados y plurales, vamos a repasar brevemente c\u00f3mo hemos implementado los mensajes de traducci\u00f3n.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">\/\/ En nuestra p\u00e1gina HTML\n&lt;h1 data-i18n-key=\"app-title\"&gt;My Appy Apperson&lt;\/h1&gt;\n\/\/ En nuestro JavaScript\nfunction translateElement(element) {\n  const key = element.getAttribute(\"data-i18n-key\");\n  const translation = translations[key];\n  element.innerText = translation;\n}\n\/\/ Dado que hemos cargado traducciones en \u00e1rabe desde ar.json:\ntranslations = {\n  \"app-title\": \"\u062a\u0637\u0628\u064a\u0642\u064a \u0627\u0644\u0645\u0637\u0628\u0642\",\n};\ntranslateElement(document.querySelector(\"[data-i18n-key='lead']\"));\n\/\/ renderiza como:\n&lt;h1 data-i18n-key=\"app-title\"&gt;\u062a\u0637\u0628\u064a\u0642\u064a \u0627\u0644\u0645\u0637\u0628\u0642&lt;\/h1&gt;\n<\/pre>\n<p>Y ese b\u00e1sicamente es nuestro sistema de traducci\u00f3n.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"interpolacion\"><\/span>Interpolaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\u00bfQu\u00e9 sucede cuando tenemos valores que cambian durante la ejecuci\u00f3n y necesitan ser insertados en nuestros mensajes? Un ejemplo com\u00fan es el nombre del usuario que ha iniciado sesi\u00f3n actualmente. Tendremos que actualizar nuestro sistema de traducci\u00f3n para manejar casos como estos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-highlight=\"12,13,14,15,16,17\" data-enlighter-group=\"269a8ae8-8a14-4a95-9863-a481f6ae89f3\" data-enlighter-title=\"index.html\" data-enlighter-linenumbers=\"false\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;!-- ... --&gt;\n    &lt;h1 data-i18n-key=\"app-title\"&gt;My Appy Apperson&lt;\/h1&gt;\n    &lt;p\n      data-i18n-key=\"lead\"\n      data-i18n-opt='{\"username\": \"Swoodesh\"}'\n    &gt;\n      Welcome to my little spot on the interwebs, {username}!\n    &lt;\/p&gt;\n  &lt;\/div&gt;\n  &lt;script src=\"js\/scripts.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Indicamos marcadores de posici\u00f3n para los valores que queremos interpolar en nuestros mensajes con la sintaxis <code>{variable}<\/code>. Un nuevo atributo <code>data-i18n-opt<\/code> almacena pares clave\/valor de interpolaci\u00f3n en un objeto JSON v\u00e1lido.<br \/>\nPor supuesto, necesitaremos los marcadores de posici\u00f3n en nuestros archivos de idioma.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f78af90a-df90-420c-a3c7-7a289dcede90\" data-enlighter-title=\"\/lang\/en.json\">{\n  \"lead\": \"Welcome to my little spot on the interwebs, {username}!\",\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"9082479b-aff1-4962-b14e-d522963e7eb8\" data-enlighter-title=\"\/lang\/ar.json\">{\n  \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a \u064a\u0627 {username}.\",\n}\n<\/pre>\n<p>Ahora podemos modificar la funci\u00f3n <code>translateElement<\/code> para manejar interpolaciones.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"13c0b59f-dfdf-45cf-8d56-911e8a61422e\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"7,8,9,11,12,13,18,19,20,21,22,23,24,25,26,27\">\/\/ ...\nfunction translateElement(element) {\n  const key = element.getAttribute(\"data-i18n-key\");\n  const translation = translations[key];\n  const options = JSON.parse(\n    element.getAttribute(\"data-i18n-opt\")\n  );\n  element.innerText = options\n    ? interpolate(translation, options)\n    : translation;\n}\n\/\/ Convertir un mensaje como \"Hola, {name}\" a \"Hola, Chad\"\n\/\/ dado el objeto de interpolaci\u00f3n {name: \"Chad\"}\nfunction interpolate(message, interpolations) {\n  return Object.keys(interpolations).reduce(\n    (interpolated, key) =&gt;\n      interpolated.replace(\n        new RegExp(`{\\s*${key}\\s*}`, \"g\"),\n        interpolations[key],\n      ),\n    message,\n  );\n}\n\/\/ ...\n<\/pre>\n<p>Si detectamos un atributo <code>data-i18n-opt<\/code> en el elemento dado a <code>translateElement()<\/code>, ejecutamos su mensaje traducido a trav\u00e9s de una nueva funci\u00f3n <code>interpolate()<\/code> antes de actualizar el elemento. Ahora, cuando cargamos nuestra p\u00e1gina, vemos el mensaje con el valor interpolado.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15571 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-en-1024x348.png\" alt=\"App de demostraci\u00f3n con interpolaci\u00f3n en ingl\u00e9s | Phrase\" width=\"1024\" height=\"348\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-en-1024x348.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-en-300x102.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-en-768x261.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-en.png 1330w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15572 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-ar-1024x348.png\" alt=\"App de demostraci\u00f3n con interpolaci\u00f3n en la configuraci\u00f3n regional \u00e1rabe | Phrase\" width=\"1024\" height=\"348\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-ar-1024x348.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-ar-300x102.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-ar-768x261.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/interpolation-ar.png 1330w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><br \/>\nPor supuesto, tener valores est\u00e1ticos en el HTML tiene un uso limitado para nosotros. Lo ideal es que podamos interpolar din\u00e1micamente con JavaScript. Eso no es muy dif\u00edcil de programar.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"traduccion-dinamica-despues-de-cargar-la-pagina\"><\/span>Traducci\u00f3n din\u00e1mica despu\u00e9s de cargar la p\u00e1gina<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Vamos a extraer una funci\u00f3n de traducci\u00f3n general de <code>translateElement()<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"6597c244-ae4d-4095-a22e-3f67cadd8248\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"6,7,9,12,13,14\">\/\/ ...\nfunction translateElement(element) {\n  const key = element.getAttribute(\"data-i18n-key\");\n  const options =\n    JSON.parse(element.getAttribute(\"data-i18n-opt\")) || {};\n  element.innerText = translate(key, options);\n}\nfunction translate(key, interpolations = {}) {\n  return interpolate(translations[key], interpolations);\n}\n\/\/ ...\n<\/pre>\n<p>Simplemente hemos extra\u00eddo el c\u00f3digo que se encarga de obtener un mensaje en el idioma activo, con interpolaciones, a una nueva funci\u00f3n <code>translate()<\/code>. Ahora podemos usar esta funci\u00f3n para actualizar la traducci\u00f3n de un elemento despu\u00e9s de cargar la p\u00e1gina. Digamos que queremos actualizar nuestro mensaje principal despu\u00e9s de que el usuario inicie sesi\u00f3n. Sin problema.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">const element =\n  document.querySelector(\"[data-i18n-key='lead']\");\n\/\/ Our new function is serving us well here\nelement.innerText =\n  translate(\"lead\", { username: \"Maggie\" });\n\/\/ Store the updated interpolations in the document\n\/\/ in case the element is re-rendered in the future\nelement.setAttribute(\n  \"data-i18n-opt\",\n  JSON.stringify({ username: \"Maggie\" }),\n);\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15573 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dynamic-interpolation-1024x348.png\" alt=\"Demo app con traducci\u00f3n din\u00e1mica al ingl\u00e9s | Phrase\" width=\"1024\" height=\"348\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dynamic-interpolation-1024x348.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dynamic-interpolation-300x102.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dynamic-interpolation-768x261.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/dynamic-interpolation.png 1330w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><br \/>\nAhora podemos actualizar las traducciones de los elementos en cualquier momento desde nuestro JavaScript.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"plurales\"><\/span>Plurales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>No solo necesitamos presentar diferentes mensajes basados en un contador, como \u201c1 seguidor\u201d o \u201c20.000 seguidor<em>es<\/em>\u201d, sino que <a href=\"https:\/\/unicode-org.github.io\/cldr-staging\/charts\/latest\/supplemental\/language_plural_rules.html\">los distintos idiomas tienen diferentes reglas al pluralizar<\/a>. Mientras que el ingl\u00e9s tiene dos formas plurales: <em>one<\/em> y <em>other<\/em>, el \u00e1rabe tiene seis formas plurales, por ejemplo. Hist\u00f3ricamente, esto significaba que implementar soporte de plurales en aplicaciones de front-end no era muy f\u00e1cil. Afortunadamente, el ahora-<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\">objeto est\u00e1ndar Intl.PluralRules<\/a> hace que manejar los plurales sea mucho m\u00e1s f\u00e1cil.<br \/>\nDigamos que somos escritores prol\u00edficos, y queremos hacer saber al mundo cu\u00e1ntos art\u00edculos hemos redactado de verdad.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;p\n  data-i18n-key=\"article-plural\"\n  data-i18n-opt='{\"count\": 122}'\n&gt;\n  {count} articles written and counting.\n&lt;\/p&gt;\n<\/pre>\n<p>Ten en cuenta que estamos usando una convenci\u00f3n de terminar la clave de mensaje plural con <code>-plural<\/code>. Y por supuesto, necesitamos un <code>count<\/code> entero requerido para seleccionar la forma plural correcta. Hablando de formas plurales, a\u00f1ad\u00e1moslas.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"75fdda27-47a8-4750-9436-e1a6d3120c1f\" data-enlighter-title=\"\/lang\/en.json\">{\n  \/\/ El ingl\u00e9s tiene dos formas plurales\n  \"article-plural\": {\n    \"one\": \"{count} article and counting\",\n    \"other\": \"{count} articles and counting\"\n  }\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f45a1ae7-53e1-4816-85d4-c79c3bd740d8\" data-enlighter-title=\"\/lang\/ar.json\">{\n  \/\/ El \u00e1rabe tiene seis formas plurales\n  \"article-plural\": {\n    \"zero\": \"\u0644\u0627 \u062a\u0648\u062c\u062f \u0645\u0642\u0627\u0644\u0627\u062a\",\n    \"one\": \"\u0645\u0642\u0627\u0644 {count}\",\n    \"two\": \"\u0645\u0642\u0627\u0644\u0627\u0646\",\n    \"few\": \"{count} \u0645\u0642\u0627\u0644\u0627\u062a\",\n    \"many\": \"{count} \u0645\u0642\u0627\u0644\",\n    \"other\": \"{count} \u0645\u0642\u0627\u0644\"\n  }\n}\n<\/pre>\n<p>Ahora actualicemos nuestra funci\u00f3n <code>translate<\/code> para manejar mensajes plurales.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"62d15884-1e5b-4035-b937-4091ed4ac4b9\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"6,7,8,9,10,11,26,27,28,29,30\">\/\/ ...\nfunction translate(key, interpolations = {}) {\n  const message = translations[key];\n  if (key.endsWith(\"-plural\")) {\n    return interpolate(\n      pluralFormFor(message, interpolations.count),\n      interpolations,\n    );\n  }\n  return interpolate(message, interpolations);\n}\n\/\/ ...\n\/*\n  Dado un objeto de formularios como\n  {\n    \"zero\": \"No articles\",\n    \"one\": \"One article\",\n    \"other\": \"{count} articles\"\n  } and a count of 3, returns \"3 articles\"\n*\/\nfunction pluralFormFor(forms, count) {\n  const matchingForm = new Intl.PluralRules(locale).select(count);\n  return forms[matchingForm];\n}\n\/\/ ...\n<\/pre>\n<p>El truco aqu\u00ed es la parte que lee <code>new Intl.PluralRules(locale).select(...)<\/code>. El objeto incorporado <code>Intl.PluralRules<\/code>, dado un idioma, conoce las reglas de plural de ese idioma. Por ejemplo, pasar <code>\"ar\"<\/code> al constructor y luego llamar a <code>select(5)<\/code> en el objeto devuelto, devuelve <code>\"few\", que es la forma correcta aqu\u00ed.<br \/>\nAs\u00ed que con muy pocas l\u00edneas de c\u00f3digo, tenemos soporte para plurales totalmente global \ud83d\ude4c<\/p>\n<p style=\"text-align: center\" data-wp-editing=\"1\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15574 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/plural-forms-1024x372.png\" alt=\"Pluralizaci\u00f3n en \u00e1rabe e ingl\u00e9s | Phrase\" width=\"1024\" height=\"372\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/plural-forms-1024x372.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/plural-forms-300x109.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/plural-forms-768x279.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/plural-forms.png 1229w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><em>Nuestro mensaje plural mostrado en ingl\u00e9s y \u00e1rabe, dadas diferentes cantidades<\/em><\/p>\n<h3><span class=\"ez-toc-section\" id=\"formato-de-numeros\"><\/span>Formato de n\u00fameros<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Trescientos mil euros se escribe \u201c\u20ac300,000.00\u201d en ingl\u00e9s (Estados Unidos), \u201c300.000,00\u00a0\u20ac\u201d en alem\u00e1n (Alemania), \u201c\u20ac3,00,000.00\u201d en hindi (indio) (f\u00edjate en las comas en ese \u00faltimo caso) y \u201c\u0663\u0660\u0660\u066c\u0660\u0660\u0660\u066b\u0660\u0660\u00a0\u20ac\u201d en \u00e1rabe (Egipto). \u00bfC\u00f3mo gestionamos todos estos formatos? No te preocupes; otro objeto <code>Intl<\/code> que es parte del est\u00e1ndar moderno de JavaScript es justo lo que necesitabas. <code>Intl.NumberFormat<\/code> \u00a1al rescate!<br \/>\nSiendo los emprendedores que somos, imaginemos que queremos iniciar una web para seguir NFTs, con estad\u00edsticas num\u00e9ricas, por supuesto.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;p\n  data-i18n-key=\"nyan-cat-price\"\n  data-i18n-opt='{\"price\": {\"number\" : 5300}}'\n&gt;\n  Nyan Cat (Official) NFT: {price}\n&lt;\/p&gt;\n<\/pre>\n<p>Nuestro <code>data-i18n-opt<\/code> identifica el valor num\u00e9rico de <code>{price}<\/code> en nuestros archivos de localizaci\u00f3n. Buscaremos esa clave <code>number<\/code> cuando actualicemos nuestro c\u00f3digo de interpolaci\u00f3n en un momento. Primero, proporcionemos las traducciones de los mensajes.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"15136bc5-7049-41a1-a400-c01c52f8df32\" data-enlighter-title=\"\/lang\/en.json\">{\n  \"nyan-cat-price\": \"Nyan Cat (Official) NFT: {price}\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8be65390-6187-4d54-bf60-d3d7fb8bcf39\" data-enlighter-title=\"\/lang\/ar.json\">{\n  \"nyan-cat-price\": \"\u0646\u064a\u0627\u0646 \u0643\u0627\u062a NFT: {price}\"\n}\n<\/pre>\n<p>OK, actualicemos nuestro JavaScript para que esto funcione.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"eebd5a57-2ac6-40b8-9138-1b9ffa458059\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"3,4,5,6,13,32-43\">\/\/ ...\nconst fullyQualifiedLocaleDefaults = {\n  en: \"en-US\",\n  ar: \"ar-EG\",\n};\n\/\/ ...\nfunction interpolate(message, interpolations) {\n  return Object.keys(interpolations).reduce(\n    (interpolated, key) =&gt; {\n      const value = formatNumber(interpolations[key]);\n      return interpolated.replace(\n        new RegExp(`{\\s*${key}\\s*}`, \"g\"),\n        value,\n      );\n    },\n    message,\n  );\n}\n\/*\n  Dado un objeto de valor como\n  {\n    \"number\" : 300000,\n    \"style\": \"currency\",\n    \"currency\": \"EUR\"\n  } y que la configuraci\u00f3n regional activa es \"en\", devuelve \"\u20ac300,000.00\"\n*\/\nfunction formatNumber(value) {\n  if (typeof value === \"object\" &amp;&amp; value.number) {\n    const { number, ...options } = value;\n    return new Intl.NumberFormat(\n      fullyQualifiedLocaleDefaults[locale],\n      options,\n    ).format(number);\n  } else {\n    return value;\n  }\n}\n\/\/ ...\n<\/pre>\n<p>Cuando interpolamos nuestros mensajes traducidos, primero pasamos el valor que vamos a intercambiar por un formateador de n\u00fameros, que a su vez utiliza el objeto incorporado <code>Intl.NumberFormat<\/code>. As\u00ed que, de nuevo, con muy pocas l\u00edneas de c\u00f3digo, tenemos un formato de n\u00fameros localizado.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15576 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-en-1024x450.png\" alt=\"Demo app with US number formatting | Phrase\" width=\"1024\" height=\"450\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-en-1024x450.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-en-300x132.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-en-768x337.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-en.png 1330w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15575 size-large\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-ar-1024x450.png\" alt=\"Demo app with Arabic number formatting | Phrase\" width=\"1024\" height=\"450\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-ar-1024x450.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-ar-300x132.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-ar-768x337.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-format-ar.png 1330w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>\u270b\ud83c\udffd <em>Heads up \u00bb<\/em> Es mejor pasar una configuraci\u00f3n regional completamente especificada, como <code>\"en-US\"<\/code>, al constructor <code>Intl.NumberFormat()<\/code>. Si pasamos solo un c\u00f3digo de idioma, como <code>\"en\"<\/code>, cada navegador decidir\u00e1 qu\u00e9 <em>regi\u00f3n<\/em> usar para formatear sus n\u00fameros: Un navegador podr\u00eda establecer por defecto <code>\"en-US\"<\/code>, mientras que otro podr\u00eda establecer por defecto <code>\"en-UK\"<\/code>. As\u00ed que usamos un mapa <code>fullyQualifiedLocaleDefaults<\/code> en nuestra funci\u00f3n <code>formatNumber()<\/code> para obtener un formato coherente en todos los navegadores.<\/p>\n<p>Debido a que estamos pasando todas las opciones definidas en nuestro objeto de interpolaciones al constructor <code>Intl.NumberFormat()<\/code>, podemos hacer uso de sus <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/NumberFormat\/NumberFormat#parameters\">m\u00faltiples opciones de formato<\/a> en cualquier momento que queramos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;p\n  data-i18n-key=\"nyan-cat-price\"\n  data-i18n-opt='{\"price\": {\n    \"number\" : 5300,\n    \"style\": \"currency\",\n    \"currency\": \"EUR\"\n  }}'\n &gt;\n  Nyan Cat (Official) NFT: {price}\n&lt;\/p&gt;\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15578 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-options-en.png\" alt=\"Ejemplo de formato de n\u00famero de EE.UU. | Phrase\" width=\"632\" height=\"94\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-options-en.png 632w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-options-en-300x45.png 300w\" sizes=\"auto, (max-width: 632px) 100vw, 632px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15577 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-options-ar.png\" alt=\"Ejemplo de formato de n\u00famero \u00e1rabe | Phrase\" width=\"420\" height=\"104\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-options-ar.png 420w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/11\/number-options-ar-300x74.png 300w\" sizes=\"auto, (max-width: 420px) 100vw, 420px\" \/><\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Puede que te guste nuestra <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/\">Gu\u00eda concisa de localizaci\u00f3n de n\u00fameros<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"formato-de-fecha\"><\/span>Formato de fecha<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Al igual que los n\u00fameros, el formato de fecha es espec\u00edfico de la regi\u00f3n. El 5 de diciembre de 2021 en su forma corta se escribe como \"12\/5\/2021\" en ingl\u00e9s de EE. UU. y \"5.12.2021\" en alem\u00e1n de Alemania, por ejemplo. Y, de nuevo, un pr\u00e1ctico objeto incorporado <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/DateTimeFormat\/DateTimeFormat\">Intl.DateTimeFormat<\/a> puede encargarse de la parte m\u00e1s complicada cuando se trata de formatear fechas.<br \/>\nDigamos que queremos mostrar la fecha y hora de publicaci\u00f3n de un art\u00edculo nuestro.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;p\n  data-i18n-key=\"publish-date\"\n  data-i18n-opt='{\"publishDate\": {\n    \"date\": \"2021-12-05 15:29:00\"\n  }}'\n&gt;\n  Published on {publishDate}\n&lt;\/p&gt;\n<\/pre>\n<p>The special <code>date<\/code> key in our <code>data-i18n-opt<\/code> object holds the datetime value we want to format. Como de costumbre, vamos a agregar nuestros mensajes localizados.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"2a53594b-f0b3-42fa-8a05-45366d8f704a\" data-enlighter-title=\"\/lang\/en.json\">{\n  \/\/ ...\n  \"publish-date\": \"Published {publishDate}\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"b7482e03-350d-4750-a944-9eeb661556a1\" data-enlighter-title=\"\/lang\/ar.json\">{\n  \/\/...\n  \"publish-date\": \"\u0646\u0634\u0631 {publishDate}\"\n}\n<\/pre>\n<p>Ahora actualicemos nuestro sistema de traducci\u00f3n para buscar claves <code>date<\/code> y formatear sus valores como fechas localizadas.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"9f97809e-bb75-48e8-85ab-d99b7aee1937\" data-enlighter-title=\"\/js\/scripts.js\" data-enlighter-highlight=\"6,7,8,30,31,32,34,35,37,38,39,40,41,42,43,44\">\/\/ ...\nfunction interpolate(message, interpolations) {\n  return Object.keys(interpolations).reduce(\n    (interpolated, key) =&gt; {\n      const value = formatDate(\n        formatNumber(interpolations[key]),\n      );\n      return interpolated.replace(\n        new RegExp(`{\\s*${key}\\s*}`, \"g\"),\n        value,\n      );\n    },\n    message,\n  );\n}\n\/\/ ...\n\/*\n  Dado un objeto de valor como\n  {\n    \"date\": \"2021-12-05 15:29:00\",\n    \"dateStyle\": \"long\",\n    \"timeStyle\": \"short\"\n  } y que el idioma actual es \"en\",\n  devuelve \"December 5, 2021 at 3:29 PM\"\n*\/\nfunction formatDate(value) {\n  if (typeof value === \"object\" &amp;&amp; value.date) {\n    const { date, ...options } = value;\n    const parsedDate =\n      typeof date === \"string\" ? Date.parse(date) : date;\n    return new Intl.DateTimeFormat(\n      fullyQualifiedLocaleDefaults[locale],\n      options,\n    ).format(parsedDate);\n  } else {\n    return value;\n  }\n}\n\/\/ ...\n<\/pre>\n<p>Despu\u00e9s de pasar nuestro objeto de valor a nuestro formateador de n\u00fameros, volvemos a pasar por nuestro nuevo formateador de fechas. Las opciones de formateo de fechas se pasan al constructor de <code>Intl.DateTimeFormat<\/code>, permitiendo una <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/DateTimeFormat\/DateTimeFormat#parameters\">bastante flexibilidad en el formateo de fechas<\/a>.<\/p>\n<p>\u270b\ud83c\udffd <em>Heads up \u00bb<\/em> We use <code>Date.parse()<\/code> to make sure that our string <code>date<\/code> is converted to a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Date\">Date object<\/a>, otherwise <code>Intl.DateTimeFormat<\/code> will throw an error.<\/p>\n<p>As\u00ed, tenemos un formato de fecha localizado \ud83d\udc4d<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;p\n  data-i18n-key=\"publish-date\"\n  data-i18n-opt='{\"publishDate\": {\n    \"date\": \"2021-12-05 15:29:00\",\n    \"dateStyle\": \"long\",\n    \"timeStyle\": \"short\"\n  }}'\n&gt;\n  Published on {publishDate}\n&lt;\/p&gt;\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15816 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n con formateo de fechas de EE. UU. | Phrase\" width=\"1330\" height=\"696\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-en-300x157.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-en-1024x536.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-en-768x402.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15817 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n con formato de fecha en \u00e1rabe | Phrase\" width=\"1330\" height=\"696\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-ar-300x157.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-ar-1024x536.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/date-format-ar-768x402.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em> Si buscas caracter\u00edsticas de formateo de fechas m\u00e1s robustas, echa un vistazo a nuestro resumen, <a href=\"https:\/\/phrase.com\/blog\/posts\/best-javascript-date-time-libraries\/\">\u00bfCu\u00e1l es la mejor biblioteca de fechas y horas de JavaScript?<\/a> Y nuestra <a href=\"https:\/\/phrase.com\/blog\/posts\/a-human-friendly-way-to-display-dates-in-typescript-javascript\/\">Forma amigable para mostrar fechas en TypeScript\/JavaScript<\/a> te permite mostrar fechas como \"hace 1 hora.\"<\/p>\n<p>\ud83d\udd17 <em>Resource \u00bb<\/em> If you\u2019re using a declarative framework like React, you can take what we built in this section further and <a href=\"https:\/\/phrase.com\/blog\/posts\/roll-your-own-javascript-i18n-library-with-typescript-part-1\/\">Roll Your Own JavaScript i18n Library with TypeScript<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfque-bibliotecas-de-internacionalizacion-de-javascript-me-recomendais\"><\/span>\u00bfQu\u00e9 bibliotecas de internacionalizaci\u00f3n de JavaScript me recomend\u00e1is?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Hemos cubierto c\u00f3mo crear tu propia biblioteca de internacionalizaci\u00f3n en JavaScript antes. Sin embargo, podr\u00eda tener m\u00e1s sentido para tu proyecto adoptar una biblioteca de internacionalizaci\u00f3n lista para usar. No te faltan las opciones, y en este art\u00edculo veremos c\u00f3mo usar las bibliotecas Polyglot, i18next y Globalize.<br \/>\nPara una selecci\u00f3n a\u00fan m\u00e1s amplia, nuestros art\u00edculos populares pueden ayudarte a comenzar con buen pie:<\/p>\n<ul>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/the-best-javascript-i18n-libraries\/\">Las mejores bibliotecas de internacionalizaci\u00f3n de JavaScript<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/best-javascript-date-time-libraries\/\">\u00bfCu\u00e1l es la mejor biblioteca de fechas y horas en JavaScript?<\/a><\/li>\n<\/ul>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Si por casualidad est\u00e1s trabajando con gettext heredado, echa un vistazo a la <a href=\"https:\/\/github.com\/messageformat\/Jed\">biblioteca Jed<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfcomo-localizo-una-pagina-web-con-polyglot\"><\/span>\u00bfC\u00f3mo localizo una p\u00e1gina web con Polyglot?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Mantenida por Airbnb, <a href=\"https:\/\/airbnb.io\/polyglot.js\/\">Polyglot<\/a> es una peque\u00f1a biblioteca de internacionalizaci\u00f3n que resuelve algunos problemas de localizaci\u00f3n que anteriormente no estaban soportados por las bibliotecas est\u00e1ndar de JavaScript. Lo m\u00e1s notable entre las caracter\u00edsticas de Polyglot es su excelente manejo de los plurales. Sin embargo, como mencionamos anteriormente, el ahora incorporado <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\/PluralRules\">Intl.PluralRules constructor<\/a> es compatible con todos los navegadores modernos y resuelve de manera efectiva el problema de pluralizaci\u00f3n. Aun as\u00ed, la \u00faltima versi\u00f3n de este art\u00edculo daba un gran protagonismo a Polyglot, as\u00ed que quer\u00edamos incluir esta secci\u00f3n en caso de que algunos de nuestros lectores a\u00fan quieran una gu\u00eda de Polyglot.<\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> A menos que tu caso de uso requiera Polyglot, consulta la biblioteca alternativa <a href=\"#How_do_I_localize_a_web_page_with_i18next\">i18next en la siguiente secci\u00f3n<\/a> antes de decidirte por una soluci\u00f3n de localizaci\u00f3n.<\/p>\n<p>Sin m\u00e1s pre\u00e1mbulos, localicemos tu peque\u00f1a aplicaci\u00f3n de demostraci\u00f3n con la biblioteca de localizaci\u00f3n de Airbnb.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"125ae0d3-e7f7-4d16-b5c2-2b8f1ebc5198\" data-enlighter-title=\"public\/index.html\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;ul class=\"navbar-list navbar-start\"&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n-key=\"home\" class=\"navbar-link\"&gt;\n              Home\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n-key=\"about\" class=\"navbar-link\"&gt;\n              About\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n        &lt;\/ul&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;h1 data-i18n-key=\"app-title\"&gt;With Polyglot&lt;\/h1&gt;\n    &lt;p data-i18n-key=\"lead\" data-i18n-opt='{\"username\": \"Cadence\"}'&gt;\n      Welcome to my little spot on the interwebs, %{username}!\n    &lt;\/p&gt;\n    &lt;p\n      data-i18n-key=\"article-plural\"\n      data-i18n-opt='{\"smart_count\": 2}'\n    &gt;\n      %{smart_count} articles written and counting.\n    &lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15818 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-before-i18n.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de JavaScript con Polyglot | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-before-i18n.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-before-i18n-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-before-i18n-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-before-i18n-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><br \/>\nVale, \u00a1pong\u00e1monos a localizar esta aplicaci\u00f3n con Polyglot!<\/p>\n<h3><span class=\"ez-toc-section\" id=\"instalacion\"><\/span>Instalaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Polyglot tiene algunas dependencias de <a href=\"https:\/\/www.npmjs.com\/\">NPM<\/a>, as\u00ed que necesita <a href=\"https:\/\/nodejs.org\/en\/\">Node<\/a> instalado localmente. Con Node en su lugar, podemos inicializar un <code>package.json<\/code> para nuestra app de demostraci\u00f3n ejecutando lo siguiente desde la l\u00ednea de comandos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm init -y\n<\/pre>\n<p>Para agrupar nuestras dependencias de NPM en un archivo que los navegadores puedan leer, instalaremos el empaquetador de m\u00f3dulos <a href=\"https:\/\/webpack.js.org\/\">Webpack<\/a> como una dependencia de desarrollo. El <a href=\"https:\/\/github.com\/webpack\/webpack-dev-server\">servidor de desarrollo de Webpack<\/a> nos ayudar\u00e1 con la actualizaci\u00f3n en caliente del paquete en el navegador mientras desarrollamos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install --save-dev webpack webpack-cli webpack-dev-server\n<\/pre>\n<p>Vale, ahora vamos a instalar a la estrella del espect\u00e1culo: Polyglot.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install node-polyglot\n<\/pre>\n<p>Un <code>index.js<\/code> servir\u00e1 como el punto de entrada para nuestra aplicaci\u00f3n, y podemos usarlo para hacer una prueba r\u00e1pida de las instalaciones de la librer\u00eda.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"13c36abe-a6bd-4f2f-a7d5-0661798eb598\" data-enlighter-title=\"src\/index.js\">import Polyglot from \"node-polyglot\";\nconsole.log({ Polyglot });\n<\/pre>\n<p>Un <code>script start<\/code> en nuestro <code>package.json<\/code> facilitar\u00e1 nuestro desarrollo al poner en marcha el servidor de desarrollo con nuestra configuraci\u00f3n personalizada.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"d15750f8-71f2-491d-bf0e-ad4c16fc24bb\" data-enlighter-title=\"package.json\" data-enlighter-highlight=\"7\">{\n  \"name\": \"polyglot-demo\",\n  \/\/ ...\n  \"scripts\": {\n    \"start\": \"webpack-dev-server --config webpack.config.js\"\n  },\n  \/\/ ...\n  \"devDependencies\": {\n    \"webpack\": \"^5.65.0\",\n    \"webpack-cli\": \"^4.9.1\",\n    \"webpack-dev-server\": \"^4.6.0\"\n  },\n  \"dependencies\": {\n    \"node-polyglot\": \"^2.4.2\"\n  }\n}\n<\/pre>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Estamos utilizando un archivo <code>webpack.config.js<\/code> relativamente simple para agrupar nuestra aplicaci\u00f3n y configurar el servidor de desarrollo. <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/blob\/main\/polyglot\/webpack.config.js\">\u00c9chale un vistazo en nuestro repo de Git en GitHub<\/a>.<\/p>\n<p>Ahora vamos a insertar nuestro JS empaquetado justo antes de la etiqueta de cierre <code>&lt;\/body&gt;<\/code> en nuestro archivo <code>public\/index.html<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">&lt;script src=\".\/bundle.js\"&gt;&lt;\/script&gt;\n<\/pre>\n<p>Con esto listo, ya podemos ejecutar nuestro script <code>start<\/code> desde la l\u00ednea de comandos para iniciar el servidor de desarrollo de Webpack.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm start\n<\/pre>\n<p>Si todo va bien, nuestra aplicaci\u00f3n deber\u00eda abrirse autom\u00e1ticamente en el navegador. Si abres las herramientas de desarrollador de tu navegador, ver\u00e1s mensajes en la consola similares a los siguientes.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15819 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-webpack-server-browser-output.png\" alt=\"Registros de consola de herramientas de desarrollador del navegador | Phrase\" width=\"1430\" height=\"342\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-webpack-server-browser-output.png 1430w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-webpack-server-browser-output-300x72.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-webpack-server-browser-output-1024x245.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-webpack-server-browser-output-768x184.png 768w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"traducciones-basicas\"><\/span>Traducciones b\u00e1sicas<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Pasemos al uso b\u00e1sico de Polyglot. Aqu\u00ed est\u00e1 la receta:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">\/\/ 1. Importa la biblioteca\nimport Polyglot from \"node-polyglot\";\n\/\/ 2. Crear una instancia\nconst polyglot = new Polyglot();\n\/\/ 3. Agregar mensajes de traducci\u00f3n para el idioma activo\npolyglot.extend({\n  \"app-title\": \"With Polyglot\",\n});\n\/\/ 4. Usa los mensajes para traducir los elementos de la p\u00e1gina\nconst element = document.querySelector(\n  \"[data-i18n-key='app-title']\",\n);\n\/\/ polyglot.t() resolves a translation message given\n\/\/ a key\nelement.innerHTML = polyglot.t(\"app-title\");\n<\/pre>\n<p>Para cambiar de idioma, podemos recargar la p\u00e1gina con los mensajes del nuevo idioma.<\/p>\n<p>\u270b\ud83c\udffd <em>Atenci\u00f3n \u00bb<\/em> Usamos <code>innerHTML<\/code> en este art\u00edculo para poner el contenido de un elemento. Ten cuidado con este atributo en producci\u00f3n; aseg\u00farate de sanear cualquier HTML que inyectes usando <code>innerHTML<\/code> para evitar ataques de secuencias de comandos en sitios cruzados (XSS).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"6\">import Polyglot from \"node-polyglot\";\nconst polyglot = new Polyglot();\npolyglot.extend({\n  \"app-title\": \"\u0645\u0639 \u0628\u0648\u0644\u064a\u062c\u0644\u0648\u062a\",\n});\nconst element = document.querySelector(\n  \"[data-i18n-key='app-title']\",\n);\nelement.innerHTML = polyglot.t(\"app-title\");\n<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"carga-asincrona-de-archivos-de-traduccion\"><\/span>Carga as\u00edncrona de archivos de traducci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Mientras que lo anterior funciona bien para las aplicaciones m\u00e1s peque\u00f1as, podr\u00edamos hacerlo un poco mejor separando nuestros mensajes de traducci\u00f3n en archivos JSON separados, uno por cada idioma.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"d3cbfa7d-9584-422b-ae6b-da4ae0f6239e\" data-enlighter-title=\"public\/lang\/en.json\">{\n  \"app-title\": \"With Polyglot\",\n  \"home\": \"Home\",\n  \"about\": \"About\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"33d548fc-48fe-4e4f-a852-51da5f540ca6\" data-enlighter-title=\"public\/lang\/ar.json\">{\n  \"app-title\": \"\u0645\u0639 \u0628\u0648\u0644\u064a\u062c\u0644\u0648\u062a\",\n  \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n  \"about\": \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\"\n}\n<\/pre>\n<p>Ahora podemos configurar un idioma predeterminado para nuestra aplicaci\u00f3n y cargar sus traducciones desde la red cuando se carga la p\u00e1gina.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"72b66df6-d597-40ec-bbda-ef066a489055\" data-enlighter-title=\"src\/index.js\">import Polyglot from \"node-polyglot\";\nconst defaultLocale = \"en\";\nconst polyglot = new Polyglot();\n\/\/ Cargar mensajes de traducci\u00f3n desde la red\nasync function loadTranslations(locale) {\n  return await fetch(`\/lang\/${locale}.json`).then(\n    (response) =&gt; response.json(),\n  );\n}\n\/\/ Traduce todos los elementos de la p\u00e1gina que tengan nuestro elemento personalizado\n\/\/ atributo data-i18n-key\nfunction translatePage() {\n  const translatableElements = document.querySelectorAll(\n    \"[data-i18n-key]\",\n  );\n  translatableElements.forEach((el) =&gt; {\n    const key = el.getAttribute(\"data-i18n-key\");\n    el.innerHTML = polyglot.t(key);\n  });\n}\n\/\/ Init\n(async function () {\n  const translations = await loadTranslations(\n    defaultLocale,\n  );\n  polyglot.extend(translations);\n  translatePage();\n})();\n<\/pre>\n<p>Con eso, obtenemos el siguiente renderizado en el navegador.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15820 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n en ingl\u00e9s con Polyglot y elementos de men\u00fa y t\u00edtulo principal faltantes | Phrase\" width=\"1430\" height=\"978\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-en.png 1430w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-en-300x205.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-en-1024x700.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-en-768x525.png 768w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><br \/>\nNuestros elementos del men\u00fa de navegaci\u00f3n y el t\u00edtulo principal est\u00e1n traducidos a nuestro idioma predeterminado, ingl\u00e9s. Sin embargo, observa los errores de Polyglot en la consola, y c\u00f3mo las claves faltantes (<code>lead<\/code> y <code>article-plural<\/code>) muestran el valor de las claves mismas. A\u00f1adiremos traducciones para estas claves en breve para solucionarlo.<br \/>\nSi cambiamos <code>defaultLocale<\/code> a <code>\"ar\"<\/code>, obtenemos el siguiente resultado.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15821 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de localizaci\u00f3n \u00e1rabe con Polyglot y con elementos de men\u00fa y t\u00edtulo principal que faltan | Phrase\" width=\"1436\" height=\"978\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-ar.png 1436w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-ar-300x204.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-ar-1024x697.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-async-ar-768x523.png 768w\" sizes=\"auto, (max-width: 1436px) 100vw, 1436px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"selector-de-idioma\"><\/span>Selector de idioma<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Nuestra aplicaci\u00f3n ahora es m\u00e1s 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\u00f3n:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"dc20fe15-b6c0-48a7-bca9-d5e1be114c97\" data-enlighter-title=\"public\/index.html\" data-enlighter-highlight=\"18,19,20,21\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n\t\t&lt;!-- ... --&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;!-- ... --&gt;\n  &lt;script src=\".\/bundle.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Conectarnos a este elemento <code>&lt;select&gt;<\/code> desde nuestro JavaScript nos permite agregar nuestro comportamiento para cambiar de idioma.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"07b2796c-fc57-4ec8-aa9a-398d7edf394f\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"11,12,14,16,17,21,22,23,24,26,28,29,30,31,34,35\">import Polyglot from \"node-polyglot\";\nconst defaultLocale = \"en\";\nconst polyglot = new Polyglot();\n\/\/ ...\n\/\/ Cargar traducciones para el idioma (o configuraci\u00f3n regional) dado y traducir\n\/\/ elementos de p\u00e1gina para este idioma\nasync function loadAndTranslate(locale) {\n  const translations = await loadTranslations(locale);\n  polyglot.replace(translations);\n  translatePage();\n}\n\/\/ Siempre que el usuario cambie el idioma activo, carga\n\/\/ los mensajes de este idioma en esta p\u00e1gina\nfunction bindLocaleSwitcher(initialValue) {\n  const switcher = document.querySelector(\n    \"[data-i18n-switcher]\",\n  );\n  switcher.value = initialValue;\n  switcher.onchange = (e) =&gt; {\n    loadAndTranslate(e.target.value);\n  };\n}\n\/\/ Init\nloadAndTranslate(defaultLocale);\nbindLocaleSwitcher(defaultLocale);\n<\/pre>\n<p>Hemos refactorizado el c\u00f3digo que carga nuestros mensajes de traducci\u00f3n y traduce los elementos de nuestra p\u00e1gina a una funci\u00f3n reutilizable <code>loadAndTranslate()<\/code>. Una nueva funci\u00f3n <code>bindLocaleSwitcher()<\/code> se conecta al selector de idioma <code>&lt;select&gt;<\/code>; utiliza <code>loadAndTranslate()<\/code> para actualizar nuestras traducciones seg\u00fan el idioma seleccionado por el usuario.<\/p>\n<p>\u270b\ud83c\udffd <em>Atenci\u00f3n \u00bb<\/em> <code>polyglot.extend()<\/code> <em>agregar\u00e1<\/em> mensajes de traducci\u00f3n a los ya cargados, as\u00ed que, usamos <code>polyglot.replace()<\/code> en su lugar para asegurarnos de que solo cargamos las traducciones de la localizaci\u00f3n activa.<\/p>\n<p>Esto deber\u00eda poner en marcha nuestro elegante cambiador de idioma.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15822 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-lang-switcher.gif\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n usando Polyglot con selector de idioma fijo | Phrase\" width=\"600\" height=\"217\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"interpolacion-2\"><\/span>Interpolaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Nuestro texto principal incluye el nombre del usuario que ha iniciado sesi\u00f3n (ficticio, por supuesto). This kind of interpolated value is handled by Polyglot using a special <code>%{variable}<\/code> syntax by default.<\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Puedes cambiar los caracteres que denotan valores interpolados usando la <code>interpolaci\u00f3n<\/code> <a href=\"https:\/\/airbnb.io\/polyglot.js\/#options-overview\">opci\u00f3n pasada al constructor de Polyglot<\/a>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"daf6cafc-7812-46e9-bcd7-963798dbd65f\" data-enlighter-title=\"public\/index.html\">&lt;!-- ... --&gt;\n    &lt;p data-i18n-key=\"lead\" data-i18n-opt='{\"username\": \"Cadence\"}'&gt;\n      Welcome to my little spot on the interwebs, %{username}!\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a78a94fe-dc3d-48c0-bf90-185531c0411d\" data-enlighter-title=\"public\/lang\/en.json\">{\n  \/\/ ...\n  \"lead\": \"Welcome to my little spot on the interwebs, %{username}!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"7868539a-4ab0-4bdc-823e-614ea96bbd06\" data-enlighter-title=\"public\/lang\/ar.json\">{\n  \/\/ ...\n  \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a \u064a\u0627 %{username}.\"\n}\n<\/pre>\n<p>Se pueden agregar algunas l\u00edneas de c\u00f3digo a la funci\u00f3n <code>translatePage()<\/code> para acomodar interpolaciones.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5bd2109a-45be-407b-9277-b08354101d9c\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"13,14,15,16,17,18,20,21,22\">\/\/ ...\n\/\/ Traducir todos los elementos de la p\u00e1gina que tienen un\n\/\/ data-i18n-key attribute\nfunction translatePage() {\n  const translatableElements = document.querySelectorAll(\n    \"[data-i18n-key]\",\n  );\n  translatableElements.forEach((el) =&gt; {\n    const key = el.getAttribute(\"data-i18n-key\");\n    \/\/ Extraer claves\/valores de interpolaci\u00f3n del HTML y\n    \/\/ convi\u00e9rtelos a JSON\n    const interpolations = el.getAttribute(\"data-i18n-opt\");\n    const parsedInterpolations = interpolations\n      ? JSON.parse(interpolations)\n      : {};\n    \/\/ Pasa las interpolaciones procesadas a polyglot.t().\n    \/\/ que maneja autom\u00e1ticamente las sustituciones\n    el.innerHTML = polyglot.t(key, parsedInterpolations);\n  });\n}\n\/\/ ...\n<\/pre>\n<p>Con el c\u00f3digo anterior en su lugar, ahora tenemos nuestro p\u00e1rrafo de introducci\u00f3n interpolado mostrado en el idioma activo.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15824 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n en ingl\u00e9s con Polyglot con p\u00e1rrafo principal fijo | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15825 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de localizaci\u00f3n \u00e1rabe con Polyglot con p\u00e1rrafo principal fijo | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-interpolation-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"plurales-2\"><\/span>Plurales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Digamos que queremos mostrarte, como usuario que ha iniciado sesi\u00f3n, cu\u00e1ntos mensajes has recibido: \u201cTienes 1 nuevo mensaje\u201d o \u201cTienes 12 nuevos mensajes\u201d, por ejemplo. Polyglot maneja los plurales as\u00ed de bien. Solo necesitamos agregar nuestros mensajes de traducci\u00f3n con el valor num\u00e9rico interpolado especial, <code>smart_count<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"dbe32626-2aef-4da8-9c76-db1941115eff\" data-enlighter-title=\"public\/index.html\">&lt;!-- ... --&gt;\n    &lt;p\n      data-i18n-key=\"new-messages\"\n      data-i18n-opt='{\"smart_count\": 12}'\n    &gt;\n      You have %{smart_count} new messages\"\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<p>Polyglot usa <code>smart_count<\/code> para seleccionar la forma de plural correcta de un mensaje de traducci\u00f3n dependiendo del idioma activo. Las formas plurales se separan usando cuatro barras verticales <code>||||<\/code> en nuestros mensajes. English tiene <code>one<\/code> y <code>other<\/code> formas de plural, y necesitamos proporcionarlas en orden:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"effada7e-fa5e-4980-bade-8326330e9360\" data-enlighter-title=\"public\/lang\/en.json\">{\n  \/\/ ...\n  \"new-messages\": \"You have %{smart_count} new message |||| You have %{smart_count} new messages\"\n}\n<\/pre>\n<p>El \u00e1rabe tiene seis formas plurales, y las a\u00f1adimos a nuestros mensajes de la misma manera.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"e551b929-010f-4505-857f-566dad4d8275\" data-enlighter-title=\"public\/lang\/ar.json\">{\n  \/\/ ...\n  \"new-messages\": \"\u0644\u0627 \u062a\u0648\u062c\u062f \u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629 |||| \u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629 |||| \u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u062a\u0627\u0646 \u062c\u062f\u0627\u062f |||| \u0644\u062f\u064a\u0643 %{smart_count} \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629 |||| \u0644\u062f\u064a\u0643 %{smart_count} \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629 |||| \u0644\u062f\u064a\u0643 %{smart_count} \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\"\n}\n<\/pre>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Consulta la secci\u00f3n <a href=\"#Plurals\">\u00bfC\u00f3mo localizo una p\u00e1gina web con JavaScript? \u279e Plurals<\/a> para obtener m\u00e1s detalles sobre las formas plurales.<\/p>\n<p>Una cosa m\u00e1s: por defecto, Polyglot est\u00e1 completamente ajeno al idioma activo, por lo que no conocer\u00e1 las reglas de pluralizaci\u00f3n del idioma activo a menos que especifiquemos expl\u00edcitamente el idioma al cargar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"4ffe4b2d-4324-4b53-a227-3d0fa25322c6\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"8\">\/\/ ...\n\/\/ Cargar traducciones para el idioma (o configuraci\u00f3n regional) dado y traducir\n\/\/ elementos de p\u00e1gina para este idioma\nasync function loadAndTranslate(locale) {\n  const translations = await loadTranslations(locale);\n  polyglot.locale(locale);\n  polyglot.replace(translations);\n  translatePage();\n}\n\/\/ ...\n<\/pre>\n<p>\u00a1Y eso es todo! Ahora tenemos soporte avanzado de plurales en nuestra app.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15826 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n en ingl\u00e9s con conteo inteligente de Polyglot | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15827 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n en \u00e1rabe con conteo inteligente de Polyglot | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/polyglot-plurals-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Obt\u00e9n todo el c\u00f3digo para nuestra aplicaci\u00f3n Polyglot desde <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/tree\/main\/polyglot\">nuestro repositorio de GitHub<\/a>.<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> La <a href=\"https:\/\/airbnb.io\/polyglot.js\/\">documentaci\u00f3n oficial de Polyglot<\/a> es tan ajustada como la propia biblioteca.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"how-do-i-localize-a-web-page-with-i18next\"><\/span>How do I localize a web page with i18next?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Mientras escribo esto, <a href=\"https:\/\/www.i18next.com\/\">i18next<\/a> es una de las <a href=\"https:\/\/www.npmtrends.com\/globalize-vs-i18next-vs-node-polyglot-vs-react-intl\">bibliotecas de internacionalizaci\u00f3n de JavaScript m\u00e1s populares<\/a>. La biblioteca \u201c<a href=\"https:\/\/www.i18next.com\/#learn-once-translate-everywhere\">aprende una vez, [usa] en todas partes<\/a>\u201d funciona de forma independiente y con numerosos frameworks de JavaScript. Un amplio ecosistema de plugins significa que a menudo est\u00e1s a un npm install de resolver un problema com\u00fan de internacionalizaci\u00f3n. Por todo esto, i18next es muy recomendable.<br \/>\nVale, basta de ch\u00e1chara. Volvamos a nuestra peque\u00f1a demostraci\u00f3n y localic\u00e9mosla con i18next.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"ebba1c2b-e62b-4423-8548-a8ccb4bcccdf\" data-enlighter-title=\"public\/index.html\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;ul class=\"navbar-list navbar-start\"&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n-key=\"home\" class=\"navbar-link\"&gt;\n              Home\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n-key=\"about\" class=\"navbar-link\"&gt;\n              About\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n        &lt;\/ul&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;h1 data-i18n-key=\"app-title\"&gt;With i18next&lt;\/h1&gt;\n    &lt;p data-i18n-key=\"lead\" data-i18n-opt='{\"username\": \"Zelda\"}'&gt;\n      Welcome to my little spot on the interwebs, {{username}}!\n    &lt;\/p&gt;\n    &lt;p data-i18n-key=\"new-messages\" data-i18n-opt='{\"count\": 12}'&gt;\n      You have {{count}} new messages\n    &lt;\/p&gt;\n  &lt;\/div&gt;\n  &lt;script src=\".\/bundle.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>No hay mucho nuevo aqu\u00ed. \u00a1Vamos a localizar!<\/p>\n<h3><span class=\"ez-toc-section\" id=\"instalacion-2\"><\/span>Instalaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Usaremos <a href=\"https:\/\/nodejs.org\/en\/\">Node<\/a> y su gestor de paquetes <a href=\"https:\/\/www.npmjs.com\/\">NPM<\/a> para instalar i18next. Primero, vamos a crear un archivo <code>package.json<\/code> para rastrear las dependencias de nuestro proyecto y los scripts de NPM, ejecutando el siguiente comando en la l\u00ednea de comandos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm init -y\n<\/pre>\n<p>La herramienta de empaquetado <a href=\"https:\/\/webpack.js.org\/\">Webpack<\/a> nos permitir\u00e1 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 \u00fatil funci\u00f3n de recarga en caliente que facilita el desarrollo:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install --save-dev webpack webpack-cli webpack-dev-server\n<\/pre>\n<p>No podemos olvidar nuestra biblioteca de internacionalizaci\u00f3n, por supuesto.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install i18next\n<\/pre>\n<p>Un pr\u00e1ctico script <code>npm start<\/code> puede facilitar el arranque de nuestro servidor de desarrollo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"ff750f62-93c9-495b-8b54-ffae0aa84a85\" data-enlighter-title=\"package.json\" data-enlighter-highlight=\"7\">{\n  \"name\": \"i18next-demo\",\n  \/\/...\n  \"scripts\": {\n    \"start\": \"webpack-dev-server --config webpack.config.js\"\n  },\n  \/\/ ...\n  \"devDependencies\": {\n    \"webpack\": \"^5.65.0\",\n    \"webpack-cli\": \"^4.9.1\",\n    \"webpack-dev-server\": \"^4.6.0\"\n  },\n  \"dependencies\": {\n    \"i18next\": \"^21.6.3\"\n  }\n}\n<\/pre>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Estamos usando un archivo <code>webpack.config.js<\/code> relativamente simple para empaquetar nuestra aplicaci\u00f3n y configurar el servidor de desarrollo. <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/blob\/main\/i18next\/webpack.config.js\">\u00c9chale un vistazo a nuestro repositorio de Git en GitHub<\/a>.<\/p>\n<p>Vamos a crear un punto de entrada <code>index.js<\/code> para nuestra app y hacer una prueba r\u00e1pida con i18next para asegurarnos de que est\u00e9 instalado correctamente.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"c346dcb9-2358-4eb7-b8a7-cbd6553cc9f1\" data-enlighter-title=\"src\/index.js\">import i18next from \"i18next\";\nconsole.log({ i18next });\n<\/pre>\n<p>Ahora, cuando ejecutas <code>npm start<\/code> desde la l\u00ednea de comandos, ver\u00e1s que el servidor de desarrollo de Webpack se inicia y carga nuestra app en el navegador autom\u00e1ticamente.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15828 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-post-install.png\" alt=\"Demo App con servidor de desarrollo Webpack cargado | Phrase\" width=\"1434\" height=\"1024\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-post-install.png 1434w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-post-install-300x214.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-post-install-1024x731.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-post-install-768x548.png 768w\" sizes=\"auto, (max-width: 1434px) 100vw, 1434px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"mensajes-de-traduccion-basicos-2\"><\/span>Mensajes de traducci\u00f3n b\u00e1sicos<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>i18next es flexible en c\u00f3mo acepta mensajes de traducci\u00f3n. Hacemos la mayor parte de nuestra configuraci\u00f3n al inicializar la biblioteca con <code>i18next.init(...)<\/code>. La configuraci\u00f3n m\u00e1s b\u00e1sica consiste en incluir nuestros mensajes de traducci\u00f3n directamente bajo la opci\u00f3n <code>resources<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"6b18cd08-103b-40a0-965c-4d0635e3b780\" data-enlighter-title=\"src\/index.js\">import i18next from \"i18next\";\ni18next.init({\n  \/\/ El idioma activo\n  lng: \"en\",\n  \/\/ Salida de consola \u00fatil habilitada durante el desarrollo\n  debug: true,\n  \/\/ Mensajes de traducci\u00f3n, asociados por c\u00f3digo de localizaci\u00f3n\n  resources: {\n    en: {\n      \/\/ Por defecto, i18next espera mensajes bajo el.\n      \/\/ espacio de nombres \"traducci\u00f3n\"\n      translation: {\n        \"app-title\": \"With Polyglot\",\n        home: \"Home\",\n        about: \"About\",\n      },\n    },\n    ar: {\n      translation: {\n        \"app-title\": \"\u0645\u0639 \u0628\u0648\u0644\u064a\u062c\u0644\u0648\u062a\",\n        home: \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n        about: \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\",\n      },\n    },\n  },\n});\n\/\/ Traducir elementos de la p\u00e1gina\nconst translatableElements = document.querySelectorAll(\n  \"[data-i18n-key]\",\n);\ntranslatableElements.forEach((el) =&gt; {\n  const key = el.getAttribute(\"data-i18n-key\");\n  el.innerHTML = i18next.t(key);\n});\n<\/pre>\n<p>Con lo anterior, no deber\u00edas ver cambios cuando tu aplicaci\u00f3n se recarga en el navegador. Sin embargo, si cambiamos <code>lng<\/code> a <code>\"ar\"<\/code>, vemos las siguientes traducciones en \u00e1rabe.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15880 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-basic-translations-1.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de localizaci\u00f3n \u00e1rabe con clave faltante | Phrase\" width=\"1434\" height=\"1270\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-basic-translations-1.png 1434w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-basic-translations-1-300x266.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-basic-translations-1-1024x907.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-basic-translations-1-768x680.png 768w\" sizes=\"auto, (max-width: 1434px) 100vw, 1434px\" \/><\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> La opci\u00f3n <code>debug: true<\/code> activa registros de consola muy \u00fatiles en el navegador. F\u00edjate en los mensajes clave que faltan arriba, por ejemplo.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"carga-asincrona-de-traducciones\"><\/span>Carga as\u00edncrona de traducciones<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Siendo desarrolladores con visi\u00f3n de futuro, hagamos que nuestra aplicaci\u00f3n sea m\u00e1s escalable dividiendo nuestras traducciones en archivos separados, uno por idioma.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"c49398ad-5069-4bad-a35c-b2c6c85dd670\" data-enlighter-title=\"public\/lang\/en.json\">{\n  \"app-title\": \"With i18next\",\n  \"home\": \"Home\",\n  \"about\": \"About\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"23ed2f14-52e9-41d3-979d-79e3fbd1c07d\" data-enlighter-title=\"public\/lang\/ar.json\">{\n  \"app-title\": \"\u0645\u0639 \u0622\u064a \u0623\u064a\u062a\u064a\u0646 \u0646\u064a\u0643\u0633\u062a\",\n  \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n  \"about\": \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\"\n}\n<\/pre>\n<p>i18next is mature and has a lot of bases covered; so we don\u2019t need to write our own code to load translation files from the network. El <a href=\"https:\/\/github.com\/i18next\/i18next-http-backend\">backend HTTP oficial<\/a> se integra en la biblioteca y hace todo el trabajo por nosotros. Inst\u00e1lalo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install i18next-http-backend\n<\/pre>\n<p>Ahora podemos integrar el backend en nuestro <code>index.js<\/code> y <code>use()<\/code> mientras inicializamos i18next.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f7604a6a-683e-43b1-b53b-e043f26d4a97\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"2,4,5,6,7,8,9,10,14,16,17,19,20,21,22,23,24,26,27,40,41,42,43\">import i18next from \"i18next\";\nimport HttpApi from \"i18next-http-backend\";\n\/\/ Hacemos la funci\u00f3n as\u00edncrona para poder usar 'await'\n\/\/ el archivo de traducci\u00f3n a medida que se transmite hacia abajo\n\/\/ Red\nasync function initI18next() {\n  \/\/ Usamos() el backend y esperamos a que se cargue\n  \/\/ la traducciones de la red\n  await i18next.use(HttpApi).init({\n    lng: \"en\",\n    debug: true,\n    \/\/ Eliminar `resources` integrados\n    \/\/ Deshabilitar la carga de la configuraci\u00f3n regional de desarrollo\n    fallbackLng: false,\n    \/\/ Configurar el backend HTTP\n    backend: {\n      loadPath: \"\/lang\/{{lng}}.json\",\n    },\n  });\n}\n\/\/ Refactorizaci\u00f3n r\u00e1pida del c\u00f3digo de traducci\u00f3n de la p\u00e1gina\n\/\/ a una funci\u00f3n\nfunction translatePageElements() {\n  const translatableElements = document.querySelectorAll(\n    \"[data-i18n-key]\",\n  );\n  translatableElements.forEach((el) =&gt; {\n    const key = el.getAttribute(\"data-i18n-key\");\n    el.innerHTML = i18next.t(key);\n  });\n}\n\/\/ Init\n(async function () {\n  await initI18next();\n  translatePageElements();\n})();\n<\/pre>\n<p>La opci\u00f3n <code>backend.loadPath<\/code> anula la ruta de archivo de traducci\u00f3n predeterminada del backend. Un marcador de posici\u00f3n especial <code>{{lng}}<\/code> se reemplaza con el idioma activo. Por ejemplo, cuando nuestra aplicaci\u00f3n se carga por primera vez, el backend buscar\u00e1 <code>\/lang\/en.json<\/code>, ya que especificamos el idioma predeterminado como <code>en<\/code> anteriormente en la configuraci\u00f3n.<br \/>\nEso es todo. Nuestras traducciones ahora se cargan desde la red en lugar de estar integradas en nuestro c\u00f3digo.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"idiomas-soportados-y-un-idioma-de-respaldo\"><\/span>Idiomas soportados y un idioma de respaldo<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A menudo queremos especificar una lista de idiomas (locales) que nuestra aplicaci\u00f3n soporta y un idioma de respaldo cuando falta una traducci\u00f3n. Podemos usar las opciones de configuraci\u00f3n <code>supportLngs<\/code> y <code>fallbackLng<\/code> de i18next, respectivamente, para conseguir esto.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"80f41c54-7e75-43b6-902e-a1e086c14cf1\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"7,8\">\/\/ ...\nasync function initI18next() {\n  await i18next.use(HttpApi).init({\n    lng: \"en\",\n    debug: true,\n    supportedLngs: [\"en\", \"ar\"],\n    fallbackLng: \"en\",\n    backend: {\n      loadPath: \"\/lang\/{{lng}}.json\",\n    },\n  });\n}\n\/\/ ...\n<\/pre>\n<p>\u270b\ud83c\udffd <em>Atenci\u00f3n \u00bb<\/em> El idioma de respaldo se <em>cargar\u00e1 siempre<\/em>, independientemente del idioma activo.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"detectando-automaticamente-el-idioma-del-usuario\"><\/span>Detectando autom\u00e1ticamente el idioma del usuario<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Es com\u00fan querer detectar los ajustes del navegador del usuario y usar su idioma si lo soportamos. Esto suele ser complicado, pero i18next tiene un <a href=\"https:\/\/github.com\/i18next\/i18next-browser-languageDetector\">plugin oficial<\/a> que puede solucionarlo r\u00e1pidamente. Empieza por instalarlo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install i18next-browser-languagedetector\n<\/pre>\n<p>Al igual que el backend HTTP, <code>importamos<\/code> el plugin detector y <code>lo usamos()<\/code> al inicializarlo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"8ffa7f23-6818-4876-8036-17aa252c47cf\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"3,8,14-16\" data-enlighter-linenumbers=\"false\">import i18next from \"i18next\";\nimport HttpApi from \"i18next-http-backend\";\nimport LanguageDetector from \"i18next-browser-languagedetector\";\nasync function initI18next() {\n  await i18next\n    .use(HttpApi)\n    .use(LanguageDetector)\n    .init({\n      debug: true,\n      supportedLngs: [\"en\", \"ar\"],\n      fallbackLng: \"en\",\n      \/\/ Permite que se use \"en\" para\n      \/\/ \"en-US\", \"en-CA\", etc.\n      nonExplicitSupportedLngs: true,\n      backend: {\n        loadPath: \"\/lang\/{{lng}}.json\",\n      },\n    });\n}\n\/\/ ...\n<\/pre>\n<p>Y eso es todo lo que necesitas para lograr una detecci\u00f3n autom\u00e1tica de idioma s\u00f3lida con i18next.<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Te estar\u00e1s preguntando qu\u00e9 criterios utiliza el detector de idioma para determinar el idioma del usuario. Cubrimos esto en detalle en nuestra <a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-react-apps-with-i18next\/#Automatically_Detecting_the_Users_Language\">Gu\u00eda de localizaci\u00f3n de React con i18next <\/a>.<\/p>\n<p>\u270b\ud83c\udffd <em>Atenci\u00f3n \u00bb<\/em> El detector de idioma almacenar\u00e1 el idioma que detecte en el almacenamiento de idioma del navegador por defecto, y usar\u00e1 <em>ese<\/em> valor cuando el usuario visite nuestro sitio de nuevo.<\/p>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em> Tenemos una gu\u00eda dedicada a <a href=\"https:\/\/phrase.com\/es\/blog\/posts\/detecting-a-users-locale\/\">detectar la preferencia de idioma del navegador con JavaScript<\/a>, que te puede interesar.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"selector-de-idioma-2\"><\/span>Selector de idioma<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>La detecci\u00f3n autom\u00e1tica del idioma est\u00e1 bien y todo, pero a menudo necesitamos tener una interfaz para que los usuarios puedan establecer expl\u00edcitamente su idioma preferido. Ya tenemos el marcado para un selector de idioma configurado.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"0ae136e3-6aab-4d56-8a59-449d5efe8768\" data-enlighter-title=\"public\/index.html\" data-enlighter-highlight=\"18,19,20,21\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n      &lt;!-- ... --&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/   &gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;script src=\".\/bundle.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Conectemos este HTML y usemos la funci\u00f3n <code>i18next.changeLanguage()<\/code> para establecer nuestro idioma activo seg\u00fan la elecci\u00f3n del usuario. Despu\u00e9s de que se carguen los mensajes del idioma, podemos encadenar <code>translatePageElements<\/code> para volver a renderizar la p\u00e1gina con traducciones actualizadas.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"799f4601-6390-44a3-ba97-66e0fd7f1412\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"3,4,5,6,8,10,11,12,13,14,15,21\">\/\/ ...\nfunction bindLocaleSwitcher(initialValue) {\n  const switcher = document.querySelector(\n    \"[data-i18n-switcher]\",\n  );\n  switcher.value = initialValue;\n  switcher.onchange = (e) =&gt; {\n    i18next\n      .changeLanguage(e.target.value)\n      .then(translatePageElements);\n  };\n}\n\/\/ Init\n(async function () {\n  await initI18next();\n  translatePageElements();\n  bindLocaleSwitcher(i18next.resolvedLanguage);\n})();\n<\/pre>\n<p>\u270b\ud83c\udffd <em>Atenci\u00f3n \u00bb<\/em> El detector de idioma podr\u00eda haber detectado un idioma que no admitimos, y <em>ese<\/em> valor existir\u00e1 en <code>i18next.language<\/code>. Usamos <code>i18next.resolvedLanguage<\/code> arriba para asegurarnos de que usamos el idioma activo, <em>soportado<\/em> al inicializar nuestro selector de idioma.<\/p>\n<p>\u00a1Y voil\u00e0! Una interfaz para cambiar de idioma:<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15830 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-locale-switcher.gif\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n Polyglot con una interfaz de usuario para cambiar de idioma | Phrase\" width=\"600\" height=\"217\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"interpolacion-3\"><\/span>Interpolaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Por defecto, i18next utiliza una sintaxis <code>{{variable}}<\/code> para denotar valores interpolados en los mensajes de traducci\u00f3n.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5a3233fd-74a1-4c21-aec9-5a5dad9750a3\" data-enlighter-title=\"public\/index.html\">&lt;!-- ... --&gt;\n    &lt;p data-i18n-key=\"lead\" data-i18n-opt='{\"username\": \"Zelda\"}'&gt;\n      Welcome to my little spot on the interwebs, {{username}}!\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"9fedd78c-4683-4235-ba4e-4e91a3117bd9\" data-enlighter-title=\"public\/lang\/en.json\">{\n  \/\/ ...\n  \"lead\": \"Welcome to my little spot on the interwebs, {{username}}!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"7d7ce46a-079e-4239-91c3-3cf21ccf0310\" data-enlighter-title=\"public\/lang\/ar.json\">{\n  \/\/ ...\n  \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a \u064a\u0627 {{username}}.\"\n}\n<\/pre>\n<p>Podemos extraer estos valores din\u00e1micos de nuestros atributos HTML y alimentarlos a <code>i18next.t()<\/code>, que maneja la interpolaci\u00f3n por nosotros.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"85eb09f1-0bcf-49d3-b44a-e803b326513f\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"11,12,13,14,16\">\/\/ ...\nfunction translatePageElements() {\n  const translatableElements = document.querySelectorAll(\n    \"[data-i18n-key]\",\n  );\n  translatableElements.forEach((el) =&gt; {\n    const key = el.getAttribute(\"data-i18n-key\");\n    const interpolations = el.getAttribute(\"data-i18n-opt\");\n    const parsedInterpolations = interpolations\n      ? JSON.parse(interpolations)\n      : {};\n    el.innerHTML = i18next.t(key, parsedInterpolations);\n  });\n}\n\/\/ ...\n<\/pre>\n<p>Con esto, nuestros valores din\u00e1micos se reemplazan en nuestros mensajes.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15831 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n con Polyglot en ingl\u00e9s y p\u00e1rrafo principal interpolado | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15832 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n con Polyglot en configuraci\u00f3n regional \u00e1rabe y p\u00e1rrafo principal interpolado | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-interpolation-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"plurales-3\"><\/span>Plurales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Entre bambalinas, i18next intenta usar el est\u00e1ndar <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\/PluralRules\">Intl.PluralRules<\/a> para gestionar los plurales. Una variable interpolada especial <code>count<\/code> determina la elecci\u00f3n de la forma plural, dependiendo del idioma activo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"6f1ac8ea-b04b-4ffa-97ff-0d5d961018fb\" data-enlighter-title=\"public\/index.html\">&lt;!-- ... --&gt;\n    &lt;p data-i18n-key=\"new-messages\" data-i18n-opt='{\"count\": 12}'&gt;\n      You have {{count}} new messages\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<p>i18next utiliza la convenci\u00f3n <code>message_form<\/code> para las claves de mensaje en plural. Por ejemplo, para manejar las formas <code>one<\/code> y <code>other<\/code> en la traducci\u00f3n al ingl\u00e9s de <code>new-messages<\/code>, podemos especificar lo siguiente.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"f5f7aa95-6e4d-4517-8245-ba0595110e31\" data-enlighter-title=\"public\/lang\/en.json\">{\n  \/\/ ...\n  \"new-messages_one\": \"You have {{count}} new message\",\n  \"new-messages_other\": \"You have {{count}} new messages\"\n}\n<\/pre>\n<p>El \u00e1rabe tiene seis formas plurales, y podemos especificarlas de forma muy similar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a4211fe0-4e56-4b3c-a0de-83e9fb259bc9\" data-enlighter-title=\"public\/lang\/ar.json\">{\n  \/\/ ...\n  \"new-messages_zero\": \"\u0644\u0627 \u062a\u0648\u062c\u062f \u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_one\": \"\u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_two\": \"\u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u062a\u0627\u0646 \u062c\u062f\u0627\u062f\",\n  \"new-messages_few\": \"\u0644\u062f\u064a\u0643 {{count}} \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_many\": \"\u0644\u062f\u064a\u0643 {{count}} \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_other\": \"\u0644\u062f\u064a\u0643 {{count}} \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\"\n}\n<\/pre>\n<p>Con poco esfuerzo, nuestra aplicaci\u00f3n puede mostrar mensajes plurales.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15833 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-en.png\" alt=\"App de demostraci\u00f3n con Polyglot en ingl\u00e9s y pluralizaci\u00f3n | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15834 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-ar.png\" alt=\"App de demostraci\u00f3n con Polyglot en configuraci\u00f3n regional \u00e1rabe y pluralizaci\u00f3n | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/i18next-plurals-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Obt\u00e9n todo el c\u00f3digo que hemos cubierto anteriormente de nuestro <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/tree\/main\/i18next\">repositorio de GitHub<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfcomo-localizo-una-aplicacion-de-react-angular-o-vue\"><\/span>\u00bfC\u00f3mo localizo una aplicaci\u00f3n de React, Angular o Vue?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>En los \u00faltimos a\u00f1os, frameworks declarativos como <a href=\"https:\/\/reactjs.org\/\">React<\/a>, <a href=\"https:\/\/angular.io\/\">Angular<\/a>, <a href=\"https:\/\/vuejs.org\/\">Vue.js<\/a> y otros han revolucionado el mundo del front-end web. Cubrimos estos frameworks en profundidad en nuestro blog. Como React es el m\u00e1s popular entre los grandes, proporcionaremos una gu\u00eda r\u00e1pida para localizar aplicaciones de React a continuaci\u00f3n. Y para otros frameworks declarativos, consulta los siguientes art\u00edculos en profundidad.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"articulos-de-localizacion-de-angular\"><\/span>Art\u00edculos de localizaci\u00f3n de Angular<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/angular-localization-i18n\/\">Traduciendo aplicaciones de Angular con el m\u00f3dulo I18n integrado<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/best-libraries-for-angular-i18n\/\">\u00bfCu\u00e1l es la mejor biblioteca de Angular para la Internacionalizaci\u00f3n?<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/angular-l10n-with-i18next\/\">Angular L10n con I18next<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/angular-10-tutorial-localization-transloco\/\">Tutorial de Angular 10 sobre localizaci\u00f3n con Transloco<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/full-stack-i18n-angular-net-core\/\">Full-Stack I18n con Angular y .NET Core<\/a><\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"articulos-de-localizacion-de-vuejs\"><\/span>Art\u00edculos de localizaci\u00f3n de Vue.js<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/ultimate-guide-to-vue-localization-with-vue-i18n\/\">La Gu\u00eda Definitiva de Localizaci\u00f3n de Vue 3<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/vue-translation-with-vue-i18next\/\">An\u00e1lisis en profundidad: Traducci\u00f3n de Vue con vue-i18next<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/nuxt-js-tutorial-i18n\/\">El \u00fanico tutorial de Nuxt.js sobre I18n que vas a necesitar jam\u00e1s<\/a> (basado en Vue)<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"articulos-de-localizacion-para-otros-frameworks\"><\/span>Art\u00edculos de localizaci\u00f3n para otros frameworks<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Nos damos cuenta de que algunos de nuestros lectores pueden estar usando Svelte, Next u otro framework, as\u00ed que siempre escribimos sobre las \u00faltimas novedades y lo m\u00e1s destacado. Aqu\u00ed hay una selecci\u00f3n de gu\u00edas para localizar la aplicaci\u00f3n que est\u00e1s construyendo en tu framework favorito:<\/p>\n<ul>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/how-to-localize-a-svelte-app-with-svelte-i18n\/\">C\u00f3mo localizar una aplicaci\u00f3n Svelte con svelte-i18n<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/a-step-by-step-guide-to-svelte-localization-with-svelte-i18n-v3\/\">Una gu\u00eda paso a paso para la localizaci\u00f3n de Svelte con svelte-i18n v3<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/solidjs-localization-i18next\/\">Localizando aplicaciones de SolidJS con I18next<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-mithril-applications\/\">Localizando Aplicaciones de Mithril<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-aureliajs-applications\/\">C\u00f3mo localizar aplicaciones usando el Framework Aurelia<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-stimulusjs-i18next\/\">Localizando aplicaciones de StimulusJS con I18next<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/full-stack-javascript-i18n\/\">Full-Stack JavaScript I18n Paso a Paso<\/a> (usando Next.js y Sails.js)<\/li>\n<\/ul>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> De manera m\u00e1s espec\u00edfica: Si vas a trabajar con n\u00fameros de tel\u00e9fono internacionales, aseg\u00farate de echar un vistazo a la <a href=\"https:\/\/phrase.com\/blog\/posts\/libphonenumber-international-phone-numbers\/\">libphonenumber library<\/a>. \u00a1Es agn\u00f3stico respecto al framework!<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfcomo-localizo-una-aplicacion-de-react-con-i18next\"><\/span>\u00bfC\u00f3mo localizo una aplicaci\u00f3n de React con i18next?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Como prometimos, vamos a repasar r\u00e1pidamente una gu\u00eda para localizar nuestras aplicaciones de React con la biblioteca i18next. Hemos cubierto i18next anteriormente en este art\u00edculo, as\u00ed que nos centraremos en su integraci\u00f3n con React.<\/p>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em> <a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-react-apps-with-i18next\/\">A Guide to React Localization with i18next<\/a> va m\u00e1s lejos y lo hace en m\u00e1s profundidad que el breve resumen que tenemos aqu\u00ed.<\/p>\n<p>Primero, tomaremos nuestra querida aplicaci\u00f3n de demostraci\u00f3n y la descompondremos en componentes de React.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"08d116ad-cce1-45d7-b2aa-3e30e3c99bf5\" data-enlighter-title=\"src\/App.js\">import \".\/App.css\";\nimport Navbar from \".\/layout\/Navbar\";\nfunction App() {\n  return (\n    &lt;div className=\"container\"&gt;\n      &lt;Navbar \/&gt;\n      &lt;h1&gt;React i18n&lt;\/h1&gt;\n      &lt;p&gt;\n        Welcome to my little spot on the interwebs, user\n      &lt;\/p&gt;\n      &lt;p&gt;You have count new messages&lt;\/p&gt;\n    &lt;\/div&gt;\n  );\n}\nexport default App;\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"08acb43f-245a-4d89-b514-85c2056fd238\" data-enlighter-title=\"src\/layout\/Navbar.js\">import LocaleSwitcher from \"..\/features\/LocaleSwitcher\";\nfunction Navbar() {\n  return (\n    &lt;nav className=\"navbar\"&gt;\n      &lt;div className=\"container\"&gt;\n        &lt;ul className=\"navbar-list navbar-start\"&gt;\n          &lt;li className=\"navbar-item\"&gt;\n            &lt;a href=\"#\" className=\"navbar-link\"&gt;\n              Home\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n          &lt;li className=\"navbar-item\"&gt;\n            &lt;a href=\"#\" className=\"navbar-link\"&gt;\n              About\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n        &lt;\/ul&gt;\n        &lt;div className=\"navbar-end\"&gt;\n          &lt;LocaleSwitcher \/&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n  );\n}\nexport default Navbar;\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"ebef0bfc-8f59-4073-ba13-411c09225f66\" data-enlighter-title=\"src\/features\/LocaleSwitcher.js\">function LocaleSwitcher() {\n  return (\n    &lt;&gt;\n      &lt;img\n        alt=\"Translation icon\"\n        src=\"img\/translation-icon@2x.png\"\n        className=\"translation-icon\"\n      \/&gt;\n      &lt;select className=\"locale-switcher\"&gt;\n        &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n        &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n      &lt;\/select&gt;\n    &lt;\/&gt;\n  );\n}\nexport default LocaleSwitcher;\n<\/pre>\n<p>El selector de idioma hace poco en este momento, pero pronto lo mejoraremos. Por ahora, tenemos una buena base para localizar.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15836 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-starter.png\" alt=\"React demo app in English with i18n library | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-starter.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-starter-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-starter-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-starter-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"instalacion-de-la-biblioteca\"><\/span>Instalaci\u00f3n de la biblioteca<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Adem\u00e1s de i18next, vamos a usar el marco de integraci\u00f3n oficial <a href=\"https:\/\/react.i18next.com\/\">react-i18next <\/a>, que hace que usar i18next con React sea muy sencillo. Desde la ra\u00edz del proyecto, ejecutemos lo siguiente en la l\u00ednea de comandos para instalar las bibliotecas.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install i18next react-i18next\n<\/pre>\n<p>A continuaci\u00f3n, inicialicemos i18next, <code>usando()<\/code> la integraci\u00f3n de React como lo hacemos. La forma m\u00e1s b\u00e1sica de proporcionar traducciones a i18next es usando la opci\u00f3n <code>resources<\/code> al inicializar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"89a353c7-4c36-417b-a10f-68d97405c398\" data-enlighter-title=\"src\/services\/i18n.js\">import i18next from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\ni18next.use(initReactI18next).init({\n  resources: {\n    en: {\n      translation: {\n        \"app-title\": \"With React\",\n      },\n    },\n    ar: {\n      translation: {\n        \"app-title\": \"\u0645\u0639 \u0631\u064a\u0623\u0643\u062a\",\n      },\n    },\n  },\n  lng: \"en\",\n  debug: true,\n  interpolation: {\n    escapeValue: false,\n  },\n});\nexport default i18next;\n<\/pre>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Establecemos <code>interpolation.escapeValue<\/code> en <code>false<\/code> para <a href=\"https:\/\/www.i18next.com\/translation-function\/interpolation#unescape\">deshabilitar el escape predeterminado que i18next hace para proteger contra ataques XSS<\/a>, ya que React lo hace por nosotros de todas formas.<\/p>\n<p>Traigamos nuestro m\u00f3dulo a nuestro <code>index.js<\/code> ra\u00edz para que podamos inicializar i18next cuando nuestra aplicaci\u00f3n se cargue.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"e0298a27-1532-4aa8-a85d-17ce5bf38526\" data-enlighter-title=\"src\/index.js\" data-enlighter-highlight=\"5\">import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport \".\/lib\/skeleton\/normalize.css\";\nimport \".\/lib\/skeleton\/skeleton.css\";\nimport \".\/services\/i18n\";\nimport App from \".\/App\";\nimport reportWebVitals from \".\/reportWebVitals\";\nReactDOM.render(\n  &lt;React.StrictMode&gt;\n    &lt;App \/&gt;\n  &lt;\/React.StrictMode&gt;,\n  document.getElementById(\"root\"),\n);\n\/\/ ...\n<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"traducciones-basicas-2\"><\/span>Traducciones b\u00e1sicas<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>La funci\u00f3n de traducci\u00f3n <code>i18next.t()<\/code> familiar se puede usar en nuestros componentes de React. Solo necesitamos importar el hook de React <code>useTranslation<\/code> para que <code>t<\/code> est\u00e9 disponible.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-highlight=\"1,6,12\">import { useTranslation } from \"react-i18next\";\nimport Navbar from \".\/layout\/Navbar\";\nimport \".\/App.css\";\nfunction App() {\n  const { t } = useTranslation();\n  return (\n    &lt;div className=\"container\"&gt;\n      &lt;Navbar \/&gt;\n      &lt;h1&gt;{t(\"app-title\")}&lt;\/h1&gt;\n      \/\/ ...\n    &lt;\/div&gt;\n  );\n}\nexport default App;\n<\/pre>\n<p>Cuando nuestra app se recarga, todo deber\u00eda verse igual. Sin embargo, si cambiamos el valor de <code>lng<\/code> a <code>\"ar\"<\/code> en nuestro archivo inicializador <code>src\/services\/i18n.js<\/code>, deber\u00edamos ver el t\u00edtulo de nuestra aplicaci\u00f3n localizado al \u00e1rabe.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15837 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-basic-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de React con biblioteca i18n y t\u00edtulo en \u00e1rabe | Phrase \" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-basic-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-basic-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-basic-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-basic-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"carga-asincrona-de-archivos-de-traduccion-2\"><\/span>Carga as\u00edncrona de archivos de traducci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Hagamos que nuestra aplicaci\u00f3n sea m\u00e1s escalable dividiendo nuestras traducciones en archivos por idioma. El pr\u00e1ctico y oficial complemento <a href=\"https:\/\/github.com\/i18next\/i18next-http-backend\">i18next-http-backend<\/a> hace que esto sea un trabajo r\u00e1pido para nosotros. Inst\u00e1lalo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">npm install i18next-http-backend\n<\/pre>\n<p>El backend buscar\u00e1 archivos en <code>\/locales\/{{lng}}\/{{ns}}.json<\/code> por defecto, donde <code>{{lng}}<\/code> se resuelve en la localizaci\u00f3n activa, y <code>{{ns}}<\/code> se resuelve en el espacio de nombres activo. Dado que el espacio de nombres por defecto es <code>translation<\/code>, podemos poner nuestros mensajes de traducci\u00f3n en ingl\u00e9s en <code>public\/locales\/en\/translation.json<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"22ff6381-59c4-4834-85c2-b2485183bb89\" data-enlighter-title=\"public\/locales\/en\/translation.json\">{\n  \"app-title\": \"With React\",\n  \"home\": \"Home\",\n  \"about\": \"About\"\n}\n<\/pre>\n<p>Nuestro archivo en \u00e1rabe sigue la misma convenci\u00f3n:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"081af770-67c2-44e7-a080-d8082020f2ec\" data-enlighter-title=\"public\/locales\/ar\/translation.json\">{\n  \"app-title\": \"\u0645\u0639 \u0631\u064a\u0623\u0643\u062a\",\n  \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n  \"about\": \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\"\n}\n<\/pre>\n<p>Ahora solo tenemos que importar el backend y <code>usar()<\/code> al inicializar i18next. Tambi\u00e9n deber\u00edamos eliminar nuestras traducciones en l\u00ednea bajo la clave <code>resources<\/code>, ya que ahora el plugin cargar\u00e1 nuestros mensajes de traducci\u00f3n desde la red.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a8be33ed-ee58-4094-849b-8c996a1750fa\" data-enlighter-title=\"src\/services\/i18n.js\" data-enlighter-highlight=\"3,7,10\">import i18next from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport HttpApi from \"i18next-http-backend\";\ni18next\n  .use(initReactI18next)\n  .use(HttpApi)\n  .init({\n    \/\/ Eliminar `resources`\n    lng: \"en\",\n    debug: true,\n    interpolation: {\n      escapeValue: false,\n    },\n  });\nexport default i18next;\n<\/pre>\n<p>Cuando nuestra aplicaci\u00f3n se recargue, deber\u00edamos ver exactamente el mismo renderizado localizado. Por supuesto, las traducciones de la localizaci\u00f3n activa ahora est\u00e1n fluyendo por la red, por lo que nuestra aplicaci\u00f3n es m\u00e1s ligera de carga y m\u00e1s f\u00e1cil de escalar y mantener.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15838 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-async.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de React con carga de archivos de traducci\u00f3n as\u00edncrona | Phrase\" width=\"1434\" height=\"1234\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-async.png 1434w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-async-300x258.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-async-1024x881.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-async-768x661.png 768w\" sizes=\"auto, (max-width: 1434px) 100vw, 1434px\" \/><\/p>\n<h3><span class=\"ez-toc-section\" id=\"selector-de-idioma-3\"><\/span>Selector de idioma<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Construir una interfaz para cambiar de idioma es pan comido con React e i18next. Podemos actualizar nuestro componente <code>LocaleSwitcher<\/code>, controlando el <code>&lt;select&gt;<\/code> en su interior para cambiar el idioma activo al que el usuario elija.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"686af5ef-bb97-4677-b31f-3e6b0710d4f7\" data-enlighter-title=\"src\/features\/LocaleSwitcher.js\" data-enlighter-highlight=\"1,4,16,17,18,19\">import { useTranslation } from \"react-i18next\";\nfunction LocaleSwitcher() {\n  const { i18n } = useTranslation();\n  return (\n    &lt;&gt;\n      &lt;img\n        src=\"img\/translation-icon@2x.png\"\n        alt=\"Translation icon\"\n        className=\"translation-icon\"\n      \/&gt;\n      &lt;select\n        className=\"locale-switcher\"\n        value={i18n.language}\n        onChange={(e) =&gt;\n          i18n.changeLanguage(e.target.value)\n        }\n      &gt;\n        &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n        &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n      &lt;\/select&gt;\n    &lt;\/&gt;\n  );\n}\nexport default LocaleSwitcher;\n<\/pre>\n<p>La integraci\u00f3n de i18next React garantiza que las traducciones se vuelvan a renderizar cuando se cambia el idioma activo.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15839 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/react-lang-switcher.gif\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de React con un selector de idioma funcional | Phrase\" width=\"600\" height=\"217\" \/><\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Obt\u00e9n el c\u00f3digo de todo lo que hemos construido anteriormente desde <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/tree\/main\/react\">nuestro repositorio de GitHub<\/a>.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"articulos-de-localizacion-de-react\"><\/span>Art\u00edculos de localizaci\u00f3n de React<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Nos encanta escribir sobre React en nuestro blog, as\u00ed que estamos felices de ofrecerte una selecci\u00f3n de nuestros an\u00e1lisis en profundidad y tutoriales basados en React, todos centrados en la localizaci\u00f3n:<\/p>\n<ul>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-react-apps-with-i18next\/\">Una gu\u00eda para la localizaci\u00f3n de React con i18next<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-react-apps-with-i18next\/\">Introducci\u00f3n a la internacionalizaci\u00f3n en JavaScript con i18next y Moment.js<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/react-redux-tutorial-internationalization-with-react-i18n-redux\/\">Tutorial de React Redux: Internacionalizaci\u00f3n con react-i18n-redux<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/roll-your-own-i18n-solution-react-redux\/\">Construye tu propia soluci\u00f3n de internacionalizaci\u00f3n con React y Redux<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-javascript-react-apps-with-linguijs\/\">Localizando Aplicaciones de JavaScript y React con LinguiJS<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localized-server-side-rendering-with-react\/\">Renderizado Localizado del Lado del Servidor con React<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-meteor-applications-react\/\">Localizaci\u00f3n de aplicaciones Meteor impulsadas por React<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/react-native-i18n-with-expo-and-i18next-part-1\/\">Una Gu\u00eda Completa para la Localizaci\u00f3n de React Native<\/a><\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/i18n-with-gatsby\/\">Todo lo que necesitas saber sobre internacionalizaci\u00f3n con Gatsby<\/a> (basado en React)<\/li>\n<li><a href=\"https:\/\/phrase.com\/blog\/posts\/full-stack-javascript-i18n\/\">Internacionalizaci\u00f3n Full Stack de JavaScript paso a paso<\/a> (usando Next.js, que est\u00e1 basado en React)<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfcomo-localizas-una-pagina-web-con-jquery-e-i18next\"><\/span>\u00bfC\u00f3mo localizas una p\u00e1gina web con jQuery e i18next?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Aunque no est\u00e1 tan de moda como hace algunos a\u00f1os, <a href=\"https:\/\/jquery.com\/\">jQuery<\/a> sigue siendo una de las bibliotecas de JavaScript m\u00e1s populares actualmente en uso. Encontrar\u00e1s que localizar aplicaciones jQuery es bastante f\u00e1cil con la biblioteca <a href=\"https:\/\/www.i18next.com\/\">i18next<\/a>. Un <a href=\"https:\/\/github.com\/i18next\/jquery-i18next\">plugin oficial de i18next para jQuery<\/a> requiere muy poco trabajo para configurarse, as\u00ed que vamos a usarlo para traducir nuestra fiable aplicaci\u00f3n de demostraci\u00f3n.<\/p>\n<p>\ud83d\uddd2 <em>Nota\u00a0\u00bb<\/em> Hemos cubierto <a href=\"#How_do_I_localize_a_web_page_with_i18next\">i18next en m\u00e1s detalle<\/a> anteriormente en este art\u00edculo, as\u00ed que nos centraremos en la integraci\u00f3n de jQuery aqu\u00ed.<\/p>\n<p>Si has estado siguiendo la lectura, la siguiente aplicaci\u00f3n de ejemplo te resultar\u00e1 familiar. Sirve como una buena base para nuestro trabajo de localizaci\u00f3n.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"93953e8f-9e89-4fa3-a120-0357ddc3d91b\" data-enlighter-title=\"index.html\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;ul class=\"navbar-list navbar-start\"&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n=\"home\" class=\"navbar-link\"&gt;\n              Home\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n=\"about\" class=\"navbar-link\"&gt;\n              About\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n        &lt;\/ul&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;h1 data-i18n=\"app-title\"&gt;jQuery i18n&lt;\/h1&gt;\n    &lt;p data-i18n=\"lead\" data-i18n-options='{\"username\": \"Jackie\"}'&gt;\n      Welcome to my little spot on the interwebs, {{username}}!\n    &lt;\/p&gt;\n    &lt;p data-i18n=\"new-messages\" data-i18n-options='{\"count\": 3}'&gt;\n      You have {{count}} new messages\n    &lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Por defecto, el complemento jQuery de i18next utiliza <code>data-i18n<\/code> (no nuestro anterior <code>data-i18n-key<\/code>) para sus claves de traducci\u00f3n. Este <a href=\"https:\/\/github.com\/i18next\/jquery-i18next#initialize-the-plugin\">se puede cambiar en las opciones del plugin<\/a>.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15883 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-start.png\" alt=\"app de demostraci\u00f3n de jQuery | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-start.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-start-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-start-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-start-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<p>\u00bfEs hora de localizar? \u00a1Vamos!<\/p>\n<h3><span class=\"ez-toc-section\" id=\"instalacion-3\"><\/span>Instalaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>La forma m\u00e1s sencilla de instalar i18next y su plugin jQuery es descargar sus archivos de distribuci\u00f3n minificados e incluirlos en nuestro HTML. Puedes obtener los archivos en las siguientes ubicaciones.<\/p>\n<ul>\n<li><a href=\"https:\/\/code.jquery.com\/jquery-3.6.0.min.js\">jQuery minificado<\/a><\/li>\n<li><a href=\"https:\/\/unpkg.com\/i18next\/dist\/umd\/i18next.min.js\">i18next minificado<\/a><\/li>\n<li><a href=\"https:\/\/raw.githubusercontent.com\/i18next\/jquery-i18next\/master\/jquery-i18next.min.js\">jQuery i18next plugin minificado<\/a><\/li>\n<\/ul>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Consulta la <a href=\"https:\/\/github.com\/i18next\/jquery-i18next#introduction\">documentaci\u00f3n oficial del plugin jQuery de i18next<\/a> en GitHub.<\/p>\n<p>Despu\u00e9s de descargar los archivos anteriores, podemos colocarlos en un directorio <code>js\/lib<\/code> en nuestro proyecto y cargarlos en nuestra p\u00e1gina HTML principal.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"699ed787-7851-4f9d-a589-affb7b514112\" data-enlighter-title=\"index.html\" data-enlighter-highlight=\"13,14,15,17,18\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n   &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;script src=\".\/js\/lib\/jquery-3.6.0.min.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/js\/lib\/i18next.min.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/js\/lib\/jquery-i18next.min.js\"&gt;&lt;\/script&gt;\n  &lt;!-- Our custom JavaScript, coming up in a second... --&gt;\n  &lt;script src=\".\/js\/scripts.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<h3><span class=\"ez-toc-section\" id=\"traducciones-basicas-3\"><\/span>Traducciones b\u00e1sicas<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Con nuestras bibliotecas instaladas, ahora podemos escribir algo de c\u00f3digo de configuraci\u00f3n para hacer que el trabajo de localizaci\u00f3n b\u00e1sico funcione.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"e193e879-0e13-4ea2-916d-6237d777b668\" data-enlighter-title=\"js\/scripts.js\">\/\/ Initialize i18next\ni18next.init({\n  lng: \"en\",     \/\/ Initial locale\n  debug: true,   \/\/ Provides helpful console messages\n  resources: {   \/\/ Translations\n    en: {\n      translation: {\n        \"app-title\": \"jQuery + i18next\",\n      },\n    },\n    ar: {\n      translation: {\n        \"app-title\": \"\u062c\u064a \u0643\u0648\u064a\u0631\u064a + \u0622\u064a \u0625\u064a\u062a\u064a\u0646 \u0646\u064a\u0643\u0633\u062a\",\n      },\n    },\n  },\n});\n\/\/ Initialize i18next jQuery plugin\njqueryI18next.init(i18next, $);\n\/\/ Translate page elements\n$(\"body\").localize();\n<\/pre>\n<p>We\u2019ve covered <code>i18next.init(...)<\/code> earlier in this article. Observa que aqu\u00ed tenemos una llamada a <code>jQueryI18next.init(...)<\/code> tambi\u00e9n. At its most basic, the jQuery i18next plugin <code>init<\/code> function takes the active <code>i18next<\/code> instance, as well as a reference to the jQuery object, <code>$<\/code>.<br \/>\nCuando se inicializa el plugin, agrega una funci\u00f3n <code>localize()<\/code> a jQuery. Llamar a <code>$(selector).localize()<\/code> hace que el plugin localice todos los elementos bajo la jerarqu\u00eda seleccionada. For each element, if a <code>data-i18n<\/code> attribute is found, its corresponding translation in the active locale is swapped in.<br \/>\nPor ejemplo:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\">\/\/ En nuestro JavaScript\ni18next.init({\n  lng: \"en\",\n  resources: {\n    en: {\n      translation: {\n        \"app-title\": \"jQuery + i18next\",\n      },\n    },\n    ar: {\n      translation: {\n        \"app-title\": \"\u062c\u064a \u0643\u0648\u064a\u0631\u064a + \u0622\u064a \u0625\u064a\u062a\u064a\u0646 \u0646\u064a\u0643\u0633\u062a\",\n      },\n    },\n  },\n});\njqueryI18next.init(i18next, $);\n$(\"#main-title\").localize();\n\/\/ En nuestro HTML\n&lt;h1 id=\"main-title\" data-i18n=\"app-title\"&gt;&lt;\/h1&gt;\n\/\/ Renderiza como:\n&lt;h1 id=\"main-title\" data-i18n=\"app-title\"&gt;jQuery + i18next&lt;\/h1&gt;\n<\/pre>\n<p>Sencillo y agradable \ud83d\ude0a<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15884 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-en.png\" alt=\"jQuery demo app with i18next library loaded English locale | Phrase\" width=\"1430\" height=\"1284\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-en.png 1430w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-en-300x269.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-en-1024x919.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-en-768x690.png 768w\" sizes=\"auto, (max-width: 1430px) 100vw, 1430px\" \/><\/div>\n<p>Y si cambiamos nuestro idioma inicial a \u00e1rabe cambiando <code>lng<\/code> a <code>\"ar\"<\/code>, obtendremos un t\u00edtulo en \u00e1rabe.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15885 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-ar.png\" alt=\"jQuery demo app with i18next library loaded Arabic locale | Phrase\" width=\"1434\" height=\"1280\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-ar.png 1434w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-ar-300x268.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-ar-1024x914.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-install-ar-768x686.png 768w\" sizes=\"auto, (max-width: 1434px) 100vw, 1434px\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"carga-asincrona-de-archivos-de-traduccion-3\"><\/span>Carga as\u00edncrona de archivos de traducci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p><em>\u00bfQu\u00e9 tal si dividimos nuestros archivos de traducci\u00f3n en archivos separados, uno por idioma?<\/em> S\u00e9 que te lo preguntas. No te preocupes, el <a href=\"https:\/\/github.com\/i18next\/i18next-http-backend\">plugin oficial de backend HTTP de i18next<\/a> nos respalda.<br \/>\nPara instalar el plugin, podemos <a href=\"https:\/\/raw.githubusercontent.com\/i18next\/i18next-http-backend\/master\/i18nextHttpBackend.min.js\">obtener el script de distribuci\u00f3n minificado de GitHub<\/a> y colocarlo en nuestro directorio <code>js\/lib<\/code>. Por supuesto, tambi\u00e9n querremos incluirlo en nuestro HTML.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"11a726ed-213c-4c67-a446-3420034397dd\" data-enlighter-title=\"index.html\" data-enlighter-highlight=\"15\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n   &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;script src=\".\/js\/lib\/jquery-3.6.0.min.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/js\/lib\/i18next.min.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/js\/lib\/i18nextHttpBackend.min.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/js\/lib\/jquery-i18next.min.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/js\/scripts.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Ahora podemos hacer que nuestra aplicaci\u00f3n sea m\u00e1s escalable moviendo nuestras traducciones a archivos JSON separados.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"39d7927a-c499-4d2a-b8db-34d65015996c\" data-enlighter-title=\"locales\/en\/translation.json\">{\n  \"app-title\": \"With jQuery + i18next\",\n  \"home\": \"Home\",\n  \"about\": \"About\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"37d46e00-a79c-4812-82e9-a89ba2ee6aa5\" data-enlighter-title=\"locales\/ar\/translation.json\">{\n  \"app-title\": \"\u0645\u0639 \u062c\u064a \u0643\u0648\u064a\u0631\u064a \u0648 \u0622\u064a \u0625\u064a\u062a\u064a\u0646 \u0646\u064a\u0643\u0633\u062a\",\n  \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n  \"about\": \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\"\n}\n<\/pre>\n<p>Tendremos que rehacer nuestro c\u00f3digo de configuraci\u00f3n para <code>usar()<\/code> el plugin HTTP al inicializar i18next. Tambi\u00e9n querremos esperar a que se descargue el archivo de traducci\u00f3n de nuestro idioma inicial antes de intentar traducir los elementos de nuestra p\u00e1gina.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"19886823-97b7-4f73-8fe9-7f1d0af23104\" data-enlighter-title=\"js\/scripts.js\" data-enlighter-highlight=\"1,2,3,4,5,8,9,21,22,23,24\">\/\/ Esperar a que las traducciones lleguen a trav\u00e9s de la red\n\/\/ antes de inicializar el plugin de jQuery\nasync function initI18n() {\n  \/\/ Usar el plugin de backend HTTP para descargar traducciones\n  await i18next.use(i18nextHttpBackend).init({\n    lng: \"en\",\n  \/\/ Eliminar la opci\u00f3n `resources`, ya que nuestras traducciones\n  \/\/ ahora est\u00e1n en archivos JSON\n  });\n  jqueryI18next.init(i18next, $);\n}\n\/\/ Refactorizar a funci\u00f3n\nfunction translatePage() {\n  $(\"body\").localize();\n}\n\/\/ Init\n(async function () {\n  \/\/ Espera a que i18next se inicialice antes\n  \/\/ traduciendo los elementos de la p\u00e1gina\n  await initI18n();\n  translatePage();\n})();\n<\/pre>\n<p>Si recargamos nuestra aplicaci\u00f3n, no notamos ninguna diferencia en la salida. Sin embargo, si miras m\u00e1s de cerca la pesta\u00f1a Red de las herramientas de desarrollo, ver\u00e1s una soluci\u00f3n m\u00e1s mantenible para los archivos de traducci\u00f3n: \"desc\u00e1rgalo cuando lo necesites\".<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15886 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-async.png\" alt=\"Demo de jQuery con carga as\u00edncrona de archivos de traducci\u00f3n | Phrase\" width=\"1432\" height=\"1454\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-async.png 1432w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-async-295x300.png 295w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-async-1009x1024.png 1009w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-async-768x780.png 768w\" sizes=\"auto, (max-width: 1432px) 100vw, 1432px\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"selector-de-idioma-4\"><\/span>Selector de idioma<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Es posible que hayas notado que tenemos algo de HTML que parece una interfaz de cambio de idioma en la demo.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"49b71021-1b5e-49ca-ac02-f2af3f97ab97\" data-enlighter-title=\"index.html\" data-enlighter-highlight=\"18,19,20,21\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;!-- ... --&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;!-- ... --&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Conectemos el elemento <code>&lt;select&gt;<\/code> de arriba para que nuestra aplicaci\u00f3n cambie sus traducciones para que coincidan con el idioma seleccionado por el usuario.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5b4b7415-ce8e-4c83-b94f-a48864669b90\" data-enlighter-title=\"js\/scripts.js\" data-enlighter-highlight=\"3,4,6,7,9,11,12,13,14,15,16,17,18,23\">\/\/ ...\nfunction bindLocaleSwitcher() {\n  const $switcher = $(\"[data-i18n-switcher]\");\n  \/\/ Initial value\n  $switcher.val(i18next.language);\n  $switcher.on(\"change\", async function () {\n    \/\/ Cambiar el idioma activo har\u00e1 que su\n    \/\/ para que las traducciones se carguen desde la red, as\u00ed que\n    \/\/ esperamos esa carga antes de actualizar\n    \/\/ elementos de la\u00a0p\u00e1gina\n    await i18next.changeLanguage($switcher.val());\n    translatePage();\n  });\n}\n(async function () {\n  await initI18n();\n  translatePage();\n  bindLocaleSwitcher();\n})();\n<\/pre>\n<p>As\u00ed de f\u00e1cil, tenemos un selector de idioma funcional.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15887 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-locale-switcher.gif\" alt=\"demo de jQuery con selector de idioma | Phrase\" width=\"600\" height=\"217\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"interpolacion-4\"><\/span>Interpolaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Manejar valores din\u00e1micos en nuestros mensajes de traducci\u00f3n viene de serie con i18next. Solo necesitamos proporcionar un mapa JSON <code>data-i18n-options<\/code> en el elemento en el que tenemos interpolaciones.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"7d27ccfb-de5d-4985-86c1-a33c28264797\" data-enlighter-title=\"index.html\">&lt;!-- ... --&gt;\n    &lt;p data-i18n=\"lead\" data-i18n-options='{\"username\": \"Jackie\"}'&gt;\n      Welcome to my little spot on the interwebs, {{username}}!\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<p>El plugin de jQuery de i18next no busca <code>data-i18n-options<\/code> por defecto; tenemos que usar su <code>useOptionsAttr<\/code> <a href=\"https:\/\/github.com\/i18next\/jquery-i18next#initialize-the-plugin\">opci\u00f3n de configuraci\u00f3n<\/a> para habilitar la interpolaci\u00f3n autom\u00e1tica.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1094953e-ed26-4d2c-b79f-afb14132c03c\" data-enlighter-title=\"js\/scripts.js\" data-enlighter-highlight=\"4\">async function initI18n() {\n  await i18next.use(i18nextHttpBackend).init({ lng: \"en\" });\n  jqueryI18next.init(i18next, $, { useOptionsAttr: true });\n}\n\/\/ ...\n<\/pre>\n<p>Por supuesto, asegur\u00e9monos de que tenemos los marcadores de posici\u00f3n <code>{{variable}}<\/code> que i18next espera en nuestros mensajes de traducci\u00f3n.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"2fda0df5-e0a2-4e37-942c-b20d43bacf1a\" data-enlighter-title=\"locales\/en\/translation.json\" data-enlighter-highlight=\"5\">{\n  \"app-title\": \"With jQuery + i18next\",\n  \"home\": \"Home\",\n  \"about\": \"About\",\n  \"lead\": \"Welcome to my little spot on the interwebs, {{username}}!\"\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8804283f-f7e0-4a9b-82a3-2e3b9d26fb58\" data-enlighter-title=\"locales\/ar\/translation.json\" data-enlighter-highlight=\"5\">{\n  \"app-title\": \"\u0645\u0639 \u062c\u064a \u0643\u0648\u064a\u0631\u064a \u0648 \u0622\u064a \u0625\u064a\u062a\u064a\u0646 \u0646\u064a\u0643\u0633\u062a\",\n  \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n  \"about\": \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\",\n  \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a \u064a\u0627 {{username}}.\"\n}\n<\/pre>\n<p>\u00a1All\u00e1 vamos! Interpolaciones interpoladas.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15888 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-en.png\" alt=\"P\u00e1rrafo introductorio en ingl\u00e9s con interpolaci\u00f3n | Phrase\" width=\"824\" height=\"98\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-en.png 824w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-en-300x36.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-en-768x91.png 768w\" sizes=\"auto, (max-width: 824px) 100vw, 824px\" \/><\/div>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15889 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-ar.png\" alt=\"P\u00e1rrafo introductorio en \u00e1rabe con interpolaci\u00f3n | Phrase\" width=\"542\" height=\"102\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-ar.png 542w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-interpolation-ar-300x56.png 300w\" sizes=\"auto, (max-width: 542px) 100vw, 542px\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"plurales-4\"><\/span>Plurales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Usamos el mismo atributo <code>data-i18n-options<\/code> para proporcionar una variable num\u00e9rica especial <code>count<\/code> cuando tenemos mensajes en plural.<\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Consulta la secci\u00f3n anterior <a href=\"#Plurals-3\">i18next \u279e Plurales<\/a> para obtener m\u00e1s detalles sobre c\u00f3mo gestiona los plurales la biblioteca.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"9ba58e5c-c7d1-49b9-8baf-4acd07bade77\" data-enlighter-title=\"index.html\">&lt;!-- ... --&gt;\n    &lt;p data-i18n=\"new-messages\" data-i18n-options='{\"count\": 3}'&gt;\n      You have {{count}} new messages\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<p>Usamos una convenci\u00f3n de <code>key_form<\/code> al especificar nuestros mensajes en plural. El ingl\u00e9s tiene dos formas plurales, <code>one<\/code> y <code>other<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"77d50ab2-ce8e-44a9-b61e-40b6146e4c19\" data-enlighter-title=\"locales\/en\/translation.json\">{\n  \/\/ ...\n  \"new-messages_one\": \"You have {{count}} new message\",\n  \"new-messages_other\": \"You have {{count}} new messages\"\n}\n<\/pre>\n<p>El \u00e1rabe tiene seis formas plurales y se gestionan autom\u00e1ticamente en i18next.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"8cf54801-115c-44bd-b70d-4be380c413c6\" data-enlighter-title=\"locales\/ar\/translation.json\">{\n  \/\/ ...\n  \"new-messages_zero\": \"\u0644\u0627 \u062a\u0648\u062c\u062f \u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_one\": \"\u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_two\": \"\u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u062a\u0627\u0646 \u062c\u062f\u0627\u062f\",\n  \"new-messages_few\": \"\u0644\u062f\u064a\u0643 {{count}} \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_many\": \"\u0644\u062f\u064a\u0643 {{count}} \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\",\n  \"new-messages_other\": \"\u0644\u062f\u064a\u0643 {{count}} \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629\"\n}\n<\/pre>\n<p>Es por eso que, sin c\u00f3digo adicional, nuestra aplicaci\u00f3n tiene un soporte avanzado para plurales.<\/p>\n<div><strong style=\"color: #ff6600\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15891 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de jQuery en ingl\u00e9s con pluralizaci\u00f3n | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/strong><\/div>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15890 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de jQuery en ingl\u00e9s con pluralizaci\u00f3n | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/jquery-plurals-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Obt\u00e9n el <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/tree\/main\/jquery\">c\u00f3digo completo en funcionamiento de la app de arriba desde nuestro repositorio de GitHub<\/a>.<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Si buscas una alternativa a i18next, consulta <a href=\"https:\/\/phrase.com\/blog\/posts\/jquery-i18n-the-advanced-guide\/\">la Gu\u00eda avanzada de jQuery i18n<\/a>, que usa la biblioteca <a href=\"https:\/\/github.com\/wikimedia\/jquery.i18n\">jQuery.i18n<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%c2%bfcomo-puedo-localizar-una-pagina-web-utilizando-el-formato-icu-con-globalize\"><\/span>\u00bfC\u00f3mo puedo localizar una p\u00e1gina web utilizando el formato ICU con Globalize?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Si deseas la cobertura de localizaci\u00f3n pr\u00e1cticamente exhaustiva de los <a href=\"https:\/\/icu.unicode.org\/home\">International Components for Unicode (ICU)<\/a> y el <a href=\"https:\/\/cldr.unicode.org\/\">Common Locale Data Repository (CLDR)<\/a> en tu aplicaci\u00f3n JavaScript, la biblioteca <a href=\"https:\/\/github.com\/globalizejs\/globalize\">Globalize<\/a> seguramente cumplir\u00e1 ese trabajo con creces. Vamos a repasar c\u00f3mo instalar Globalize y usarlo para localizar nuestra humilde aplicaci\u00f3n de demostraci\u00f3n.<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> <a href=\"https:\/\/phrase.com\/blog\/posts\/guide-to-the-icu-message-format\/\">La gu\u00eda imprescindible sobre el formato de mensajes ICU<\/a> cubre qu\u00e9 son ICU y CLDR con m\u00e1s detalle.<\/p>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> A diferencia de muchas otras bibliotecas que cubrimos en este art\u00edculo, Globalize <em>s\u00ed<\/em> es compatible con Internet Explorer 9+.<\/p>\n<p><em>Qu\u00e9 demostraci\u00f3n<\/em>, te estar\u00e1s preguntando. Nuestra fiable demo de una p\u00e1gina, por supuesto.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"3120f7b6-18a4-4377-a423-5d0f2490a644\" data-enlighter-title=\"index.html\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;ul class=\"navbar-list navbar-start\"&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n-key=\"home\" class=\"navbar-link\"&gt;\n              Home\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n          &lt;li class=\"navbar-item\"&gt;\n            &lt;a href=\"#\" data-i18n-key=\"about\" class=\"navbar-link\"&gt;\n              About\n            &lt;\/a&gt;\n          &lt;\/li&gt;\n        &lt;\/ul&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;h1 data-i18n-key=\"app-title\"&gt;With Globalize&lt;\/h1&gt;\n    &lt;p data-i18n-key=\"lead\" data-i18n-opt='{\"username\": \"Stella\"}'&gt;\n      Welcome to my little spot on the interwebs, {username}!\n    &lt;\/p&gt;\n    &lt;p data-i18n-key=\"new-messages\" data-i18n-opt='{\"count\": 100}'&gt;\n      You have # new messages\n    &lt;\/p&gt;\n  &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15892 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-start.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-start.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-start-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-start-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-start-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<p>Empecemos a localizar.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"instalacion-4\"><\/span>Instalaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Globalize es muy modular, por lo que puede ser un poco engorroso instalarlo con JavaScript simple. A\u00fan as\u00ed, el proceso es relativamente simple.<\/p>\n<ul>\n<li>Descarga la <a href=\"https:\/\/github.com\/globalizejs\/globalize\/releases\">\u00faltima versi\u00f3n de Globalize<\/a><\/li>\n<li>Descarga <a href=\"https:\/\/github.com\/rxaviers\/cldrjs\/releases\">la \u00faltima versi\u00f3n del traverser CLDR (cldr.js)<\/a><\/li>\n<li>Descarga la <a href=\"https:\/\/github.com\/unicode-org\/cldr-json\/releases\">\u00faltima versi\u00f3n de datos JSON de CLDR<\/a>; aseg\u00farate de descargar la variante <code>-full<\/code> de la lista de versiones para seguir nuestro ejemplo<\/li>\n<\/ul>\n<p>Con esto deber\u00edamos tener tres archivos ZIP. Vamos a descomprimirlos, a renombrar los directorios de nivel superior descomprimidos y a moverlos a nuestro directorio de proyecto. He colocado los m\u00edos en un directorio <code>\/lib<\/code> de mi proyecto, as\u00ed que mi proyecto ahora tiene este aspecto:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-linenumbers=\"false\">.\n\u251c\u2500\u2500 css\/\n\u251c\u2500\u2500 img\/\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 cldr\/          &lt;&lt; renombrado desde cldr-x.x.x\n\u2502   \u251c\u2500\u2500 cldr-json\/     &lt;&lt; renombrado desde cldr-x.x.x-json-full\n\u2502   \u2514\u2500\u2500 globalize\/     &lt;&lt; renombrado desde globalize.x.x\n\u2514\u2500\u2500 index.html\n<\/pre>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> La documentaci\u00f3n oficial repasa <a href=\"https:\/\/github.com\/globalizejs\/globalize#installation\">diferentes formas de instalar Globalize<\/a>.<\/p>\n<h4>Usar la herramienta de requisitos para determinar los scripts necesarios<\/h4>\n<p>Siempre necesitamos algunas partes de Globalize y cldr.js; otras depender\u00e1n de las funcionalidades de localizaci\u00f3n de nuestra aplicaci\u00f3n. Una herramienta \u00fatil, <a href=\"https:\/\/johnnyreilly.github.io\/globalize-so-what-cha-want\/#\/?currency=true&amp;date=true&amp;message=true&amp;number=true&amp;plural=true&amp;relativeTime=true&amp;unit=true\">So What'cha Want<\/a>, nos permite saber qu\u00e9 archivos incluir en nuestro proyecto dependiendo de las caracter\u00edsticas seleccionadas. Podemos comenzar deseleccionando todas las caracter\u00edsticas excepto <em>message<\/em>.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15893 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool.png\" alt=\"Captura de pantalla de So What&apos;cha Want - Globalize | Phrase\" width=\"2856\" height=\"1262\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool.png 2856w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool-300x133.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool-1024x452.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool-768x339.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool-1536x679.png 1536w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-reqs-tool-2048x905.png 2048w\" sizes=\"auto, (max-width: 2856px) 100vw, 2856px\" \/><\/div>\n<p>Observa las dos listas de archivos cerca de la parte inferior de la p\u00e1gina. La lista de la izquierda indica qu\u00e9 archivos incluir de Globalize y cldr.js; podemos usar la etiqueta <code>&lt;script&gt;<\/code> para estos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1079e969-94cb-435c-8773-0ad911048d64\" data-enlighter-title=\"index.html\" data-enlighter-highlight=\"13,14,15,16,17,18,20,21\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;!-- Globalize requirements --&gt;\n  &lt;script src=\".\/lib\/cldr\/dist\/cldr.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/cldr\/dist\/cldr\/event.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/cldr\/dist\/cldr\/supplemental.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/globalize\/dist\/globalize.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/globalize\/dist\/globalize\/message.js\"&gt;&lt;\/script&gt;\n  &lt;!-- Our app entry point --&gt;\n  &lt;script src=\".\/index.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>La lista en la esquina inferior derecha de <em>So What'cha Want<\/em> tiene datos JSON de CLDR que necesitamos. Este JSON lo necesitaremos obtener en nuestro c\u00f3digo y pasarlo a Globalize mediante su funci\u00f3n <code>load()<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"819ea5ad-0a88-42b0-9f9a-e3245b463959\" data-enlighter-title=\"index.js\">\/\/ Agregaremos m\u00e1s a esta lista a medida que avancemos\nconst supplementals = [\"likelySubtags\"];\nasync function loadIntoGlobalize(featureUrls) {\n  await Promise.all(\n    featureUrls.map((url) =&gt; fetchJson(url))\n  ).then((downloaded) =&gt;\n    downloaded.forEach((feature) =&gt; Globalize.load(feature))\n  );\n}\nfunction supplementalUrlsFor(options) {\n  return options.map(\n    (feature) =&gt;\n      `\/lib\/cldr-json\/cldr-core\/supplemental\/${feature}.json`\n  );\n}\nasync function fetchJson(url) {\n  const response = await fetch(url);\n  return await response.json();\n}\n(async function () {\n  \/\/ Load supplemental requirements\n  await loadIntoGlobalize(\n    supplementalUrlsFor(supplementals)\n  );\n})();\n<\/pre>\n<p>Eso es todo en cuanto a configuraci\u00f3n. Bueno, no es la biblioteca m\u00e1s f\u00e1cil de instalar, pero para un proyecto que necesita una localizaci\u00f3n infalible, vale la pena el esfuerzo.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"traducciones-basicas-4\"><\/span>Traducciones b\u00e1sicas<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Usar Globalize para traducir elementos de la p\u00e1gina es un proceso sencillo de tres pasos.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"7f6d347f-e2aa-4e6a-b077-f960eea2e6fa\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"8,9,10,11,12,13,14,15,16,18,19,21,22,23,24\">\/\/ ...\n(async function () {\n  await loadIntoGlobalize(\n    supplementalUrlsFor(supplementals)\n  );\n  \/\/ 1. Load translation messages\n  Globalize.loadMessages({\n    en: {\n      \"app-title\": \"Hello Globalize!\",\n    },\n    ar: {\n      \"app-title\": \"\u0623\u0647\u0644\u0627\u064b \u062c\u0644\u0648\u0628\u0627\u0644\u0627\u064a\u0632\",\n    },\n  });\n  \/\/ 2. Establece el idioma predeterminado\n  Globalize.locale(\"en\");\n  \/\/ 3. Usa formatMessage() para obtener la traducci\u00f3n por clave\n  document.querySelector(\n    \"[data-i18n-key='app-title']\"\n  ).textContent = Globalize.formatMessage(\"app-title\");\n})();\n<\/pre>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> La documentaci\u00f3n oficial de Globalize tiene una <a href=\"https:\/\/github.com\/globalizejs\/globalize#api\">pr\u00e1ctica secci\u00f3n de la API<\/a> que enumera las funciones disponibles.<\/p>\n<p>Disco. Nuestro t\u00edtulo se muestra utilizando las traducciones de nuestro idioma predeterminado.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15894 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-en.png\" alt=\"Traducci\u00f3n al ingl\u00e9s de la aplicaci\u00f3n de demostraci\u00f3n de Globalize | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<p>Si cambiamos la llamada anterior para que diga <code>Globalize.locale(\"ar\")<\/code>, veremos el t\u00edtulo de nuestra aplicaci\u00f3n en \u00e1rabe.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15895 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-ar.png\" alt=\"Traducci\u00f3n al \u00e1rabe de la aplicaci\u00f3n de demostraci\u00f3n de Globalize | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-basic-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<h3><span class=\"ez-toc-section\" id=\"manejo-de-errores-por-mensajes-faltantes\"><\/span>Manejo de errores por mensajes faltantes<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Generalicemos el c\u00f3digo que traduce los elementos de la p\u00e1gina escribiendo una <code>translatePageElements()<\/code> funci\u00f3n. A diferencia de otras bibliotecas de i18n, Globalize lanzar\u00e1 un error y se detendr\u00e1 si encuentra un mensaje que falta para una clave dada en <code>formatMessage()<\/code>. Nada que un poco de <code>try\/catch<\/code> no pueda suavizar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5ea79d29-3db0-486a-896a-e57c7423c91a\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"3,4,5,6,8,9,11,12,13,14,15,16,17,19,20,21,22,23,24,25,26,35\">\/\/ ...\nfunction translatePageElements() {\n  const elements = document.querySelectorAll(\n    \"[data-i18n-key]\"\n  );\n  elements.forEach((element) =&gt; {\n    const key = element.getAttribute(\"data-i18n-key\");\n    try {\n      element.innerHTML = Globalize.formatMessage(key);\n    } catch (error) {\n      if (error.code === \"E_MISSING_MESSAGE\") {\n        \/\/ Mostrar advertencias en la consola sobre mensajes faltantes\n        \/\/ en lugar de detenerse por completo.\n        console.warn(error.message);\n        \/\/ Mostrar el valor de la clave en la p\u00e1gina para el mensaje que falta\n        element.innerHTML = key;\n      } else {\n        console.error(error);\n      }\n    }\n  });\n}\n(async function () {\n  \/\/ ...\n  Globalize.loadMessages(\/* ... *\/);\n  Globalize.locale(\"en\");\n  translatePageElements();\n})();\n<\/pre>\n<p>Menos \"fallo en caso de mensaje faltante\", m\u00e1s \"mostrar la clave y su valor y mostrar una advertencia en la consola\".<\/p>\n<h3><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15910 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-missing-messages.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize con advertencia en el c\u00f3digo | Phrase\" width=\"1432\" height=\"888\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-missing-messages.png 1432w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-missing-messages-300x186.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-missing-messages-1024x635.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-missing-messages-768x476.png 768w\" sizes=\"auto, (max-width: 1432px) 100vw, 1432px\" \/><\/h3>\n<h3><span class=\"ez-toc-section\" id=\"carga-asincrona-de-archivos-de-traduccion-4\"><\/span>Carga as\u00edncrona de archivos de traducci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Como hemos hecho con otras soluciones en este art\u00edculo, dividamos nuestros mensajes de traducci\u00f3n en archivos JSON por cada localizaci\u00f3n para escalabilidad y mantenimiento.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"fb73dd42-e042-4d6a-9c1c-e96886cf5603\" data-enlighter-title=\"lang\/en.json\">{\n  \/\/ Globalize espera que el c\u00f3digo de idioma sea la clave principal\n  \"en\": {\n    \"app-title\": \"With Globalize\",\n    \"home\": \"Home\",\n    \"about\": \"About\"\n  }\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"a710d68f-9adb-4364-b4ef-437ec0c6ff35\" data-enlighter-title=\"lang\/ar.json\">{\n  \"ar\": {\n    \"app-title\": \"\u0645\u0639 \u062c\u0644\u0648\u0628\u0627\u0644\u0627\u064a\u0632\",\n    \"home\": \"\u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629\",\n    \"about\": \"\u0646\u0628\u0630\u0629 \u0639\u0646\u0627\"\n  }\n}\n<\/pre>\n<p>Una funci\u00f3n reutilizable <code>setLocale()<\/code> puede cargar nuestro archivo de traducci\u00f3n, configurar Globalize y renderizar de nuevo los elementos de la p\u00e1gina.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"346671e3-919a-4566-a041-8086dea23f25\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"1,5,6,7,8,9,10,19\">const defaultLocale = \"en\";\n\/\/ ...\n async function setLocale(locale) {\n  const messages = await fetchJson(`\/lang\/${locale}.json`);\n  Globalize.loadMessages(messages);\n  Globalize.locale(locale);\n  translatePageElements();\n}\n\/\/ ...\n(async function () {\n  await loadIntoGlobalize(\n    supplementalUrlsFor(supplementals)\n  );\n  setLocale(defaultLocale);\n})();\n<\/pre>\n<p>Carga de archivo de traducci\u00f3n as\u00edncrona: listo.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"selector-de-idioma-5\"><\/span>Selector de idioma<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Podemos usar la funci\u00f3n <code>setLocale()<\/code> que acabamos de escribir para que nuestra interfaz de usuario de cambio de idioma funcione. Puede que recuerdes que nuestra aplicaci\u00f3n de demostraci\u00f3n ya tiene algo de HTML para el selector.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"15ce0f80-f807-4c90-9fbd-22d73a8287f6\" data-enlighter-title=\"index.html\" data-enlighter-highlight=\"18,19,20,21\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;nav class=\"navbar\"&gt;\n      &lt;div class=\"container\"&gt;\n        &lt;!-- ... --&gt;\n        &lt;div class=\"navbar-end\"&gt;\n          &lt;img src=\"img\/translation-icon@2x.png\" class=\"translation-icon\" \/&gt;\n          &lt;select data-i18n-switcher class=\"locale-switcher\"&gt;\n            &lt;option value=\"en\"&gt;English&lt;\/option&gt;\n            &lt;option value=\"ar\"&gt;Arabic (\u0627\u0644\u0639\u0631\u0628\u064a\u0629)&lt;\/option&gt;\n          &lt;\/select&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n    &lt;\/nav&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;!-- ... --&gt;\n  &lt;script src=\".\/index.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Con un poco de JavaScripting conseguiremos que el selector cambie.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"5a823850-e4e9-473e-9653-d9a5438b1a71\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"5,6,7,8,10,11,13,14,15,16,25\">const defaultLocale = \"en\";\n\/\/ ...\nfunction bindLocaleSwitcher() {\n  const switcher = document.querySelector(\n    \"[data-i18n-switcher]\"\n  );\n  \/\/ Globalize.locale() devuelve la configuraci\u00f3n regional activa\n  switcher.value = Globalize.locale().locale;\n  switcher.onchange = (e) =&gt; {\n    setLocale(e.target.value);\n  };\n}\n(async function () {\n  await loadIntoGlobalize(\n    supplementalUrlsFor(supplementals)\n  );\n  await setLocale(defaultLocale);\n  bindLocaleSwitcher(defaultLocale);\n})();\n<\/pre>\n<p>Y con eso, nuestros usuarios pueden elegir su idioma preferido.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15896 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-locale-switcher.gif\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize con selector de idioma | Phrase\" width=\"600\" height=\"217\" \/><\/div>\n<h4>Mostrando los nombres de los idiomas en el idioma activo<\/h4>\n<p>Una de las caracter\u00edsticas m\u00e1s poderosas de usar una biblioteca ICU como Globalize es el acceso a una variedad de datos de localizaci\u00f3n CLDR inmensamente rica. Por ejemplo, podemos mostrar los idiomas en nuestro selector de idiomas <em>en el idioma activo<\/em>... el ingl\u00e9s ser\u00eda \u201c\u0627\u0644\u0625\u0646\u062c\u0644\u064a\u0632\u064a\u0629\u201d en \u00e1rabe, por ejemplo. Necesitar\u00edamos obtener los datos principales de CLDR y luego actualizar nuestro <code>select &gt; option<\/code> texto.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"eda6ed92-1c6d-4d7e-8d19-cbdab81843d6\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"2,3,4,11,16,19,20,21,22,23,24,25,26,27,28,29,30,32,33,34,35,36,37,38,40,41,43,44,45,46,48,49,51,52,53,54,55,56\">const defaultLocale = \"ar\";\nconst mains = {\n    localenames: [\"languages\"],\n};\nconst supplementals = \"likelySubtags\"];\nasync function setLocale(locale) {\n  const messages = await fetchJson(`\/lang\/${locale}.json`);\n  Globalize.loadMessages(messages);\n  await loadIntoGlobalize(mainUrlsFor(mains, locale));\n  Globalize.locale(locale);\n  translatePageElements();\n  setLocaleSwitcherDisplayNames();\n}\n\/\/ Opciones dadas = {\n\/\/   nombresLocales: [\"idiomas\"],\n\/\/   fechas: [\"ca-generic\", \"ca-gregorian\"],\n\n\/\/ y, locale = \"en\", devuelve un array como\n\/\/ [\n\/\/    \"\/lib\/cldr-json\/cldr-localenames-full\/main\/en\/languages.json\",\n\/\/    \"\/lib\/cldr-json\/cldr-dates-full\/main\/en\/ca-generic.json\",\n\/\/    \"\/lib\/cldr-json\/cldr-dates-full\/main\/en\/ca-gregorian.json\"\n\/\/ ]\nfunction mainUrlsFor(options, locale) {\n  const result = [];\n  Object.keys(options).forEach((key) =&gt; {\n    options[key].forEach((collection) =&gt; {\n      result.push(\n        `\/lib\/cldr-json\/cldr-${key}-full\/main\/${locale}\/${collection}.json`\n      );\n    });\n  });\n  return result;\n}\nfunction setLocaleSwitcherDisplayNames() {\n  const options = document.querySelectorAll(\n    \"[data-i18n-switcher] option\"\n  );\n  options.forEach((option) =&gt; {\n    const localeCode = option.value;\n    \/\/ Obtener datos principales de CLDR por ruta\n    option.textContent = Globalize.cldr.main(\n      `localeDisplayNames\/languages\/${localeCode}`\n    );\n  });\n}\n\/\/ ...\n<\/pre>\n<p>Podemos usar nuestro c\u00f3digo principal de carga de JSON siempre que queramos cargar los datos principales de CLDR en JSON. De lo contrario, solo un poco de manipulaci\u00f3n del DOM cuando se elige un nuevo idioma nos da nombres de idiomas localizados (\u00a1tan meta!).<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15897 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-locale-display-names.gif\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize con nombres de localizaci\u00f3n localizados | Phrase\" width=\"600\" height=\"217\" \/><\/div>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> La documentaci\u00f3n oficial de cldr.js explica <a href=\"https:\/\/github.com\/rxaviers\/cldrjs#get-item-given-its-path\">c\u00f3mo recuperar datos JSON de CLDR<\/a> con m\u00e1s detalle.<\/p>\n<h3><span class=\"ez-toc-section\" id=\"interpolacion-5\"><\/span>Interpolaci\u00f3n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>El manejo de valores din\u00e1micos en nuestros mensajes se gestiona mediante el formato de mensaje ICU mediante la sintaxis <code>{variable}<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1c1af1b3-a6c4-4113-9def-94a483d7cc7a\" data-enlighter-title=\"lang\/en.json\">{\n  \"en\": {\n    \/\/ ...\n    \"lead\": \"Welcome to my little spot on the interwebs, {username}!\"\n  }\n}\n<\/pre>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"175d373e-1962-4588-80c4-f65b80eff5d2\" data-enlighter-title=\"lang\/ar\">{\n  \"ar\": {\n    \/\/ ...\n    \"lead\": \"\u0623\u0647\u0644\u0627\u064b \u0628\u0643 \u0641\u064a \u0645\u0643\u0627\u0646\u064a \u0627\u0644\u0635\u063a\u064a\u0631 \u0639\u0644\u0649 \u0627\u0644\u0646\u062a \u064a\u0627 {username}.\"\n  }\n}\n<\/pre>\n<p>Podemos proporcionar pares de sustituci\u00f3n clave\/valor en nuestro HTML.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"677e0228-8329-44a2-bcb6-63c6ee81b5c7\" data-enlighter-title=\"index.html\">&lt;!-- ... --&gt;\n    &lt;p data-i18n-key=\"lead\" data-i18n-opt='{\"username\": \"Stella\"}'&gt;\n      Welcome to my little spot on the interwebs, {username}!\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<p>Y ahora solo necesitamos leer esos pares clave\/valor y alimentarlos a <code>Globalize.formatMessage()<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"61ef9e83-97f1-453f-8037-ebf3044facce\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"11,12,13,14,15,20\">\/\/ ...\nfunction translatePageElements() {\n  const elements = document.querySelectorAll(\n    \"[data-i18n-key]\"\n  );\n  elements.forEach((element) =&gt; {\n    const key = element.getAttribute(\"data-i18n-key\");\n    const interpolations =\n      element.getAttribute(\"data-i18n-opt\");\n    const parsedInterpolations = interpolations\n      ? JSON.parse(interpolations)\n      : {};\n    try {\n      element.innerHTML = Globalize.formatMessage(\n        key,\n        parsedInterpolations\n      );\n    } catch (error) {\n      if (error.code === \"E_MISSING_MESSAGE\") {\n        console.warn(error.message);\n      } else {\n        console.error(error);\n      }\n    }\n  });\n}\n\/\/ ...\n<\/pre>\n<p>No hay problema.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15898 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize con interpolaci\u00f3n en la localizaci\u00f3n en ingl\u00e9s | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15899 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize con interpolaci\u00f3n en la localizaci\u00f3n en \u00e1rabe | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-interpolation-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<div><\/div>\n<h3><span class=\"ez-toc-section\" id=\"plurales-5\"><\/span>Plurales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>El manejo de plurales en el formato ICU es insuperable y cubre formas plurales complejas como las del ruso o el \u00e1rabe. Tenemos que configurar la funci\u00f3n de plural antes de poder usarla.<\/p>\n<h4>A\u00f1adir requisitos de plural<\/h4>\n<p>Vayamos a <a href=\"https:\/\/johnnyreilly.github.io\/globalize-so-what-cha-want\/#\/?currency=true&amp;date=true&amp;message=true&amp;number=true&amp;plural=true&amp;relativeTime=true&amp;unit=true\">So What'cha Want<\/a> para averiguar qu\u00e9 necesitaremos incluir si habilitamos el <em>plural<\/em>.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15900 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs.png\" alt=\"Captura de pantalla de So What&apos;cha Want - Globalize | Phrase\" width=\"2876\" height=\"1270\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs.png 2876w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs-300x132.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs-1024x452.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs-768x339.png 768w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs-1536x678.png 1536w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-reqs-2048x904.png 2048w\" sizes=\"auto, (max-width: 2876px) 100vw, 2876px\" \/><\/div>\n<p>No est\u00e1 tan mal: primero necesitaremos agregar una etiqueta <code>&lt;script&gt;<\/code> para la funci\u00f3n.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"83186151-0cf9-432c-95cf-e4e7b1dbaf29\" data-enlighter-title=\"index.html\" data-enlighter-highlight=\"18\">&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;!-- ... --&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"container\"&gt;\n    &lt;!-- ... --&gt;\n  &lt;\/div&gt;\n  &lt;script src=\".\/lib\/cldr\/dist\/cldr.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/cldr\/dist\/cldr\/event.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/cldr\/dist\/cldr\/supplemental.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/globalize\/dist\/globalize.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/globalize\/dist\/globalize\/message.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/lib\/globalize\/dist\/globalize\/plural.js\"&gt;&lt;\/script&gt;\n  &lt;script src=\".\/index.js\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/pre>\n<p>Tambi\u00e9n necesitaremos el JSON suplementario de <code>plurales<\/code>, y probablemente el de <code>ordinales<\/code> si queremos cubrir formatos como \u00ab3.\u00ba\u00bb y \u00ab4.\u00ba\u00bb en nuestros mensajes. Solo necesitaremos agregar los requisitos a nuestro arreglo de configuraci\u00f3n <code>supplementals<\/code>. Nuestra aplicaci\u00f3n ya est\u00e1 configurada para cargar el JSON correspondiente al cargar.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"1b2d9cb4-79c6-4aa9-960f-8d34d4764fc3\" data-enlighter-title=\"index.js\" data-enlighter-highlight=\"4,5\">const defaultLocale = \"ar\";\nconst supplementals = [\n  \"likelySubtags\",\n  \"plurals\",\n  \"ordinals\",\n];\n\/\/ ...\n<\/pre>\n<p>Ahora podemos agregar nuestros mensajes plurales. La sintaxis plural de ICU es en gran medida intuitiva: una variable <code>count<\/code> determina la forma plural elegida, y un s\u00edmbolo especial <code>#<\/code> se reemplaza con el valor <code>count<\/code> al mostrarse.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-linenumbers=\"false\" data-enlighter-group=\"188a8655-10bf-427b-b559-8905194c51e5\" data-enlighter-title=\"lang\/en.json\">{\n  \"en\": {\n    \/\/ ...\n    \"new-messages\": [\n      \/\/ Podemos llamar a `count` como queramos, siempre que\n      \/\/ coincida con la clave en nuestra llamada a formatMessage()\n      \"You have {count, plural,\",\n      \"    one {# new message}\",\n      \"  other {# new messages}\",\n      \"}\"\n    ]\n  }\n}\n<\/pre>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> Globalize nos permite dividir mensajes multil\u00ednea utilizando matrices.<\/p>\n<p>Las seis formas plurales en \u00e1rabe se gestionan mediante el formato ICU.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"8039927b-6183-45cb-822a-b52ccadd2653\" data-enlighter-title=\"lang\/ar.json\" data-enlighter-linenumbers=\"false\">{\n  \"ar\": {\n    \/\/ ...\n    \"new-messages\": [\n      \"{count, plural, \",\n      \"   zero {\u0644\u0627 \u062a\u0648\u062c\u062f \u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629}\",\n      \"    one {\u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629}\",\n      \"    two {\u0644\u062f\u064a\u0643 \u0631\u0633\u0627\u0644\u062a\u0627\u0646 \u062c\u062f\u0627\u062f}\",\n      \"    few {\u0644\u062f\u064a\u0643 # \u0631\u0633\u0627\u0626\u0644 \u062c\u062f\u064a\u062f\u0629}\",\n      \"   many {\u0644\u062f\u064a\u0643 # \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629}\",\n      \"  other {\u0644\u062f\u064a\u0643 # \u0631\u0633\u0627\u0644\u0629 \u062c\u062f\u064a\u062f\u0629}\",\n      \"}\"\n    ]\n  }\n}\n<\/pre>\n<p>\ud83e\udd3f <em>Profundiza m\u00e1s \u00bb<\/em> Tratamos los plurales de ICU con m\u00e1s detalle en <a href=\"https:\/\/phrase.com\/blog\/posts\/guide-to-the-icu-message-format\/#Plurals\">La gu\u00eda imprescindible sobre el formato de mensajes ICU<\/a>.<\/p>\n<p>Por supuesto, necesitamos asegurarnos de que <code>count<\/code> se suministre a <code>Globalize.formatMessage()<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-group=\"e9ab1b69-a2cd-44a1-95cf-06d075283840\" data-enlighter-title=\"index.html\" data-enlighter-linenumbers=\"false\">&lt;!-- ... --&gt;\n    &lt;p data-i18n-key=\"new-messages\" data-i18n-opt='{\"count\": 110}'&gt;\n      You have # new messages\n    &lt;\/p&gt;\n&lt;!-- ... --&gt;\n<\/pre>\n<p>Y eso es b\u00e1sicamente todo sobre los plurales.<\/p>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15901 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-en.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize en ingl\u00e9s con pluralizaci\u00f3n | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-en.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-en-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-en-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-en-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<div><\/div>\n<div><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-15902 size-full\" src=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-ar.png\" alt=\"Aplicaci\u00f3n de demostraci\u00f3n de Globalize en \u00e1rabe con pluralizaci\u00f3n | Phrase\" width=\"1330\" height=\"480\" srcset=\"https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-ar.png 1330w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-ar-300x108.png 300w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-ar-1024x370.png 1024w, https:\/\/phrase.com\/wp-content\/uploads\/2021\/12\/globalize-plural-ar-768x277.png 768w\" sizes=\"auto, (max-width: 1330px) 100vw, 1330px\" \/><\/div>\n<div><\/div>\n<div>\ud83d\udd17 <em>Recurso \u00bb<\/em> Obt\u00e9n todo el c\u00f3digo demo de Globalize de nuestro <a href=\"https:\/\/github.com\/PhraseApp-Blog\/javascript-l10n-ultimate-guide\/tree\/main\/globalize\">repo de GitHub<\/a>.<\/div>\n<p>\ud83d\uddd2 <em>Nota \u00bb<\/em> La implementaci\u00f3n de ICU de Globalize abarca los formatos completos de fechas y n\u00fameros. Echa un vistazo a la <a href=\"https:\/\/github.com\/globalizejs\/globalize#api\">documentaci\u00f3n oficial<\/a> para m\u00e1s informaci\u00f3n.<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Te mostramos m\u00e1s opciones de instalaci\u00f3n de Globalize y casos de uso en <a href=\"https:\/\/phrase.com\/blog\/posts\/js-i18n-with-globalizejs\/\">JS I18n con Globalize.js<\/a>.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"conclusion-de-nuestra-guia-de-localizacion-de-javascript\"><\/span>Conclusi\u00f3n de nuestra gu\u00eda de localizaci\u00f3n de JavaScript<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Y con esto terminamos por esta vez. Esperamos que hayas disfrutado de esta incursi\u00f3n en algunas de las soluciones de localizaci\u00f3n de JavaScript m\u00e1s populares.<\/p>\n<p>\ud83d\udd17 <em>Recurso \u00bb<\/em> Si trabajas con Ruby on Rails, te puede interesar <a href=\"https:\/\/phrase.com\/blog\/posts\/localizing-javascript-in-rails-apps\/\">la gu\u00eda para localizar JavaScript en aplicaciones Rails<\/a>.<\/p>\n<p>Y si buscas llevar tu proceso de localizaci\u00f3n al siguiente nivel, echa un vistazo a Phrase. Phrase admite el formato ICU, todos los dem\u00e1s formatos de traducci\u00f3n que hemos cubierto aqu\u00ed y muchos m\u00e1s. Con su CLI y la sincronizaci\u00f3n con Bitbucket, GitHub y GitLab, podr\u00e1s dejar tu internacionalizaci\u00f3n en piloto autom\u00e1tico. La consola web de Phrase tiene todas las funciones, con aprendizaje autom\u00e1tico y sugerencias inteligentes, y es un placer de usar para los traductores. Una vez que las traducciones est\u00e9n listas, se pueden sincronizar autom\u00e1ticamente con tu proyecto. Lo configuras y te olvidas, permiti\u00e9ndote centrarte en el c\u00f3digo que amas.<\/p>\n<p>Consulta todas las <a href=\"https:\/\/phrase.com\/es\/roles\/developers\/\">caracter\u00edsticas de Phrase para desarrolladores<\/a> y comprueba t\u00fa mismo c\u00f3mo puede agilizar tus flujos de trabajo de localizaci\u00f3n de software.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pon en marcha la localizaci\u00f3n de JavaScript para tu navegador con esta gu\u00eda completa y prepara tu aplicaci\u00f3n para los usuarios internacionales.<\/p>\n","protected":false},"author":41,"featured_media":2612,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"post-refresh-updated","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":false,"_modified_date":"","_searchwp_excluded":"","episode_type":"","audio_file":"","podmotor_file_id":"","podmotor_episode_id":"","cover_image":"","cover_image_id":"","duration":"","filesize":"","filesize_raw":"","date_recorded":"","explicit":"","block":"","itunes_episode_number":"","itunes_title":"","itunes_season_number":"","itunes_episode_type":"","footnotes":""},"categories":[2520],"class_list":["post-133327","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-localizacion-de-software-es"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/posts\/133327","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/comments?post=133327"}],"version-history":[{"count":4,"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/posts\/133327\/revisions"}],"predecessor-version":[{"id":136312,"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/posts\/133327\/revisions\/136312"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/media?parent=133327"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/es\/wp-json\/wp\/v2\/categories?post=133327"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}