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
FluentResourceLoaderto 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.