eckler n.f. = un truc neuneu sur internet
le thème sélectionné est SOMBRE
la police sélectionnée est CLASSIQUE (pixelisée)

mettre en place un site plurilingue avec Zola

> revenir à : mon blog

la doc officielle de zola était incomplète, donc voici comment je me suis débrouillé·e

pourquoi cet article existe

le but premier de cet article est de découvrir comment Zola permet de créer un site plurilingue, ainsi que ce qu'un peu de magie de template vous laisse faire

son contenu est fortement inspiré de ma propre expérience avec Zola et de mes tentatives pour mettre en place un site en version française et anglaise

ce que zola nous propose

commençons par regarder ce que le moteur de rendu de Zola nous autorise

traduction automatique de termes

par défaut, le fichier config.toml permet de définir des traductions de variables. jettons un oeil à ce à quoi ça peut ressembler avec l'exemple suivant :

# La langue par défaut est l'anglais
default_language = "en"

# Variantes anglophones de quelques termes
[translations]
foo = "foo"
bar = "bar"

# Variantes francophones de quelques termes
[languages.fr.translations]
foo = "toto"
bar = "titi"

dans l'exemple ci-dessus, la langue par défaut du site sera en (Anglais). on a ensuite listé les traductions par défaut de deux variables dans la section [translations]

nous avons aussi défini les traductions de celles-ci en fr (Français) dans la partie [languages.fr.translations]

ces traductions sont ensuite exposées à tous les templates (fichiers finissant en .html) et peuvent être récupérées en utilisant la fonction trans, comme ceci :

<button>{{ trans(key="foo") }}</button>

et le résultat sera : (ce que nous avons défini comme traduction par défaut)

ce qui n'est pas forcément évident en lisant la documentation, c'est que cette fonction prend un second argument optionnel appelé lang. cet argument doit être un code de langue valide défini dans votre fichier de configuration pour l'argument key que vous avez passé à la fonction. on s'attendrait à ce que le comportement par défaut soit de passer la variable lang de la page actuelle à la fonction. Cependant, ce que Zola fait est de passer la variable lang par défaut définie dans le fichier config.toml

plus simplement, ce que cela engendre c'est que

{{ trans(key="bar") }}

sera équivalent à

{{ trans(key="bar", lang="en") }}

dans mon exemple

ne vous inquiétez pas, une manière simple de contourner le problème est de toujours utiliser la fonction ainsi :

{{ trans(key="bar", lang=lang) }}

cette variante est capable de récupérer directement le variable lang de la page courante, et de la passer comme argument de la fonction pour que la traduction fonctionne comme on le souhaite. cela mène à un code légèrement plus verbeux, mais au moins ça fonctionne

texte variant selon la langue

comme dit précédemment, chaque page et section fournit une variable lang ayant pour valeur le code de sa langue. cette variable peut ensuite être utilisée pour définir des parties d'html variables dans un template

1{% if lang == 'en' %}
2<p>FOO <i>bar</i></p>
3{% elif lang == 'fr' %}
4<p>BAR <i>foo</i></p>
5{% endif %}

par exemple, le code précédent afficherait <p>FOO <i>bar</i></p> sur les pages en anglais, et <p>BAR <i>foo</i></p> sur celles en français

dans cette page, le résultat est

BAR foo

de plus, si jamais on modifie le code ainsi :

5{% else %}
6<p><b>Langue "{{ lang }}" non supportée</b></p>
7{% endif %}

, il afficherait un message disant que la langue de la page actuelle n'a pas de variante pour cette partie du template. si l'on enlève la partie après le {% else %}, il n'affichera juste rien

cette manière différente de travailler avec plusieurs langues est particulièrement pratique dans les parties de code où il est plus lisible d'avoir toutes les versions au même endroit, plutôt que dans le fichier config.toml, qui est surtout réservé à du texte qui sera utilisé à plusieurs endroits ou qui est plutôt court

de plus, le fait de laisser un cas par défaut quand la langue n'est pas supportée permet de travailler d'abord sur une partie du travail de traduction, tout en laissant certaines parties en attente et que le site fonctionne toujours

par défaut, toute page donne accès à une variable translations, décrite ici (en Anglais) dans la documentation

cette variable est accessible depuis les templates, en utilisant les variables section.translations ou page.translations. par exemple, on pourrait créer une liste de variantes pour une page comme ceci :

{% if page.translations | length > 1 %}
    <ul>
    {% for t in page.translations %}
        <li><a href="{{ t.permalink }}">{{ t.lang }} version: {{ t.title }}</a></li>
    {% endfor %}
    </ul>
{% endif %}

remarque: on vérifie que la liste des traductions a plus d'un élément, car elle contiendra dans tous les cas la version de la page actuelle

ce qui est possible avec quelques bidouilles

maintenant, allons voir quelles autres choses on est capables de faire en utilisant les connaissances fraîchement acquises

améliorer la navigation entre les pages traduites pour la rendre automatique

un des plus gros problèmes avec la méthode décrite dans la section précédente, c'est que l'on a besoin d'ajouter la liste des traductions disponibles à chaque template, l'adaptant pour qu'elle fonctionne à la fois avec les sections et les pages. une façon de le faire est d'utiliser un petit fichier html qui est ensuite inclus dans la page, comme celui-ci :

<!-- translations_list.html -->
{% if page.translations %}
    {% set translations = page.translations %}
{% else %}
    {% set translations = section.translations %}
{% endif %}

{% if translations | length > 1 %}
<ul>
    {% for t in translations %}
        {% if lang != t.lang %}
            {% if t.lang == 'fr' %}
            <li>cette page existe aussi en <a href="{{ t.permalink }}"><i>français : {{ t.title }}</i></a></li>
            {% elif t.lang == 'en' %}
            <li>this page is also available in <a href="{{ t.permalink }}"><i>english: {{ t.title }}</i></a></li>
            {% endif %}
        {% endif %}
    {% endfor %}
</ul>
{% endif %}

intégrer tout ça dans une base de templates déjà existante

un souci que l'on pourrait rencontrer est que la plupart des templates que l'on utilise sont étendus d'un template base.html, et la version actuelle du moteur n'accepte pas l'inclusion d'un autre template quand celui actuel en étend déjà un. dit plus simplement, le code ci-dessous ne fonctionne pas :

{% extends "base.html" %}

{% block content %}

{% include "translations_list.html" %}

{% endblock content %}

pour résoudre ce problème, j'ai simplement inclus le fichier translations_list.html dans mon base.html dans un bloc spécifique, et ai ensuite ajouté ce bloc dans chaque template où je pourrais en avoir besoin, et ajoutant un peu de contexte autour comme dans l'exemple ci-dessous :

<!-- base.html -->
{% block top %}

{% include "components/translations_list.html" %}
                
{% endblock top %}

<!-- page.html -->
{% extends "base.html" %}

{% block top %}

<h1>{{ page.title }}</h1>

{{ super() }}

<h2>{{ page.description }}</h2>

{% endblock top %}

ce système est ce qui me permet d'avoir la liste de "traductions disponibles" en haut de chaque page et section de ce site

à part ça, quoi d'autre est possible avec Zola

maintenant que l'on a jeté un oeil à ce qui est possible avec les traductions, je vais présenter d'autres choses intéressantes que j'ai obtenues avec Zola et son moteur de rendu

liens de retour en arrière automatiques

par défaut, chaque section et page met à disposition une liste d'ancestors (ancêtres). cette liste contient les noms des fichiers définissant les sections précédentes. par exemple, cet article aurait :

// version en Français
page.ancestors = [ "_index.fr.md", "blog/_index.fr.md" ]

// version en Anglais
page.ancestors = [ "_index.md", "blog/_index.md" ]

cet ensemble d'ancêtres est très utile, car il permet de créer un Fil d'Ariane ou des liens menant à la section précédente. pour être capable de faire cela, il faut utiliser la foncion du moteur appelée get_section, qui retourne un ensemble de métadonnées à propos de la section dont a spédifié le chemin d'accès (relatif au dossier content/). ceux qui nous intéressent dans notre cas sont :

  1. section.title: le titre de la section que l'on a récupéré
  2. section.permalink: le lien y menant

ces deux informations nous offrent la possibilité de créer des liens menant à la section obtenue avec la fonction get_section, comme ceci :

{% set ancestor = get_section(path=ancestors | last) %}
<a href="{{ ancestor.permalink }}">{{ ancestor.title }}</a>

fil d'ariane automatique

précédemment, nous avons utilisé la transformée last pour obtenir uniquement le dernier élément des ancêtres de la page. c'est ce que j'utilise dans ce site pour créer le petit lien "revenir à : [truc]" en haut de mes pages. cependant, on pourrait aussi l'utiliser pour créer un fil d'ariane pour notre site, comme cela :

{% for a in ancestors %}
    {% set ancestor = get_section(path=a) %}
    > <a href="{{ ancestor.permalink }}">{{ ancestor.title }}</a>
{% endfor %}
> {% if page.title %} {{ page.title }} {% else %} {{ section.title }} {% endif %}

pour cette page, le résultat ressemblerait à :

> bienvenue dans mon antre > mon blog > mettre en place un site plurilingue avec Zola

remarque sur cette section

dans les extraits de code de cette section, j'ai fait le présupposé que la variable ancestors était accessible. ce n'est cependant pas le comportement par défaut de Zola, il est donc nécessaire d'ajouter le code ci-dessous avant chaque utilisation de la variable pour que cela fonctionne invariablement entre les sections et les pages. il expose la variable correspondante au type de template html actuellement traité

{% if page.ancestors %}
    {% set ancestors = page.ancestors %}
{% else %}
    {% set ancestors = section.ancestors %}
{% endif %}

de plus, la partie concernant les liens de retour arrière est correcte pour toutes les pages, à l'exception de la page d'accueil

ce problème est dû à la manière dont Tera se débrouille pour que {{ ancestors | last }} retourne toujours un élément, même quand le tableau ancestors est vide. dans ce cas, la transformée retourne une chaîne de caractères vide, ce qui engendre l'erreur ci-dessous quand la page d'accueil est rendue :

Error: Reason: Function call 'get_section' failed
Error: Reason: Section `` not found.

pour éviter cette erreur, on peut simplement entourer l'appel à la variable ancestors dans une branche if, donnant le code suivant :

{% if page.ancestors %}
    {% set ancestors = page.ancestors %}
{% else %}
    {% set ancestors = section.ancestors %}
{% endif %}

{% if ancestors | length > 0  %}
    {% set parent = get_section(path=(ancestors | last)) %}
    <a href="{{ parent.permalink }}" style="margin-left: 0.5rem;">> {{ trans(key="back", lang=lang )}} {{ parent.title }}</a>
{% endif %}

remarque : dans l'extrait de code ci-dessus, on fait référence à la variable traduite back. pour voir comment ces variables sont mises en place, allez voir la section traduction automatique de termes de cet article

conclusion

Zola est un super générateur de site statique, et il permet beaucoup de choses de base. cependant, il manquait des fonctionnalités que j'avais vraiment envie d'avoir sur mon site. elles n'étaient pas triviales à implémenter, et la documentation officielle n'était pas toujours d'une très grande aide pour comprendre comment tout fonctionne sous le capot

astuce si jamais vous vous perdez dans ce que l'objet que vous manipulez contient, écrivez simplement le bout de code ci-dessous dans n'importe quel template que la page actuelle utilise et vous verrez de quels champs vous disposez

{% for key, value in OBJECT %}
<p><b>{{ key }}</b> -> {{ value }}</p>
{% endfor %}

cette petite chose m'a pas mal aidée quand j'essayais d'implémenter les fonctionnalités décrites dans cet article, donc je peux personnellement attester de tout le bien qu'elle fait

merci d'avoir lu cet article, tout retour que vous auriez à son propos sera le bienvenu 💜

-- eckler