Pergunta

I'm pretty new to both Python and Flask (with Jinja2 as template engine) and I am not sure I am doing it the right way. I am using Flask-Babel extension to add i18n support to my web application. I want to get localized strings from my js code, for instance:

var helloWorld = gettext('Hello, world');
console.log(helloWorld); //should log a localized hello world message

For this, I configured babel (babel.cfg):

[python: **/**.py]
[jinja2: **/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
[javascript: **/**.js]
encoding = utf-8

And its initialization is (imports omitted for simplicity):

#main Flask app
app = Flask(__name__)

#localization
babel = Babel(app)

LANGUAGES = {
    'ca': 'Catalan',
    'en': 'English',
    'de': 'Deutsch',
    'es': 'Español',
    'fi': 'Finnish',
    'it': 'Italian'
}

@babel.localeselector
def get_locale():
    return request.accept_languages.best_match(LANGUAGES.keys())

#some more stuff...

Babel identifies that string when building the POT/PO language files, but it seems I can't access these localized strings from js code since gettext function is not defined. It seems like Jinja2 is ignoring this part.

Any hints?

Foi útil?

Solução

I have finally found a solution, although I am not sure it is the way to go. The idea is to wrap the javascript code within an html template, which is interpretated by Jinja2 before it is rendered and apply a custom Jinja2 filter to get rid of some minor issues. I tried to keep js files separately but it did not work.

It seems that gettext function can be used like so:

var helloWorld = {{gettext('Hello, world')}};

But then, no quotes are inserted, and hence, js interpreter throws an error:

var helloWorld = Hello, world;

That's why I have finally applied a custom filter. A working example would be as follows.

hello_world.html:

<script type="text/javascript">
   var x = {{gettext('Hello, world')|generate_string|safe}};
   console.log(x);    //logs the localized hello world message
</script>

app.py:

#Jinja2 filters
from jinja2 import evalcontextfilter, Markup

#Mind the hack! Babel does not work well within js code
@app.template_filter()
@evalcontextfilter
def generate_string(eval_ctx, localized_value):
    if localized_value is None:
        return ""
    else:
        return Markup("\"" + localized_value + "\"").unescape()

Hope this helps!

Outras dicas

Providing translations in rendered JavaScript is a bit fragile. Also, I usually do not generate JavaScript using Jinja because it uses the same type of brackets and easily turns into mess when abused (it's always possible to have dynamic data and static JavaScript).

Alternative lightweight approach is to do the same JSON trick, but using data-attributes:

<div id="view-i18n" data-i18n='{{ view_i18n|tojson }}'> ... </div>

(NB: single quotes!)

But it's also good for a limited number of translations.

Perhaps, the most solid approach is to have the same translations in JavaScript as there are in the Flask app.

With a help of a utility called pojson it is possible to convert po-files to json (see https://github.com/ZeWaren/flask-i18n-example for an example) as part of the build process (for instance, right after making mo-files). Translations can be easily added to some unique enough global namespace variable by prepending the output of pojson with var some_unique_name = to have access to it. Or put the file under static into locale-specific file (eg static/view-es.json , static/view-fr.json, etc) and get it with ajax call.

Some things to consider though. You may need to break your translation domain into separate Python and Javascript by controlling babel extraction options if you really want to make JSON smaller. Also, having all translation strings in Javascript has security aspects. Maybe, you do not want to expose certain phrases only admins see to be open to other category of users. But then more translation domains are needed for different levels of access. Also header information may need to be removed to prevent leaking translator's emails and whatnot. This, of course, complicates the build process somewhat, but the more translation JavaScript side needs with time, the more automation pays itself off.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top