Au printemps dernier, un client m'a donné un brief qui m'aurait fait gémir en 2022. Cinq langues. Onze marchés. Un développeur. « On a déjà WPML en place », a-t-il dit, avec la confiance de quelqu'un qui n'avait apparemment jamais regardé ses scores Lighthouse.
J'ai supprimé l'installation WordPress le deuxième jour. Reconstruit le tout avec Astro. Huit semaines plus tard, trois de ces régions étaient en première page de Google. La quatrième a suivi dans le mois. Cette expérience a essentiellement mis fin à ma relation avec WordPress pour le travail multilingue.
Voilà. Voici exactement ce que je fais maintenant.
Pourquoi le multilingue WordPress continue d'échouer sur la performance
Écoute, je n'aime pas critiquer WordPress. Seahawk a construit des centaines de sites WP et on en construira probablement des centaines de plus. Mais l'écart de performance sur les projets multilingues est vraiment difficile à défendre.
WPML et Polylang fonctionnent tous deux en générant des posts traduits au niveau de la base de données et en routant par PHP. Chaque requête frappe le serveur. Même avec un cache pleine page (WP Rocket, W3TC, ou celui que vous préférez), vous luttez toujours contre la surcharge d'initialisation PHP, les cache misses sur les pages locale à faible trafic, et la gestion des hreflang qui devient un cauchemar dès que vous avez plus de deux langues.
J'ai mesuré un site WP multilingue d'un client à un Time to First Byte de 1,3 secondes sur la locale italienne. Le même contenu, reconstruit en Astro en HTML pleinement statique : 94ms. Ce n'est pas une amélioration marginale. C'est une catégorie de produit différente.
Core Web Vitals est un facteur de classement confirmé, et les fichiers statiques servis depuis un nœud edge d'un CDN gagnent simplement cette bataille à chaque fois., and static files served from a CDN edge node simply win that battle every time.
Astro's Built-in i18n Routing (And Why It Changed Everything)
Astro a livré un vrai routage i18n en v3 et l'a affiné significativement en v4. Au moment où nous opérons en 2026, l'API est stable et genuinely pleasant à travailler avec.
Voici la configuration basique que j'utilise. Votre astro.config.mjs obtient un bloc i18n :astro.config.mjs gets an i18n block:
`` i18n: { defaultLocale: 'en', locales: ['en', 'de', 'fr', 'it', 'es'], routing: { prefixDefaultLocale: false } } `` i18n: { defaultLocale: 'en', locales: ['en', 'de', 'fr', 'it', 'es'], routing: { prefixDefaultLocale: false } } ``
Cette seule config dit à Astro de servir l'anglais à /, l'allemand à /de/, le français à /fr/, et ainsi de suite. Aucun plugin. Aucun paquet extra. C'est baked in./, German at /de/, French at /fr/, and so on. No plugin. No extra package. It's baked in.
Le prefixDefaultLocale: false est délibéré. La plupart des publics primaires de mes clients sont anglophones, donc je ne veux pas que /en/ pollue la structure canonique. Google peut comprendre la relation via hreflang.prefixDefaultLocale: false is deliberate. Most of my clients' primary audience is English-speaking, so I don't want /en/ polluting the canonical structure. Google can figure out the relationship via hreflang.
La Structure de Fichiers Qui Fait Vraiment Évoluer
C'est là que la plupart des tutoriels se trompent. Ils te montrent une simple structure pages/[locale]/index.astro et considèrent que c'est fait. Correct pour un site vitrine. Ça s'écroule dès que tu as 40 routes.pages/[locale]/index.astro and call it done. Fine for a brochure. Falls apart the moment you have 40 routes.
Ce que je fais à la place :
`` src/ pages/ index.astro (English) about.astro (English) [locale]/ index.astro about.astro i18n/ en.json de.json fr.json it.json es.json `` src/ pages/ index.astro (English) about.astro (English) [locale]/ index.astro about.astro i18n/ en.json de.json fr.json it.json es.json ``
Le répertoire i18n/ contient tes chaînes de traduction sous forme de fichiers JSON plats. en.json est la source de vérité. J'écris chaque autre locale par rapport à celui-ci. Une clé manquante dans de.json ? La page allemande bascule vers l'anglais pour cette chaîne au lieu de lever une erreur, parce que j'ajoute une petite fonction utilitaire t() qui gère le fallback avec élégance.i18n/ directory holds your translation strings as flat JSON files. en.json is the source of truth. I write every other locale against it. One key missing in de.json ? The German page falls back to English for that string rather than throwing an error, because I add a small t() utility function that handles fallback gracefully.
La Couche de Traduction : Reste Simple
J'ai essayé Paraglide JS, i18next, et une poignée d'autres. Pour les builds statiques Astro, ma recommandation honnête est : ne sur-ingénierie pas ça.
Un simple fichier utilitaire qui importe le JSON de la locale et retourne une fonction getter suffit pour 90 % des projets. Paraglide JS d'Inlang vaut le coup d'être considéré si tu as besoin de traductions type-safe et si ton équipe est assez grande pour se soucier des erreurs de compilation sur les clés manquantes. Pour du travail en solo ou des petites agences, c'est de l'overhead dont tu n'as pas besoin.Paraglide JS from Inlang is worth considering if you need type-safe translations and your team is large enough to care about compile-time errors on missing keys. For solo work or small agencies, it's overhead you don't need.
Ce qui m'importe : garder les fichiers de traduction plats plutôt que profondément imbriqués. "hero.cta.button.label" comme clé a l'air organisé jusqu'à ce que tu la cherches à 23h. "heroCTALabel" est moche mais trouvable."hero.cta.button.label" as a key sounds organised until you're searching for it at 11pm. "heroCTALabel" is ugly but findable.
Gérer les Pluriels et les Chaînes Dynamiques
La pluralisation allemande t'humiliera. L'arabe aussi si tu y vas (j'ai un jour aidé un client à s'étendre aux Émirats Arabes Unis et j'ai passé un après-midi complet juste sur le formatage des nombres).
Pour les pluriels, j'utilise un petit helper en ligne :
`` export function plural(count, one, other) { return count === 1 ? one : other; } `` export function plural(count, one, other) { return count === 1 ? one : other; } ``
Rudimentaire ? Oui. Mais ça couvre 95 % des cas pour les langues européennes. Si vous visez l'arabe, le russe ou le polonais, vous avez besoin des règles de pluriel CLDR appropriées. Le projet Unicode CLDR les documente exhaustivement. Construisez votre helper de pluriel selon ces spécifications ou vous vous tromperez.The Unicode CLDR project documents these exhaustively. Build your plural helper against those specs or you will get it wrong.
hreflang : La Partie Que Tout le Monde Se Trompe
Exactement. C'est là que le référencement multilingue vit ou meurt vraiment.
hreflang indique à Google quelle version d'une page servir à quelle locale. Si vous vous trompez, vous cannibalisez vos propres classements ou vous servez la version allemande aux francophones. J'ai vu les deux se produire sur des sites que j'ai repris.
Les règles :
- Chaque page doit inclure des balises hreflang pour chaque locale, y compris elle-même.
- Les balises doivent être bidirectionnelles. Si /de/about référence /fr/about, alors /fr/about doit référencer /de/about en retour.
/de/aboutreferences/fr/about, then/fr/aboutmust reference/de/aboutback. - Incluez une balise x-default pointant vers votre locale par défaut.
x-defaulttag pointing to your default locale. - Utilisez des URLs absolues. Toujours.
Dans Astro, je les génère dans un composant BaseLayout.astro que chaque page importe. Je passe le chemin actuel, je boucle sur le tableau de locales, et je génère automatiquement les balises <link rel="alternate">. Aucune maintenance manuelle.BaseLayout.astro component that every page imports. I pass in the current path, loop over the locale array, and spit out the <link rel="alternate"> tags automatically. No manual maintenance.
Oublier la balise x-default est l'erreur la plus courante que je vois. La documentation hreflang de Google elle-même est véritablement claire sur ce point. Lisez-la une fois correctement.x-default tag is the single most common mistake I see. Google's own hreflang documentation is genuinely clear on this. Read it once properly.
Sitemaps spécifiques à la locale
Un sitemap avec toutes les locales mélangées fonctionne. Mais des sitemaps séparés par locale (soumis individuellement dans Google Search Console) vous donnent des données d'indexation plus nettes et rendent beaucoup plus facile le repérage des erreurs de crawl sur une langue spécifique.
Mon build Astro génère cinq fichiers : sitemap-en.xml, sitemap-de.xml, et ainsi de suite. @astrojs/sitemap supporte cela avec l'option i18n directement. Utilisez-la.sitemap-en.xml , sitemap-de.xml , and so on. @astrojs/sitemap supports this with the i18n option out of the box. Use it.
Stratégie de contenu pour les sites statiques multilingues
Voici ce que la plupart des développeurs sautent complètement : la traduction elle-même.
Vous pouvez avoir une configuration Astro techniquement parfaite avec des hreflang sans faille et un sitemap qui ferait pleurer de joie un employé de Google. Si votre copie allemande a été passée dans DeepL et jamais retouchée par un humain, le site ne se classera pas bien en Allemagne.
Je ne fais pas mon difficile là-dessus. J'utilise DeepL constamment. C'est une excellente première ébauche. Mais pour n'importe quelle page sur laquelle vous voulez vraiment vous classer, un locuteur natif doit la lire. Pas nécessairement la réécrire. Juste la lire, corriger les passages bizarres, et confirmer que la formulation ressemble à quelque chose qu'une personne allemande dirait réellement.
Seahawk a eu un projet fintech l'année dernière où le client voulait sept langues et un délai de quatre semaines. Nous avons utilisé DeepL pour la base, puis embauché sept freelancers via Gengo pour des passes de révision à environ 0,04 £ par mot. Le budget total de traduction était inférieur à 800 £. Ce site se classe maintenant dans le top cinq sur quatre de ces marchés. Les économies fonctionnent.
Métadonnées spécifiques à la locale
Les balises de titre et les méta-descriptions doivent aussi être localisées. Pas seulement traduites. « Best accounting software » ne correspond pas directement à l'équivalent allemand en termes de recherche, car les volumes de mots-clés diffèrent.
Je conserve les métadonnées spécifiques à la locale dans les fichiers de traduction JSON sous un espace de noms meta dédié. Le modèle de page extrait de t('meta.title') et ne revient à une valeur par défaut que si la clé est manquante. Cela signifie qu'un responsable SEO peut modifier un fichier JSON par locale sans jamais toucher au code.meta namespace. The page template pulls from t('meta.title') and falls back to a default only if the key is missing. This means an SEO can edit one JSON file per locale without ever touching the code.
Déploiement : où héberger un site statique en 5 langues
Vercel et Netlify gèrent tous les deux correctement les builds Astro statiques. Mais pour le multilingue spécifiquement, vous voulez un cache edge par locale et la capacité de définir des en-têtes spécifiques à la locale.
J'ai standardisé sur Cloudflare Pages. L'offre gratuite couvre la plupart des budgets clients. Les requêtes sont servies depuis le nœud edge le plus proche mondialement, ce qui a énormément d'importance quand votre audience italienne reçoit les fichiers d'un centre de données en Virginie. Le réseau de Cloudflare a des nœuds à Milan, Francfort, Amsterdam. La différence de TTFB pour les visiteurs européens est mesurable.
Une chose à configurer explicitement : les en-têtes Cache-Control. Les fichiers statiques doivent avoir des TTL de cache longs (max-age=31536000, immutable pour les actifs hashés). Les pages HTML doivent avoir des TTL plus courts ou une politique stale-while-revalidate, particulièrement si vous faites des builds incrémentiels après des mises à jour de contenu.Cache-Control headers. Static files should have long cache TTLs ( max-age=31536000, immutable for hashed assets). HTML pages should have shorter TTLs or a stale-while-revalidate policy, particularly if you're doing incremental builds after content updates.
Déboguer les builds i18n avant leur mise en ligne
Quelques choses que je vérifie à chaque build Astro multilingue avant de pousser en production :
- Exécutez astro build localement et inspectez la sortie dist/. Chaque dossier de locale doit contenir chaque page. Si une route manque, l'étape build est l'endroit où la détecter.
astro buildlocally and inspect thedist/output. Every locale folder should have every page. If a route is missing, the build step is the place to catch it. - Utilisez Screaming Frog pour explorer le déploiement de prévisualisation. Filtrez par hreflang. Les pages orphelines (pages non référencées par aucune balise hreflang) apparaissent immédiatement.Screaming Frog to crawl the preview deployment. Filter by hreflang. Any orphaned pages (pages not referenced by any hreflang tag) show up immediately.
- Vérifiez la balise canonical x-default dans les DevTools du navigateur sur au moins la page d'accueil et une page interne.
x-defaultcanonical in browser DevTools on at least the homepage and one inner page. - Visitez manuellement une URL de locale dans un navigateur configuré pour la langue de cette locale. Cela semble évident. Cela rattrape quand même les problèmes.
Le problème le plus courant au moment du build que j'ai rencontré est d'oublier d'ajouter une nouvelle locale au tableau locales dans astro.config.mjs après avoir ajouté le fichier JSON. Le build ne génère pas d'erreur. Il ignore simplement la nouvelle locale en silence. Ennuyeux.astro.config.mjs locales array after adding the JSON file. The build doesn't error. It just silently ignores the new locale. Annoying.
FAQ
La i18n d'Astro fonctionne-t-elle avec le rendu côté serveur (SSR) ou uniquement avec la sortie statique ?
Les deux. Le routage i18n d'Astro fonctionne également en mode SSR. Les fonctions d'aide getRelativeLocaleUrl() et getAbsoluteLocaleUrl() fonctionnent de manière identique quel que soit le mode de sortie. Cela dit, si vous optez pour du statique complet, les avantages de performance sont nettement plus importants car vous servez du HTML prébâti sans coût d'exécution.getRelativeLocaleUrl() and getAbsoluteLocaleUrl() helper functions work identically regardless of output mode. That said, if you're going fully static, the performance benefits are significantly greater because you're serving pre-built HTML with no runtime cost.
Comment gérer la détection de locale et les redirections pour les nouveaux visiteurs ?
Pour les sites statiques, je le fais au niveau du CDN/edge, pas à l'intérieur d'Astro. Les Cloudflare Workers peuvent lire l'en-tête Accept-Language et rediriger vers le chemin de locale approprié. Ne faites pas cela à l'intérieur de votre application Astro car cela ajoute un aller-retour et rend la sortie statique non déterministe.Accept-Language header and redirect to the appropriate locale path. Don't do this inside your Astro app because it adds a round trip and makes the static output non-deterministic.
Quel est le meilleur moyen de gérer les fichiers de traduction à mesure que le site se développe ?
Conservez-les dans le contrôle de version aux côtés du code, pas dans un CMS séparé ou un système tiers de gestion de traduction, jusqu'à ce que le projet le justifie vraiment. À partir du moment où vous avez plus de trois langues et une équipe éditoriale active, regardez du côté de Tolgee ou Localazy. Les deux s'intègrent avec des fichiers JSON plats et offrent une synchronisation GitHub. En deçà de ce seuil, un document Notion partagé et des mises à jour JSON manuelles, c'est honnêtement suffisant.Tolgee or Localazy. Both integrate with flat JSON files and have GitHub sync. Below that threshold, a shared Notion doc and manual JSON updates is honestly fine.
Puis-je utiliser Astro Content Collections avec i18n ?
Oui, et c'est mon approche préférée pour les sites orientés blog. Structurez votre collection de contenu avec la langue comme segment de chemin : src/content/blog/en/, src/content/blog/de/. Filtrez ensuite par langue dans votre fonction getStaticPaths(). Chaque langue obtient son propre HTML statique au moment du build. Propre, rapide, et l'équipe éditoriale peut gérer les fichiers markdown sans toucher à aucun composant.src/content/blog/en/, src/content/blog/de/. Then filter by locale in your getStaticPaths() function. Each locale gets its own static HTML at build time. Clean, fast, and the editorial team can manage markdown files without touching any components.
Le fait d'avoir plusieurs pages de langue nuit-il au SEO par contenu dupliqué ?
Non, à condition que votre implémentation hreflang soit correcte. Google traite les variantes de langue correctement annotées comme des pages liées mais distinctes, non comme des doublons. Le risque vient d'une implémentation incorrecte de hreflang, particulièrement l'absence d'annotations réciproques ou une mauvaise gestion de x-default. Maîtrisez ces deux éléments et le contenu dupliqué ne sera pas un problème.incorrectly implementing hreflang, particularly missing reciprocal annotations or incorrect x-default handling. Nail those two things and duplicate content is not a concern.
---
Le passage de sites multilingues rendus dynamiquement en PHP à des sites pré-construits statiques ne concerne pas vraiment Astro en particulier. C'est accepter que la plupart du contenu traduit ne change pas d'heure en heure. Construisez-le une fois, mettez-le en cache partout, laissez le CDN faire le travail. Astro rend simplement ce flux de travail genuinely agréable plutôt qu'une lutte contre le framework.
Cinq langues, un développeur, huit semaines. Les outils existent. Utilisez-les.
