|
| 1 | +Internals of fluent.runtime |
| 2 | +=========================== |
| 3 | + |
| 4 | +The application-facing API for ``fluent.runtime`` is ``FluentLocalization``. |
| 5 | +This is the binding providing basic functionality for using Fluent in a |
| 6 | +project. ``FluentLocalization`` builds on top of ``FluentBundle`` on top of |
| 7 | +``FluentResource``. |
| 8 | + |
| 9 | +``FluentLocalization`` handles |
| 10 | + |
| 11 | +* Basic binding as an application-level API |
| 12 | +* Language fallback |
| 13 | +* uses resource loaders like ``FluentResourceLoader`` to create ``FluentResource`` |
| 14 | + |
| 15 | +``FluentBundle`` handles |
| 16 | + |
| 17 | +* Internationalization with plurals, number formatting, and date formatting |
| 18 | +* Aggregating multiple Fluent resources with message and term references |
| 19 | +* Functions exposed to Select and Call Expressions |
| 20 | + |
| 21 | +``FluentResource`` handles parsing of Fluent syntax. |
| 22 | + |
| 23 | +Determining which language to use, and which languages to fall back to is |
| 24 | +outside of the scope of the ``fluent.runtime`` package. A concrete |
| 25 | +application stack might have functionality for that. Otherwise it needs to |
| 26 | +be built, `Babel <http://babel.pocoo.org/en/latest/>`_ has |
| 27 | +`helpers <http://babel.pocoo.org/en/latest/api/core.html#babel.core.negotiate_locale>`_ |
| 28 | +for that. ``fluent.runtime`` uses Babel internally for the international |
| 29 | +functionality. |
| 30 | + |
| 31 | + |
| 32 | +These bindings benefit from being adapted to the stack. Say, |
| 33 | +a Django project would configure the localization binding through |
| 34 | +``django.conf.settings``, and load Fluent files from the installed apps. |
| 35 | + |
| 36 | +Subclassing FluentLocalization |
| 37 | +------------------------------ |
| 38 | + |
| 39 | +In the :doc:`usage` documentation, we used ``DemoLocalization``, which we'll |
| 40 | +use here to exemplify how to subclass ``FluentLocalization`` for the needs |
| 41 | +of specific stacks. |
| 42 | + |
| 43 | +.. code-block:: python |
| 44 | +
|
| 45 | + from fluent.runtime import FluentLocalization, FluentResource |
| 46 | + class DemoLocalization(FluentLocalization): |
| 47 | + def __init__(self, fluent_content, locale='en', functions=None): |
| 48 | + # Call super() with one locale, no resources nor loader |
| 49 | + super(DemoLocalization, self).__init__([locale], [], None, functions=functions) |
| 50 | + self.resource = FluentResource(fluent_content) |
| 51 | +
|
| 52 | +This set up the custom class, passing ``locale`` and ``functions`` to the |
| 53 | +base implementation. What's left to do is to customize the resource loading. |
| 54 | + |
| 55 | +.. code-block:: python |
| 56 | +
|
| 57 | + def _bundles(self): |
| 58 | + bundle = self._create_bundle(self.locales) |
| 59 | + bundle.add_resource(self.resource) |
| 60 | + yield bundle |
| 61 | +
|
| 62 | +That's all that we need for our demo purposes. |
| 63 | + |
| 64 | +Using FluentBundle |
| 65 | +------------------ |
| 66 | + |
| 67 | +The actual interaction with Fluent content is implemented in ``FluentBundle``. |
| 68 | +Optimizations between the parsed content in ``FluentResource`` and a |
| 69 | +representation suitable for the resolving of Patterns is also handled inside |
| 70 | +``FluentBundle``. |
| 71 | + |
| 72 | +.. code-block:: python |
| 73 | +
|
| 74 | + >>> from fluent.runtime import FluentBundle, FluentResource |
| 75 | +
|
| 76 | +You pass a list of locales to the constructor - the first being the |
| 77 | +desired locale, with fallbacks after that: |
| 78 | + |
| 79 | +.. code-block:: python |
| 80 | +
|
| 81 | + >>> bundle = FluentBundle(["en-US"]) |
| 82 | +
|
| 83 | +The passed locales are used for internationalization purposes inside Fluent, |
| 84 | +being plural forms, as well as formatting of values. The locales passed in |
| 85 | +don't affect the loaded messages, handling multiple localizations and the |
| 86 | +fallback from one to the other is done in the ``FluentLocalization`` class. |
| 87 | + |
| 88 | +You must then add messages. These would normally come from a ``.ftl`` |
| 89 | +file stored on disk, here we will just add them directly: |
| 90 | + |
| 91 | +.. code-block:: python |
| 92 | +
|
| 93 | + >>> resource = FluentResource(""" |
| 94 | + ... welcome = Welcome to this great app! |
| 95 | + ... greet-by-name = Hello, { $name }! |
| 96 | + ... """) |
| 97 | + >>> bundle.add_resource(resource) |
| 98 | +
|
| 99 | +To generate translations, use the ``get_message`` method to retrieve |
| 100 | +a message from the bundle. This returns an object with ``value`` and |
| 101 | +``attributes`` properties. The ``value`` can be ``None`` or an abstract pattern. |
| 102 | +``attributes`` is a dictionary mapping attribute names to abstract patterns. |
| 103 | +If the the message ID is not found, a ``LookupError`` is raised. An abstract |
| 104 | +pattern is an implementation-dependent representation of a Pattern in the |
| 105 | +Fluent syntax. Then use the ``format_pattern`` method, passing the message value |
| 106 | +or one of its attributes and an optional dictionary of substitution parameters. |
| 107 | +You should only pass patterns to ``format_pattern`` that you got from that same |
| 108 | +bundle. As per the Fluent philosophy, the implementation tries hard to recover |
| 109 | +from any formatting errors and generate the most human readable representation |
| 110 | +of the value. The ``format_pattern`` method thereforereturns a tuple containing |
| 111 | +``(translated string, errors)``, as below. |
| 112 | + |
| 113 | +.. code-block:: python |
| 114 | +
|
| 115 | + >>> welcome = bundle.get_message('welcome') |
| 116 | + >>> translated, errs = bundle.format_pattern(welcome.value) |
| 117 | + >>> translated |
| 118 | + "Welcome to this great app!" |
| 119 | + >>> errs |
| 120 | + [] |
| 121 | +
|
| 122 | + >>> greet = bundle.get_message('greet-by-name') |
| 123 | + >>> translated, errs = bundle.format_pattern(greet.value, {'name': 'Jane'}) |
| 124 | + >>> translated |
| 125 | + 'Hello, \u2068Jane\u2069!' |
| 126 | +
|
| 127 | + >>> translated, errs = bundle.format_pattern(greet.value, {}) |
| 128 | + >>> translated |
| 129 | + 'Hello, \u2068{$name}\u2069!' |
| 130 | + >>> errs |
| 131 | + [FluentReferenceError('Unknown external: name')] |
| 132 | +
|
| 133 | +You will notice the extra characters ``\u2068`` and ``\u2069`` in the |
| 134 | +output. These are Unicode bidi isolation characters that help to ensure |
| 135 | +that the interpolated strings are handled correctly in the situation |
| 136 | +where the text direction of the substitution might not match the text |
| 137 | +direction of the localized text. These characters can be disabled if you |
| 138 | +are sure that is not possible for your app by passing |
| 139 | +``use_isolating=False`` to the ``FluentBundle`` constructor. |
0 commit comments