7

我正在开发一个 Grails / Backbone / Handlebars 应用程序,它是一个更大的遗留 Java 系统的前端,其中(出于历史和可定制性的原因)国际化消息深入隐藏在几个 SOAP 服务后面的数据库中,这些服务反过来隐藏在各种内部 Java 库后面。从 Grails 层获取这些消息很容易并且工作正常。

不过,我想知道的是如何将(例如)国际化标签放入我的 Handlebars 模板中。

现在,我正在使用 GSP 片段来生成模板,包括获取我感兴趣的消息的自定义标签,例如:

<li><myTags:message msgKey="title"/> {{title}}</li>

但是,出于性能和代码布局的原因,我想摆脱 GSP 模板并将它们直接转换为 HTML。我已经研究了一些客户端国际化选项,例如i18n.js,但它们似乎取决于我没有得到的消息文件的存在。(我可能会生成它,但它会非常庞大​​且昂贵。)

到目前为止,我能想到的最好的事情是将标签也楔入 Backbone 模型中,所以我最终会得到类似的东西

<li>{{titleLabel}} {{title}}</li>

然而,这确实偏离了在干净的 RESTful JSON API 之上构建 Backbone 模型的理想——要么由 RESTful 服务返回的 JSON 被表示数据(即本地化标签)混杂在一起,要么我必须做一些额外的工作来将标签注入到 Backbone 模型中——并且用演示数据混淆 Backbone 模型似乎也是错误的。

我认为,就干净的数据和干净的 API 而言,我想做的是编写另一个 RESTful 服务,该服务采用消息键和类似列表,并返回包含所有本地化消息的 JSON 数据结构。然而,问题仍然存在:

  1. 指示(可能在模板中)给定视图需要哪些消息键的最佳方法是什么?
  2. 数据的正确格式是什么?
  3. 如何将本地化消息放入 Backbone 视图?
  4. 是否有任何现有的 Javascript 库可以提供帮助,或者我应该开始制作东西吗?
  5. 有没有更好/更标准的替代方法?
4

2 回答 2

3

我认为您可以通过结合 Handelbars 助手和一些正则表达式来创建一个非常优雅的解决方案。

以下是我的建议:

  1. 创建一个接收消息键的 JSON 数组并返回 JSON 对象的服务,其中键是消息键,值是本地化文本。
  2. 定义一个 Handlebars 助手,它接收消息键(与服务器上的消息键匹配)并输出翻译后的文本。类似的东西{{localize "messageKey"}}。使用此助手进行所有模板本地化。
  3. 编写一个模板预处理器,它从模板中提取消息键并请求您的服务。预处理器缓存它获得的所有消息键,并且只请求它还没有的那些。
    • 您可以在需要渲染模板时按需调用此预处理器,也可以预先调用它并缓存消息键,以便在您需要它们时准备好它们。
    • 为了进一步优化,您可以将缓存持久化到浏览器本地存储中。

这是一个小概念证明。它还没有本地存储持久性或支持一次获取多个模板的文本以进行缓存,但它很容易组合在一起,我认为通过进一步的工作它可以很好地工作。

客户端 API 可能如下所示:

var localizer = new HandlebarsLocalizer();

//compile a template
var html = $("#tmpl").html();
localizer.compile(html).done(function(template) { 
  //..template is now localized and ready to use 
});

这是懒惰的读者的来源:

var HandlebarsLocalizer = function() {

  var _templateCache = {};
  var _localizationCache = {};

  //fetches texts, adds them to cache, resolves deferred with template
  var _fetch = function(keys, template, deferred) {
    $.ajax({
      type:'POST',
      dataType:'json',
      url: '/echo/json',
      data: JSON.stringify({
        keys: keys 
      }),
      success: function(response) {
        //handle response here, this is just dummy
        _.each(keys, function(key) { _localizationCache[key] = "(" + key + ") localized by server";  });
        console.log(_localizationCache);
        deferred.resolve(template);
      },
      error: function() {
        deferred.reject();
      }
    });
  };

  //precompiles html into a Handlebars template function and fetches all required
  //localization keys. Returns a promise of template.
  this.compile = function(html) {

    var cacheObject = _templateCache[html],
        deferred = new $.Deferred();

    //cached -> return
    if(cacheObject && cacheObject.ready) { 
      deferred.resolve(cacheObject.template);
      return deferred.promise();
    }
    //grep all localization keys from template
    var regex = /{{\s*?localize\s*['"](.*)['"]\s*?}}/g, required = [], match;
    while((match = regex.exec(html))) {
      var key = match[1];
      //if we don't have this key yet, we need to fetch it
      if(!_localizationCache[key]) {
        required.push(key);
      }
    }

    //not cached -> create
    if(!cacheObject) {
      cacheObject = {
        template:Handlebars.compile(html),
        ready: (required.length === 0)
      };   
      _templateCache[html] = cacheObject;
    }

    //we have all the localization texts ->
    if(cacheObject.ready) {
      deferred.resolve(cacheObject.template);
    } 
    //we need some more texts ->
    else {
      deferred.done(function() { cacheObject.ready = true; });
      _fetch(required, cacheObject.template, deferred);    
    }

    return deferred.promise();
  };

  //translates given key
  this.localize = function(key) {
    return _localizationCache[key] || "TRANSLATION MISSING:"+key;
  };

  //make localize function available to templates
  Handlebars.registerHelper('localize', this.localize);
}
于 2013-01-08T02:52:20.540 回答
2

我们在 Backbone/Handlebars 应用程序中使用http://i18next.com进行国际化。(还有 Require.js,它也通过插件加载和编译模板。)

i18next 可以配置为动态加载资源。它支持 gettext 格式的 JSON(支持复数和上下文变体)。他们页面中有关如何加载远程资源的示例:

var option = { 
  resGetPath: 'resources.json?lng=__lng__&ns=__ns__',
  dynamicLoad: true 
};

i18n.init(option);

(您当然需要更多配置,例如设置语言、备用语言等)

然后,您可以配置一个 Handlebars 助手,在提供的变量上调用 i18next(最简单的版本,没有复数,没有上下文):

// namespace: "translation" (default)
Handlebars.registerHelper('_', function (i18n_key) {
    i18n_key = Handlebars.compile(i18n_key)(this);
    var result = i18n.t(i18n_key);
    if (!result) {
        console.log("ERROR : Handlebars-Helpers : no translation result for " + i18n_key);
    }
    return new Handlebars.SafeString(result);
});

在您的模板中,您可以提供一个扩展为键的动态变量:

<li>{{_ titleLabeli18nKey}} {{title}}</li>

或直接指定密钥:

<li>{{_ "page.fancy.title"}} {{title}}</li>

对于日期时间的本地化,我们使用http://momentjs.com(转换为本地时间、格式、翻译等)。

于 2013-07-18T16:19:46.037 回答