Thursday, December 26, 2013

A Possible Approach to Polymer i18n


I think someone's wrong and it may be a Dart library. But it's probably me.

I continue to explore options for localization solutions in Polymer. Yesterday's bright idea includes having web developers provide JSON localizations with the same basename as the corresponding Polymer along with a locale:
    <link rel="import" href="packages/i18n_example/hello-you.html">
    <link rel="import" href="packages/i18n_example/hello-you-en.json">
    <link rel="import" href="packages/i18n_example/hello-you-fr.json">
    <link rel="import" href="packages/i18n_example/hello-you-es.json">
My assumption was that Polymer would polyfill the <link> import tag, making the referenced data available programatically. Only it did not work in the Dart version of Polymer (at least with a pub transformer present). Rather than banging my head against the proverbial wall, I quickly shifted to a JavaScript implementation, which worked. Well, the import seemed to work. Today, I try to actually use the local data.

This takes a bit of digging, but my Polymer ends up looking like this:
    Polymer('hello-you', {
      hello: 'Hello',
      done: 'Done',
      your_name: '',
      locale: 'en',
      count: null,
      count_message: '',

      locales: {},

      ready: function() {
        var that = this;
        var list = this.ownerDocument.
              querySelectorAll('link[rel=import]').
              array().
              filter(function(el) {
                return el.href.match(/hello-you-\w+.json/)
              });

        var re = /hello-you-(\w+).json/;
        list.forEach(function(link) {
          var result = re.exec(link.href);
          var json = link.import.textContent.replace(/import/, '');
          that.locales[result[1]] = JSON.parse(json);
        });

        for (var prop in this.locales[this.locale]) {
          this[prop] = this.locales[this.locale][prop];
        }
      },
      // ...
    });
The ready() is where the action takes place. The first thing that it does is find all of the import <link> elements in the containing document:
        var list = this.ownerDocument.
              querySelectorAll('link[rel=import]').
              array().
              filter(function(el) {
                return el.href.match(/hello-you-\w+.json/)
              });
With that in hand, it builds up the list of known locales by parsing the JSON that is imported. For some reason, the textContent includes the text “import” in the JSON. For instance the French JSON locale textContent looks like:
import{
  "hello": "Bonjour",
  "done": "Fin",
  "how_many": "Combien?",
  "instructions": "Présentez-vous une expérience personnalisée incroyable!"
}
So I have to strip the word “import” to get actual JSON, which can then be parsed. The locale is embedded in the URL in the convention that I am using here. Using that, I can populate a map of locale data:
        var re = /hello-you-(\w+).json/;
        list.forEach(function(link) {
          var result = re.exec(link.href);
          var json = link.import.textContent.replace(/import/, '');
          that.locales[result[1]] = JSON.parse(json);
        });
Finally, given a locale for the Polymer, I can work through each of the properties from the JSON data to set the Polymer property of the same name:
        for (var prop in this.locales[this.locale]) {
          this[prop] = this.locales[this.locale][prop];
        }
With that, I can specify the locale from the Polymer attribute and have a locale specific view:



Nice! This isn't a full-fledge internationalization solution by any stretch. I have no support for dates and pluralization. Still, this seems pretty reasonable basic implementation that builds on some of the same stuff of which Polymer is made. I really need to get this working in Dart, though for any part of it to make it into Patterns in Polymer. So I will switch back to that implementation tomorrow.


Day #977

No comments:

Post a Comment