1.. role:: html(code)
2   :language: html
3
4.. role:: js(code)
5   :language: javascript
6
7=============================
8Fluent for Firefox Developers
9=============================
10
11
12This tutorial is intended for Firefox engineers already familiar with the previous
13localization systems offered by Gecko - `DTD`_ and  `StringBundle`_ - and assumes
14prior experience with those systems.
15
16
17Using Fluent in Gecko
18=====================
19
20`Fluent`_ is a modern localization system introduced into
21the Gecko platform with a focus on quality, performance, maintenance and completeness.
22
23The legacy DTD system is deprecated, and Fluent should be used where possible.
24
25Getting a Review
26----------------
27
28If you work on any patch that touches FTL files, you'll need to get a review
29from `fluent-reviewers`__. There's a Herald hook that automatically sets
30that group as a blocking reviewer.
31
32__ https://phabricator.services.mozilla.com/tag/fluent-reviewers/
33
34Guidelines for the review process are available `here`__.
35
36__ ./fluent_review.html
37
38Major Benefits
39==============
40
41Fluent `ties tightly`__ into the domain of internationalization
42through `Unicode`_, `CLDR`_ and `ICU`_.
43
44__ https://github.com/projectfluent/fluent/wiki/Fluent-and-Standards
45
46More specifically, the most observable benefits for each group of consumers are
47
48
49Developers
50----------
51
52 - Support for XUL, XHTML, HTML, Web Components, React, JS, Python and Rust
53 - Strings are available in a single, unified localization context available for both DOM and runtime code
54 - Full internationalization (i18n) support: date and time formatting, number formatting, plurals, genders etc.
55 - Strong focus on `declarative API via DOM attributes`__
56 - Extensible with custom formatters, Mozilla-specific APIs etc.
57 - `Separation of concerns`__: localization details, and the added complexity of some languages, don't leak onto the source code and are no concern for developers
58 - Compound messages link a single translation unit to a single UI element
59 - `DOM Overlays`__ allow for localization of DOM fragments
60 - Simplified build system model
61 - No need for pre-processing instructions
62 - Support for pseudolocalization
63
64__ https://github.com/projectfluent/fluent/wiki/Get-Started
65__ https://github.com/projectfluent/fluent/wiki/Design-Principles
66__ https://github.com/projectfluent/fluent.js/wiki/DOM-Overlays
67
68
69Product Quality
70------------------
71
72 - A robust, multilevel, `error fallback system`__ prevents XML errors and runtime errors
73 - Simplified l10n API reduces the amount of l10n specific code and resulting bugs
74 - Runtime localization allows for dynamic language changes and updates over-the-air
75 - DOM Overlays increase localization security
76
77__ https://github.com/projectfluent/fluent/wiki/Error-Handling
78
79
80Fluent Translation List - FTL
81=============================
82
83Fluent introduces a file format designed specifically for easy readability
84and the localization features offered by the system.
85
86At first glance the format is a simple key-value store. It may look like this:
87
88.. code-block:: fluent
89
90  home-page-header = Home Page
91
92  # The label of a button opening a new tab
93  new-tab-open = Open New Tab
94
95But the FTL file format is significantly more powerful and the additional features
96quickly add up. In order to familiarize yourself with the basic features,
97consider reading through the `Fluent Syntax Guide`_ to understand
98a more complex example like:
99
100.. code-block:: fluent
101
102  ### These messages correspond to security and privacy user interface.
103  ###
104  ### Please choose simple and non-threatening language when localizing
105  ### to help user feel in control when interacting with the UI.
106
107  ## General Section
108
109  -brand-short-name = Firefox
110      .gender = masculine
111
112  pref-pane =
113      .title =
114          { PLATFORM() ->
115              [windows] Options
116             *[other] Preferences
117          }
118      .accesskey = C
119
120  # Variables:
121  #   $tabCount (Number) - number of container tabs to be closed
122  containers-disable-alert-ok-button =
123      { $tabCount ->
124          [one] Close { $tabCount } Container Tab
125         *[other] Close { $tabCount } Container Tabs
126      }
127
128  update-application-info =
129      You are using { -brand-short-name } Version: { $version }.
130      Please read the <a>privacy policy</a>.
131
132The above, of course, is a particular selection of complex strings intended to exemplify
133the new features and concepts introduced by Fluent.
134
135.. important::
136
137  While in Fluent it’s possible to use both lowercase and uppercase characters in message
138  identifiers, the naming convention in Gecko is to use lowercase and hyphens, avoiding
139  CamelCase and underscores. For example, `allow-button` should be preferred to
140  `allow_button` or `allowButton`, unless there are technically constraints – like
141  identifiers generated at run-time from external sources – that make this impractical.
142
143In order to ensure the quality of the output, a lot of checks and tooling
144is part of the build system.
145`Pontoon`_, the main localization tool used to translate Firefox, also supports
146Fluent and its features to help localizers in their work.
147
148
149.. _fluent-tutorial-social-contract:
150
151Social Contract
152===============
153
154Fluent uses the concept of a `social contract` between developer and localizers.
155This contract is established by the selection of a unique identifier, called :js:`l10n-id`,
156which carries a promise of being used in a particular place to carry a particular meaning.
157
158The use of unique identifiers is shared with legacy localization systems in
159Firefox.
160
161.. important::
162
163  An important part of the contract is that the developer commits to treat the
164  localization output as `opaque`. That means that no concatenations, replacements
165  or splitting should happen after the translation is completed to generate the
166  desired output.
167
168In return, localizers enter the social contract by promising to provide an accurate
169and clean translation of the messages that match the request.
170
171In Fluent, the developer is not to be bothered with inner logic and complexity that the
172localization will use to construct the response. Whether `declensions`__ or other
173variant selection techniques are used is up to a localizer and their particular translation.
174From the developer perspective, Fluent returns a final string to be presented to
175the user, with no l10n logic required in the running code.
176
177__ https://en.wikipedia.org/wiki/Declension
178
179
180Markup Localization
181===================
182
183To localize an element in Fluent, the developer adds a new message to
184an FTL file and then has to associate an :js:`l10n-id` with the element
185by defining a :js:`data-l10n-id` attribute:
186
187.. code-block:: html
188
189  <h1 data-l10n-id="home-page-header" />
190
191  <button data-l10n-id="pref-pane" />
192
193Fluent will take care of the rest, populating the element with the message value
194in its content and all localizable attributes if defined.
195
196The developer provides only a single message to localize the whole element,
197including the value and selected attributes.
198
199The value can be a whole fragment of DOM:
200
201.. code-block:: html
202
203  <p data-l10n-id="update-application-info" data-l10n-args='{"version": "60.0"}'>
204    <a data-l10n-name="privacy-url" href="http://www.mozilla.org/privacy" />
205  </p>
206
207.. code-block:: fluent
208
209  -brand-short-name = Firefox
210  update-application-info =
211      You are using { -brand-short-name } Version: { $version }.
212      Please read the <a data-l10n-name="privacy-url">privacy policy</a>.
213
214
215Fluent will overlay the translation onto the source fragment preserving attributes like
216:code:`class` and :code:`href` from the source and adding translations for the elements
217inside. The resulting localized content will look like this:
218
219.. code-block:: html
220
221  <p data-l10n-id="update-application-info" data-l10n-args='{"version": "60.0"}'">
222    You are using Firefox Version: 60.0.
223    Please read the <a href="http://www.mozilla.org/privacy">privacy policy</a>.
224  </p>
225
226
227This operation is sanitized, and Fluent takes care of selecting which elements and
228attributes can be safely provided by the localization.
229The list of allowed elements and attributes is `maintained by the W3C`__, and if
230the developer needs to allow for localization of additional attributes, they can
231allow them using :code:`data-l10n-attrs` list:
232
233.. code-block:: html
234
235  <label data-l10n-id="search-input" data-l10n-attrs="style" />
236
237The above example adds an attribute :code:`style` to be allowed on this
238particular :code:`label` element.
239
240
241External Arguments
242------------------
243
244Notice in the previous example the attribute :code:`data-l10n-args`, which is
245a JSON object storing variables exposed by the developer to the localizer.
246
247This is the main channel for the developer to provide additional variables
248to be used in the localization.
249
250Arguments are rarely needed for situations where it’s currently possible to use
251DTD, since such variables would need to be computed from the code at runtime.
252It's worth noting that, when the :code:`l10n-args` are set in
253the runtime code, they are in fact encoded as JSON and stored together with
254:code:`l10n-id` as an attribute of the element.
255
256__ https://www.w3.org/TR/2011/WD-html5-20110525/text-level-semantics.html
257
258
259Runtime Localization
260====================
261
262In almost every case the JS runtime code will operate on a particular document, either
263XUL, XHTML or HTML.
264
265If the document has its markup already localized, then Fluent exposes a new
266attribute on the :js:`document` element - :js:`document.l10n`.
267
268This property is an object of type :js:`DOMLocalization` which maintains the main
269localization context for this document and exposes it to runtime code as well.
270
271With a focus on `declarative localization`__, the primary method of localization is
272to alter the localization attributes in the DOM. Fluent provides a method to facilitate this:
273
274.. code-block:: javascript
275
276  document.l10n.setAttributes(element, "new-panel-header");
277
278This will set the :code:`data-l10n-id` on the element and translate it before the next
279animation frame.
280
281The reason to use this API over manually setting the attribute is that it also
282facilitates encoding l10n arguments as JSON:
283
284.. code-block:: javascript
285
286  document.l10n.setAttributes(element, "containers-disable-alert-ok-button", {
287    tabCount: 5
288  });
289
290__ https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers
291
292
293Non-Markup Localization
294-----------------------
295
296In rare cases, when the runtime code needs to retrieve the translation and not
297apply it onto the DOM, Fluent provides an API to retrieve it:
298
299.. code-block:: javascript
300
301  let [ msg ] = await document.l10n.formatValues([
302    {id: "remove-containers-description"}
303  ]);
304
305  alert(msg);
306
307This model is heavily discouraged and should be used only in cases where the
308DOM annotation is not possible.
309
310.. note::
311
312  This API is available as asynchronous. In case of Firefox,
313  the only non-DOM localizable calls are used where the output goes to
314  a third-party like Bluetooth, Notifications etc.
315  All those cases should already be asynchronous. If you can't avoid synchronous
316  access, you can use ``mozILocalization.formatMessagesSync`` with synchronous IO.
317
318
319Internationalization
320====================
321
322The majority of internationalization issues are implicitly handled by Fluent without
323any additional requirement. Full Unicode support, `bidirectionality`__, and
324correct number formatting work without any action required from either
325developer or localizer.
326
327__ https://github.com/projectfluent/fluent/wiki/BiDi-in-Fluent
328
329.. code-block:: javascript
330
331  document.l10n.setAttributes(element, "welcome-message", {
332    userName: "اليسع",
333    count: 5
334  });
335
336A message like this localized to American English will correctly wrap the user
337name in directionality marks, allowing the layout engine to determine how to
338display the bidirectional text.
339
340On the other hand, the same message localized to Arabic will use the Eastern Arabic
341numeral for number "5".
342
343
344Plural Rules
345------------
346
347The most common localization feature is the ability to provide different variants
348of the same string depending on plural categories. Fluent ties into the Unicode CLDR
349standard called `Plural Rules`_.
350
351In order to allow localizers to use it, all the developer has to do is to pass
352an external argument number:
353
354.. code-block:: javascript
355
356  document.l10n.setAttributes(element, "unread-warning", { unreadCount: 5 });
357
358Localizers can use the argument to build a multi variant message if their
359language requires that:
360
361.. code-block:: fluent
362
363  unread-warning =
364      { $unreadCount ->
365          [one] You have { $unreadCount } unread message
366         *[other] You have { $unreadCount } unread messages
367      }
368
369If the variant selection is performed based on a number, Fluent matches that
370number against literal numbers as well as its `plural category`__.
371
372If the given translation doesn't need pluralization for the string (for example
373Japanese often will not), the localizer can replace it with:
374
375.. code-block:: fluent
376
377  unread-warning = You have { $unreadCount } unread messages
378
379and the message will preserve the social contract.
380
381One additional feature is that the localizer can further improve the message by
382specifying variants for particular values:
383
384.. code-block:: fluent
385
386  unread-warning =
387      { $unreadCount ->
388          [0] You have no unread messages
389          [1] You have one unread message
390         *[other] You have { $unreadCount } unread messages
391      }
392
393The advantage here is that per-locale choices don't leak onto the source code
394and the developer is not affected.
395
396
397.. note::
398
399  There is an important distinction between a variant keyed on plural category
400  `one` and digit `1`. Although in English the two are synonymous, in other
401  languages category `one` may be used for other numbers.
402  For example in `Bosnian`__, category `one` is used for numbers like `1`, `21`, `31`
403  and so on, and also for fractional numbers like `0.1`.
404
405__ https://unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
406__ https://unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#bs
407
408Partially-formatted variables
409-----------------------------
410
411When it comes to formatting data, Fluent allows the developer to provide
412a set of parameters for the formatter, and the localizer can fine tune some of them.
413This technique is called `partially-formatted variables`__.
414
415For example, when formatting a date, the developer can just pass a JS :js:`Date` object,
416but its default formatting will be pretty expressive. In most cases, the developer
417may want to use some of the :js:`Intl.DateTimeFormat` options to select the default
418representation of the date in string:
419
420.. code-block:: javascript
421
422  document.l10n.formatValue("welcome-message", {
423  startDate: FluentDateTime(new Date(), {
424      year: "numeric",
425      month: "long",
426      day: "numeric"
427    })
428  });
429
430.. code-block:: fluent
431
432  welcome-message = Your session will start date: { $startDate }
433
434In most cases, that will be enough and the date would get formatted in the current
435Firefox as `February 28, 2018`.
436
437But if in some other locale the string would get too long, the localizer can fine
438tune the options as well:
439
440.. code-block:: fluent
441
442  welcome-message = Początek Twojej sesji: { DATETIME($startDate, month: "short") }
443
444This will adjust the length of the month token in the message to short and get formatted
445in Polish as `28 lut 2018`.
446
447At the moment Fluent supports two formatters that match JS Intl API counterparts:
448
449 * **NUMBER**: `Intl.NumberFormat`__
450 * **DATETIME**: `Intl.DateTimeFormat`__
451
452With time more formatters will be added. Also, this feature is not exposed
453to ``setAttributes`` at this point, as that serializes to JSON.
454
455__ https://projectfluent.org/fluent/guide/functions.html#partially-formatted-variables
456__ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
457__ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
458
459Registering New L10n Files
460==========================
461
462Fluent uses a wildcard statement, packaging all localization resources into
463their component's `/localization/` directory.
464
465That means that, if a new file is added to a component of Firefox already
466covered by Fluent like `browser`, it's enough to add the new file to the
467repository in a path like `browser/locales/en-US/browser/component/file.ftl`, and
468the toolchain will package it into `browser/localization/browser/component/file.ftl`.
469
470At runtime Firefox uses a special registry for all localization data. It will
471register the browser's `/localization/` directory and make all files inside it
472available to be referenced.
473
474To make the document localized using Fluent, all the developer has to do is add
475localizable resources for Fluent API to use:
476
477.. code-block:: html
478
479  <link rel="localization" href="branding/brand.ftl"/>
480  <link rel="localization" href="browser/preferences/preferences.ftl"/>
481
482The URI provided to the :html:`<link/>` element are relative paths within the localization
483system.
484
485
486Custom Localizations
487====================
488
489The above method creates a single localization context per document.
490In almost all scenarios that's sufficient.
491
492In rare edge cases where the developer needs to fetch additional resources, or
493the same resources in another language, it is possible to create additional
494Localization object manually using the `Localization` class:
495
496.. code-block:: javascript
497
498  const { Localization } =
499    ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
500
501
502  const myL10n = new Localization([
503    "branding/brand.ftl",
504    "browser/preferences/preferences.ftl"
505  ]);
506
507
508  let [isDefaultMsg, isNotDefaultMsg] =
509    await myL10n.formatValues({id: "is-default"}, {id: "is-not-default"});
510
511
512.. admonition:: Example
513
514  An example of a use case is the Preferences UI in Firefox, which uses the
515  main context to localize the UI but also to build a search index.
516
517  It is common to build such search index both in a current language and additionally
518  in English, since a lot of documentation and online help exist only in English.
519
520  A developer may create manually a new context with the same resources as the main one,
521  but hardcode it to `en-US` and then build the search index using both contexts.
522
523
524By default, all `Localization` contexts are asynchronous. It is possible to create a synchronous
525one by passing an `sync = false` argument to the constructor, or calling the `SetIsSync(bool)` method
526on the class.
527
528
529.. code-block:: javascript
530
531  const { Localization } =
532    ChromeUtils.import("resource://gre/modules/Localization.jsm", {});
533
534
535  const myL10n = new Localization([
536    "branding/brand.ftl",
537    "browser/preferences/preferences.ftl"
538  ], false);
539
540
541  let [isDefaultMsg, isNotDefaultMsg] =
542    myL10n.formatValuesSync({id: "is-default"}, {id: "is-not-default"});
543
544
545Synchronous contexts should be always avoided as they require synchronous I/O. If you think your use case
546requires a synchronous localization context, please consult Gecko, Performance and L10n Drivers teams.
547
548
549Designing Localizable APIs
550==========================
551
552When designing localizable APIs, the most important rule is to resolve localization as
553late as possible. That means that instead of resolving strings somewhere deep in the
554codebase and then passing them on, or even caching, it is highly recommended to pass
555around :code:`l10n-id` or :code:`[l10n-id, l10n-args]` pairs until the top-most code
556resolves them or applies them onto the DOM element.
557
558
559Testing
560=======
561
562When writing tests that involve both I18n and L10n, the general rule is that
563result strings are opaque. That means that the developer should not assume any particular
564value and should never test against it.
565
566In case of raw i18n the :js:`resolvedOptions` method on all :js:`Intl.*` formatters
567makes it relatively easy. In case of localization, the recommended way is to test that
568the code sets the right :code:`l10n-id`/:code:`l10n-args` attributes like this:
569
570.. code-block:: javascript
571
572  testedFunction();
573
574  const l10nAttrs = document.l10n.getAttributes(element);
575
576  deepEquals(l10nAttrs, {
577    id: "my-expected-id",
578    args: {
579      unreadCount: 5
580    }
581  });
582
583If the code really has to test for particular values in the localized UI, it is
584always better to scan for a variable:
585
586.. code-block:: javascript
587
588  testedFunction();
589
590  equals(element.textContent.contains("John"));
591
592.. important::
593
594  Testing against whole values is brittle and will break when we insert Unicode
595  bidirectionality marks into the result string or adapt the output in other ways.
596
597
598Pseudolocalization
599==================
600
601When working with a Fluent-backed UI, the developer gets a new tool to test their UI
602against several classes of problems.
603
604Pseudolocalization is a mechanism which transforms messages on the fly, using
605specific logic to help emulate how the UI will look once it gets localized.
606
607The three classes of potential problems that this can help with are:
608
609 - Hardcoded strings.
610
611   Turning on pseudolocalization should expose any strings that were left
612   hardcoded in the source, since they won't get transformed.
613
614
615 - UI space not adapting to longer text.
616
617   Many languages use longer strings than English. For example, German strings
618   may be 30% longer (or more). Turning on pseudolocalization is a quick way to
619   test how the layout handles such locales.
620
621
622 - Bidi adaptation.
623
624   For many developers, testing the UI in right-to-left mode is hard.
625   Pseudolocalization shows how a right-to-left locale will look like.
626
627To turn on pseudolocalization, add a new string pref :js:`intl.l10n.pseudo` and
628select the strategy to be used:
629
630 - :js:`accented` - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
631
632   This strategy replaces all Latin characters with their accented equivalents,
633   and duplicates some vowels to create roughly 30% longer strings.
634
635
636 - :js:`bidi` - ɥsıʅƃuƎ ıpıԐ
637
638   This strategy replaces all Latin characters with their 180 degree rotated versions
639   and enforces right to left text flow using Unicode UAX#9 `Explicit Directional Embeddings`__.
640   In this mode, the UI directionality will also be set to right-to-left.
641
642__ https://www.unicode.org/reports/tr9/#Explicit_Directional_Embeddings
643
644Inner Structure of Fluent
645=========================
646
647The inner structure of Fluent in Gecko is out of scope of this tutorial, but
648since the class and file names may show up during debugging or profiling,
649below is a list of major components, each with a corresponding file in `/intl/l10n`
650modules in Gecko.
651
652FluentBundle
653--------------
654
655FluentBundle is the lowest level API. It's fully synchronous, contains a parser for the
656FTL file format and a resolver for the logic. It is not meant to be used by
657consumers directly.
658
659In the future we intend to offer this layer for standardization and it may become
660part of the :js:`mozIntl.*` or even :js:`Intl.*` API sets.
661
662That part of the codebase is also the first that we'll be looking to port to Rust.
663
664
665Localization
666------------
667
668Localization is a higher level API which uses :js:`FluentBundle` internally but
669provides a full layer of compound message formatting and robust error fall-backing.
670
671It is intended for use in runtime code and contains all fundamental localization
672methods.
673
674
675DOMLocalization
676---------------
677
678DOMLocalization extends :js:`Localization` with functionality to operate on HTML, XUL
679and the DOM directly including DOM Overlays and Mutation Observers.
680
681DocumentL10n
682------------
683
684DocumentL10n implements the DocumentL10n WebIDL API and allows Document to
685communicate with DOMLocalization.
686
687L10nRegistry
688------------
689
690L10nRegistry is our resource management service. It
691maintains the state of resources packaged into the build and language packs,
692providing an asynchronous iterator of :js:`FluentBundle` objects for a given locale set
693and resources that the :js:`Localization` class uses.
694
695
696.. _Fluent: https://projectfluent.org/
697.. _DTD: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Localization
698.. _StringBundle: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Tutorial/Property_Files
699.. _Firefox Preferences: https://bugzilla.mozilla.org/show_bug.cgi?id=1415730
700.. _Unprivileged Contexts: https://bugzilla.mozilla.org/show_bug.cgi?id=1407418
701.. _System Add-ons: https://bugzilla.mozilla.org/show_bug.cgi?id=1425104
702.. _CLDR: http://cldr.unicode.org/
703.. _ICU: http://site.icu-project.org/
704.. _Unicode: https://www.unicode.org/
705.. _Fluent Syntax Guide: https://projectfluent.org/fluent/guide/
706.. _Pontoon: https://pontoon.mozilla.org/
707.. _Plural Rules: http://cldr.unicode.org/index/cldr-spec/plural-rules
708