Other implementations and comparisons
fluent.runtime
0.1 also has an implementation of FluentBundle
, which
implements an interpreter for the FTL Abstract Syntax Tree.
fluent_compiler
, by contrast, works by compiling a set of FTL messages to a
set of Python functions using Python ast. This results in very good
performance (see below for more info).
There are some differences to note:
Our usage API is different from that in
fluent.runtime
.fluent_compiler
0.1 had aFluentBundle
API compatible withfluent.runtime.FluentBundle
0.1, but after that point both APIs have changed in slightly different directions.fluent.runtime
has some protection against malicious FTL input which could attempt things like a billion laughs attack to consume a large amount of memory or CPU time. For the sake of performance,fluent_compiler
does not have these protections.It should be noted that both implementations are able to detect and stop infinite recursion errors (
fluent_compiler
does this at compile time), which is important to stop infinite loops and memory exhaustion which could otherwise occur due to accidental cyclic references in messages.fluent_compiler
has the concept of compile-time static error checking, whilefluent.runtime
basically only has syntax and run-time checking. This allows us to report some errors earlier - although mostly the same types of errors will be reported.When errors occur (e.g. a missing value in the arguments dictionary, or a cyclic reference, or a string is passed to
NUMBER()
builtin), the exact errors returned byformat
may be different between the two implementations.Also, when an error occurs, in some cases (such as a cyclic reference), the error string embedded into the returned formatted message may be different. For cases where there is no error, the output is identical (or should be).
fluent_compiler
includes support for Escaping and markup.fluent_compiler
does additional checking on custom functions (see Custom functions) and has a more clearly defined error handling strategy for custom functions.
Performance
Due to the strategy of compiling to Python, along with some aggressive
optimizations, fluent_compiler
has very good performance, especially for the
simple common cases. The tools/benchmark/runtime.py
script includes some
benchmarks that compare speed to GNU gettext as a reference. Below is a rough
summary:
For the simple but very common case of a message defining a static string,
FluentBundle.format
is very close to GNU gettext, or much faster, depending
on your Python version and implementation (e.g. CPython or PyPy). (The worst
case we found was 7% slower than gettext on CPython 3.9, and the best case was
about 3.5 times faster for PyPy3.6 7.3.0). For cases of substituting a single
string into a message, FluentBundle.format
was between 7% slower and 3.4
times faster than an equivalent implementation using GNU gettext and Python
%
interpolation.
For message where plural rules are involved, currently fluent_compiler
can be significantly slower than using GNU gettext, partly because it uses
plural rules from CLDR that can be much more complex (and correct) than the ones
that gettext normally does. Further work could be done to optimize some of these
cases though.
For more complex operations (for example, using locale-aware date and number formatting), formatting messages can take a lot longer. Comparisons to GNU gettext fall down at this point, because it doesn’t include a lot of this functionality. However, usually these types of messages make up a small fraction of the number of internationalized strings in an application.
Some of the advanced features of Fluent, such as terms and parameterised terms, often come at zero run-time penalty, because we are usually able to inline them and entirely compile these away to static strings. We’re also careful with our generated code so that our advanced features such as escaping add zero run-time cost if you are not using them.
The implementation in fluent.runtime
is generally significantly slower than
the one in fluent_compiler
, although the performance of the former is
improving. In cases where there are a large number of messages,
fluent_compiler
will probably be slower to format the first message because
it first compiles all the messages, whereas fluent.runtime
does not have
this compilation step, and tries to reduce any up-front work to a minimum
(sometimes at the cost of run-time performance).