Site Astro multilingue : routage, contenu et le piège du prix par langue
Le routage i18n d'Astro est solide. Le difficile, c'est le modèle de contenu derrière, et le modèle tarifaire devant, où la plupart des CMS facturent chaque langue.
Un site bilingue est le cas normal dans une bonne partie de l’Europe, pas une fonctionnalité avancée. Une entreprise française avec la moindre exposition au tourisme, à l’export ou aux expatriés veut une seconde langue, souvent une troisième.
Astro gère bien la partie routage. Les deux choses qui font vraiment mal sont le modèle de contenu derrière et, moins évidemment, le modèle tarifaire devant.
Ce qu’Astro vous donne
Le routage i18n intégré couvre la mécanique :
export default defineConfig({
i18n: {
defaultLocale: 'fr',
locales: ['fr', 'en'],
routing: { prefixDefaultLocale: false },
},
});
Vous obtenez /services/ en français et /en/services/ en anglais, plus des aides pour construire des liens conscients de la langue. C’est sans opinion et ça ne vous gêne pas, ce qui est la bonne conception.
Ce que ça ne dit volontairement pas, c’est quelle forme doit prendre le contenu, et c’est là que se logent les vraies décisions.
Décision une : une traduction est-elle une entrée séparée, ou une entrée à plusieurs valeurs ?
C’est l’embranchement dont tout le reste découle, et se tromper coûte une migration plus tard.
Des entrées séparées signifie que services/plomberie.md et services/plumbing.md sont deux fichiers, deux lignes, deux objets indépendants reliés par une clé quelconque. C’est simple, chaque langue peut avoir sa propre structure, et une langue peut exister sans l’autre. Le coût, c’est que le lien entre les deux est une convention que vous entretenez, et que « quelles pages n’ont pas de version anglaise » devient une question à laquelle personne ne répond sans écrire un script.
Une entrée, plusieurs valeurs par champ signifie une seule entrée dont le title vaut { fr: "Plomberie", en: "Plumbing" }. Le lien est structurel, donc rien ne peut dériver. La complétude devient une question à laquelle le système sait répondre : il sait qu’un champ manque pour une langue. Le coût, c’est que les deux langues partagent la même forme, et qu’un champ qui n’a de sens que dans une langue devient bancal.
Nous avons choisi la seconde, champ par champ, et poussé un cran plus loin : chaque langue porte son propre état de publication. Le français peut être en ligne pendant que l’anglais reste en brouillon, sur la même entrée. Ça ressemble à un détail jusqu’à ce qu’on regarde un vrai client travailler, parce que l’alternative l’oblige soit à publier une page anglaise vide, soit à retenir la version française en otage d’une traduction qui n’est pas arrivée.
Décision deux : quels champs sont réellement traduits
Pas tous. Un prix est un prix. Un horaire est un horaire. Une photo est en général la même photo.
Marquer tous les champs comme traduisibles produit une expérience d’édition où le client passe d’un onglet à l’autre pour saisir deux fois le même nombre, et c’est le moyen le plus rapide de lui faire détester l’outil. En marquer trop peu donne une page anglaise avec du français au milieu.
La règle qui tient : traduisez ce qu’un humain réécrirait, pas ce qu’il retaperait. Titres, résumés, corps de texte, descriptions de référencement, textes alternatifs : oui. Nombres, dates, choix d’images, liens : non, jusqu’à preuve du contraire.
En pratique, c’est un indicateur par champ :
title: fields.text({ required: true, localized: true }),
price: fields.number({ unit: '€' }),
photo: fields.image(),
Trois champs, un seul traduit. L’éditeur voit des onglets de langue sur le titre et une valeur unique sur les deux autres, ce qui correspond à ce qu’il attend.
Décision trois : que faire quand une traduction manque
Trois options, et la mauvaise est celle par défaut dans la plupart des installations.
Retomber sur la langue par défaut. La page anglaise affiche du français. Le visiteur voit quelque chose, et c’est assez déroutant pour que beaucoup partent. Les moteurs voient du contenu dupliqué sur deux URL qui prétendent être dans deux langues.
Renvoyer une 404. Honnête, et ça casse votre sélecteur de langue : le lecteur clique sur « English » et atterrit sur une erreur, ce qui se lit comme un site cassé.
Ne pas proposer ce qui n’existe pas. Le sélecteur de langue n’offre que les langues qui ont réellement une version publiée de cette page, et le sitemap ne liste que ce qui existe. Personne ne rencontre jamais la page manquante.
La troisième demande plus de travail et c’est la seule qui respecte le lecteur. Elle suppose que le CMS sache, entrée par entrée et langue par langue, si quelque chose est publié, ce qui nous ramène à la décision une.
Le détail hreflang que tout le monde rate
Une fois le même contenu présent à deux URL, il faut le dire aux moteurs. Les règles sont simples et régulièrement mal appliquées :
- Chaque page déclare toutes ses versions linguistiques, y compris elle-même.
- Les déclarations doivent être réciproques. Si la page française pointe vers l’anglaise, l’anglaise doit pointer en retour, sinon les deux sont ignorées.
- Ajoutez un
x-defaultpour la version que doit recevoir un visiteur dont aucune langue ne correspond. - Utilisez des URL absolues.
Et celui qui piège ceux qui construisent un site multilingue avec un CMS : ne déclarez que des versions réellement publiées. Pointer un hreflang vers un brouillon est pire que de ne rien pointer, parce que vous venez de dire à un robot d’indexer une page qui renvoie une 404.
Voilà pourquoi l’état de publication par langue n’est pas un confort. C’est ce dont dépend la véracité de vos balises hreflang.
Quelle langue mettre à la racine
Question sans réponse universellement juste, et qui mérite d’être tranchée volontairement plutôt que par accident.
La langue par défaut à la racine (/services/ en français, /en/services/ en anglais) garde des URL courtes pour votre marché principal et préserve le référencement acquis si vous ajoutez une langue à un site vivant. C’est ce que donne prefixDefaultLocale: false. Le coût est l’asymétrie : une langue a des URL propres et l’autre ressemble à un ajout tardif, ce qui agace parfois le client dont le marché est celui qui porte le préfixe.
Toutes les langues préfixées (/fr/services/ et /en/services/) est symétrique, honnête, et plus simple à tenir quand une troisième langue arrive. Le coût est qu’ajouter cela à un site existant suppose de rediriger chaque ancienne URL, et que la racine doit désormais décider où envoyer les gens.
Cette décision sur la racine est un piège en soi. Rediriger / selon l’en-tête Accept-Language du navigateur paraît serviable et casse deux choses : un lien partagé amène désormais des gens différents sur des pages différentes, et les robots, qui n’envoient aucune préférence de langue, voient ce que vaut votre repli. Si vous le faites, faites-le uniquement sur la page d’accueil, respectez un choix explicite mémorisé par le visiteur, et ne redirigez jamais un lien profond.
Nous avons fait les deux choix sur notre propre site : l’anglais à la racine parce que le marché est plus grand, le français sous /fr/, et une redirection sur la seule page d’accueil, écrasée par la préférence enregistrée du visiteur.
Sitemaps et traductions manquantes, à nouveau
La règle qui gouverne votre sélecteur de langue gouverne aussi votre sitemap : listez ce qui existe, et ne déclarez que des alternatives publiées.
Le raccourci tentant est un générateur de sitemap configuré pour produire des paires hreflang par motif d’URL. Ça marche quand vos chemins correspondent d’une langue à l’autre, et ça ment discrètement quand ce n’est pas le cas. Nos propres pages légales françaises vivent à /fr/conditions/ quand les anglaises sont à /terms/, donc un appariement par motif aurait produit des alternatives pour la moitié du site et rien pour le reste. Nous avons désactivé cette option et laissé chaque page déclarer ses propres paires, ce qu’elle peut faire correctement puisqu’elle connaît ses traductions.
Le piège devant tout ça : le prix par langue
Voici la partie dont personne ne parle avant la facture.
Relevé à la source le 8 août 2026 : Storyblok comprend 2 langues dans son offre Growth à 99 $ par mois, puis facture 20 $ par mois pour chaque langue supplémentaire. Contentful donne 2 langues en gratuit et 3 en Lite, et Lite est à 300 € par mois. Prismic donne 2 langues en gratuit, jusqu’à 8 sur son palier à 675 $.
Relisez cela dans le contexte d’un site vitrine européen. Une entreprise française qui ajoute l’anglais et l’allemand demande trois langues. Chez Storyblok, cela fait 99 $ plus 20 $ par mois, indéfiniment, pour un seul site. Ce seul supplément coûte plus cher qu’un plan Menestrel Studio entier couvrant dix sites.
La logique est compréhensible : les langues sont une montée en gamme facile, parce que les clients qui en ont besoin sont en général plus gros. C’est simplement la mauvaise forme pour le marché où nous sommes, où le bilingue est la base plutôt qu’un supplément.
Nos langues sont illimitées dès le premier plan payant, ce qui est autant un choix de positionnement qu’un choix technique. Le plan gratuit s’arrête à deux, et c’est la seule chose de notre offre gratuite que nous changerions si cela ne coûtait rien.
Ce que le loader donne à vos pages
Côté rendu, le contenu arrive par un loader Content Layer standard, donc rien de votre code Astro n’est particulier. Les entrées portent leur langue, et les traductions sont adressables, ce qu’il vous faut pour le sélecteur de langue et les balises hreflang.
La forme pratique est : récupérer les entrées de la langue courante, les afficher, et pour chacune demander quelles autres langues ont une version publiée. Cette dernière question est celle qu’un CMS doit trancher pour vous, et celle à laquelle un dossier de fichiers markdown ne sait pas répondre.
Ce que nous ne faisons pas
Pas de traduction automatique. Nous avons retiré une intégration DeepL du plan en juillet 2026, volontairement. La traduction automatique du texte commercial d’un client produit un texte qui se lit comme une traduction automatique, et facturer au caractère ajoutait une ligne à l’usage dans un produit dont tout l’argument est un prix auquel on ne réfléchit pas. Si vous en voulez, traduisez à l’extérieur et collez.
Pas de circuit de validation par langue. Pas de rôle traducteur, pas de file de relecture, pas d’affectation. Si vous pilotez une équipe de traduction avec des échéances, il vous faut un outil fait pour ça, et ce n’est pas nous.
Pas de structure spécifique à une langue. Les deux langues partagent une forme, c’est le prix du modèle que nous avons choisi.
Si vous pesez tout cela face à un outil précis, le comparatif Storyblok détaille la tarification des langues, et la page tarifs répond directement aux questions gênantes.