Internals of fluent.runtime¶
The application-facing API for fluent.runtime
is FluentLocalization
.
This is the binding providing basic functionality for using Fluent in a
project. FluentLocalization
builds on top of FluentBundle
on top of
FluentResource
.
FluentLocalization
handles
- Basic binding as an application-level API
- Language fallback
- uses resource loaders like
FluentResourceLoader
to createFluentResource
FluentBundle
handles
- Internationalization with plurals, number formatting, and date formatting
- Aggregating multiple Fluent resources with message and term references
- Functions exposed to Select and Call Expressions
FluentResource
handles parsing of Fluent syntax.
Determining which language to use, and which languages to fall back to is
outside of the scope of the fluent.runtime
package. A concrete
application stack might have functionality for that. Otherwise it needs to
be built, Babel has
helpers
for that. fluent.runtime
uses Babel internally for the international
functionality.
These bindings benefit from being adapted to the stack. Say,
a Django project would configure the localization binding through
django.conf.settings
, and load Fluent files from the installed apps.
Subclassing FluentLocalization¶
In the Using fluent.runtime documentation, we used DemoLocalization
, which we’ll
use here to exemplify how to subclass FluentLocalization
for the needs
of specific stacks.
from fluent.runtime import FluentLocalization, FluentResource
class DemoLocalization(FluentLocalization):
def __init__(self, fluent_content, locale='en', functions=None):
# Call super() with one locale, no resources nor loader
super(DemoLocalization, self).__init__([locale], [], None, functions=functions)
self.resource = FluentResource(fluent_content)
This set up the custom class, passing locale
and functions
to the
base implementation. What’s left to do is to customize the resource loading.
def _bundles(self):
bundle = self._create_bundle(self.locales)
bundle.add_resource(self.resource)
yield bundle
That’s all that we need for our demo purposes.
Using FluentBundle¶
The actual interaction with Fluent content is implemented in FluentBundle
.
Optimizations between the parsed content in FluentResource
and a
representation suitable for the resolving of Patterns is also handled inside
FluentBundle
.
>>> from fluent.runtime import FluentBundle, FluentResource
You pass a list of locales to the constructor - the first being the desired locale, with fallbacks after that:
>>> bundle = FluentBundle(["en-US"])
The passed locales are used for internationalization purposes inside Fluent,
being plural forms, as well as formatting of values. The locales passed in
don’t affect the loaded messages, handling multiple localizations and the
fallback from one to the other is done in the FluentLocalization
class.
You must then add messages. These would normally come from a .ftl
file stored on disk, here we will just add them directly:
>>> resource = FluentResource("""
... welcome = Welcome to this great app!
... greet-by-name = Hello, { $name }!
... """)
>>> bundle.add_resource(resource)
To generate translations, use the get_message
method to retrieve
a message from the bundle. This returns an object with value
and
attributes
properties. The value
can be None
or an abstract pattern.
attributes
is a dictionary mapping attribute names to abstract patterns.
If the the message ID is not found, a LookupError
is raised. An abstract
pattern is an implementation-dependent representation of a Pattern in the
Fluent syntax. Then use the format_pattern
method, passing the message value
or one of its attributes and an optional dictionary of substitution parameters.
You should only pass patterns to format_pattern
that you got from that same
bundle. As per the Fluent philosophy, the implementation tries hard to recover
from any formatting errors and generate the most human readable representation
of the value. The format_pattern
method thereforereturns a tuple containing
(translated string, errors)
, as below.
>>> welcome = bundle.get_message('welcome')
>>> translated, errs = bundle.format_pattern(welcome.value)
>>> translated
"Welcome to this great app!"
>>> errs
[]
>>> greet = bundle.get_message('greet-by-name')
>>> translated, errs = bundle.format_pattern(greet.value, {'name': 'Jane'})
>>> translated
'Hello, \u2068Jane\u2069!'
>>> translated, errs = bundle.format_pattern(greet.value, {})
>>> translated
'Hello, \u2068{$name}\u2069!'
>>> errs
[FluentReferenceError('Unknown external: name')]
You will notice the extra characters \u2068
and \u2069
in the
output. These are Unicode bidi isolation characters that help to ensure
that the interpolated strings are handled correctly in the situation
where the text direction of the substitution might not match the text
direction of the localized text. These characters can be disabled if you
are sure that is not possible for your app by passing
use_isolating=False
to the FluentBundle
constructor.