This article documents a few opportunities to improve the localization of Gaia (the UI layer of Firefox OS) by using L20n. In all presented examples, we 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:
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.