Le CMS · 3

Les mots,
séparés de la structure

1681 textes dans un fichier plat, où la clé est le chemin du texte dans le document. Traduire, c'est copier le fichier et changer les valeurs : la structure est partagée, donc elle ne peut pas diverger d'une langue à l'autre.

Textes
1 681
clés dans txt_fr.json
Langues au catalogue
27
endonymes et drapeaux
Langues compilées
3
de, en, fr
Pour en ajouter une
1 fichier
aucun autre réglage
§1

Un texte par clé, à plat

site/textes/txt_fr.json contient 1681 textes, sans aucune imbrication. Pas d'objets dans des objets : une clé, une chaîne.

Cette platitude est un choix. Une structure imbriquée obligerait le traducteur à reproduire l'arborescence du document — et à la maintenir quand elle change. À plat, un simple diff de clés révèle instantanément ce qui manque dans une traduction.

{
  "_langue": "fr",
  "_nom": "Français",
  "_aide": "Un texte par clé. La clé décrit le CHEMIN…",

  "a11y.menu":
      "Menu…",
  "a11y.nav-principale":
      "Navigation principale…",
  "a11y.ouvrir-menu":
      "Ouvrir le menu…",
  …
}

Mesuré. Modifier un texte dans ce fichier change exactement une ligne du HTML produit. La mise en page n'est pas concernée — c'est la garantie que la séparation entre les mots et la structure est réelle.

§2

La clé décrit le chemin dans le document

Une clé n'est pas un identifiant arbitraire : c'est l'adresse du texte dans l'arborescence de la page. Elle se lit de gauche à droite comme un chemin.

Cela a une conséquence pratique : on retrouve un texte sans chercher, et un agent IA peut construire la clé par déduction plutôt que par consultation.

accueil . concept . groupe-1 . etape-2 . titre
   │         │         │          │        │
   │         │         │          │        └─ la propriété
   │         │         │          └────────── le 2ᵉ bloc étape
   │         │         └───────────────────── le 1ᵉʳ groupe
   │         └─────────────────────────────── la section « concept »
   └───────────────────────────────────────── la page
Comment la structure appelle un texte

Les fichiers de site/structure/ ne contiennent aucun mot. Ils nomment des clés, préfixées d'un @.

{
  "type": "etape",
  "numero": "02",
  "etiquette": "@accueil.concept.groupe-1.etape-2.etiquette",
  "titre":     "@accueil.concept.groupe-1.etape-2.titre",
  "texte":     "@accueil.concept.groupe-1.etape-2.texte",
  "image":     "etape-02"
}

C'est ce qui permet de compiler la même structure en français et en allemand : seule la table de résolution change. La structure est partagée, donc elle ne peut pas diverger d'une langue à l'autre.

§3

Ajouter une langue : déposer un fichier

Il n'y a pas de réglage à activer, pas de case à cocher, pas de menu d'administration. La présence du fichier est la déclaration.

Déposer txt_de.json puis txt_en.json dans site/textes/ a produit 42 pages en 3 langues, avec leurs URL préfixées, leurs liens hreflang réciproques, le sélecteur de langue et les entrées de sitemap. Aucune autre intervention.

Une seule structure, plusieurs tables de textes. Les pages produites partagent la même architecture — elle ne peut pas diverger d'une langue à l'autre.
Une seule structure, plusieurs tables de textes. Les pages produites partagent la même architecture — elle ne peut pas diverger d'une langue à l'autre.
$ cp site/textes/txt_fr.json site/textes/txt_de.json
$ # …traduire les valeurs, garder les clés…
$ ./verifier.sh

  ✓ de     13 pages   318 Ko
  ✓ fr     13 pages   315 Ko
  26 pages en 0.043 s
Ce que ces balises font — et ne font pas

Trois croyances très répandues n'ont pas résisté à la vérification aux sources primaires. Le détail complet est sur la page Multilingue.

Ce qu'on lit partoutCe que dit la source primaire
hreflang évite une pénalité de contenu dupliquéUne page traduite n'est pas un doublon, par définition de Google. hreflang sert à choisir l'URL à afficher, pas à éviter une sanction
<html lang> aide au référencementGoogle le classe dans ses Unsupported tags and attributes, à côté de meta keywords. Il reste obligatoire pour l'accessibilité — WCAG 3.1.1, niveau A
Déclarer hreflang en HTML et en sitemap est plus sûr« There's no benefit in Search. » Les méthodes sont équivalentes ; il faut en choisir une seule

Le convertisseur déclare donc hreflang en HTML uniquement, et pose lang pour l'accessibilité — jamais en le présentant comme un levier SEO.

Ce que le convertisseur produit automatiquement : les URL préfixées (/de/…), les liens hreflang réciproques entre toutes les versions, la balise x-default, l'attribut lang et le sens de lecture sur <html>, le sélecteur de langue dans la navigation et le pied de page, et les entrées de sitemap correspondantes.

§4

Comment nommer un fichier de langue

Un seul motif, sans exception : txt_XX.json, où XX est le code de la langue tel qu'il figure au catalogue.

Le code suit la norme BCP 47 — celle qu'attend l'attribut lang du HTML. Deux formes sont admises : xx pour une langue, xx-XX pour une variante régionale.

site/textes/
  txt_fr.json          français          ← langue maîtresse
  txt_de.json          allemand
  txt_en.json          anglais
  txt_pt.json          portugais         ← il suffit de le déposer
  txt_de-CH.json       allemand (Suisse) ← variante régionale
  txt_ar.json          arabe             ← sens de lecture inversé
FormeExempleQuand l'employer
txt_xx.jsontxt_es.jsonune langue, sans distinction de pays — le cas courant
txt_xx-XX.jsontxt_de-CH.jsonune variante régionale, quand le vocabulaire ou les prix diffèrent réellement d'un pays à l'autre

La langue maîtresse est celle déclarée dans site/donnees/site.json (langue_principale). C'est elle qui sert de référence : toute autre langue est comparée à ses clés, et compilée sous une URL préfixée — /de/, /de-CH/. La maîtresse, elle, reste à la racine.

Ce que le moteur refuse à la lecture du catalogue

Trois champs conditionnent directement le HTML produit : code_html devient l'attribut lang, sens décide de dir, locale alimente og:locale.

Une valeur malformée ne casse rien de visible — elle produit du balisage faux, en silence. Le moteur vérifie donc le catalogue avant toute compilation :

  • code non conforme à xx ou xx-XX
  • champ absent parmi les cinq
  • sens autre que ltr ou rtl
  • locale hors du format xx_XX

Ce contrôle a trouvé un vrai défaut : les trois variantes suisses portaient une locale de-CH_DE-CH — produite par un repli automatique. Corrigées en de_CH.

§5

Les 27 langues du catalogue

site/langues/catalogue.json décrit chaque langue disponible : son nom dans sa propre langue, son drapeau, son code HTML et son sens de lecture.

Le nom est un endonyme — « Deutsch », pas « Allemand ». C'est la règle en interface multilingue : un visiteur cherche le nom qu'il reconnaît, pas sa traduction dans une langue qu'il ne lit pas.

Fichier à déposerCodeEndonymeDrapeauSensog:locale
txt_ar.jsonarالعربية🇸🇦rtlar_AR
txt_bg.jsonbgБългарски🇧🇬ltrbg_BG
txt_cs.jsoncsČeština🇨🇿ltrcs_CZ
txt_da.jsondaDansk🇩🇰ltrda_DK
txt_de.jsondeDeutsch🇩🇪ltrde_CH
txt_de-CH.jsonde-CHDeutsch (Schweiz)🇨🇭ltrde_CH
txt_el.jsonelΕλληνικά🇬🇷ltrel_GR
txt_en.jsonenEnglish🇬🇧ltren_GB
txt_es.jsonesEspañol🇪🇸ltres_ES
txt_fi.jsonfiSuomi🇫🇮ltrfi_FI
txt_fr.jsonfrFrançais🇫🇷ltrfr_CH
txt_fr-CH.jsonfr-CHFrançais (Suisse)🇨🇭ltrfr_CH
txt_he.jsonheעברית🇮🇱rtlhe_IL
txt_hr.jsonhrHrvatski🇭🇷ltrhr_HR
txt_hu.jsonhuMagyar🇭🇺ltrhu_HU
txt_it.jsonitItaliano🇮🇹ltrit_CH
txt_it-CH.jsonit-CHItaliano (Svizzera)🇨🇭ltrit_CH
txt_nl.jsonnlNederlands🇳🇱ltrnl_NL
txt_no.jsonnoNorsk🇳🇴ltrnb_NO
txt_pl.jsonplPolski🇵🇱ltrpl_PL
txt_pt.jsonptPortuguês🇵🇹ltrpt_PT
txt_ro.jsonroRomână🇷🇴ltrro_RO
txt_ru.jsonruРусский🇷🇺ltrru_RU
txt_sk.jsonskSlovenčina🇸🇰ltrsk_SK
txt_sv.jsonsvSvenska🇸🇪ltrsv_SE
txt_tr.jsontrTürkçe🇹🇷ltrtr_TR
txt_uk.jsonukУкраїнська🇺🇦ltruk_UA
Le sens de lecture, et sa limite honnête

Le champ sens vaut ltr ou rtl. Le convertisseur le pose sur <html dir>, et la feuille de style utilise des propriétés logiques (margin-inline-start plutôt que margin-left) pour que la mise en page se retourne d'elle-même.

La limite : ce mécanisme existe mais n'a jamais été testé avec une traduction arabe ou hébraïque réelle. Il est correct en théorie ; il n'est pas prouvé. C'est une limite connue, pas une garantie.

§6

Le sélecteur de langue produit

Il est engendré automatiquement dès qu'une seconde langue existe : à droite de la navigation sur ordinateur, dans le pied de page sur mobile.

Trois décisions d'accessibilité y sont prises par construction.

<details class="lang">
  <summary aria-label="Choisir la langue">
    <span aria-hidden="true">🇫🇷</span>
    <span>Français</span>
  </summary>
  <ul>
    <li><a href="/de/" lang="de" hreflang="de">
      <span aria-hidden="true">🇩🇪</span> Deutsch</a></li>
  </ul>
</details>
Les trois décisions, et leur motif
  1. Un <details> natif, pas du JavaScript. Le menu s'ouvre et se ferme au clavier sans une ligne de script, fonctionne si le JavaScript échoue, et est annoncé correctement par les lecteurs d'écran.
  2. Le drapeau porte aria-hidden. Un drapeau n'est pas une langue — il désigne un pays. Lu à voix haute, « drapeau de l'Allemagne » n'aide pas ; « Deutsch » si. Le drapeau reste visible, mais il est retiré de l'arbre d'accessibilité.
  3. Chaque lien porte lang et hreflang. Le premier fait prononcer « Deutsch » avec la phonétique allemande ; le second annonce la correspondance à Google.
§7

Une traduction incomplète est refusée

Si une clé manque dans txt_de.json, le convertisseur ne remplit pas le trou avec le français. Il ne compile pas cette langue — et compile les autres normalement.

Le motif est de référencement autant que d'honnêteté : une page partiellement traduite est un signal de mauvaise qualité, et un mélange de deux langues sur une même page est pire qu'une page absente.

$ ./verifier.sh

  ⚠ de : traduction incomplète — 2,6 % couvert
      1 527 clés manquantes, dont :
        accueil.concept.chapo
        accueil.concept.groupe-1.etape-1.titre
        …
      cette langue n'est pas compilée.

  ✓ fr     13 pages   315 Ko

Vérifié. Avec une traduction allemande couvrant 2,6 % des clés, l'allemand a bien été refusé et le français a compilé normalement. Le refus est par langue, jamais global : une traduction en chantier ne bloque pas la publication du site.