Next JS app routeur : comment localiser et traduire
J'ai migré mon site web vers le routeur de répertoire app
. Cependant, c'est un site multilingue avec du contenu en français et en anglais, et la bibliothèque de traduction que j'utilisais, i18next, ne fonctionne pas côté serveur. Voici donc l'histoire de mon propre système de traduction et de ce que j'ai dû mettre en place pour le faire fonctionner.
Et même si vous n'avez pas besoin de traduire votre site NextJS, vous pourriez avoir besoin d'utiliser un middleware ou un lookup pour quelque chose d'autre. Si ce n'est pas le cas, ce article sur les nouvelles fonctionnalités SEO de Next JS pourrait vous intéresser.
Pour ceux d'entre vous qui sont encore ici, il y a trois parties à cet article :
- premièrement, comment j'ai structuré le répertoire de mon application pour gérer les locales
- deuxièmement, comment j'ai codé la fonction de recherche pour traduire les chaînes de caractères dans les pages
- troisièmement, comment j'ai mis en place le middleware pour faire passer les utilisateurs d'une version à l'autre en fonction de la langue de leur navigateur.
Plongeons dans le vif du sujet.
Gérer les langues dans Next avec le routeur app
Tout d'abord, comment est structuré le dossier de mon application ?
J'ai un dossier de paramètres dynamiques appelé [lang]
à la racine de mon dossier d'application. Cela me permet de gérer les versions linguistiques. J'ai des versions françaises et anglaises et les routes en/
et fr/
renvoient à ce dossier.
Maintenant, si nous nous penchons sur le composant de la page d'accueil dans le fichier pages.tsx
dans le dossier [lang]
, nous pouvons voir que le routeur passe des props contenant un objet appelé params
. Cet objet contient un membre appelé lang
. La lecture de cet objet me permet de savoir (dans le code) quelle est la langue demandée, ce qui est la base de la traduction.
Traduire des chaînes de caractères dans des pages
Comment traduire le texte d'une page en fonction de la langue demandée ?
Création des dictionnaires JSON
La première chose que j'ai faite a été de créer deux dictionnaires JSON, un pour l'anglais et un pour le français. Le dictionnaire français s'appelle fr.json
et le dictionnaire anglais s'appelle en.json
. J'ai utilisé un site web appelé "Localise.biz" pour éditer mes traductions, et j'exporte mon JSON à partir de là.
Chargement des dictionnaires JSON
Deuxièmement, à la racine de mon répertoire [lang]
, j'ai créé un fichier dictionnary.ts
, qui nécessite des données côté serveur, donc il commence par :
import 'server-only' ;
Dans ce fichier, je commence par importer les fichiers JSON :
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
fr: () => import('./dictionaries/fr.json').then((module) => module.default),
};
Commençons par vérifier les dictionnaires JSON. Ce sont des paires clé-valeur. Ils me permettent de faire correspondre une clé (de localisation) à un texte traduit. Ainsi, par exemple, dans le dictionnaire anglais, j'ai le contenu suivant :
{
"menu": {
"home": "Home",
"about": "About",
"blog": "Blog",
"resources": "Resources"
},
}
Et j'ai le contenu suivant en français :
{
"menu": {
"home": "Accueil",
"about": "À propos",
"blog": "Blog",
"resources": "Ressources"
},
}
Cela signifie que menu.home
est "Home" en anglais et "Accueil" en français, et que menu.about
est "About" en anglais et "À propos" en français.
Création de la fonction de recherche
Maintenant, dans le dossier src/
, j'ai un autre dossier appelé utils
, et dans ce dossier, j'ai un fichier appelé i18n.ts
.
La troisième chose que j'ai faite, c'est d'écrire une fonction appelée _t
à l'intérieur du fichier i18n
. Cette fonction prend la clé de localisation et le dictionnaire JSON et retourne le texte localisé sous forme de chaîne.
Que fait _t
?
Vous avez peut-être remarqué que menu.home
n'est pas une clé dans le dictionnaire. Au lieu de cela, nous avons deux niveaux, un sous menu
et une clé home
en dessous. C'est un choix personnel parce que je trouve qu'il est plus facile d'organiser mes traductions de cette façon, mais cela fonctionnerait de la même façon si le dictionnaire était plat.
Donc dans mon cas, pour obtenir la valeur traduite, je coupe ma clé de en sous parties avec le caractère "point". Ensuite, je passe le tableau keys
que j'ai créé à une autre fonction appelée getFromDictionnary
, et je retourne la valeur.
export const _t = (key: string, dict: Dict): string => {
const keys = key.split(".");
return getFromDictionnary(keys, dict);
}
(Je fais aussi quelques vérifs supplémentaires pour m'assurer que les valeurs que je reçois et récupère sont valides, mais la logique de base est là).
Comme vous pouvez le voir, la fonction getFromDictionnary
prend deux paramètres : le premier, un tableau, et le second, une valeur qui peut être soit une chaîne de caractères, soit un dictionnaire.
Si la seconde valeur est juste une chaîne de caractères, la fonction getFromDictionnary
la retourne directement. Si la seconde valeur est vide, la fonction renvoie une chaîne vide.
Si rien de tout cela ne s'est produit, la fonction getFromDictionnary
lit (et supprime) le premier élément du tableau. Ensuite, la fonction s'appelle elle-même en utilisant le nouveau tableau plus court et la valeur trouvée dans le dictionnaire à l'entrée key
.
const key = keys.shift() || '';
return getFromDictionnary(keys, dict[key]);
Cela me permet d'avoir une recherche dans le dictionnaire en plusieurs étapes et d'utiliser l'espace de noms pour mes clés de localisation. Si vous n'êtes pas à l'aise avec cela, vous pouvez également opter pour une structure plate, où chaque valeur à l'intérieur du JSON est une chaîne de caractères, sans sous-objets.
La fonction factory
La quatrième étape, à l'intérieur du fichier dictionnary.ts
que j'ai mentionné au début, était de créer une fonction "factory". Cette fonction prend la langue en paramètre et renvoie une nouvelle fonction, qui appelle la fonction _t
qui fait la recherche dans le dictionnaire spécifique à la langue :
export type Translator = (key:string) => string;
export const getTranslations = async (lang:Lang):Promise<Translator> => {
const dict = await getDictionary(lang);
return (key:string) => _t(key, dict);
}
Maintenant, comment utiliser tout cela ? Penchons-nous sur ma page d'accueil. Dans le fichier page.tsx
qui est à la racine de mon dossier [lang]
dynamique, mon composant récupère le paramètre de langue via les props. Ensuite, la première chose que je fais est d'obtenir la fonction de recherche de traduction.
const Page = async ({params : {lang}}: PageProps) => {
// retrieve the transaction function
const t = await getTranslations(lang);
}
Maintenant, je passe cette fonction aux différents props, donc par exemple si nous nous penchons sur le composant <Home>
, nous pouvons voir que j'appelle le composant <LatestPosts>
avec un titre et un sous-titre traduits, ce qui appelle la fonction de recherche de traduction.
<LatestPosts
variant
lang={lang} posts={posts}
title={t('blog.title')}
subtitle={t('blog.subtitle')}
/>
Un petit mot sur la localisation des articles de blog
Maintenant, un petit mot : je ne traduis pas chaque article de blog. Les articles de blog sont des éléments de contenu distincts. Cependant, lorsqu'un article de blog (ou une page) est traduit, je lie les deux pages ensemble en tant qu'alternatives dans les metadata. Si vous souhaitez en savoir plus, je vous préparerai une présentation plus approfondie.
Maintenant, j'ai deux versions de mon site web, une sous en/
et une sous fr/
. Mais que se passe-t-il quand quelqu'un va juste sur le site de base, sans aller dans un sous-dossier ?
Et bien ici, j'utilise une autre fonctionnalité de NextJS, un middleware. C'est un script qui se trouve dans un fichier appelé middleware.ts
. Ce fichier n'est pas dans le répertoire app
, il est à la racine du projet. Le script fait trois choses :
- Premièrement, il vérifie si nous sommes dans un sous-dossier de langue. Si c'est le cas, c'est bon.
- Deuxièmement, si ce n'est pas le cas, j'utilise le paquetage npm "Negotiator" pour vérifier les en-têtes et voir si le navigateur a indiqué des préférences de langue. Ensuite, j'utilise le paquet npm
@formatjs/intl-localematcher
pour les faire correspondre aux langues disponibles. Si aucune ne correspond, je mets l'anglais par défaut. - Enfin, je redirige vers le sous-dossier avec la nouvelle locale ajoutée au début.
La chose importante à noter ici est que le middleware nous permet de ne l’utiliser que sur certains chemins. Typiquement, nous ne voulons pas appeler cette redirection pour une route api/
ou pour le fichier robots
et le plan du site.
Donc dans le fichier middleware, nous pouvons définir une fonction matcher
qui utilise un RegEx pour définir quelles URLs déclenchent le middleware. Cela nous permet d'exclure les différentes routes.
J'espère que cela vous aura donné les outils dont vous avez besoin pour traduire votre NextJs. N'hésitez pas à me faire savoir dans les commentaires si vous rencontrez des difficultés. Vous pouvez aussi consulter mon article de blog que j’ai mis dans la description.
Et si vous voulez en savoir plus sur Next JS, j'ai expliqué comment fonctionne le routeur app
. Et j'ai aussi expliqué les nouvelles fonctionnalités SEO. Je vous donne rendez-vous dans l'une ou l'autre.
Abonnez-vous pour mieux comprendre le développement logiciel. Recevez les dernière nouvelles, vidéos et conseils.