Created on 19 Jan 2021 ;    Modified on 19 Jan 2021

How create a minimal flask project (part 3: managing the URLs)


This is the third part of an article about Flask, as follows:

Objective

This is the third part of a Flask project to show a single page in two different versions:

  • single language;
  • two languages.

Here we approach how manage URLs with two (or more) languages.

We have seen how display an html page with any characteristic about its used language. And how display a page using two different languages.

So our application is fully functioning, but ... there is a but. It is responding automatically to our web browser configuration, without showing something as User Interface.

You can argue: "it's showing a page with a complete different language! what else do you want?".

Yes. Speaking about we, humans, this is our scope and it's more than enough. But if we concentrate on how search engines work, it isn't enough. They reason about URLs. If we have two html pages that differ each other just because of the language, and these pages show up from the same URL (it is our case), a search engine cannot understand they are two pages: one URL, one page.

So we need a mechanism to modify URLs to reflect our different languages. Often this is accomplished using a language code inserted in the URL. E.g. using http://domain/en/home.html to link an home page in English language (do you see the en language code after the domain and before the page address?). The same page in Italian language would have address: http://domain/it/home.html.

Methodology

So we need to modify our URLs. Flask helps us to do so, using URL Processors.

Again, we work on the second blueprint: single_page/twoels.

Development

No need to install something, nor to change structure.

We begin changing file single_page/twoels/views.py.

 1  from flask import Blueprint, current_app, render_template, request, g   # +-
 2 
 3  from flask_babel import _
 4 
 5  from single_page import babel
 6 
 7  # this app will respond to srv/<lang_code>/2l/... URLs
 8  twoels = Blueprint('twoels',
 9                     __name__,
10                     static_folder='static',
11                     template_folder='templates',
12                     url_prefix='/<lang_code>/2l')                        # +-
13 
14  @twoels.url_defaults                                                    # +
15  def add_language_code(endpoint, values):
16      values.setdefault('lang_code', g.lang_code)
17 
18  @twoels.url_value_preprocessor                                          # +
19  def pull_lang_code(endpoint, values):
20      lc = values.get('lang_code', None)
21      if lc in current_app.config['LANGUAGES']:
22          g.lang_code = values.pop('lang_code')                # we'll use this even to set request.accept_languages
23      else:
24          raise ValueError(f"language {lc} is not accepted, because not in {tuple(current_app.config['LANGUAGES'].keys())}")
25 
26  @twoels.route('/')                 # index URLs
27  @twoels.route('/index')
28  @twoels.route('/index.html')
29  def index():
30      # default language code is in babel.default_locale
31      return render_template('2lindex.html', title=_('two languages title'))
32 
33  @babel.localeselector                                                   # +-
34  def get_locale():
35      #<! to test Italian language: configure web browser OR ...
36      #   ... decomment the following line of code
37      #return 'it'
38      if g.get('lang_code', None):
39          return request.accept_languages.best_match([g.lang_code,])
40      return request.accept_languages.best_match(current_app.config['LANGUAGES'].keys())

The trick is in pull_lang_code: it grabs the language code and assign it to g.lang_code. Then in get_locale, we use it to set babel.default_locale, used to select the desired language. Note we don't need to manage explicitly our language code in endpoints (the function index): Flask does it for us.

In file single_page/twoels/templates/2lbase.html we add a couple of links just to remember how manage them using the lang_code.

 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    <li><a href="{{ url_for('twoels.index', lang_code='it') }}">{{ _('two languages, italian index') }}</a> <!--  +  -->
11    <li><a href="{{ url_for('twoels.index', lang_code='en') }}">{{ _('two languages, english index') }}</a> <!--  +  -->
12    </ul>
13  </nav>
14  <section class="content">
15    <header>
16      {% block header %}{% endblock %}
17    </header>
18 
19    {% for message in get_flashed_messages() %}
20      <div class="flash">{{ message }}</div>
21    {% endfor %}
22 
23    {% block content %}{% endblock %}
24 
25  </section>

Now, let us execute python run.py, and we say web browser is configured with English as preferred language.

Browsing url http://localhost:5000/2l/ we get http error 404 (not found), because now our application needs the language code in the URL.

I we try: http://localhost:5000/it/2l/ web browser gives us the page content in Italian language (even if browser option tells we prefer English language):

single page, two languages, italian language by URL

While asking for http://localhost:5000/en/2l/ we get the page content in English language:

single page, two languages, English language by URL

Just a last note about links. If we place the mouse pointer over two languages, italian index link, we can see at the bottom of the browser that it is aiming at http://localhost:5000/it/2l/index.html: Flask function url_for built it using it as language code, as we have request.

single page, two languages, aiming to italian language index

NOW we have done!

Enjoy, ldfa