Pour mon application, je souhaite un routage de ce type :
- préfixe d’URL présentant la langue (
fr,enouzh), par exemple/fr/about,/en/about, etc… - URL dynamiques basées sur les collections de contenus, par exemple
/fr/resources/my-resource,/zh/articles/my-article, etc… - Si aucune langue n’est spécifiée dans l’URL, redirection automatique vers la langue préférée (à partir des langues configurées dans le navigateur)
Configuration
L’intégration du multilingue s’effectue très facilement au sein d’une application Astro.
Dans le fichier de configuration principal :
// astro.config.mjs
export default defineConfig({
i18n: {
locales: [
{ path: "fr", codes: ["fr", "fr-FR", "fr-CA"] },
{ path: "en", codes: ["en", "en-US", "en-GB"] },
{ path: "zh", codes: ["zh", "zh-CN", "zh-TW"] },
],
defaultLocale: "fr",
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: false,
},
},
//...
});
-
Les
codesprésents pour chaque langue (path) permettent de déclarer les codes de langues supportés, tels qu’ils sont configurés dans le navigateur. Les codes doivent être compatibles avec la syntaxe du header Accept-Language -
prefixDefaultLocalepermet d’avoir toujours la locale dans l’URL, commehttps://domain.com/fr/page. -
redirectToDefaultLocale: falsepermet de gérer manuellement notre redirection vers la bonne langue à l’arrivée de l’utilisateur sur la page d’accueil.
Fichiers d’internationalisation
Suivant la recette pratique présente dans la documentation d’Astro, on créera également les fichiers suivants :
src/i18n/ui.ts: déclare les langues de notre application et les chaînes de traduction présentes dans l’interface (menus, footer, etc…)
export const languages = {
fr: "Français",
en: "English",
zh: "繁體中文",
};
export const ui = {
fr: {
"nav.home": "Accueil",
"nav.about": "À propos",
},
en: {
"nav.home": "Home",
"nav.about": "About",
},
zh: {
//...
},
} as const;
Par la suite, toujours suivant la recette, on déclarera des fonctions utilitaires nous permettant de récupérer ces chaînes localisées (useTranslation dans src/i18n/utils.ts).
Détection de la langue du navigateur
À l’arrivée du visiteur sur la page d’accueil du site, si aucune langue n’est indiquée dans l’URL, alors on souhaite le rediriger vers sa langue préférée. La page concernée sera simplement la page d’accueil de l’application (/).
Si sa langue n’est pas supportée par notre application, alors on affichera la langue par défaut (defaultLocale déclarée dans la configuration). L’utilisateur pourra alors sélectionner la langue de son choix avec le sélecteur de langues, dans l’interface.
Dans la page d’accueil (sans code de langue prédéfini) :
---
// src/pages/index.astro
import { i18n } from "astro:config/server";
import { getPathByLocale } from "astro:i18n";
export const prerender = false;
const targetLocale = Astro.preferredLocale
? getPathByLocale(Astro.preferredLocale)
: i18n!.defaultLocale;
return Astro.redirect(`/${targetLocale}/`);
---
Redirecting to {`/${targetLocale}/`}...
Toute la partie frontmatter (entre les ---) s’exécute côté serveur. Avec Astro, tout est prérendu de manière statique au build de l’application, à moins qu’on indique, comme ci-dessus, export const prerender = false;: dès qu’une requête sera reçue sur cette URL, elle sera évaluée par le serveur.
Astro.preferredLocale
Cette propriété n’est disponible que pour les rendus à la demande (prerender = false). Elle contiendra la langue configurée dans le navigateur correspondant à une langue de notre application (calculée par Astro). Si le navigateur déclare comme première langue fr-FR, alors Astro va trouver dans nos codes configurés fr-FR et remplir Astro.preferredLocale avec la même valeur.
Si notre application ne supporte pas la langue du navigateur (par exemple nous n’incluons pas d’espagnol es-ES), alors Astro.preferredLocale contiendra undefined : on peut se rabattre sur la defaultLocale.
getPathByLocale
La fonction utilitaire getPathByLocale vient du module astro:i18n : à partir d’un code de langue reçu du navigateur (en-US par exemple, ou zh-TW), on récupère le préfixe d’URL vers lequel rediriger (en ou zh). Tout est déclaré dans la configuration avec les propriétés path et codes :
// astro.config.mjs
export default defineConfig({
i18n: {
locales: [
// Les codes reçus du navigateur correspondent
// à un path dans notre application
{ path: "fr", codes: ["fr", "fr-FR", "fr-CA"] },
{ path: "en", codes: ["en", "en-US", "en-GB"] },
{ path: "zh", codes: ["zh", "zh-CN", "zh-TW"] },
],
//...
},
//...
});
C’est comme ça que les utilisateurs fr-FR se verront redirigés vers /fr/, les utilisateurs en-GB vers /en/, etc…
Arborescence
La page principale de l’application (src/pages/index.astro) se charge de rediriger vers la bonne langue pour l’utilisateur. Mais pour le reste des pages, nous allons créer un dossier src/pages/[locale]/ qui nous permettra de gérer chaque page dans plusieurs langues.
Ainsi, pour les pages statiques comme la page d’accueil ou À propos, on pourra créer un seul fichier, et s’appuyer sur les chaînes localisées.
Pour les contenus plus fournis comme les articles, on va créer une page pour l’affichage puis on rangera nos contenus dans des collections de contenus.
Collections de contenus
En point d’entrée des contenus, on va créer un nouveau dossier src/content.
À l’intérieur, on pourra créer un dossier par langue. J’aurai donc :
src/content/frsrc/content/ensrc/content/zh
Ensuite, en-dessous de chaque langue, je pourrai créer un nouveau sous-dossier correspondant à la collection de contenu (par exemple des articles) :
src/content/fr/articlessrc/content/en/articlessrc/content/zh/articles
Pour charger ces contenus, je déclare une collection de contenus dont le pattern inclut toutes mes langues :
const articlesCollection = defineCollection({
loader: glob({
pattern: localizedPattern("articles/**/*.{md,mdx}"),
base: "src/content/",
}),
schema: z.object({
title: z.string(),
}),
});
localizedPattern préfixe le pattern avec les différentes langues :
export function localizedPattern(pattern: string): string {
const langCodes = Object.keys(languages);
return `(${langCodes.join("|")})/${pattern}`;
}
Ainsi, comme dans l’exemple ci-dessus, articles/**/*.{md,mdx} donnera (fr|en|zh)/articles/**/*.{md,mdx} (avec comme base le dossier src/content/, bien sûr).
getStaticPaths
Avoir [locale] comme dossier racine implique que toutes les pages ont au moins un paramètre d’URL. Pour des pages statiques, cela peut vite représenter une répétition pour récupérer toutes les valeurs de ce paramètre (/articles, /about, /contact, …) et générer toutes les URL.
On va créer une fonction utilitaire localizedPaths qui sera chargée de renvoyer des objets contenant déjà notre paramètre d’URL locale pour chaque langue.
S’il n’y a pas d’autres paramètre d’URL à renseigner, alors on peut directement réexporter cette fonction sous le nom getStaticPaths : Astro génèrera les URL pour chaque langue.
type LocalizedPath = {
params: {
locale: string;
};
};
/**
* For a given page, grab all languages and create the necessary paths with the locale param
*
* @returns An array of all localized paths
*/
export async function localizedPaths(): Promise<LocalizedPath[]> {
const langs = Object.keys(languages);
return langs.map((locale) => {
return { params: { locale } };
});
}
Puis dans une page [locale]/about.astro par exemple :
export { localizedPaths as getStaticPaths } from "@/lib/utils";