Marionette.Renderer, Underscore templates and inte

2019-04-29 14:50发布

问题:

We are currently in the need of adding internationalization to a medium sized app using Backbone.Marionette and underscore templates.

After some thorough research, two valid options are emerging :

  • underi18n which provides direct integration with underscore, but lacks pluralization, which becomes essential for supporting more than french and english
  • i18next which provides a powerful API but only direct integration with handlebars templates

We will need on a longer term to localize in many more languages (hopefully!) so underi18n will probably fall short and the only viable solutions then stands out to be i18next.

Before going further, I will outline my two questions and then provide the full context and research process I've went through.

  1. How can I centralize the localization of my templates using i18next and Marionette
  2. How can I inject a global helper into all my underscore templates

Centralize localization of templates

One thing I find very bothersome with i18n is how you have to call it in all your onRender functions, which means adding a call in every of our dozens of current views and all our future views. As I see it, correct me if I'm mistaken, this would look like so :

MyView = Marionette.ItemView.extend({

  template: myUnlocalizedTemplate,

  onRender: function () {
    /* ... some logic ... */
    this.$el.i18n();
  }
  /* And everything else... */
});

and be repeated over and over.

I find this very inconvenient from an implementation and maintenance perspective, so I started digging into Backbone and Marionette, remembering from past project there was some way of globally pre-processing templates.
I stumble upon Marionette.Renderer which seems to be the right tool for the job. But before going into a full installation and implementation of i18next, I want to make sure I'm on the right path.
Because if I can clearly see how underi18n and _.template(under18n.template(myTemplate, t)); could be well integrated with the Renderer and provide me a global solution to pre-process and localize my templates, I'm not so sure about the way to go with i18next in this case.
The fact I couldn't find any example of anyone doing that also worries me, does everyone either go with handlebars templates or manually call .i18n() in each view? At this point, there is no jquery elements on which to bind the translation so I'm pretty puzzled about how this might be possible.

Would be a greatly appreciated answer anything providing an example of what I am trying to accomplish, further documentation or tips on the way to go!

回答1:

I finally found a good way out of this. Here it is, as I hope it can help others in this situation.

/* Some initializer
 * ...
 */

// Init i18n and
// Start the app in the callback
$(function() { 
  i18n.init({
    function (t) {
      App.start();
    }
  });
});

// Following in the initialize:after
// We'll override the default Marionette.Renderer.render function
App.on('initialize:after', function() {
  overwriteRenderer();
});

function overwriteRenderer() {
  // Simply use a closure to close over the current render function
  var render = Marionette.Renderer.render;

  // Then override it
  Marionette.Renderer.render = function (template, data){

    // Extend data to inject our translate helper    
    data = _.extend(data, {_t: i18n.t});

    // And finally return the result of calling the original render function
    // With our injected helper
    return render(template, data);
  };
}


// Then in any template, simply use it as follow
// Do not forget the `=` to output the translation in the final DOM
<div>
  <%= _t("my_key", {options: "my options"} %>
</div>

As shown, I worked it out injecting the translate function into all the template as a view helper. This solution is nice and clean as it delegates most of the translation to the templates, where I believe it should be. It doesn't either require modifying any view, drastically limiting the amount of changes necessary to accomplish the localization on an existing project.

One neat detail as well is you can also inject variable in your interpolation, meaning something like this :

<div>
  <%= myVar %>
</div>

can be turn into something like this, for interpolation

<div>
  <%= _t("my_key", {option: myVar} %>
</div>

Or even

<div>
  <%= _t("my_key", {option: _t(myDynamicKey)} %>
</div>

And in the po file

msgid "my_translation_with_interpolation"
msgstr "My translation with __option__"

Allowing you to dynamically inject a key which will be localized and then interpolated in the original string.

Hope it helps someone else.



回答2:

Another solution if you need access to the view instance in the helpers:

// Global template helpers in Marionette
var _mixinTemplateHelpers = Marionette.View.prototype.mixinTemplateHelpers;
Marionette.View.prototype.mixinTemplateHelpers = function(target) {
    var _this = this;
    target = _.extend({
        // generate a unique id in the view's scope
        _id: function(baseName) {
            return _this.cid + '-' + baseName;
        },
    }, target);
    return _mixinTemplateHelpers.call(this, target);
};