Ce site est un blog en 3 langues développé avec Gatsby.js. Il est réalisé sans aucun plugin ou module i18n, mais uniquement avec le routage de gatsby-node.js. *Il a fonctionné avec Next.js jusqu'en avril 2023.
Gatsby.js n'a pas de fonction intégrée pour obtenir la langue par défaut ou la langue d'affichage actuelle, comme useRouter()
de Next.js. Il n'y a pas non plus de distinction entre la "langue par défaut" et les "autres langues" dans les paramètres.
Cet article vous montrera comment j'ai internationalisé (i18n) ce site, les étapes à suivre et les points clés. Il existe probablement d'autres méthodes d'internationalisation, mais je vous invite à lire cet article à titre d'exemple.
Le code de ce blog est publié sur GitHub repository.
Points clés pour la création d'un site multilingue avec Gatsby.js
- générer des pages par langue avec
gatsby-node.js
- passer la langue actuelle à
pageContext
dans la première étape
- Dans chaque template, récupérer la langue actuelle dans
pageContext
et la passer à des composants tels que Header et Footer pour qu'ils s'affichent différemment pour chaque langue.
De cette façon, vous pouvez créer un site multilingue avec Gatsby.js sans avoir besoin de plugins i18n.
Structure des fichiers Markdown
Les fichiers Markdown sont organisés comme suit ;
src/
├─ content/
| └─ posts/
| ├─ first-post/
| | ├─ en.md
| | ├─ fr.md
| | └─ ja.md
| ├─ second-post/
| | ├─ en.md
| | ├─ fr.md
| | └─ ja.md
J'utilise le nom du dossier comme slug et je nomme les fichiers [lang].md
pour chaque langue. En faisant cela, j'évite d'avoir à ajouter le slug et le code de la langue aux métadonnées du frontmatter et à chaque nom de fichier.
Par conséquent, dans gatsby-node.js
, j'ajoute le schéma slug
et language
avec le code suivant pour que la requête GraphQL dans chaque post (MarkdownRemark) puisse récupérer le nom de fichier (en tant que slug) et la langue.
gatsby-node.js
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === "MarkdownRemark") {
const fileNode = getNode(node.parent)
createNodeField({
node,
name: "language",
value: fileNode.name,
})
createNodeField({
node,
name: "slug",
value: fileNode.relativeDirectory.match(/\/(.+)/)[1],
})
}
}
Lien - onCreateNode | Gatsby.js
Le code ci-dessus vous permettra d'obtenir le nom de la langue et le slug de chaque article à partir d'une requête GraphQL. Bien évidemment, il peut également être utilisé pour le filtrage et le tri.
query {
markdownRemark {
fields {
language
slug
}
frontmatter {
...
}
}
}
Ensuite, nous allons utiliser gatsby-node.js
pour créer les paths pour les pages que nous voulons créer.
C'est un peu pénible, mais la requête doit être générée en fonction de la langue.
- Nom du template pour un article individuel : post.js
- Nom du template pour la page d'accueil (tous les articles) : index.js
En générant des requêtes par langue, nous pouvons obtenir les avantages suivants :
- La pagination est plus facile lorsque le nombre d'articles dans chaque langue est différent.
- Les articles précédents/suivants peuvent être récupérés par langue.
Générer des pages d'articles individuelles
gatsby-node.js
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const blogresult = await graphql(`
query {
allPostsEN: allMarkdownRemark(
filter: { fields: { language: { eq: "en" } } }
sort: { frontmatter: { date: DESC } }
) {
edges {
node {
id
fields {
slug
}
}
}
}
allPostsFR: allMarkdownRemark(
filter: { fields: { language: { eq: "fr" } } }
sort: { frontmatter: { date: DESC } }
) {
edges {
node {
id
fields {
slug
}
}
}
}
allPostsJA: allMarkdownRemark(
filter: { fields: { language: { eq: "ja" } } }
sort: { frontmatter: { date: DESC } }
) {
edges {
node {
id
fields {
slug
}
}
}
}
}
`)
if (blogresult.errors) {
reporter.panicOnBuild(`Query error!`)
return
}
const allPosts = {
en: blogresult.data.allPostsEN,
fr: blogresult.data.allPostsFR,
ja: blogresult.data.allPostsJA,
}
Object.keys(allPosts).forEach(key => {
allPosts[key].edges.forEach(({ node }) => {
createPage({
path: `/${key}/post/${node.fields.slug}/`,
component: require.resolve(`./src/templates/post.js`),
context: {
id: node.id,
slug: node.fields.slug,
language: key,
},
})
})
})
}
Je n'expliquerai pas la pagination ici, mais la possibilité que le nombre total de billets soit différent d'une langue à l'autre doit également être prise en compte.
Si une page d'inscription contient une cinquième page en français mais seulement quatre pages en anglais, la prise en compte des requêtes par langue permettra d'éviter la création d'une page inutile.
Créer la page de liste d'articles
Ensuite, ajoutez le code pour créer une page de liste d'articles, comme indiqué sur la première page de ce blog.
gatsby-node.js
exports.createPages = async ({ actions, graphql, reporter }) => {
Object.keys(allPosts).forEach(key => {
createPage({
path: `/${key}/`,
component: require.resolve(`./src/templates/index.js`),
context: {
language: key,
},
})
})
}
À titre de référence, ce blog, qui comprend une fonction de pagination, effectue les opérations suivantes (le dépôt);
gatsby-node.js
exports.createPages = async ({ actions, graphql, reporter }) => {
const postsPerPage = 5
Object.keys(allPosts).forEach(key => {
const totalPages = Math.ceil(allPosts[key].totalCount / postsPerPage)
for (let i = 0; i < totalPages; i++) {
createPage({
path: i === 0 ? `/${key}/` : `/${key}/page/${i + 1}/`,
component: require.resolve(`./src/templates/index.js`),
context: {
language: key,
skip: postsPerPage * i,
limit: postsPerPage,
currentPage: i + 1,
isFirst: i + 1 === 1,
isLast: i + 1 === totalPages,
pages: totalPages,
},
})
}
})
}
Comment déterminer la langue d'affichage actuelle et les articles traduits
Cette section décrit la création sur les pages d'articles de billets individuels. Le même concept (obtenir la langue d'affichage) peut être utilisé pour générer des pages de listes d'articles.
Obtenir la langue d'affichage
Dans la génération de la page de gatsby-node.js
mentionnée plus haut, j'ai ajouté language
à la propriété context
pour envoyer le code de la langue comme valeur.
Ce code sera disponible dans le template en tant que pageContext
pour récupérer les données.
src/template/post.js
const SinglePost = ( { pageContext } ) => {
const currentLang = pageContext.language
return (
)
}
Cette valeur peut être envoyée à chaque composant et utilisée pour basculer l'affichage par langue dans le Header et le Footer.
Vérifier la présence d'articles traduits
Vous pouvez vérifier s'il existe des traductions de l'article actuellement affiché, également dans le template.
La génération de la page gatsby-node.js
inclut le slug de la page générée dans la propriété context
, ainsi l'article correspondant avec le slug peut être récupéré dans allMarkdownRemark
.
La requête récupérée est développée dans jsx par map()
et stockée sous forme de tableau dans la constante availLangs
.
src/template/post.js
const SinglePost = ( { pageContext, data } ) => {
const currentLang = pageContext.language
const availLangs = data.allMarkdownRemark.nodes.map(
node => node.fields.language
)
return (
)
}
export const query = graphql`
query($id: String!, $slug: String!) {
markdownRemark(id: { eq: $id }) {
... // Current post data
}
allMarkdownRemark(
filter: { fields: { slug: { eq: $slug } } }
sort: { fields: { language: ASC } }
) {
nodes {
id
fields {
language
}
}
}
}
`
De cette façon, le paramètre availLangs
est envoyé au composant du sélecteur de langue et le lien n'est affiché dans le sélecteur que si une traduction est disponible.
*Je n'entrerai pas dans la création du sélecteur de langue ici.
Ajouter l'attribut lang aux balises html dans Gatsby Head API
Gatsby.js vous permet d'envoyer des données dynamiques aux balises <html>
et <body>
en utilisant Gatsby Head API
dans chaque modèle.
Comme pour la description JSX du template, la langue courante est récupérée à partir du pageContext
et l'attribut language de la balise <html>
peut être spécifié.
src/template/post.js
export const Head = ({ pageContext }) => {
const currentLang = pageContext.language
return <html lang={currentLang} />
}
Lien - Gatsby Head API
Pour la page de liste d'articles
Dans le cas des pages de liste d'articles, telles que la page d'accueil et les pages de tags, la langue d'affichage actuelle est transmise à la propriété context
dans gatsby-node.js
, de sorte qu'elles peuvent être créées de la même manière en utilisant l'approche ci-dessus.
Création de la page d'accueil et de la page 404 dans différentes langues
Processus de redirection vers la page d'accueil
La page d'accueil pour chaque langue a été créée dans gatsby-node.js
plus tôt pour chaque langue, mais l'URL du domaine racine doit être gérée.
Lorsque je l'ai créée avec Next.js, https://route360.dev/
était la page d'accueil en anglais. Donc maintenant, si on accède à cette URL, elle sera redirigée vers https://route360.dev/en/
.
L'hébergement est Cloudflare Pages. La configuration de la redirection est la suivante. *La même configuration devrait fonctionner pour Netlify.
src/static/_redirects
Affichage de la page 404
Comme Cloudflare Pages ne prend pas en charge les redirections personnalisées (Netlify le fait), j'ai créé la page 404 comme suit ; (le code réel ci-dessous est modifié pour des raisons d'illustration)
src/pages/404.js
const NotFoundPage = ({ location }) => {
const browserLang = location.pathname.slice(1, 3)
const languageMap = {
ja: "ja",
fr: "fr",
en: "en",
}
const backToHome = {
en: "Back to Home",
fr: "Retour à la page d'accueil",
ja: "ホームに戻る",
}
let currentLang = languageMap[browserLang] || languageMap["en"]
return (
<Layout currentLang={currentLang}>
<div className={classes.postsContainer}>
<p>404 Not Found</p>
<Link to={`/${currentLang}/`}>{backToHome[currentLang]}</Link>
</div>
</Layout>
)
}
Si le chemin d'accès à la page 404 contient un code de langue tel que /en/
, j'affiche "Return to Home" dans cette langue. Si ce n'est pas le cas, c'est en anglais.
*Avec l'évaluation ci-dessus, si les deuxième et troisième caractères sont en
comme length
, il sera évalué comme English
. Si vous souhaitez évaluer le path de manière plus stricte, veuillez réécrire le code en conséquence.
À des fins de SEO, s'il y a des pages traduites, ajoutez du code dans la balise <head>
concernant la disponibilité des traductions et des pages de langue par défaut.
Dans ce site, nous envoyons les données availLangs
dans le composant SEO via l'API Gatsby Head et ajoutons les données suivantes seulement s'il y a une traduction/des traductions.
Notifier à Google la version localisée de la page
<link rel="alternate" hreflang="language_code" href="current_url" />
Tout est expliqué dans le guide de Google Signaler les versions localisées de votre page à Google.
Métadonnées pour OGP (Open Graph Protocol)
<meta property="og:locale:alternate" content="language_code" />
Ajouter également des métadonnées pour l'OGP.
Utilisez gatsby-plugin-feed pour générer des flux RSS pour chaque langue.
La manière de créer un flux RSS spécifique à une langue avec le plugin est expliquée dans l'article suivant :
Créer des flux RSS spécifiques à la langue pour un site Gatsby multilingue
Créer un sitemap XML pour un site web multilingue
Le plugin officiel gatsby-plugin-sitemap
de Gatsby peut générer un sitemap XML, mais il doit être optimisé pour les sites web multilingues.
La façon de générer un sitemap XML personnalisé avec le plugin est expliquée dans l'entrée suivante :
Générer un plan de site XML pour un site Gatsby multilingue
Résumé
Avec une bonne utilisation de la création de chemins gatsby-node.js
, j'ai pu créer un site multilingue avec Gatsby.js, sans utiliser de plugins spéciaux pour l'i18n.
Lorsque j'ai créé un site multilingue avec Astro, la documentation officielle d'Astro m'a beaucoup aidé à comprendre la structure. Le concept a fonctionné et a été appliqué à nouveau.
Il semble que les langues de droite à gauche (par exemple, l'arabe) soient également acceptables si le composant de mise en page est séparé par langue.
Voilà, c'est tout.