Created on 17 Jan 2021 ; Modified on 18 Jan 2021
This is the second part of an article about Flask, as follows:
This is the second part of Flask project to show a single page in two different versions:
Here we approach how render a page with two (or more) languages. This characteristic usually is said internationalization.
We have seen how display an html page with any characteristic about its used language.
Now we work on the second blueprint: single_page/twoels
About the environment: we need the Flask-Babel app. So, let us install it.
As usual, in shell:
1 # installation 2 >cd flask_single_page 3 >venv\Scripts\activate # activate python's virtual environment 4 (venv) >pip install Flask-Babel # install flask-babel 5 ...
Our structure changes as follow (remember: ▼ means directory; it isn't part of directory's name):
▼ flask_single_page run.py babel.cfg # + babel configuration ▼ single_page __init__.py ▼ oneel __init__.py views.py ▼ static ▼ css style.css ▼ templates base.html index.html ▼ twoels __init__.py views.py ▼ static # + this dir holds static resources ▼ css style.css ▼ templates # + blueprint templates are here 2lbase.html 2lindex.html ▼ docs ▼ tests ▼ venv
File single_page/twoels/static/css/style.css is a copy of that present in single_page/oneel.
But, before to work with our target blueprint (single_page/twoels) we take a look at Babel.
We need to configure it using the file flask_single_page/babel.cfg
[python: twoels/**.py] [jinja2: twoels/templates/**.html] extensions=jinja2.ext.autoescape,jinja2.ext.with_ silent=False
This file tells to Babel commands where and how search text to translate: Python and html files. And what extensions to use beyond the basic logic.
But we need to configure even our application. So our single_page/__init__.py changes:
1 from flask import Flask 2 from flask_babel import Babel # + 3 4 babel = Babel() # + 5 6 def create_app(): 7 '''create and configure the app''' 8 app = Flask(__name__) 9 10 babel.init_app(app) # + 11 12 app.config.from_mapping( 13 SECRET_KEY='leave-hope-to-enter', 14 LANGUAGES = {'en': 'english', 'it': 'italiano',}, # + 15 ) 16 17 # a simple page that says hello 18 @app.route('/hello') 19 def hello(): 20 return 'Hello, World!' 21 22 from .oneel import views as views1 23 app.register_blueprint(views1.oneel) 24 25 from .twoels import views as views2 26 app.register_blueprint(views2.twoels) 27 28 return app
In Python programs we tell to Babel what strings we are going to translate using the function gettext() shortened as _(). So single_page/twoels/views.py become:
1 from flask import Blueprint, current_app, render_template , request # +- 2 from flask_babel import _ # + 3 from single_page import babel # + 4 5 # this app will respond to srv/2l/... URLs 6 twoels = Blueprint('twoels', # +- 7 __name__, 8 static_folder='static', 9 template_folder='templates', 10 url_prefix='/2l') 11 12 @twoels.route('/') # index URLs 13 @twoels.route('/index') 14 @twoels.route('/index.html') 15 def index(): 16 return render_template('2lindex.html', title=_('two languages title')) # +- 17 18 @babel.localeselector # + 19 def get_locale(): # + 20 #<! to test Italian language: configure web browser OR ... 21 # ... decomment the following line of code 22 #return 'it' 23 return request.accept_languages.best_match(current_app.config['LANGUAGES'].keys())
Here the key point is the decorator @babel.localeselector and the following function get_locale. These are called before the dispatch of an URL, setting babel.default_locale, used to select the desired language.
In html templates we mark text to translate using {{ _() }}. So our templates now are :
file single_page/twoels/templates/2lbase.html
1 <!doctype html> 2 3 <title>{% block title %}{% endblock %} - {{ _('Single Page, two languages') }}</title> <!-- +- --> 4 <link rel="stylesheet" href="{{ url_for('oneel.static', filename='css/style.css') }}"> 5 <nav> 6 <h1>{{ _('Single Page - single language') }}</h1> <!-- +- --> 7 <ul> 8 <li><a href="{{ url_for('oneel.index') }}">{{ _('single language index') }}</a> <!-- +- --> 9 <li><a href="{{ url_for('twoels.index') }}">{{ _('two languages index') }}</a> <!-- + --> 10 </ul> 11 </nav> 12 <section class="content"> 13 <header> 14 {% block header %}{% endblock %} 15 </header> 16 17 {% for message in get_flashed_messages() %} 18 <div class="flash">{{ message }}</div> 19 {% endfor %} 20 21 {% block content %}{% endblock %} 22 23 </section>
file single_page/twoels/templates/2lindex.html
1 {% extends '2lbase.html' %} 2 3 {% block header %} 4 <h1>{% block title %}Home{% endblock %}</h1> 5 {% endblock %} 6 7 {% block content %} 8 <p>{{ _('Single page, single language, content') }}</p> <!-- +- --> 9 {% endblock %}
A note about template filenames. We changed them compared to equivalent filenames in single_page/oneel to avoid collision between each other. Flask's search strategy for template files does not ensure the distinction between templates with the same name belonging to different blueprints.
Next steps are:
To do these tasks we use pybabel.exe and poedit.exe in shell as follow (current directory is flask_single_page; operating system is MS Windows 10, in Linux you have to use venv/bin/pybabel):
(venv) >venv\Scripts\pybabel.exe extract -F babel.cfg -o messages.pot single_page ...writing PO template file to messages.pot (venv) >venv\Scripts\pybabel.exe init -i messages.pot -d single_page\translations -l it # init italian language using "-l it" creating catalog single_page\translations\it\LC_MESSAGES\messages.po based on messages.pot (venv) >\bin\poedit\poedit.exe # GUI of poedit to translate messags in catalog messages.po ... # ... we have to write translations in GUI and save them (venv) >venv\Scripts\pybabel.exe compile -d single_page\twoels\translations compiling catalog single_page\translations\it\LC_MESSAGES\messages.po to single_page\translations\it\LC_MESSAGES\messages.mo
A note about poedit.exe. This isn't the only tool to write text translations in messages.po. Feel free to choose another one if you like.
In case we need to update messages, we skip the creation of translations\<language>\LC_MESSAGES\messages.po but we have to do again the tasks:
as follow:
(venv) >venv\Scripts\pybabel.exe extract -F babel.cfg -o messages.pot single_page
...writing PO template file to messages.pot
(venv) >venv\Scripts\pybabel.exe update -i messages.pot -d single_page\translations
updating catalog single_page\translations\it\LC_MESSAGES\messages.po based on messages.pot
(venv) >\bin\poedit\poedit.exe # GUI of poedit to translate messags in catalog messages.po
(venv) >venv\Scripts\pybabel.exe compile -d single_page\translations
compiling catalog single_page\translations\it\LC_MESSAGES\messages.po to single_page\translations\it\LC_MESSAGES\messages.mo
Now, executing python run.py, if web browser configuration tells we like English as preferred language, browsing url http://localhost:5000/2l/ we get the page content in English language:
But if we configure our browser to use Italian as our preferred language (e.g. in Firefox as follow)
we get the page content in Italian language:
Good. It's working.
So, have we done? No, not yet. We need another little piece to complete the puzzle. Please read part 3 :-)
Enjoy, ldfa