Monday, December 23, 2013

Dart Intl.message (and Polymer)


I have to confess that I am still a little confused by the intl package in Dart. I think that last night's solution for localizing Polymer elements contains pretty much what I need without making any use of the intl package. But I know there must be useful functionality in intl that I am overlooking so tonight I set out to see if I can understand it.

Part of my problem is, I think, a forest through the trees situation. The examples in the source code contain a lot of library lookup and base classes that I do not quite follow. Perhaps I can adapt my existing code to the Dart Way™.

I currently have maps of label values:
class Labels {
  // ...
  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!'
  };
}
I can use a locale string to chose one of those maps at runtime to be interpolated into my Polymer:
<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>
        {{instructions}}
      </p>
    </div>
  </template>
  <script type="application/dart" src="hello_you.dart"></script>
</polymer-element>
So where do those composite message classes come into play? In the spirit of forest / trees, I am going to look at two trees really close and move on from there.

The two trees in questions are Intl.message() and Intl.plural(). The latter lets me return different messages based on a number supplied. In my case, I will supply the count from the Polymer. The other tree, er… method “returns a message that can be internationalized.” Using the count from the Polymer, I return the number of red balloons in my possession, in French:

  // ...
  static Map get fr => {
    'hello': 'Bonjour',
    'done': 'Fin',
    'how_many': 'Combien?',
    'instructions': 'Présentez-vous une expérience personnalisée incroyable!',
    'count_message': (count) =>
      Intl.message(
      '''${Intl.plural(count,
             zero: "Je n'ai pas de ballons rouge",
             one: "J'ai un ballon rouge",
             other: "J'ai $count ballons rouge")}.''')
  };
  // ...
And that more or less does the trick. I can bind a button to an updateCount() method:
<polymer-element name="hello-you">
  <template>
      <!-- ... -->
      <p>
        {{how_many}}
        <input value="{{count}}">
        <input type=submit value="{{done}}!" on-click="{{updateCount}}">
      </p>
      <p>
        {{count_message}}
      </p>
      <!-- ... -->
  <script type="application/dart" src="hello_you.dart"></script>
</polymer-element>
And my backing method can update the count_message string:
@CustomTag('hello-you')
class HelloYou extends PolymerElement {
  // ...
  @observable int count;
  @observable String count_message;
  // ...
  updateCount() {
    count_message = _labels['count_message'](int.parse(count));
  }
}
The result is that I can have 99 red balloons in French:



And I can have 99 English balloons:



In order to get the latter, I need to duplicate the Intl.message() in English:
// ...
  static Map get en => {
    // ...
    'count_message': (count) =>
      Intl.message(
      '''${Intl.plural(count,
             zero: "I have no red balloons",
             one: "I have one red balloon",
             other: "I have $count red balloons")}.''')
  };
  // ...
I think that is proper use of Intl.plural, but I have not quite got Intl.message() right. Ultimately, I would like to either be able to set the default locale of my Polymer at runtime to choose the correct version of a “count message” function. My current approach kinda / sorta does that, but there is an actual Intl.defaultLocale property that I am currently not using at all.

In order to do so, I at least need to supply the name parameter to Intl.message(). I am still somewhat vague on how that fits into the grander scheme of intl and its locale lookups, but hopefully that will become clearer as I continue to explore. Tomorrow.

Day #974

6 comments:

  1. The correct spelling :
    zero: "Je n'ai pas de ballon rouge",
    one: "J'ai un ballon rouge",
    other: "J'ai $count ballons rouges"

    ReplyDelete
    Replies
    1. Thanks! I should have know the “other” one. I was 50/50 on the “zero” one being singular, but Google translate was pretty sure it was plural. Anyway, I've got it fixed now, so future screenshots won't be quite so obviously written by a dumb American. Yay!

      Delete
  2. I've had similar experiences with the Intl package. The indirection in the examples makes them hard to follow, and there isn't an overall explanation of what the library does. I sent a G+ message to Seth Ladd asking for some better tutorials, but haven't gotten a response yet.

    I'm no expert in i18n, but I think the Intl lib is trying to be all things to all people. It apparently provides string interpolation, but it isn't clear how to use it. It seems to also provide localized date/time and currency formatting, but it isn't clear how to use that either. Same with plurality and text direction.

    ReplyDelete
    Replies
    1. Thanks for the info. Glad it's not just me that is struggling with it! I've got the plural working above (and I think currency and dates would be similar). I just don't see how they all fit together. But I'll give it a go tonight :)

      Delete