One of the interesting problems that I had to address when porting my Flask Mega-Tutorial to Flask 0.10 was in supporting Flask-Babel. There is an issue when a "lazy" text generated with the
lazy_gettext function is flashed. This issue was reported more than a year ago and hasn't been addressed yet.
In this short post I will show you how I solved this issue for the Mega-Tutorial, as this solution is applicable to any Flask project.
When you call
lazy_gettext() to make a lazily evaluated translated text Flask-Babel uses the services of another package called speaklater, also written by Armin Ronacher.
Speaklater supports lazily evaluated texts through a string-like class called
_LazyString. Instances of this class take a lookup function (provided by Flask-Babel) and the text to translate. Each time an instance of this class is evaluated to a string the lookup function is invoked with the text as argument, and its return value replaces the
_LazyString object. This works great in most cases, you can treat the
_LazyString instance as a regular string most of the time.
However, a common use of lazy texts is to flash them using Flask's
flash() function. For example, consider this example:
login_manager.login_message = lazy_gettext('Please log in to access this page.')
This is how Flask-Login is configured to show an error message when a user tries to access a protected page. When Flask-Login needs to show this message it flashes it, so that the application's template can pick it up. This text needs to appear in several languages, with the chosen language for a given user depending on the language configuration in the user's web browser. By making this text lazy the message will be auto-translated each time it is used.
The problem is that flashed messages are written to the user session, which in Flask 0.10 is serialized to JSON. But the JSON encoder does not know how to deal with the
_LazyString instance and fails.
It was nice to find that Flask 0.10 allows applications to install a custom JSON encoder, so all I needed to do to address this bug is to create my own JSON encoder that forces the
_LazyString instance to render to a regular string. The code is shown below:
from flask import Flask from flask.json import JSONEncoder app = Flask(__name__) class CustomJSONEncoder(JSONEncoder): """This class adds support for lazy translation texts to Flask's JSON encoder. This is necessary when flashing translated texts.""" def default(self, obj): from speaklater import is_lazy_string if is_lazy_string(obj): try: return unicode(obj) # python 2 except NameError: return str(obj) # python 3 return super(CustomJSONEncoder, self).default(obj) app.json_encoder = CustomJSONEncoder
default() method of the JSON encoder is invoked for any complex objects that have no known translation. If I find that the
obj is a lazy string I just convert it to a string. I first try
unicode(obj), which will only work in Python 2, and if that fails then I do it the Python 3 way, which is
With this simple fix it is possible to flash Flask-Babel lazy texts with Flask 0.10, in both Python 2 and Python 3.