¿Cómo hacer i18n con Handlebars.js (plantillas de bigote)?

10 minutos de lectura

avatar de usuario
mdcarter

Actualmente estoy usando Handlebars.js (asociado con Backbone y jQuery) para hacer que una aplicación web se represente casi en su totalidad en el lado del cliente, y tengo problemas con la internacionalización de esta aplicación.

¿Cómo puedo hacer que esto funcione?

¿Hay complementos?

avatar de usuario
átomo de poder

Sé que esto ha sido respondido, pero me gustaría compartir mi solución simple. Para construir sobre la solución de Gazler usando I18n.js (que usamos con nuestro proyecto en el trabajo), solo usé un asistente de Handlebars muy simple para facilitar el proceso para hacer la localización sobre la marcha:

Manipulador

Handlebars.registerHelper('I18n',
  function(str){
    return (I18n != undefined ? I18n.t(str) : str);
  }
);

Modelo

<script id="my_template" type="x-handlebars-template">
    <div>{{I18n myVar}}</div>
</script>

La principal ventaja de esto es que no hay un procesamiento previo o posterior costoso en todo el objeto json. Sin mencionar que si el json entrante tiene objetos/matrices anidados, el tiempo dedicado a buscarlos y analizarlos puede ser costoso si el objeto es enorme.

¡Salud!

  • Preferiría esta solución en comparación con las de @Gazler

    –Max Schmidt

    10 de enero de 2012 a las 9:02

  • Buena solución. hice este gist.github.com/1589125. Pero el de @poweratom parece más limpio.

    – Zag zag..

    10 de enero de 2012 a las 13:40

  • Estoy recibiendo este error: TypeError: I18n.t is not a function. ¿Cualquier sugerencia?

    – slackmart

    25 de marzo de 2015 a las 22:17

  • @sgmart, el requisito previo para usar I18n incluye la biblioteca I18n (o equivalente): github.com/fnando/i18n-js

    – átomo de poder

    25 de marzo de 2015 a las 23:38

  • ¿Qué pasa si un objeto con valores de traducción dinámicos tiene que ser analizado por el asistente de traducción? {{ I18n 'key' {dynamic: 'value'} }} no parece funcionar.

    – Rvanlaak

    22 de noviembre de 2018 a las 10:16

avatar de usuario
mirador

https://github.com/fnando/i18n-js es una gema rubí que creará un archivo de internacionalización desde su carpeta config/locales. Sin embargo, si no está utilizando rieles, puede encontrar el javascript utilizado por sí solo aquí.

Luego, simplemente almacena las traducciones en un objeto anidado.

I18n.translations = {"en":{"date":{"formats":{"default":"%Y-%m-%d","short":"%b %d","long":"%B %d, %Y"}}}};

Algo que también te puede servir que yo uso en mis proyectos es un parche para bigote que automáticamente traduce cadenas en el formato @@[email protected]@

i18nize = function (result) {
    if (I18n) {
      var toBeTranslated = result.match(/@@([^@]*)@@/gm);
      if (!toBeTranslated) return result;
      for(var i = 0; i < toBeTranslated.length; i++) {
        result = result.replace(toBeTranslated[i], I18n.t(toBeTranslated[i].replace(/@/g, "")));
      }
    }
    return result;
};

Luego llamas a i18nize después renderizar para permitirle poner traducciones en sus plantillas en lugar de pasarlas.

Tenga cuidado con parchear el bigote, ya que no podrá portar sus plantillas a las implementaciones estándar del bigote. Sin embargo, en mi caso, los beneficios ofrecidos superaron este problema.

Espero que esto ayude.

  • Eso es realmente genial, me perdí por completo el hecho de que podía “volver a analizar” la plantilla una vez renderizada con una función mía, eso es exactamente lo que haré, creo, tal vez con R.js o algo así. muchas gracias =)

    – mdcarter

    13/10/2011 a las 21:09


avatar de usuario
jvenezia

Basado en la respuesta de @poweratom:

Solo con ember.js lo mismo con las opciones pasadas a I18n.js.

Se volverá a cargar mágicamente si se utilizan propiedades calculadas.

Ember.Handlebars.helper "t", (str, options) ->
  if I18n? then I18n.t(str, options.hash) else str

Modelo:

{{t 'sharings.index.title' count=length}}

Yml:

en:
  sharings:
    index:
      title: To listen (%{count})

La pregunta está respondida, pero puede ser un caso en el que no desee depender de ninguna i8n lib y usar completamente la suya. Estoy usando mi propia inspiración de https://gist.github.com/tracend/3261055

avatar de usuario
presa1

Con NodeJs/Express :

  • nodo-i18n (detectar el encabezado Accept-Language)

      app.use(i18n.init); 
    
  • Ejemplo de archivo de traducción

    {   
     "hello": "hello",   
     "home-page": {
       "home": "Home",
        "signup": "Sign Up"  
     } 
    }
    
  • En el controlador Express

    ...
    data.tr = req.__('home-page');
    var template = Handlebars.compile(source);
    var result = template(data);
    
  • Plantilla de manillares

        <li class="active"><a href="https://stackoverflow.com/">{{tr.home}}</a></li>
    

avatar de usuario
equivalente8

para aquellos que no usan ningún marco JS http://i18next.com parece prometedor también

simplemente cree handlebars helper para llamar a traducciones como aquí http://i18next.com/pages/doc_templates.html

Como ya se estableció, el uso de Handlebars para la internacionalización significa que tendrá que registrar un asistente personalizado para vincular a la biblioteca i18n de su elección. La mayoría de las bibliotecas i18n no tienen este “pegamento” listo para usar, pero es bastante fácil de agregar.

Sobre la base de la respuesta de @poweratom (que a su vez se basa en la de @Glazer), uno puede registrar un ayudante que permite pasar los parámetros del manillar.

Handlebars.registerHelper('i18n',
  function(str){
    return new Handlebars.SafeString((typeof(i18n) !== "undefined" ? i18n.apply(null, arguments) : str));
  }
);

Las respuestas existentes usan otras bibliotecas, pero prefiero http://i18njs.com (npm/roddeh-i18n) porque tiene un mejor soporte para los aspectos más importantes de la internacionalización del lado del cliente, como las reglas gramaticales (y no crea una dependencia de YML y/o Ember, y no requiere del lado del servidor). renderizado con nodejs).

Con el ayudante registrado arriba, podemos agregar traducciones usando solo lado del cliente JSON/JavaScript:

i18n.translator.add({
  "values":{
    "Yes": "はい",
    "No": "いいえ",
    "It is %n": [[0,null,"%nです"]],
    "Do you want to continue?": "続けたいですか?",
    "Don't worry %{name}": "%{name}を心配しないでください",
    "%{name} uploaded %n photos to their %{album} album": "%{name}は彼の%{album}アルバムに写真%n枚をアップロードしました"
  },
  "contexts":[
    {
      "matches": { "gender": "male" },
      "values": { "%{name} uploaded %n photos to their %{album} album": [[0,null,"%{name}は彼の%{album}アルバムに写真%n枚をアップロードしました"]] }
    },
    {
      "matches": { "gender": "female" },
      "values": { "%{name} uploaded %n photos to their %{album} album": [[0,null,"%{name}は彼女の%{album}アルバムに写真%n枚をアップロードしました"]] }
    }
  ]
});

Ahora, cualquier plantilla de manillar que creemos se puede internacionalizar simplemente pasando los parámetros a la biblioteca. Por ejemplo, formatear un número (es decir, “%n”) requiere que el primer parámetro sea la ruta al número. Entonces, para obtener el recuento de un objeto {“count”:3}, podríamos hacer referencia a la ruta “./count” o simplemente “count”. Las coincidencias condicionales requieren que el último parámetro sea la ruta al objeto donde se encontrarán las coincidencias; generalmente solo el objeto raíz “.”.

<script id="messagestemplate" type="text/x-handlebars-template">
  <p>
    {{i18n 'Do you want to continue?'}} {{i18n 'Yes'}}<br>
    {{i18n 'Don\'t worry %{name}' . }}<br>
    {{i18n 'It is %n' count}}<br>
    {{i18n '%{name} uploaded %n photos to their %{album} album' count . .}}
  </p>
</script>

Y finalmente, la plantilla se puede representar de forma normal con Handlebars:

var userData = {
  gender: "male",
  name: "Scott",
  album: "Precious Memories",
  count: 1
};

var templateSource = $("#messagestemplate").html();
var messagesTemplate = Handlebars.compile(templateSource);
var renderedMessages = messagesTemplate(userData);

$('#target-message').html(renderedMessages);

Aquí hay un ejemplo más completo:

// Using http://i18njs.com (npm/roddeh-i18n)

// Includes:
//   cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js
//   rawgit.com/components/handlebars.js/master/handlebars.js
//   cdn.jsdelivr.net/npm/[email protected]/dist/i18n.min.js


// REGISTER I18N HELPER   {{i18n 'Text to translate'}}

Handlebars.registerHelper('i18n',
  function(str){
    return new Handlebars.SafeString((typeof(i18n) !== "undefined" ? i18n.apply(null, arguments) : str));
  }
);


// REGISTER THE TEMPLATE

var templateSource = $("#atemplate").html();
var template = Handlebars.compile(templateSource);

function updateMessage(data) {
  $('#target-message').html(template(data));
}


// ADD TRANSLATIONS

function setLanguage(lang) {
  // Spanish
  if (lang == 'es') {
    i18n.translator.reset();
    i18n.translator.add({
      "values":{
        "Yes": "Si",
        "No": "No",
        "Do you want to continue?": "¿Quieres continuar?",
        "Don't worry %{name}": "No te preocupes %{name}",
        "It is %n": [[0,null,"Es %n"]],
        "%{name} uploaded %n photos to their %{album} album":[
            [0, 0, "%{name} ha subido %n fotos a su album %{album}"],
            [1, 1, "%{name} ha subido %n foto a su album %{album}"],
            [2, null, "%{name} ha subido %n fotos a su album %{album}"]
         ]
      }
    });
  }

  // Japanese
  else if (lang == 'jp') {
    i18n.translator.reset();
    i18n.translator.add({
      "values":{
        "Yes": "はい",
        "No": "いいえ",
        "It is %n": [[0,null,"%nです"]],
        "Do you want to continue?": "続けたいですか?",
        "Don't worry %{name}": "%{name}を心配しないでください",
        "%{name} uploaded %n photos to their %{album} album": "%{name}は彼の%{album}アルバムに写真%n枚をアップロードしました"
      },
      "contexts":[
        {
          "matches":{ "gender":"male" },
          "values":{ "%{name} uploaded %n photos to their %{album} album": [[0,null,"%{name}は彼の%{album}アルバムに写真%n枚をアップロードしました"]] }
        },
        {
          "matches":{ "gender":"female" },
          "values":{ "%{name} uploaded %n photos to their %{album} album": [[0,null,"%{name}は彼女の%{album}アルバムに写真%n枚をアップロードしました"]] }
        }
      ]
    });
  }

  // Default Language (English)
  else {
    i18n.translator.reset();
    i18n.translator.add({
      "values":{
        "Yes": "Yes",
        "No": "No",
        "Do you want to continue?": "Do you want to continue?",
        "Don't worry %{name}": "Not to worry %{name}",
        "It is %n": [[0,null,"It's %n"]],
        "%{name} uploaded %n photos to their %{album} album":[
            [0, 0, "%{name} uploaded %n photos to their %{album} album"],
            [1, 1, "%{name} uploaded %n photo to their %{album} album"],
            [2, null, "%{name} uploaded %n photos to their %{album} album"]
         ]
      }
    });
  }
}


// SET DEFAULT LANGUAGE TO BROWSER/SYSTEM SETTINGS

var browserLanguage = (navigator.languages && navigator.languages[0] || navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage || 'en').split('-')[0];

setLanguage(browserLanguage);


// RENDER THE TEMPLATE WITH DATA

var userData = {
  gender: "female",
  name: "Scott",
  album: "Precious Memories",
  count: 1
};

updateMessage(userData);


// USER-TRIGGERED LANGUAGE SELECTION

// note: the array around browserLanguage is important when setting radio buttons!
$("input[name=lang]")
  .val([browserLanguage])
  .click(
    function() {
      var lang = $('input[name=lang]:checked').val();
      setLanguage(lang);
      updateMessage(userData);
    }
  );
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/i18n.min.js"></script>
<script src="https://rawgit.com/components/handlebars.js/master/handlebars.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>

<h1>i18n with Handlebars</h1>

<label><input type="radio" name="lang" value="en"> English</label><br>
<label><input type="radio" name="lang" value="es"> Espanol</label><br>
<label><input type="radio" name="lang" value="jp"> Japanese</label>

<div id="target-message"></div>

<!--
  NOTE: The helper {{i18n ...}} is just a passthrough for
    the i18n library. Parameters come from the single object
    passed into the handlebars template. Formatting a
    number (i.e. "%n") requires the first parameter to be
    the path to the number.  For example, count from the
    object {"count":3} could be referenced by the path
    "./count" or just "count".  Conditional matches require
    the last parameter to be the path to the object where
    the matches will be found; usually just the root object ".".

    see:
      handlebarsjs paths:   https://handlebarsjs.com/#paths
      i18n formatting:      http://i18njs.com/#formatting
-->

<script id="atemplate" type="text/x-handlebars-template">
  <p>
    {{i18n 'Do you want to continue?'}} {{i18n 'Yes'}}<br>
    {{i18n 'Don\'t worry %{name}' . }}<br>
    {{i18n 'It is %n' count}}<br>
    {{i18n '%{name} uploaded %n photos to their %{album} album' count . .}}
  </p>
</script>

  • El sitio estático para i18njs <i18njs.com> no es compatible con https por alguna razón. Sin embargo, a SO le gusta cambiar todos los enlaces a HTTPS de vez en cuando. Si el enlace está roto, intente volver a http://….

    – Jaime

    12 abr a las 16:10

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad