Sunday, December 22, 2013

A Slightly Better i18n Polymer


Last night, I got application-wide localization working in my Polymer application. It is not real internationalization and localization—it only allows for a single application-wide localization, but it is a start. Tonight, I hope to use some of Dart's intl package to come up with something better. Specifically, I would like to be able to dynamically change the locale at runtime.

There is an example of Polymer internationalization in the Dart repository. I am not quite following along with that, but it does prove invaluable.

Yesterday's solution relied on attributes and <content> tags to include information from other locales:
      <hello-you hello="Bonjour" done="Fin">
        <p>Présentez-vous une expérience personnalisée incroyable!</p>
      </hello-you>
Those attributes and <content> are then included in the template as:
<polymer-element name="hello-you">
  <template>
    <div>
      <h2>{{hello}} {{your_name}}</h2>
      <p>
        <input value="{{your_name}}">
        <input type=submit value="{{done}}!" on-click="{{feelingLucky}}">
      </p>
      <p class=instructions>
        <content></content>
      </p>
    </div>
  </template>
  <script type="application/dart" src="hello_you.dart"></script>
</polymer-element>
As I mentioned, the attributes like hello and done have to to hard-coded in the page for this approach to work. Instead, I want those properties to be dynamic getters.

In the end, I do not even use the intl package, which seems useful for strings requiring parameters (numbers or dates). I find that I can achieve what I want with a Labels class like:
class Labels {
  var locale;
  Labels(this.locale);

  operator [](label) {
    var lookup;
    switch (locale) {
      case 'fr' : lookup = fr; break;
      default: lookup = en;
    }

    return lookup[label];
  }

  static Map get en => {
    'hello': 'Hello',
    'done': 'Done',
    'instructions': 'Introduce yourself for an amazing personalized experience!'
  };

  static Map get fr => {
    'hello': 'Bonjour',
    'done': 'Fin',
    'instructions': 'Présentez-vous une expérience personnalisée incroyable!'
  };
}
The localized labels are included as static methods. An instance of the Labels class provides an [] lookup, switching the labels based on the current locale.

The various getters in the Polymer can then be written as lookups of Labels:
@CustomTag('hello-you')
class HelloYou extends PolymerElement {
  // ...
  @published String locale = 'en';

  HelloYou.created(): super.created();

  String get hello => _labels['hello'];
  String get done => _labels['done'];
  String get instructions => _labels['instructions'];

  var __labels;
  get _labels {
    if (__labels == null) __labels = new Labels(locale);
    return __labels;
  }
  // ...
}
That still will not quite work if I wanted to allow a human to change the locale on the fly, but it allows the Polymer to change locales with a simple attribute:
<hello-you locale="fr"></hello-you>
Which results in:



I will pick back up tomorrow trying to allow a human to hot swap locales. I would also like to come up with a contrived example to use for intl messages. I do not think that I will end up using them in Patterns in Polymer (there's not an easy JavaScript equivalent), but I misunderstood them badly enough tonight to confuse myself into thinking that I needed them. This seems a fine chance to understand what they really can do.


Day #973

2 comments:

  1. I played with this a while ago and like the approach using filters http://stackoverflow.com/questions/20323815/how-to-initialize-internationalization-for-a-polymer-element-before-it-loads-on/20377702#20377702

    ReplyDelete
    Replies
    1. That is pretty nice. The filters make the Polymer template self-documenting for which fields get i18n. I'll give that a try in a day or two. Thanks!

      Delete