Staś Małolepszy

Default values and local entities and attributes in L20n

Another set of syntax changes will make L20n more robust by improving error recovery and make it easier for localizers to create local helper entities and macros.

The last batch of syntax changes planned for the 1.0 release are two new prefixes which denote default values in a hash (*) and indicate the scope (public vs. local) of entities and attributes (_).

Default hash values

Let's take a fairly simple entity with a hash value and let's reference it in two other entities.

    
    <brandName {
      nominative: "Firefox",
      genitive: "Firefox's"
    }>

    <about1 "About {{ brandName.nominative }}">
    <about2 "About {{ brandName }}">
    

While about1 produces About Firefox, it's unclear what about2 should evaluate to. It references the whole entity whose value is a hash, but expects it to return a string.

With the new feature in L10n called default hash values, it takes a smal change to fix this. Notice the addition of the asterisk (*) prefix in the snippet below.

    
    <brandName {
     *nominative: "Firefox",
      genitive: "Firefox's"
    }>

    <about1 "About {{ brandName.nominative }}">
    <about2 "About {{ brandName }}">  // not a problem anymore!
    

Now, about2 evaluates to About Firefox. Thanks to the asterisk in *nominative, you can safely reference brandName without any particular key specified and still be sure to receive a string in the end.

Default hash values can also be used to reduce redundancy. In my previous post, I showed how to take advantage of globals to create OS-specific variants of the same entity.

The example was slightly too verbose for my taste, though. Note how mac and lin are exactly the same.

    
    <syncConnect[@os] {
      win: "Select “Pair a device” in your desktop Firefox options.",
      mac: "Select “Pair a device” in your desktop Firefox preferences.",
      lin: "Select “Pair a device” in your desktop Firefox preferences."
    }>
    

We can fix this, again, by making the Mac & Linux variant of the translation the default one.

    
    <syncConnect[@os] {
      win: "Select “Pair a device” in your desktop Firefox options.",
     *nix: "Select “Pair a device” in your desktop Firefox preferences.",
    }>
    

If @os equals win, the first variant will be chosen. Otherwise, regardless of the value of @os, syncConnect will evaluate to the second variant, corresponding to the nix hash key.

Public and local scope

Another new feature in L20n is the concept of local entities, macros and attributes, denoted with the underscore prefix (_).

In short: local objects cannot be accessed by the developer via the L20n API and will not appear in reports prepared by the L20n toolchain (diffing and updating tools) -- otherwise they would always show up as obsolete, because they're missing from the source locale.

Background

Prior to this change, all entities, macros and attributes could be accessed by the developer. The developer could make a call to the L20n API and ask for the localized value of the brandName variable, or the accesskey attribute of the syncConnect label. All objects were thus public.

Consider the implications of this statement. The developer can safely assume that in a 100% complete localization, these public objects will be present. After all, if they were not, the localization wouldn't be 100% complete.

How does one know if a localization is 100% complete, or for that matter, 20% or 50% complete? In the L20n workflow, there will be tools to achieve the most common tasks, like:

  • bootstrapping a new locale based on the source locale,
  • comparing (diffing) the target locale with the source locale, and
  • updating (syncing) the target locale to make it on the par with the source locale.

These tools will be able to tell, when diffing, that there have been new entites added to the source locale, or old ones removed. If an entity is present in the target locale, but is missing from the source locale, the tools will report it as obsolete, which should tell the localizer that she needs to remove the entity in question.

This workflow effectively means that the localizer cannot create her own helper entites and macros, which may store interesting information about the locale. For example, the localizer could create a helper entity for the word Tab, which stores all the declensions of the word in a single place.

We really wanted to let localizers create their own entities and macros, and not be limited to the ones present in the source locale.
And we came up with the concept of local objects, denoted with the underscore prefix (_foo), to make this work with our intended worklfow and toolchain.

Example: a local entity

The Polish word for Cancel is, on all platforms today, Anuluj. Back in the old days, however, Mac OS would use a different translation, Poniechaj. Let's imagine for the sake of this example, that this is still the case and that we need to use a different word for Cancel in the Polish localization, depending on the user's operating system.

It's certainly possible when using just public entities, but admittedly, not very elegant. (Also, note the use of the asterisk to denote the default value.)

    
    <installCancel[@os] {
     *default: "Anuluj",
      mac: "Poniechaj"
    }>
    <syncConnectCancel[@os] {
     *default: "Anuluj",
      mac: "Poniechaj"
    }>
    

Instead of putting the @os index on each entity, we can create a local helper entity with the necessary logic and reference it in each affected public entity.

    
    <_cancel[@os] {
     *default: "Anuluj",
      mac: "Poniechaj"
    }>
    <installCancel "{{ _cancel }}">
    <syncConnectCancel "{{ _cancel }}">
    <saveFileCancel "{{ _cancel }}">
    <printDialogCancel "{{ _cancel }}">
    

Since the _cancel entity is local, it will not affect the reports produced by the diffing tools.

Example: a local macro

Let's imagine a locale in which there's a simple and strict rule that says that you must use the formal register from Monday through Friday, and the informal register on weekends.

    
    <_register() { @dayOfWeek == "Sat" || @dayOfWeek == "Sun" ? "informal" : "formal" }>
    <feelGood[_register()] {
      formal: "You, sir, are a genius!",
      informal: "Dude, you rock!"
    }>
    

The localizer defines a _register macro which stores the logic of the register rule. The identifier of the macro is prefixed with the underscore which tells tools to skip this macro in the reports.
Because there is no such macro in en-US (and assuming that en-US is the source locale for this translation), tools will not complain about the _register macro being missing from en-US. The macro is local to the target locale.

Example: a local attribute

For this last example, let's take a look at the brandName entity which in Polish we'll extend with aditional metadata.

First, en-US:

    
    <brandName "Aurora"
      title: "An experimental update channel">
    

And the Polish translation, with the additional, local _gender attribute.

    
    <brandName "Aurora"
     _gender: "female"
      title: "Eksperymentalny kanał aktualizacji">
    

The first implication of the fact that the _gender attribute is local is that tools will not report it as obsolete when comparing the Polish localization with the English one.

There also is another consequence to this fact. L20n's bindings for XML/DOM will skip local attributes when populating the value and the attributes of the DOM element corresponding to the entity.

And so, for the following raw HTML...

    
    <h1 l10n-id="brandName"></h1>
    

...the L20n library will inject the text value of the brandName entity into the DOM element and add the title attribute to it, thus producing the following HTML:

    
    <h1 l10n-id="brandName" 
        title="An experimental update channel">
        Aurora
    </h1>
    

As you can see, the _gender attribute has been ignored.

Underscore prefix in hash keys has no special meaning

A final note: the underscore prefix only has a special meaning when used in names of entites, macros and attributes. When used in hash keys names, it loses the special meaning.

    
    <brandName {
     _nominative: "Firefox",
     _genitive: "Firefox's"
    }>
    

_nominative and _genitive are allowed names for hash keys, although are not recommended, so as to avoid confusion.

Discussion

Please post your thoughts and questions in the l20n newsgroup.

Published on 11.05.2012
Permalink: http://informationisart.com/6

Staś Małolepszy

Thoughts about the Internet, the information society, Mozilla and human-computer interactions.

Latest notes