angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Construire un site web multilingue Flask avec Flask-Babel

Il n'existe pas beaucoup d'exemples de multilinguisme Flask. Nous suivons ici les suggestions de la Flask documentation.

7 août 2019
Dans Babel
post main image
Original photo unsplash.com/@jantined.

Avec une seule langue, il n'y a pas vraiment de problème, nous oublions simplement le reste du monde et construisons notre Flask application en une seule langue. Nous commençons à avoir mal à la tête quand le site doit supporter plusieurs langues. Qu'est-ce qu'un site web supportant plusieurs langues exactement ? Combien de langues seront prises en charge, et quelles langues ? Pour l'anglais, il existe par exemple en-GB et en-US. Quelles parties du site Web doivent être disponibles dans toutes les langues ? Quelles sont les parties d'un site web de toute façon ? Je me limiterai ici à répondre aux questions les plus triviales. Pour une bonne introduction, vous pouvez également consulter par exemple le guide du plugin Wordpress Polylang, voir ci-dessous.

Lorsque vous construisez un site web sans supporter plusieurs langues, vous utilisez des urls pour votre contenu, lorsque vous voulez ajouter plus de langues, vous devez maintenir toutes les urls, c'est-à-dire ce qui a déjà été créé. D'un point de vue SEO, vous feriez mieux de commencer par tous les composants multilingues en place, même si vous doutez maintenant si vous allez ajouter une autre langue.

Sélection de la langue

Lorsque la langue est dans un cookie (session), l'affichage correct de la langue peut être un problème lorsque les cookies sont désactivés. Avec la langue dans le domaine / url ce problème est éliminé et aussi le site est plus convivial SEO.

Option 1 : langue dans l'extension du domaine

example.com
example.es
example.pl

Exemple : toyota
https://www.toyota.de
https://www.toyota.es

Option 2 : langue dans le sous-domaine

en.example.com
es.example.com
pl.example.com

Exemple : cnn
https://edition.cnn.com
https://cnnespanol.cnn.com

Option 3 : langue dans le chemin de l'url

example.com/fr
example.com/es
example.com/es /pl

URL traduites

Les urls traduites sont très conviviales, mais introduisent aussi plus de complexité :

https://edition.cnn.comentertainment
https://cnnespanol.cnn.com/seccion/entretenimiento /entertainment /seccion/entretenimiento

Certains sites Web n'ont pas d'urls traduites :

https://www.tesla.com/nl_NL/model3/design#battery
https://www.tesla.com/de_CH/model3/design#battery

Si nous voulons avoir des urls de langue traduites, et pourquoi ne le ferions-nous pas, alors nous avons aussi besoin de plusieurs points finaux pour une fonction de vue :

@pages.route('/<lang_code>/about-us', methods=['GET', 'POST'])
@pages.route('/<lang_code>/uber-uns', methods=['GET', 'POST'])
def about():
    return render_template(...)

Bien sûr, nous voulons que ce soit automatisé... difficile.

Faire des choix

Pour le moment, je me concentre sur un site web avec le'language id' dans le chemin de l'url, voir option 3 ci-dessus, et je ne m'occuperai pas des urls traduites. Parce que nous utilisons Flask, nous utilisons Flask-Babel pour nos traductions. Certains textes, comme les blogs, les catégories, les tags, sont dans la base de données. Nous y reviendrons plus tard.

Vous pouvez vouloir un site Web avec la langue principale sans l''id de langue' dans le chemin de l'url et les autres langues avec l''id de langue' dans le chemin de l'url. Je ne sais pas vraiment comment faire cela pour Flask le moment et je crois aussi que cela complique beaucoup les choses, pensez aussi au référencement. En supposant que vous voulez ajouter une autre langue après quelques années, vous ne pouvez pas changer vos urls. résumé : je pense que ce n'est pas une mauvaise idée de commencer par la langue dans le chemin url.

Dans une première implémentation, j'ai utilisé un cookie pour la sélection de la langue, maintenant je dois supprimer ceci et utiliser la langue dans l'url. Heureusement, la Flask documentation donne de bonnes informations sur l'internationalisation. Je vous suggère de lire ceci (je l'ai fait plusieurs fois). J'ai beaucoup blueprints, pour la page d'accueil view.py j'ajoute ce qui suit à la vue :

...
home_blueprint = Blueprint('home', __name__)

# lang_code in urls
@home_blueprint.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

@home_blueprint.url_value_preprocessor
def pull_lang_code(endpoint, values):

    url_lang_code_items_values = get_url_lang_code_items_values()
    url_lang_code_default_item_value = get_url_lang_code_default_item_value()

    g.lang_code = url_lang_code_default_item_value
    if values:
        if 'lang_code' in values:
            if values['lang_code'] in url_lang_code_items_values:
                g.lang_code = values.pop('lang_code', None)
            else:
                pass
...

La fonction get_url_lang_code_items_values() retourne une liste de lang_codes : en, nl, es, et la fonction get_url_lang_code_default_item_value() retourne en, l'anglais est donc la langue par défaut. Puis dans __init__.py j'enregistre la maison blueprint:

    from .blueprints.home.views import home_blueprint
    app.register_blueprint(home_blueprint, url_prefix='/<lang_code>')

Que se passe-t-il si on tape une url sans chemin, ou une url totalement aléatoire ? Vous obtiendrez un message d'erreur :

TypeError: homepage() got an unexpected keyword argument 'lang_code'

La Flask documentation ne donne pas de solution, mais après quelques maux de tête (de nouveau) je pense que j'ai trouvé une solution pour résoudre ce problème en utilisant le gestionnaire before_request. Dans ce gestionnaire, je regarde l'url de la requête. Le chemin de cette url est divisé en plusieurs parties. La première partie doit être notre langue. Si la première partie est dans notre liste de langues, alors continuez. Si la page ne peut pas être trouvée Flask générera une 404, page non trouvée, ce qui est bien. Si la première partie n'est pas notre liste de langues, alors le gestionnaire before_request retourne une url de redirection vers la page d'accueil de la langue par défaut.

Après l'implémentation, le site a été affiché sans styles et j'ai reçu des messages d'erreur étranges dans la console d'outils Developer. La solution était d'exclure le répertoire statique. Voici donc ce qui se passe dans le gestionnaire before_request :

    @app.before_request
    def before_request():

        #  try to handle missing lang_code in url interceptor

        url_lang_code_items_values = get_url_lang_code_items_values()
        url_lang_code_default_item_value = get_url_lang_code_default_item_value()

        # check for a valid url = starts with /lang_code/
        request_path = request.path.strip('/')
        request_path_parts = urlparse(request_path).path.split('/')
        if request.method in ['GET'] and len(request_path_parts) > 0:
            request_path_part_0 = request_path_parts[0]

            # do nothing with static urls !!!
            if request_path_part_0 != 'static' and request_path_part_0 not in url_lang_code_items_values:
                # fucked up url
                redir_url_parts = []
                redir_url_parts.append( request.url_root.strip('/') )
                redir_url_parts.append( url_lang_code_default_item_value.strip('/') )
                redir_url = '/'.join(redir_url_parts)
                return redirect(redir_url)

Cela fonctionne mais comment changer la langue ?

Dans le template base.html, j'ai un sélecteur de langue déroulant :

	<li class="nav-item dropdown">
		<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
		{{ language_selected }}
		</a>
		<div class="dropdown-menu" aria-labelledby="navbarDropdown">
			<a class="dropdown-item" href="{{ request.path }}?lc=en_US">{{ _('EN') }}</a>
			<a class="dropdown-item" href="{{ request.path }}?lc=nl_NL">{{ _('NL') }}</a>
			<a class="dropdown-item" href="{{ request.path }}?lc=es_ES">{{ _('ES') }}</a>
		</div>
	</li>

Ensuite, nous déclenchons la Babel langue localeselector lorsque la langue est changée :

    @babel.localeselector
    def get_locale():
        request_lc = request.args.get('lc')
        if not request_lc:
            if not 'lang_code' in g:
                # use default
                g.lang_code = 'en'
                request_lc = 'en_US'
            else:
                if g.lang_code == 'es':
                    request_lc = 'es_ES'
                elif g.lang_code == 'nl':
                    request_lc = 'nl_NL'
                else:
                    request_lc = 'en_US'

        else:
            # set g.lang_code to the requested language
            if request_lc == 'nl_NL':
               g.lang_code = 'nl'
            elif request_lc == 'es_ES':
               g.lang_code = 'es'
            else:
                request_lc = 'en_US'
                g.lang_code = 'en'
            #sys.exit()
        session['lc'] = request_lc
        return request_lc

Ok, ça marche maintenant mais doit être optimisé. En outre, il y a encore deux problèmes en ce moment :

  • Lorsque la langue est changée, ceci n'est pas affiché dans l'url par la suite.
    Vous devez aller sur une nouvelle page. Après avoir changé la langue avec la liste déroulante, l'url n'affiche pas la langue dans l'url immédiatement, seulement au clic suivant. Peut-être que je devrais déplacer le Babel code vers le gestionnaire before_request ?
  • Beaucoup d'appels à url_defaults sur chaque appel
    que je voudrais avoir le code :
    @home_blueprint.url_defaults
    @home_blueprint.url_value_preprocessor
    dans le __init__.py au lieu de dans blueprint views.py mais cela ne fonctionne pas.
    Pourquoi est-ce que je veux ça ? Parce que je n'aime pas dupliquer le même code et je vois beaucoup d'appels à @auth_blueprint.url etc. Je crois qu'elles devraient être faites une fois dans __init__.py mais peut-être que j'ai tort.

Liens / crédits

Flask Series: Internationalization
https://damyanon.net/post/flask-series-internationalization/

Flask-multilang-demo
https://github.com/DusanMadar/Flask-multilang-demo

How to Easily Create a Multilingual WordPress Site
https://www.wpbeginner.com/beginners-guide/how-to-easily-create-a-multilingual-wordpress-site/

Internationalized Application URLs
https://flask.palletsprojects.com/en/1.1.x/patterns/urlprocessors/

Multilingual flask application
https://stackoverflow.com/questions/3420897/multilingual-flask-application

Set languages and locales
https://docs.microsoft.com/en-us/windows-hardware/customize/mobile/mcsf/set-languages-and-locales

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires (1)

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.

avatar

From where 'get_url_lang_code_items_values()' and 'get_url_lang_code_default_item_value()' come from?