Changer le thème

Guide i18n Astro : site multilingue en 30 minutes (avec sélecteur de langue)

Easton editorial illustration: monorepo project desk

Votre blog Astro est prêt pour l’international, mais le multilingue vous bloque. locales, defaultLocale, prefixDefaultLocale — ces options ne sont pas évidentes, l’organisation du contenu reste floue, Content Collections semble abstraite, et le sélecteur de langue, aucune idée par où commencer.

La config i18n Astro n’est pas si complexe ; la doc officielle est juste très technique. Cet article parcourt tout le flux : de la configuration de base au sélecteur de langue, avec du code complet à chaque étape. En 30 minutes, votre site supporte plusieurs langues.

Configuration i18n de base Astro (10 minutes)

Détail de astro.config.mjs

Commençons par l’essentiel — le fichier de configuration. Depuis Astro v4.0, l’i18n est intégré et la mise en place est simple. Ouvrez astro.config.mjs et ajoutez :

// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  i18n: {
    // Indique à Astro les langues supportées
    locales: ['en', 'zh-cn', 'ja'],

    // Langue par défaut (doit être dans locales)
    defaultLocale: 'en',

    // Préfixe de chemin pour la langue par défaut
    prefixDefaultLocale: false,
  }
});

Examinons chaque option :

locales — toutes les langues du site. Utilisez des codes standard : 'en' (anglais), 'zh-cn' (chinois simplifié), 'ja' (japonais). Pour une granularité plus fine : 'en-US', 'en-GB', etc.

defaultLocale — langue affichée lors de la première visite. Doit obligatoirement figurer dans le tableau locales, sinon Astro lève une erreur.

prefixDefaultLocale — option que j’ai longtemps pesée. Avec false, l’URL de la langue par défaut n’a pas de préfixe (/about), les autres oui (/zh-cn/about, /ja/about). Avec true, toutes les langues ont un préfixe (/en/about, /zh-cn/about).

Dans la plupart des cas, false suffit pour des URLs plus courtes. Si l’uniformité des URLs ou des contraintes SEO l’exigent, choisissez true.

Comparaison des trois stratégies de routage

Astro propose trois stratégies i18n. Voici un tableau pour choisir :

StratégieConfigurationExemples d’URLCas d’usageAvantages / inconvénients
Stratégie 1 : langue par défaut sans préfixeprefixDefaultLocale: false/about
/zh-cn/about
/ja/about
La plupart des sites (recommandé)✅ URL par défaut concise
❌ Format d’URL non uniforme
Stratégie 2 : toutes les langues avec préfixeprefixDefaultLocale: true/en/about
/zh-cn/about
/ja/about
Uniformité des URLs
ou besoins SEO spécifiques
✅ Format uniforme
✅ Logique de bascule simple
❌ URL par défaut plus longue
Stratégie 3 : mode manuelrouting: 'manual'Entièrement personnaliséBesoins multilingues complexes
contrôle total du routage
✅ Grande flexibilité
❌ Configuration lourde, logique à gérer

Pour un projet simple (blog, doc), la stratégie 1 convient. Pour des URLs plus régulières, la stratégie 2. La stratégie 3 s’adresse aux cas particuliers : choix dynamique selon le comportement utilisateur, langues sur des sous-domaines distincts, etc.

Pour un blog bilingue chinois-anglais, copiez la config ci-dessus et adaptez : locales: ['zh-cn', 'en'], defaultLocale: 'zh-cn' si votre audience principale est chinoise — c’est tout.

Configurer le support multilingue i18n Astro

Étapes complètes pour un site Astro multilingue en 30 minutes

  1. 1

    Step 1: Configurer astro.config.mjs

    Ajoutez la config i18n dans astro.config.mjs :
  2. 2

    Step 2: • locales

    liste des langues (ex. [‘en’, ‘zh-cn’, ‘ja’])
  3. 3

    Step 3: • defaultLocale

    langue par défaut (doit être dans locales)
  4. 4

    Step 4: • prefixDefaultLocale

    stratégie de préfixe URL
  5. 5

    Step 5: false

    langue par défaut sans préfixe (recommandé)
  6. 6

    Step 6: true

    toutes les langues avec préfixe
  7. 7

    Step 7: Organiser le contenu multilingue

    Choisissez une approche :
  8. 8

    Step 8: Approche 1

    dossiers par langue (débutant)
  9. 9

    Step 9: Approche 2

    routes dynamiques (avancé)
  10. 10

    Step 10: Créer un dictionnaire UI

    Créez le dictionnaire dans src/i18n/ui.ts :
  11. 11

    Step 11: • Traductions des textes UI

    navigation, boutons, etc.
  12. 12

    Step 12: Implémenter le sélecteur de langue

    Construisez le composant avec getRelativeLocaleUrl et Astro.currentLocale :
  13. 13

    Step 13: • Détection navigateur

    redirection selon Accept-Language
  14. 14

    Step 14: • Cookie

    mémoriser la préférence linguistique
  15. 15

    Step 15: • Icônes

    drapeaux ou codes de langue
  16. 16

    Step 16: Optimisation SEO

    Ajoutez hreflang dans le Layout, configurez le sitemap, localisez les meta

Organisation du contenu multilingue (deux approches)

Configuration faite, place à l’organisation du contenu — souvent le point de blocage. Astro propose deux chemins.

Approche 1 : dossiers par langue (recommandé pour débuter)

La plus intuitive : un dossier par langue.

src/pages/
├── about.astro        # Langue par défaut (chinois ici)
├── blog.astro
├── index.astro
├── en/                # Version anglaise
│   ├── about.astro
│   ├── blog.astro
│   └── index.astro
└── ja/                # Version japonaise
    ├── about.astro
    ├── blog.astro
    └── index.astro

Avec prefixDefaultLocale: false, les pages de la langue par défaut restent à la racine de pages. Les autres langues ont leur sous-dossier.

Avantage : structure claire, pages indépendantes. Inconvénient : duplication — 20 pages × 5 langues = 100 fichiers à maintenir.

Approche 2 : routes dynamiques (recommandé pour avancés)

Un seul fichier gère toutes les langues :

src/pages/
└── [lang]/
    └── [...slug].astro

Dans [...slug].astro, rendu dynamique selon lang :


---

// src/pages/[lang]/[...slug].astro
export function getStaticPaths() {
  const locales = ['zh-cn', 'en', 'ja'];
  const slugs = ['about', 'blog', 'contact'];

  return locales.flatMap((lang) =>
    slugs.map((slug) => ({
      params: { lang, slug },
    }))
  );
}

const { lang, slug } = Astro.params;
// Charger le contenu selon lang et slug

---

Meilleure réutilisation du code, maintenance plus légère — mais il faut maîtriser le routage dynamique Astro. Débutant ? Commencez par l’approche 1.

Content Collections multilingue (indispensable pour un blog)

Pour un blog, Content Collections est la pièce centrale — la gestion de contenu recommandée par Astro, idéale pour blogs et documentation.

Structure :

src/content/
└── blog/
    ├── en/
    │   ├── post-1.md
    │   └── post-2.md
    ├── zh-cn/
    │   ├── post-1.md
    │   └── post-2.md
    └── ja/
        ├── post-1.md
        └── post-2.md

Définissez le schema dans src/content/config.ts :

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blogCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    author: z.string(),
    date: z.date(),
    lang: z.enum(['en', 'zh-cn', 'ja']),  // Champ langue
  }),
});

export const collections = {
  blog: blogCollection,
};

Récupérez les articles de la langue courante :


---

// src/pages/blog/index.astro
import { getCollection } from 'astro:content';

const currentLang = Astro.currentLocale;  // Langue courante
const posts = await getCollection('blog', ({ data }) => {
  return data.lang === currentLang;  // Articles de la langue courante uniquement
});

---

Les articles s’affichent selon la langue choisie. La doc officielle Astro utilise cette approche — fiable et éprouvée.

Gestion des traductions UI

Au-delà du contenu, traduisez les textes UI fixes : navigation, boutons, labels de formulaire. Créez un dictionnaire :

// src/i18n/ui.ts
export const ui = {
  'en': {
    'nav.home': 'Home',
    'nav.about': 'About',
    'nav.blog': 'Blog',
    'btn.readMore': 'Read More',
  },
  'zh-cn': {
    'nav.home': '首页',
    'nav.about': '关于',
    'nav.blog': '博客',
    'btn.readMore': '阅读更多',
  },
  'ja': {
    'nav.home': 'ホーム',
    'nav.about': '概要',
    'nav.blog': 'ブログ',
    'btn.readMore': '続きを読む',
  },
} as const;

Deux fonctions utilitaires :

// src/i18n/utils.ts
import { ui } from './ui';

// Langue courante depuis l'URL
export function getLangFromUrl(url: URL) {
  const [, lang] = url.pathname.split('/');
  if (lang in ui) return lang as keyof typeof ui;
  return 'zh-cn';  // Langue par défaut
}

// Fonction de traduction
export function useTranslations(lang: keyof typeof ui) {
  return function t(key: keyof typeof ui[typeof lang]) {
    return ui[lang][key] || ui['zh-cn'][key];
  }
}

Utilisation dans un composant :


---

// Un composant
import { getLangFromUrl, useTranslations } from '@/i18n/utils';

const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);

---

<nav>
  <a href="/">{t('nav.home')}</a>
  <a href="/about">{t('nav.about')}</a>
  <a href="/blog">{t('nav.blog')}</a>
</nav>

Simple et pratique : toutes les traductions dans un fichier. Pour de gros volumes, scindez en JSON par module.

Implémenter le sélecteur de langue (code complet)

Configuration et contenu en place — le sélecteur de langue. C’était ma partie la plus pénible au début ; une fois les helpers Astro compris, c’est direct.

Comprendre les helpers i18n Astro

Astro fournit des fonctions dédiées aux URLs multilingues :

getRelativeLocaleUrl(locale, path) — chemin relatif pour une langue.

import { getRelativeLocaleUrl } from 'astro:i18n';

// URL anglaise de la page about
const url = getRelativeLocaleUrl('en', 'about');
// Retourne : '/en/about' ou '/about' (selon la config)

getAbsoluteLocaleUrl(locale, path) — URL absolue (avec domaine).

import { getAbsoluteLocaleUrl } from 'astro:i18n';

const url = getAbsoluteLocaleUrl('en', 'about');
// Retourne : 'https://example.com/en/about'

Astro.currentLocale — langue de la page courante.


---

const currentLang = Astro.currentLocale;
// Retourne : 'zh-cn', 'en', etc.

---

Astro.preferredLocale — langue préférée du navigateur (si supportée).


---

const browserLang = Astro.preferredLocale;
// Retourne : langue du navigateur (si dans vos locales)

---

Avec ces outils, construisons le sélecteur.

Composant de bascule de langue

Un sélecteur simple, prêt à copier-coller :


---

// src/components/LanguageSwitcher.astro
import { getRelativeLocaleUrl } from 'astro:i18n';

// Langues supportées (idéalement lues depuis la config)
const locales = {
  'zh-cn': '简体中文',
  'en': 'English',
  'ja': '日本語',
};

const currentLang = Astro.currentLocale || 'zh-cn';
const currentPath = Astro.url.pathname
  .replace(`/${currentLang}/`, '/')  // Retirer le préfixe langue
  .replace(/^\//, '');  // Retirer le slash initial

---

<div class="language-switcher">
  <button class="lang-button">
    {locales[currentLang]} ▼
  </button>
  <div class="lang-dropdown">
    {Object.entries(locales).map(([lang, label]) => {
      const url = getRelativeLocaleUrl(lang, currentPath);
      return (
        <a
          href={url}
          class={lang === currentLang ? 'active' : ''}
        >
          {label}
        </a>
      );
    })}
  </div>
</div>

<style>
  .language-switcher {
    position: relative;
    display: inline-block;
  }

  .lang-button {
    padding: 8px 16px;
    background: #f3f4f6;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    cursor: pointer;
  }

  .lang-button:hover {
    background: #e5e7eb;
  }

  .lang-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 4px;
    background: white;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    min-width: 150px;
  }

  .language-switcher:hover .lang-dropdown {
    display: block;
  }

  .lang-dropdown a {
    display: block;
    padding: 10px 16px;
    color: #374151;
    text-decoration: none;
    transition: background 0.2s;
  }

  .lang-dropdown a:hover {
    background: #f3f4f6;
  }

  .lang-dropdown a.active {
    background: #dbeafe;
    color: #1e40af;
    font-weight: 500;
  }
</style>

<script>
  // Bascule au clic sur mobile
  document.querySelector('.lang-button')?.addEventListener('click', (e) => {
    e.stopPropagation();
    const dropdown = document.querySelector('.lang-dropdown');
    dropdown?.classList.toggle('show');
  });

  // Fermer en cliquant ailleurs
  document.addEventListener('click', () => {
    document.querySelector('.lang-dropdown')?.classList.remove('show');
  });
</script>

Intégrez-le dans votre Layout ou barre de navigation :


---

// src/layouts/Layout.astro
import LanguageSwitcher from '@/components/LanguageSwitcher.astro';

---

<header>
  <nav>
    <!-- Autres éléments de navigation -->
    <LanguageSwitcher />
  </nav>
</header>

Logique du composant :

  1. Récupérer langue et chemin courants
  2. Générer l’URL pour chaque langue supportée
  3. Mettre en évidence la langue active
  4. Basculer vers la version linguistique correspondante

getRelativeLocaleUrl(lang, currentPath) garantit que l’utilisateur reste sur la même page, dans une autre langue — pas de retour forcé à l’accueil.

Détection de la langue du navigateur (optionnel mais recommandé)

Rediriger automatiquement vers la langue du navigateur à la première visite — via middleware :

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware((context, next) => {
  const url = context.url;
  const currentLocale = context.currentLocale;
  const preferredLocale = context.preferredLocale;

  // Racine + langue navigateur différente → redirection
  if (url.pathname === '/' && preferredLocale && preferredLocale !== currentLocale) {
    return context.redirect(`/${preferredLocale}/`);
  }

  return next();
});

Attention : ne forcez pas la redirection à chaque visite — l’utilisateur qui change manuellement de langue serait renvoyé en arrière.

Mieux : combiner avec un cookie pour mémoriser le choix :

// Middleware amélioré
export const onRequest = defineMiddleware((context, next) => {
  const url = context.url;
  const currentLocale = context.currentLocale;
  const preferredLocale = context.preferredLocale;
  const savedLang = context.cookies.get('user-lang')?.value;

  // Préférence utilisateur en priorité
  if (savedLang && savedLang !== currentLocale && url.pathname === '/') {
    return context.redirect(`/${savedLang}/`);
  }

  // Sinon, langue du navigateur
  if (!savedLang && url.pathname === '/' && preferredLocale && preferredLocale !== currentLocale) {
    context.cookies.set('user-lang', preferredLocale, {
      path: '/',
      maxAge: 31536000,  // 1 an
    });
    return context.redirect(`/${preferredLocale}/`);
  }

  return next();
});

Mettez à jour le cookie lors du changement de langue :

<script>
  document.querySelectorAll('.lang-dropdown a').forEach((link) => {
    link.addEventListener('click', (e) => {
      const lang = e.target.getAttribute('data-lang');
      document.cookie = `user-lang=${lang}; path=/; max-age=31536000`;
    });
  });
</script>

La préférence est mémorisée pour les visites suivantes.

Techniques avancées (fallback, domaines, SEO)

Les bases sont en place. Quelques techniques avancées — vous pouvez sauter cette section si votre projet est simple.

Configuration du fallback

Traduction progressive : la version japonaise d’une page n’existe pas encore. Afficher l’anglais en secours :

// astro.config.mjs
export default defineConfig({
  i18n: {
    locales: ['en', 'zh-cn', 'ja'],
    defaultLocale: 'en',
    fallback: {
      ja: 'en',      // Japonais absent → anglais
      'zh-cn': 'en', // Chinois absent → anglais
    },
  }
});

Si /ja/some-page n’existe pas, Astro affiche le contenu de /en/some-page au lieu d’une 404. Très utile pendant la traduction.

Mapping de domaines personnalisés

Certains produits internationaux utilisent un domaine par langue :

  • Anglais : example.com
  • Chinois : example.cn
  • Japonais : example.jp

Astro le supporte — uniquement en mode SSR :

// astro.config.mjs
export default defineConfig({
  output: 'server',  // SSR obligatoire
  adapter: node(),   // Adaptateur requis
  i18n: {
    locales: ['en', 'zh-cn', 'ja'],
    defaultLocale: 'en',
    domains: {
      'zh-cn': 'https://example.cn',
      ja: 'https://example.jp',
    },
  }
});

Le contenu chinois se déploie sur example.cn, le japonais sur example.jp. En site statique (mode par défaut), cette option n’est pas disponible.

Points clés SEO

Pour le SEO multilingue, indiquez aux moteurs les versions linguistiques — surtout via les balises hreflang.

Astro facilite le SEO i18n ; avec une config correcte, beaucoup est automatique. Quelques points à traiter manuellement :

1. Balises hreflang dans le Layout


---

// src/layouts/Layout.astro
import { getAbsoluteLocaleUrl } from 'astro:i18n';

const locales = ['en', 'zh-cn', 'ja'];
const currentPath = Astro.url.pathname
  .replace(/^\/(en|zh-cn|ja)\//, '')
  .replace(/^\//, '');

---

<html>
<head>
  <!-- hreflang pour chaque langue -->
  {locales.map((lang) => (
    <link
      rel="alternate"
      hreflang={lang}
      href={getAbsoluteLocaleUrl(lang, currentPath)}
    />
  ))}

  <!-- Langue par défaut -->
  <link
    rel="alternate"
    hreflang="x-default"
    href={getAbsoluteLocaleUrl('en', currentPath)}
  />

  <!-- Meta description localisée -->
  <meta name="description" content={description[currentLang]} />

  <!-- URL canonique -->
  <link rel="canonical" href={getAbsoluteLocaleUrl(currentLang, currentPath)} />
</head>
</html>

2. sitemap.xml multilingue

Avec @astrojs/sitemap, le sitemap multilingue se génère automatiquement. Configurez le paramètre site :

// astro.config.mjs
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://example.com',  // Obligatoire
  integrations: [sitemap()],
});

3. Meta localisées

Traduisez title, description, keywords :


---

const meta = {
  'en': {
    title: 'Welcome to My Blog',
    description: 'A blog about web development',
  },
  'zh-cn': {
    title: '欢迎来到我的博客',
    description: '一个关于 Web 开发的博客',
  },
};

const currentLang = Astro.currentLocale || 'en';

---

<head>
  <title>{meta[currentLang].title}</title>
  <meta name="description" content={meta[currentLang].description} />
</head>

Avec ces éléments, votre site multilingue sera correctement indexé.

Conclusion

Récapitulons le flux i18n Astro :

Étape 1 : configurer astro.config.mjs (5 minutes)

  • Définir le tableau locales
  • Définir defaultLocale
  • Choisir la stratégie prefixDefaultLocale

Étape 2 : organiser le contenu multilingue

  • Débutant : dossiers par langue
  • Avancé : routes dynamiques + Content Collections
  • N’oubliez pas le dictionnaire UI

Étape 3 : implémenter le sélecteur de langue

  • getRelativeLocaleUrl pour les URLs
  • Astro.currentLocale pour la langue courante
  • Optionnel : détection navigateur et cookie

Après plusieurs mois d’usage, l’i18n Astro se révèle pratique : config simple, helpers efficaces, bonnes performances (routes pré-générées au build). Pour un site multilingue, la solution intégrée vaut le coup.

Lancez-vous ! En cas de blocage, consultez la documentation i18n officielle Astro pour le détail des API.

Partagez vos retours d’expérience en commentaire — on apprend ensemble.

FAQ

Combien de temps pour configurer l'i18n Astro ?
La configuration de base prend 5 à 10 minutes :
• Définir locales, defaultLocale et prefixDefaultLocale dans astro.config.mjs

Un site multilingue complet (sélecteur de langue + SEO) demande environ 30 minutes.
prefixDefaultLocale : true ou false ?
Dans la plupart des cas, false suffit :
• L'URL de la langue par défaut reste concise (ex. /about)

Pour des URLs uniformes ou des besoins SEO particuliers, passez à true (ex. /en/about).
Comment organiser le contenu multilingue ?
Deux approches :

Approche 1 — dossiers par langue (recommandé pour débuter) :
• Structure claire, mais plus de fichiers

Approche 2 — routes dynamiques (recommandé pour avancés) :
• Meilleure réutilisation du code, maintenance plus légère

Pour un blog, gérez les articles multilingues avec Content Collections.
Comment implémenter un sélecteur de langue ?
Utilisez getRelativeLocaleUrl pour générer les URLs multilingues et Astro.currentLocale pour la langue courante.

Combinez détection de la langue du navigateur et mémorisation par cookie pour améliorer l'expérience.
Comment optimiser le SEO d'un site multilingue ?
Étapes principales :
• Ajouter les balises hreflang dans le Layout pour indiquer les versions linguistiques
• Configurer le plugin sitemap pour un sitemap multilingue automatique
• Localiser title, description et autres meta

Astro facilite le SEO i18n ; une grande partie est automatique.

11 min de lecture · Publié le: 2 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog