Using Flask-Babel with Flask 0.10

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.

The Problem

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.

The Solution

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):
                return unicode(obj)  # python 2
            except NameError:
                return str(obj)  # python 3
        return super(CustomJSONEncoder, self).default(obj)

app.json_encoder = CustomJSONEncoder

The 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 str(obj).

With this simple fix it is possible to flash Flask-Babel lazy texts with Flask 0.10, in both Python 2 and Python 3.



  • #1 Andy said 2014-09-24T12:52:41Z

    Hi Miguel,

    Nice, regarding of Flask-Babel I have a problem with Flask-wtf: How can I use i18n with Flask-wtf? I searched on google, but nothing found, just got some info relates to Flask-Babel. As I know we can use i18n with Wtforms, and I wonder if there is a way with Flask-wtf as well. Looking forward to your reply.

  • #2 Miguel Grinberg said 2014-09-24T14:22:56Z

    @Andy: what is the specific problem that you have? Flask-WTF translates texts by default, it should work without you doing anything.

  • #3 Andy said 2014-09-24T14:46:12Z

    @Miguel: for example, in form model I define username = StringField(validators=[Required()]), then when I submit leave username empty, it prompt me: "This field is required.", I want to translates this message to other language, I don't know how to do it with Flask-wtf, could you advise, thanks.

  • #4 Miguel Grinberg said 2014-09-24T15:52:00Z

    Andy, wtforms already comes with all those validation messages translated. Here is the Spanish version of the required message, for example: https://github.com/wtforms/wtforms/blob/master/wtforms/locale/es/LC_MESSAGES/wtforms.po#L63.

  • #5 Andy said 2014-09-25T05:39:34Z

    Miguel, thank you for your replying,

    Should I use the wtforms.po with Flask-Babel? Flask-WTF official docs said "WTF_I18N_ENABLED Disable/enable I18N support. This should work together with Flask-Babel", I will take a try.

  • #6 Miguel Grinberg said 2014-09-25T16:52:38Z

    Andy, I18N support is enabled by default. Just by having Flask-Babel you should be good to go.

  • #7 Andy said 2014-09-26T03:39:03Z

    Miguel, Great thanks!! working fine with wtforms.po using Flask-Babel.

  • #8 Andy said 2014-09-27T02:39:11Z

    Miguel, Today, I work with flask_login,want to transalte the error messages to Chinese, but met a problem about JSON serializable. It has been fixed with your solution, you done a great job. Thank you very much.

  • #9 Jon said 2016-06-25T15:38:28Z

    Miguel, thank you so much for this explanation. It seems like WTForms does not yet support Urdu (a form of Hindi spoken in Pakistan). I understand how to manually add a translation .po file (under the appropriate directory under site-packages), but that would only work for my local machine... How can I provide a translation file for the server as well?

    Thanks in advance for your help! Jon

  • #10 Miguel Grinberg said 2016-07-14T17:59:16Z

    @Jon: the translation files should be considered source files, they should be part of your application and included in source control. The deployment script should build these files on the server.

Leave a Comment