Créer des thèmes
Tout ce que vous écrivez dans FolioPress — pages, articles, étiquettes — vit à part de la façon dont c’est affiché. Cette couche de présentation est un thème, et ce guide en démonte un : le dossier, les gabarits, le fichier de réglages et exactement quelles données chaque gabarit reçoit. Si vous êtes à l’aise avec HTML et CSS, vous avez tout ce qu’il faut.
Un thème est un dossier
Changez de thème et les mêmes pages, articles et menu sont redessinés — rien dans le contenu ne change, seulement sa tenue. Un thème est un dossier contenant trois choses : un fichier de réglages, des gabarits et des ressources statiques.
Quatre gabarits sont obligatoires — base.html, home.html, page.html et post.html — et un thème qui en manque ne se charge pas. Tout le reste est facultatif : une archive d’étiquette, une page de recherche, des pages d’erreur personnalisées, une mise en page dédiée à une page précise, et un dossier locales/ pour les mots du thème lui-même. Les thèmes se modifient directement dans l’administration, sous Thèmes, s’exportent en zip et s’importent sur un autre site.
D’une URL à une page
Il est utile de voir ce que FolioPress fait d’une requête avant même que votre gabarit ne s’exécute, car cela explique pourquoi les gabarits sont si courts.
Deux choses à retenir. La langue est lue dans le préfixe de l’URL, si bien que /fr-fr/a-propos et /about sont la même page en deux langues, et le contexte remis à votre gabarit est déjà dans la bonne — chaque titre, slug, libellé de menu et contenu. Et le gabarit est choisi pour vous : pour une page dont le slug en langue principale est about, FolioPress cherche d’abord page-about.html et se rabat sur page.html. Il n’y a rien à câbler.
Un squelette, plusieurs pages
Les gabarits sont du Jinja2 — du HTML avec deux sortes d’accolades en plus. {{ page.title }} affiche une valeur. {% if page.poster_image_url %} … {% endif %} est une instruction : boucles, conditions et héritage.
base.html contient tout ce que les pages ont en commun — le <head>, la navigation, le sélecteur de langue, le pied de page, les scripts — avec un seul emplacement vide au milieu. Tous les autres gabarits commencent de la même façon et remplissent cet emplacement :
{% extends "base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
{% endblock %}
Tout ce qui se répète d’un gabarit à l’autre — une carte d’article, une barre de pagination — va dans partials/ et s’inclut avec {% include "partials/post-card.html" %}.
theme.yml : le panneau de réglages
C’est ici qu’un thème se décrit et configure quelques comportements, sans toucher à un gabarit. Voici ce qu’utilisent les thèmes fournis :
title: "Ink"
description: "Un thème sobre et minimaliste"
version: "1.0.0"
author: "FolioPress"
meta:
supports_dark_mode: true
responsive: true
categories: [minimal, portfolio]
settings: # libre ; disponible dans les gabarits sous theme_settings
primary_color: "#2563eb"
home:
posts: 10 # combien d’articles la page d’accueil reçoit
order_by: "-published_at" # les plus récents d’abord
has_pagination: false # une coupure, ou de vrais liens de page ?
image_ratios:
poster: "16:9" # ce que propose l’outil de recadrage de l’administration ici
gallery: "free"
page: { posts: 10, order_by: "-published_at", has_pagination: false }
post: { posts: 3, order_by: "-published_at" } # articles liés sous un article
tag: { posts: 12, order_by: "-published_at", has_pagination: true }
page-about: # surcharge pour une page, par son slug en langue principale
image_ratios: { poster: "1:1" }
post-tutorials: # surcharge pour les articles dont la première étiquette a ce slug
image_ratios: { poster: "4:3" }
| Clé | Ce qu’elle contrôle |
|---|---|
title, description, version, author |
Affichés sur la page Thèmes de l’administration. |
meta.supports_dark_mode, meta.responsive, meta.categories |
Descriptifs ; utilisés pour la galerie de thèmes. |
meta.social_image |
Un fichier dans le static/ du thème, en 1200x630, utilisé comme image d'aperçu quand une page n'a pas d'image à la une. Sans lui, cette page est partagée avec l'icône de 180x180. |
settings |
Ce que vous voulez. Transmis à chaque gabarit sous theme_settings, pour qu’une couleur ou un slogan puisse vivre ici plutôt que dans le CSS. |
home, page, post, tag |
La liste d’articles que reçoit ce type de gabarit : posts (combien), order_by, et si has_pagination ajoute de vrais liens de page ou coupe simplement la liste. |
order_by |
-published_at (les plus récents d’abord) ou published_at ; de même created_at et updated_at, avec ou sans le - initial ; ou random. |
image_ratios |
Ce que l’outil de recadrage de l’administration propose lors de l’édition de ce type de contenu : un ratio comme 16:9, une liste de ratios, ou free. Valeurs distinctes pour poster et gallery. |
page-<slug>, post-<tag-slug> |
Les mêmes clés, surchargeant page/post pour une page (par son slug en langue principale) ou pour les articles dont la première étiquette porte ce slug. |
Ce que chaque gabarit peut voir
Un gabarit ne va jamais rien chercher. On lui remet un ensemble de valeurs prêtes à l’emploi — FolioPress l’appelle le contexte — et il affiche ce dont il a besoin. Une partie de l’ensemble est identique sur chaque page ; le reste dépend du gabarit en cours de rendu.
Commun à tous les gabarits
| Variable | Ce que c’est |
|---|---|
page |
La page ou l’article consulté, déjà dans la langue courante (voir plus bas). None sur l’archive d’étiquette et la recherche. |
site_title, site_description, footer |
Issus des Réglages du site, dans la langue courante. footer est déjà rendu depuis Markdown. |
navigation_menu |
Le menu configuré dans l’administration : une liste de {title, url, slug, page_id}, chaque url prête à l’emploi dans cette langue. |
languages |
Les langues du site, indexées par code. Chaque entrée : code, name, native_name, short_code, menu_label, flag. |
current_language, current_language_code, default_language_code |
La langue en cours de rendu (même forme qu’une entrée de languages), son code, et la langue principale du site. |
language_switch_urls |
Indexé par code de langue : l’URL de cette même page dans cette langue, slug traduit compris. |
home_url, url_language_code |
La page d’accueil dans la langue courante (/ ou /pt-pt), et le préfixe en minuscules à placer devant toute URL que vous construisez vous-même ('' pour la langue principale). |
static_url, media_url |
D’où sont servis les fichiers de ce thème (/static/<thème>) et où vivent les images téléversées. |
page_title, meta_description, canonical_url, hreflang_urls, social_image_url, twitter_card_type, json_ld |
Tout ce dont le <head> a besoin : un titre d’onglet sensé, des URL canonique et alternatives par langue absolues, une image Open Graph et des données structurées — le tout précalculé. |
theme_name, theme_info, theme_settings |
Le nom du thème actif, son theme.yml complet et son bloc settings. |
t |
Le traducteur du thème lui-même : {{ t('Read more') }} cherche la phrase dans locales/<langue>.json (voir plus bas). |
request |
La requête FastAPI, pour le rare cas où vous avez besoin du chemin ou d’un paramètre. |
L’objet page
La même forme, qu’il s’agisse d’une page, d’un article ou d’un élément de n’importe quelle liste d’articles :
| Champ | Notes |
|---|---|
title, slug, url |
Dans la langue courante. url est complète, préfixe de langue ou domaine propre à la langue compris. |
content |
Déjà converti de Markdown en HTML — affichez-le avec {{ page.content | safe }}. |
excerpt, meta_description |
Les formes courtes, dans la langue courante. |
poster_image_url |
L’URL complète de l’image de couverture, ou None. |
gallery, content_gallery |
Des listes d’images : {id, url, title, width, height, original_filename}. Utilisez image.url directement. |
tags |
{slug, title, url} pour chaque étiquette publique. Les étiquettes privées ne sont jamais incluses. |
is_post, is_featured, published_at, created_at, updated_at |
S’il s’agit d’un article, s’il est mis en avant, et ses dates. |
id |
L’identifiant en base — pratique pour les ancres et les clés de cache. |
Listes d’articles
| Gabarit | Variable | Contenu |
|---|---|---|
home.html |
posts (alias recent_posts), pages, is_home |
Les articles partageant une étiquette avec la page d’accueil, en nombre et ordre définis par la section home de theme.yml. Une page d’accueil sans étiquette ne reçoit aucun article — étiquetez-la pour la remplir. |
page.html |
posts (alias page_posts) |
Les articles partageant une étiquette avec cette page, selon la section page. C’est ainsi qu’une page « Blog » liste ses articles. |
post.html |
related_posts, is_post |
Les autres articles partageant une étiquette avec celui-ci, selon la section post. |
tag.html |
tag (title, slug), posts |
Tous les articles publiés portant cette étiquette, selon la section tag. |
search.html |
search_query, search_results, results_count |
Le terme saisi et les pages et articles correspondants. |
Chaque liste d’articles est accompagnée d’une pagination de même forme : has_pagination, current_page, total_pages, has_previous, has_next, previous_page, next_page, et pagination_urls avec first, previous, next et last — None là où il n’y a nulle part où aller. Avec `has_pagination` désactivé dans `theme.yml`, la liste est simplement coupée à `posts` et les indicateurs restent à faux.
Les langues et le sélecteur
Deux des champs de langue sont calculés pour vous. short_code est le code de langue seul — EN — sauf si le site active aussi une autre variante de la même langue, auquel cas il garde sa région : EN-US et EN-GB. menu_label suit la même règle avec les noms : English, ou English (US) et English (UK). C’est exactement la règle qu’utilise le sélecteur de langue de l’administration elle-même, si bien que le site et l’administration ne se contredisent jamais sur un libellé. Un sélecteur complet est donc court :
{% if languages | length > 1 %}
<button type="button" aria-label="{{ t('Change language') }}">
{{ current_language.short_code }}
</button>
<ul>
{% for lang in languages.values() | sort(attribute='menu_label') %}
<li>
{% if lang.code == current_language.code %}
<span aria-current="true">{{ lang.menu_label }}</span>
{% else %}
<a href="{{ language_switch_urls[lang.code] }}" hreflang="{{ lang.code }}">{{ lang.menu_label }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
language_switch_urls pointe déjà vers la même page dans chaque langue — le slug traduit, ou la page d’accueil si cette traduction n’existe pas encore —, de sorte qu’un sélecteur ne construit jamais d’URL lui-même.
Les mots du thème lui-même
Le contenu est traduit par l’auteur dans l’administration. Les mots propres au thème — « Lire la suite », « Rechercher », « Tous droits réservés » — sont traduits par le thème, dans templates/locales/<langue>.json :
{
"Read more": "Lire la suite",
"Search": "Rechercher",
"Change language": "Changer de langue"
}
Entourez la phrase de {{ t('Read more') }} et elle s’affiche dans la langue du visiteur. Une phrase absente du fichier de cette langue se rabat sur le fichier de la langue principale, puis sur la phrase elle-même — un thème à moitié traduit fonctionne donc toujours, simplement en anglais là où la traduction n’a pas encore été écrite. Les fichiers voyagent avec le thème lors de l’export.
Une page, sa propre mise en page
Une page d’atterrissage, un formulaire de contact, une grille de portfolio — parfois une page mérite une mise en page dont aucune autre n’a besoin. Ajoutez un gabarit nommé page-<slug>.html, où <slug> est le slug de cette page dans la langue principale, et FolioPress l’utilise pour cette page dans toutes les langues. Supprimez le fichier et la page revient discrètement à page.html.
{# templates/page-about.html #}
{% extends "base.html" %}
{% block content %}
<section class="about">
<img src="{{ page.poster_image_url }}" alt="">
<h1>{{ page.title }}</h1>
{{ page.content | safe }}
</section>
{% endblock %}
La page qu’un administrateur désigne comme page du formulaire de contact reçoit un petit supplément : is_contact_page est vrai, contact_form_action indique où le formulaire doit envoyer, et contact_guard_token / contact_honeypot_field sont deux champs cachés qui tiennent les robots à l’écart. Après un envoi, contact_status vous dit s’il faut afficher un remerciement ou une erreur.
Partez d’un thème qui fonctionne
Le moyen le plus rapide de construire un thème est d’ouvrir l’un des thèmes fournis — chacun a une démonstration en direct — et de le lire à côté de cette page. Dupliquez-le dans l’administration, modifiez le CSS, puis les gabarits, et vous aurez quelque chose à vous sans jamais être parti d’un fichier vide.
Deux habitudes qui font gagner du temps. Affichez les valeurs que le contexte vous donne plutôt que de les reconstruire — page.url, tag.url et language_switch_urls connaissent déjà les préfixes de langue et les domaines personnalisés. Et gardez le squelette dans base.html : si vous vous surprenez à coller un en-tête dans un second gabarit, il a sa place dans la base.