Contenu bilingue avec une i18n de premier ordre
Comment fonctionne le routage anglais-à-la-racine + français-sous-/fr, comment apparier les articles traduits avec translationKey, et comment ajouter une troisième locale.
Ce site est bilingue. L’anglais est servi à la racine de l’URL, et
le français vit sous /fr. Le sélecteur de langue dans la barre
de navigation supérieure est contextuel : sur une page d’article, il
vous emmène sur l’article traduit équivalent ; sur une page de
listing, il échange le préfixe de locale en préservant le reste du
chemin.
Vous lisez cet article en français. Cliquez sur le sélecteur de langue
en haut de la page pour le lire en anglais — ce trajet fonctionne
parce que les deux fichiers partagent la même translationKey.
Si vous ne publiez qu’en une seule langue, définissez
multilingual: false dans src/config.ts. Le
sélecteur de langue disparaît de la barre supérieure et les balises
<link rel="alternate" hreflang> ne sont plus émises. Pour retirer
également les routes de l’autre locale du build, supprimez ses
dossiers de contenu (src/content/posts/<lang>/,
src/content/pages/<lang>/), son miroir src/pages/<lang>/, et
retirez-la de SITE.locales.
Vous pouvez laisser multilingual: true et ne traduire que les
articles de votre choix. Le thème inspecte chaque page d’article
au build et détecte les locales qui possèdent réellement une
traduction :
- Le sélecteur de langue ne propose que les locales dont le pendant existe. Si aucune autre locale n’a de traduction de l’article courant, le sélecteur est entièrement masqué pour cette page — les visiteurs ne tombent jamais sur une 404.
<link rel="alternate" hreflang="...">(et lex-defaultcorrespondant) n’est émis que pour les locales qui possèdent bien l’article, afin que les moteurs de recherche ne voient pas d’alternates cassées.- Les pages de listing, de tags, de catégories, les archives, la recherche et la page « À propos » existent dans toutes les locales configurées ; le sélecteur reste donc visible sur ces routes et se contente de changer le préfixe d’URL.
Appariez les articles traduits en leur donnant la même
translationKey dans le frontmatter — voir Apparier les traductions avec translationKey
plus bas.
| Locale | Racine | Articles | Tags |
|---|---|---|---|
en | / | /posts/<slug> | /tags/... |
fr | /fr/ | /fr/posts/<slug> | /fr/tags/... |
La locale par défaut (anglais) n’a jamais de préfixe. C’est appliqué à trois endroits qui s’accordent :
astro.config.mjs→i18n.routing.prefixDefaultLocale: falsesrc/config.ts→defaultLocale: 'en'src/i18n/utils.ts→localePrefix()retourne''pour la locale par défaut.
Les articles vivent dans des dossiers par locale :
src/content/posts/├── en/│ ├── welcome.md│ ├── i18n-bilingual-content.md│ └── ...└── fr/ ├── welcome.md ├── i18n-bilingual-content.md └── ...Le champ lang est inféré depuis le chemin du fichier — vous
n’avez pas à le définir dans le frontmatter. Override uniquement dans
des cas inhabituels.
Deux articles qui partagent la même translationKey sont considérés
comme traductions l’un de l’autre. Le sélecteur de langue s’en sert
pour atterrir sur l’article équivalent au lieu de rebondir vers la
page d’accueil de la locale.
translationKey: welcometranslationKey: welcomeSi la traduction correspondante n’existe pas pour la page active
(article anglais sans version française), le sélecteur retombe sur
la page d’accueil de la locale (/ ou /fr/) au lieu de servir
une 404 au lecteur.
Le fallback de slug fonctionne aussi : si vous omettez
translationKey, le thème utilise le slug du fichier. Donc des slugs
identiques entre en/ et fr/ sont auto-appariés sans métadonnée
supplémentaire.
Chaque libellé d’interface — navigation, boutons, messages d’erreur,
labels d’accessibilité — vit dans
src/i18n/ui.ts, indexé par locale. TypeScript
garantit que toutes les clés sont présentes dans toutes les locales :
en oublier une fait échouer le build.
const t = useTranslations('fr');t('nav.home'); // 'Accueil'formatDate(d, 'fr'); // '24 avril 2026'Le helper formatDate utilise Intl.DateTimeFormat pour que dates,
nombres et chaînes de temps de lecture s’affichent dans la locale
active.
- Un flux RSS distinct est généré par locale :
/rss.xml(EN) et/fr/rss.xml(FR). Le lien est dans la rangée des liens sociaux de la barre latérale. - Le sitemap (intégration
@astrojs/sitemap) émet des alternates hreflang pour chaque page traduite. Les moteurs de recherche s’en servent pour servir la bonne langue au bon lecteur. - Les balises
<link rel="alternate" hreflang="...">sont aussi injectées dans le<head>parSEO.astro.
Le code-base est conçu pour que ce soit une tâche en cinq étapes.
Pour ajouter de :
- Ajouter
'de'àSITE.localesdanssrc/config.ts. - Ajouter un bloc
dedanssrc/i18n/ui.ts— TypeScript se plaindra tant que toutes les clés ne sont pas présentes, c’est voulu. - Mettre à jour les switches conscients de la locale dans
src/i18n/utils.ts—htmlLang,localeLabel,formatDate. Chacun a un fallback, donc le site continue de compiler avant que vous n’ayez tout rempli. - Refléter les dossiers de routes sous
src/pages/de/...(copier depuissrc/pages/fr/...— chaque fichier appelle essentiellementgetPosts('de')). - Ajouter des articles sous
src/content/posts/de/...et des pages soussrc/content/pages/de/.... DéfinisseztranslationKeypour les apparier avec leurs frères EN/FR.
C’est tout le cérémonial. Le sitemap, le RSS, la recherche, les widgets de la barre latérale et le sélecteur de langue captent la nouvelle locale automatiquement.
Pagefind respecte l’attribut lang sur <html>, que le thème
définit par page depuis la locale inférée. La modale de recherche
filtre les résultats à la locale courante, donc les lecteurs
français n’ont pas accidentellement de résultats anglais.
| Symptôme | Solution |
|---|---|
Le sélecteur tombe toujours sur / | La traduction de la page active n’est pas encore écrite. |
| Format de date erroné | Mettez à jour le switch formatDate dans src/i18n/utils.ts. |
| Le build se plaint d’une clé manquante | Ajoutez la clé à chaque bloc de locale dans ui.ts. |
| Article FR avec UI anglaise | Le chemin doit être posts/fr/... (pas posts/fr-FR/...). |