Skip to content

Commit ac88065

Browse files
committed
Add a base Localization class to fluent.runtime
This corresponds to what @fluent/bundle does on the js side, but doesn't try to be async at all.
1 parent a05729e commit ac88065

File tree

11 files changed

+456
-80
lines changed

11 files changed

+456
-80
lines changed

fluent.runtime/CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ fluent.runtime next
88
``fluent.runtime.FluentBundle.add_resource``.
99
* Removed ``fluent.runtime.FluentBundle.add_messages``.
1010
* Replaced ``bundle.format()`` with ``bundle.format_pattern(bundle.get_message().value)``.
11+
* Added ``fluent.runtime.FluentLocalization`` as main entrypoint for applications.
1112

1213
fluent.runtime 0.2 (September 10, 2019)
1314
---------------------------------------

fluent.runtime/docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
author = 'Luke Plant'
2525

2626
# The short X.Y version
27-
version = '0.1'
27+
version = '0.3'
2828
# The full version, including alpha/beta/rc tags
29-
release = '0.1'
29+
release = '0.3'
3030

3131

3232
# -- General configuration ---------------------------------------------------

fluent.runtime/docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ significant changes.
1515

1616
installation
1717
usage
18+
internals
1819
history

fluent.runtime/docs/internals.rst

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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.

fluent.runtime/docs/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fluent.pygments

0 commit comments

Comments
 (0)