如何使用Handlebars.js(小胡子模板)制作i18n?

时间:2011-10-13 20:57:33

标签: javascript jquery internationalization handlebars.js mustache

我目前正在使用Handlebars.js(与Backbone和jQuery相关联)来制作几乎完全由客户端渲染的Web应用程序,而且我在使用此应用程序的国际化时遇到了问题。

我该如何做到这一点?

有插件吗?

7 个答案:

答案 0 :(得分:83)

我知道这已经得到了解答,但我想分享我的简单解决方案。为了使用I18n.js(我们在工作中使用我们的项目时)使用Gazler的解决方案,我只使用了一个非常简单的Handlebars帮助程序来促进流程本地进行本地化:

<强>处理程序

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

<强>模板

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

这样做的主要优点是整个json对象上没有昂贵的前/后处理。更不用说传入的json是否有嵌套的对象/数组,如果对象很大,花在寻找和解析它们上面的时间可能会很昂贵。

干杯!

答案 1 :(得分:5)

https://github.com/fnando/i18n-js是一个ruby gem,它将从config / locales文件夹创建一个国际化文件。但是,如果您不使用rails,则可以找到自己使用的javascript here

然后,您只需将翻译存储在嵌套对象中。

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

我在项目中使用的对你来说也有用的东西是小胡子的补丁,它以@@ translation_key @@

格式自动翻译字符串
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;
};

然后在渲染后调用i18nize ,以允许您将翻译放入模板中,而不是将其传递过来。

小心修补小胡子,因为您无法将模板移植到标准小胡子实现。但在我的情况下,所提供的好处超过了这个问题。

希望这有帮助。

答案 2 :(得分:4)

基于@poweratom的回答:

仅限 ember.js ,与传递给I18n.js的选项相同。

如果使用计算属性,它会神奇地重新加载。

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

模板:

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

YML:

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

答案 3 :(得分:1)

使用 NodeJs / Express

  • node-i18n(检测Accept-Language标头)

      app.use(i18n.init); 
    
  • 示例翻译文件

    {   
     "hello": "hello",   
     "home-page": {
       "home": "Home",
        "signup": "Sign Up"  
     } 
    }
    
  • 在Express控制器中

    ...
    data.tr = req.__('home-page');
    var template = Handlebars.compile(source);
    var result = template(data);
    
  • 把手模板

        <li class="active"><a href="/">{{tr.home}}</a></li>
    

答案 4 :(得分:1)

问题已得到解答但是他们可能不希望依赖任何i8n lib并完全使用你自己的。我使用自己的灵感来自https://gist.github.com/tracend/3261055

答案 5 :(得分:0)

对于那些不使用任何JS框架的人来说http://i18next.com看起来很有希望

只需创建手柄助手即可调用此处http://i18next.com/pages/doc_templates.html

等翻译

答案 6 :(得分:0)

正如已经建立的那样,使用Handlebars进行国际化意味着您将必须注册一个自定义帮助程序以链接到您选择的i18n库。大多数i18n库都没有开箱即用的“胶水”,但是添加起来很容易。

以@poweratom的答案为基础(反过来以@Glazer的答案为基础),可以注册一个允许传递Handlebars参数的助手。

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

现有答案使用其他库,但我更喜欢i18njs.com(npm / roddeh-i18n),因为它对客户端国际化的更重要方面(如语法规则)提供了更好的支持(但它没有创建对YML和/或Ember的依赖关系,并且不需要使用nodejs进行服务器端渲染。

使用上面注册的帮助程序,我们可以仅使用客户端 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枚をアップロードしました"]] }
    }
  ]
});

现在,我们创建的任何车把模板都可以通过传递参数到库来进行国际化。例如,格式化数字(即“%n”)要求第一个参数是数字的路径。因此,要从对象{“ count”:3}获取计数,我们可以引用路径“ ./count”或仅引用“ count”。条件匹配要求最后一个参数是找到匹配对象的路径。通常只是根对象“。”。

<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>

最后,模板可以使用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);

这是一个更完整的示例:

// 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/roddeh-i18n@1.2.0/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/roddeh-i18n@1.2.0/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>