I set out to explore in a few examples how we could improve Firefox OS's interface with L20n. How can I create more natural-sounding translations?

This post documents a few opportunities to improve the localization of Gaia (the UI layer of Firefox OS) by using L20n. In all presented examples, I try to show the existing code, explain the problem from the localizers' or developers' perspective, and suggest a solution.

Declensions

In Gaia's Browser app, there's an about-browser string defined as follows in apps/browser/locales/browser :

about-browser=About {{browserBrandShortName}}

Problem definition

In many inflected languages (e.g. German, Finnish, Hungarian, all Slavic languages), the about preposition governs the grammatical case of the complement. It might be the accusative (German), ablative (Latin) or locative (Slavic languages). Let's take Slovenian as an example.

In Slovenian, the ideal string would inflect the noun, like so:

O Firefoxu

However, since we want the name of the browser to be stored in the browserBrandShortName entity, we can't modify it. The work-around is to inflect an auxiliary noun complement, e.g. browser, to give About the Firefox browser. Needless to say, this ends up being long and often unnaturally-sounding to the native speakers.

about-browser=O brskalniku {{browserBrandShortName}}

Solution

In L20n, this problem can be easily solved by defining multiple variants of the browserBrandShortName entity, to match different grammatical cases of the noun.

In shared/branding/official/branding , we can define browserBrandShortName as:

<browserBrandShortName { nominative : "Firefox" , genitive : "Firefoxa" , dative : "Firefoxu" , accusative : "Firefox" , locative : "Firefoxu" , instrumental : "Firefoxom" } >

And in shared/branding/unofficial/branding , we can define browserBrandShortName as follows, to mean Web Browser:

<browserBrandShortName { nominative : "Brskalnik" , genitive : "Brskalnika" , dative : "Brskalniku" , accusative : "Brskalnik" , locative : "Brskalniku" , instrumental : "Brskalnikom" } >

Now, coming back to apps/browser/locales/browser , we can take advantage of the grammatical cases. Slovenian uses the locative case with its About preposition:

<aboutBrowser "O {{ browserBrandShortName.locative }}">

For the official branding, we get:

O Firefoxu

And for the unofficial branding, we end up with:

O Brskalniku

Genders

In the System app's apps/system/locales/system , there's a string called crash-banner-os2 .

crash-banner-os2={{brandShortName}} just crashed.

Problem definition

In some languages, past participles (crashed) must be accorded with the gender of the subject (in case of reflexive verbs) or the gender of the object.

In Polish, the grammatical form of crashed depends on the gender of brandShortName . In order to construct a grammatically-correct and naturally-sounding message, we must know the gender of the subject.

Solution

In L20n, the localizer can define the gender (or, in fact, any arbitrary trait) as an attribute of the entity.

For Polish, we can define brandShortName in shared/branding/official/branding as:

<brandShortName "Firefox OS" _gender : "masculine" >

And in shared/branding/unofficial/branding , as:

<brandShortName "Boot2Gecko" _gender : "neutral" >

Now we can translate crash-banner-os2 into Polish without sounding like a robot:

<crashBannerOS2 [ brandShortName ::_gender ] { masculine : "{{ brandShortName }} uległ awarii" , feminine : "{{ brandShortName }} uległa awarii" , neutral : "{{ brandShortName }} uległo awarii" } >

This will give us, depending on the current branding, the following messages:

Firefox OS uległ awarii Boot2Gecko uległo awarii

Isolation

Let's look at how the Settings app formats sizes. First, there is DeviceStorageHelper.showFormatedSize (sic):

function showFormatedSize ( element , l10nId , size ) { if ( size === undefined || isNaN ( size )) { element . textContent = '' ; return ; } // KB - 3 KB (nearest ones), MB, GB - 1.2 MB (nearest tenth) var fixedDigits = ( size < 1024 * 1024 ) ? 0 : 1 ; var sizeInfo = FileSizeFormatter . getReadableFileSize ( size , fixedDigits ); var _ = navigator . mozL10n . get ; element . textContent = _ ( l10nId , { size : sizeInfo . size , unit : _ ( 'byteUnit-' + sizeInfo . unit ) }); }

The function is used like so:

// Application Storage updateAppFreeSpace : function storage_updateAppFreeSpace () { var self = this ; this . getFreeSpace ( this . appStorage , function ( freeSpace ) { DeviceStorageHelper . showFormatedSize ( self . appStorageDesc , 'availableSize' , freeSpace ); }); },

Problem definition

For all values of freeSpace , the following string is enough to construct a grammatically-correct sentence in English:

availableSize = {{size}} {{unit}} available

However, other languages might need to pluralize this string with different forms of the available adjective. Consequently, using the existing localization frameworks, the developer needs to predict which strings might need pluralization in other languages, and pluralize them even in English:

availableSize = {[ plural(size) ]} availableSize[other] = {{size}} {{unit}} available

An Italian translation might look like this:

availableSize = {[ plural(size) ]} availableSize[one] = {{size}} {{unit}} disponibile availableSize[other] = {{size}} {{unit}} disponibili

It's easy to imagine how developers might forget to pluralize some strings that in other languages might require pluralization (or other grammatical features that the framework supports).

Solution

L20n isolates each language so that grammatical requirements of one don't affect others. This takes the responsibility away from the developers and puts it in the hands of localizers, at the same time transforming it from a burden into an opportunity.

In L20n, the English string could be as simple as:

<availableSize "{{ $size }} {{ $unit }} available">

On the other hand, the Italian translation would make use of proper pluralization rules:

<plural ( $n ) { $n == 1 ? "one" : "other" } > <availableSize [ plural ( $size )] { one : "{{ $size }} {{ $unit }} disponibile" , other : "{{ $size }} {{ $unit }} disponibili" } >

So far, so good. The JavaScript code hasn't changed yet, but there's another improvement that can be introduced, which is described below.

Asymmetry

The example above could benefit from another of L20n's features: local variables. In L20n, localization files can be asymmetrical and have more entities that the source language.

You'll notice that DeviceStorageHelper.showFormatedSize passes a localized name of the unit to availableSize :

function showFormatedSize ( element , l10nId , size ) { // … var _ = navigator . mozL10n . get ; element . textContent = _ ( l10nId , { size : sizeInfo . size , unit : _ ( 'byteUnit-' + sizeInfo . unit ) }); }

Problem definition

Even though there's no need to localize the units in English at all, we still need to do it, because in other languages we might need to use localized names.

byteUnit-B = B byteUnit-KB = KB byteUnit-MB = MB byteUnit-GB = GB byteUnit-TB = TB

For example in French, the unit abbreviations are as follows:

byteUnit-B = o byteUnit-KB = Ko byteUnit-MB = Mo byteUnit-GB = Go byteUnit-TB = To

Solution

In L20n, the French localizer could provide the translation of unit abbreviations locally in the localization file, without impacting the English localization file.

In the JavaScript code, the developer needs to pass sizeInfo.unit instead of a localized value:

function showFormatedSize ( element , l10nId , size ) { // … element . textContent = document . l10n . get ( l10nId , { size : sizeInfo . size , unit : sizeInfo . unit }); }

And then use the $unit variable verbatim in the English message:

<availableSize "{{ $size }} {{ $unit }} available">

In French, the localizer can then use the value of $unit to match it against a translated abbreviation, like so:

<_uniteDeMesure { B : "o" , KB : "Ko" , MB : "Mo" , GB : "Go" , TB : "To" } > <availableSize "Il reste {{ $size }} {{ _uniteDeMesure[$unit] }}" >

Similarly, other local variables could be used to provide better and more natural translations in French and other languages alike.

Learn more about L20n

You can find out more about L20n at: