En raison des changements de mise au point et des priorités, nous ne pouvons plus maintenir ce package. Il ne recevra pas de mises à jour, de corrections de bogues ou de nouvelles fonctionnalités et peut devenir incompatible dans le temps. Nous vous recommandons de passer aux packages qui prennent en charge le dernier répertoire d'application Next.js.
next-multilingual est une solution de bout en bout avisée pour les applications Next.js ✱ qui nécessite plusieurs langues.
Consultez notre application de démonstration!
✱ next-multilingual ne fonctionne que avec? Répertoire pages . Nous réparons toujours notre solution pour soutenir le nouveau? Répertoire de app , car l'internationalisation ne fait plus partie de la configuration de next.js.
npm install next-multilingual
useMessages qui prend en charge MessageFormat de l'USI et l'injection JSX hors de la boîte./en-us/contact-us pour nous anglais et /fr-ca/nous-joindre pour le français canadien) ✱ . ✱ Vos limaces régionales par défaut doivent correspondre au système de fichiers du répertoire pages (par exemple, une limace pour "à propos de nous" devrait être dans un répertoire about-us ). Si le paramètre par défaut dont vous avez besoin utilise des caractères au-delà de ceux pris en charge par le système de fichiers, il n'a pas été testé et ne fonctionnera probablement pas. Les demandes de traction sont les bienvenues ?.
next-multilingual a mis beaucoup d'efforts à l'ajout de TSDOC à toutes ses API. Veuillez vérifier directement dans votre IDE si vous ne savez pas comment utiliser certaines API fournies dans nos exemples.
De plus, avoir une opinion sur les «meilleures pratiques» n'est pas une tâche facile. C'est pourquoi nous avons documenté nos décisions de conception dans un document spécial qui peut être consulté ici. Si vous pensez que certaines de nos API n'offrent pas ce à quoi vous vous attendez, assurez-vous de consulter ce document avant d'ouvrir un problème.
Pour ceux qui préfèrent sauter directement dans l'action, regardez dans l' example de répertoire pour une implémentation de bout en bout de next-multilingual . Pour le reste, la section ci-dessous fournira un guide de configuration complet et étape par étape.
Il existe de nombreuses options à configurer dans Next.js pour atteindre nos objectifs. next-multilingual se soucie principalement:
Nous offrons deux API pour simplifier cette étape:
getConfig (configuration simple) Cette fonction générera une configuration Next.js qui répondra à la plupart des cas d'utilisation. getConfig prend les arguments suivants:
applicationId - L'identifiant d'application unique qui sera utilisé comme préfixe de clé de messages.locales - Les lieux de votre candidature.defaultLocale - Le paramètre par défaut de votre application (il doit également être inclus dans locales )❗ Seules les balises de langue BCP 47 suivant le format
language-countrysont acceptées. Pour plus de détails sur pourquoi, reportez-vous au document des décisions de conception.
options (facultative) - Partie d'options d'un objet de configuration Next.js. getConfig renverra un objet de configuration Next.js.
Pour l'utiliser, ajoutez simplement le code suivant dans next.config.js de votre application:
const { getConfig } = require ( 'next-multilingual/config' )
const config = getConfig ( 'exampleApp' , [ 'en-US' , 'fr-CA' ] , 'en-US' , {
// Put your optional options below.
poweredByHeader : false ,
} )
module . exports = config Toutes les options de configuration ne sont pas prises en charge par getConfig . Si vous en utilisez un, un message d'erreur vous indiquera directement vers la section suivante: configuration avancée.
Config (configuration avancée) Si vous avez des besoins plus avancés, vous pouvez utiliser directement l'objet Config et insérer la configuration requise par next-multilingual directement dans un suivant existant next.config.js . Les arguments de Config sont presque identiques à getConfig (moins les options ) - vérifiez votre IDE (TSDOC) pour plus de détails. Voici un exemple de la façon dont il peut être utilisé:
const { Config , webpackConfigurationHandler } = require ( 'next-multilingual/config' )
const config = new Config ( 'exampleApp' , [ 'en-US' , 'fr-CA' ] , 'en-US' )
module . exports = {
reactStrictMode : true ,
i18n : {
locales : config . getUrlLocalePrefixes ( ) ,
defaultLocale : config . getDefaultUrlLocalePrefix ( ) ,
localeDetection : false ,
} ,
poweredByHeader : false ,
webpack : webpackConfigurationHandler ,
}Si vous devez personnaliser votre propre configuration Webpack, nous vous recommandons d'étendre notre gestionnaire comme ceci:
import Webpack from 'webpack'
import { webpackConfigurationHandler , WebpackContext } from 'next-multilingual/config'
export const myWebpackConfigurationHandler = (
config : Webpack . Configuration ,
context : WebpackContext
) : Webpack . Configuration => {
const myConfig = webpackConfigurationHandler ( config , context )
// Do stuff here.
return myConfig
} Ou directement dans next.config.js :
// Webpack handler wrapping next-multilingual's handler.
const webpack = ( config , context ) => {
config = webpackConfigurationHandler ( config , context )
// Do stuff here.
return config
} next-multilingual/config fait 2 choses en tirant parti de la capacité de routage actuelle de Next.js:
next-multilingual/config gère également la configuration spéciale de webpack requise pour le rendu côté serveur des URL localisées à l'aide de next-multilingual/link/ssr pour les composants Link et des liens canoniques et alternatifs next-multilingual/head/ssr pour les liens Head et alternatifs.
Pour plus de détails sur la mise en œuvre, comme pourquoi nous utilisons les caractères UTF-8, reportez-vous au document des décisions de conception.
next-multilingual/messages/babel-plugin Pour afficher des messages localisés avec le crochet useMessages() , nous devons configurer notre plugin Babel personnalisé qui injectera automatiquement les chaînes en pages et composants. La façon recommandée de le faire est d'inclure un .babelrc à la base de votre application:
{
"presets" : [ " next/babel " ],
"plugins" : [ " next-multilingual/messages/babel-plugin " ]
} Si vous ne configurez pas le plugin, vous obtiendrez une erreur lorsque vous essayez d'utiliser useMessages .
App personnalisée ( _app.tsx ) Nous devons créer une App personnalisée en ajoutant _app.tsx dans le répertoire pages :
import { useActualLocale } from 'next-multilingual'
import type { AppProps } from 'next/app'
const ExampleApp : React . FC < AppProps > = ( { Component , pageProps } ) => {
useActualLocale ( ) // Forces Next.js to use the actual (proper) locale.
return < Component { ... pageProps } / >
}
export default ExampleAppCela fait essentiellement deux choses, comme mentionné dans les commentaires:
/ ). Si vous ne souhaitez pas utiliser la détection des paramètres régionaux de next-multilingual vous pouvez utiliser useActualLocale(false) à la place.Document personnalisé ( _document.tsx ) Nous devons également créer un Document personnalisé en ajoutant _document.tsx dans le répertoire pages :
import { getHtmlLang } from 'next-multilingual'
import { DocumentProps , Head , Html , Main , NextScript } from 'next/document'
const Document : React . FC < DocumentProps > = ( documentProps ) => {
return (
< Html lang = { getHtmlLang ( documentProps ) } translate = "no" className = "notranslate" >
< Head >
< meta name = "google" content = "notranslate" / >
< / Head >
< body >
< Main / >
< NextScript / >
< / body >
< / Html >
)
}
export default Document Cela ne sert qu'à 1 objectif: affichez les paramètres régionaux du serveur correct dans la balise <html> . Étant donné que nous utilisons un "faux" par défaut par défaut, il est important de maintenir le bon balisage SSR, en particulier lors de la résolution d'un lieu dynamique sur / .
next-multilingual/head fournit un composant <Head> qui crée automatiquement un lien canonique et des liens alternatifs dans l'en-tête. C'est quelque chose qui n'est pas fourni hors de la boîte par suivant.js.
NEXT_PUBLIC_ORIGIN Selon Google, les liens alternatifs doivent être entièrement qualifiés, y compris la méthode de transport (HTTP / HTTPS). Parce que Next.js ne sait pas quelle URL est utilisée au moment de la construction, nous devons spécifier l'URL absolue qui sera utilisée, dans une variable d'environnement. Par exemple, pour l'environnement de développement, créez un fichier .env.development à la racine de votre application avec la variable suivante (ajustez en fonction de votre configuration):
NEXT_PUBLIC_ORIGIN =http://localhost:3000 Quel que soit l'environnement, next-multilingual recherchera une variable appelée NEXT_PUBLIC_ORIGIN pour générer des URL complètement qualifiées. Si vous utilisez basePath de Next.js, il sera ajouté automatiquement à l'URL de base.
NEXT_PUBLIC_ORIGIN n'acceptera que des domaines entièrement qualifiés (par exemple, http://example.com ), sans cheminement.
next-multilingual ? Maintenant que tout a été configuré, nous pouvons nous concentrer sur l'utilisation de next-multilingual !
Afin de faire fonctionner next-multilingual telle que conçue, nous avons dû trouver des solutions à 2 problèmes:
undefined : Parce que Suivant.js prend en charge les sites sans lieux, ses types natifs permettent d'avoir des valeurs undefined , ce qui, pour notre cas, est plus une gêne et nécessite une casting supplémentaire.next-multilingual doit créer un paramètre par défaut que nous n'utilisons jamais. Cela signifie que pour accéder aux informations liées aux paramètres pertinents, nous ne pouvons pas compter sur les API de Next.js.Nous avons créé les API suivantes pour permettre des valeurs locales cohérentes sur votre application:
useRouter Il s'agit d'un simple wrapper au-dessus de useRouter de next.js qui fournit tous deux les bons localités mais qui ne revient jamais undefined .
import { NextPage } from 'next'
import { useRouter } from 'next-multilingual/router'
const Page : NextPage = ( ) => {
const router = useRouter ( )
return < > { router . locale } < / >
}
export default Page getStaticPropsLocales import { getStaticPropsLocales } from 'next-multilingual'
export const getStaticProps : GetStaticProps = async ( context ) => {
const { locale , locales , defaultLocale } = getStaticPropsLocales ( context )
// do stuff
return { props : { } }
} getStaticPathsLocales import { getStaticPathsLocales } from 'next-multilingual'
export const getStaticPaths : GetStaticProps = async ( context ) => {
const { locales , defaultLocale } = getStaticPathsLocales ( context )
// do stuff
return { props : { } }
} getServerSidePropsLocales import { getServerSidePropsLocales } from 'next-multilingual'
export const getServerSideProps : GetServerSideProps = async ( context ) => {
const { locale , locales , defaultLocale } = getServerSidePropsLocales ( context )
// do stuff
return { props : { } }
}
️ Notez que si nous recommandons d'utiliser la détection des paramètres régionaux intelligents pour rendre dynamiquement la page d'accueil, cela est complètement facultatif. En utilisant une configuration avancée aveclocaleDetection: true, vous restaurerez le comportement par défaut Next.js sans avoir besoin d'utilisergetServerSideProps.
La page d'accueil est un peu plus complexe que les autres pages, car nous devons implémenter la détection des paramètres régionaux dynamiques (et l'affichage) pour la raison suivante:
/ peut avoir un impact négatif sur le référencement et n'est pas la meilleure expérience utilisateur.next-multilingual est livré avec une API getPreferredLocale qui offre une détection automatique plus intelligente que l'implémentation par défaut Next.js.Vous pouvez trouver une implémentation complète dans l'exemple, mais voici une version dépouillée:
import type { GetServerSideProps , NextPage } from 'next'
import { ResolvedLocaleServerSideProps , resolveLocale , useResolvedLocale } from 'next-multilingual'
import { useRouter } from 'next-multilingual/router'
const Homepage : NextPage < ResolvedLocaleServerSideProps > = ( { resolvedLocale } ) => {
// Force Next.js to use a locale that was resolved dynamically on the homepage (this must be the first action on the homepage).
useResolvedLocale ( resolvedLocale )
const { locale } = useRouter ( )
return < h1 > { locale } </ h1 >
}
export default Homepage
export const getServerSideProps : GetServerSideProps < ResolvedLocaleServerSideProps > = async (
context
) => {
return {
props : {
resolvedLocale : resolveLocale ( context ) ,
} ,
}
}En un mot, c'est ce qui se passe:
next-multilingual .useResolvedLocale pour faire cette dynamique à travers l'application. Chaque fois que vous créez un fichier tsx , ts , jsx ou js (compilable) et que vous avez besoin de messages localisés, vous pouvez simplement créer un fichier de message dans vos lieux pris en charge qui ne seront utilisables que par ces fichiers. Tout comme les modules CSS, l'idée est que vous pouvez avoir des fichiers de message associés à la portée locale d'un autre fichier. Cela a l'avantage de rendre les messages plus modulaires et d'éviter également le partage de messages dans différents contextes (plus de détails dans le document des décisions de conception sur les raisons pour lesquelles cela est mauvais).
Les fichiers de message ont 2 cas d'utilisation principaux:
pages , vous pouvez spécifier un segment URL localisé (partie d'une URL entre / ou à la fin du chemin) à l'aide de l'identifiant de clé de slug . Plus de détails sur la façon de procéder ci-dessous.useMessages . Imaginez CSS mais pour les cordes localisables.Pour résumer:
La création et la gestion de ces fichiers sont aussi simples que la création d'une feuille de style, mais voici les détails importants:
.properties . Oui, vous vous demandez peut-être pourquoi, mais il y a de bonnes raisons documentées dans le document de décision de conception.UTF-8 . Ne pas le faire remplacera les caractères non latins par �.properties , nous suivons une convention de dénomination stricte: <PageFilename>.<locale>.properties<applicationId>.<context>.<id> Où:next-multilingual/configaboutUsPage ou footerComponent pourrait être de bons exemples de contexte. Chaque fichier ne peut contenir qu'un seul contexte et le contexte ne doit pas être utilisé sur de nombreux fichiers car cela peut provoquer une «collision de clés» (clés non uniques).. et ne peut contenir entre 1 et 50 caractères alphanumériques - nous vous recommandons d'utiliser un cas de chameau pour la lisibilité.slug .title .getTitle fournie dans next-multilingual/messages pour se replier automatiquement entre les touches title et slug .useMessages .Assurez-vous également de vérifier votre journal de console pour les avertissements sur les problèmes potentiels avec vos messages. Il peut être difficile de s'habituer à la façon dont cela fonctionne en premier, mais nous avons essayé de faciliter la détection et la résolution des problèmes. Notez que ces journaux ne seront affichés que dans les environnements non productions.
Il n'est pas rare d'avoir besoin de messages localisés tout en étant incapables d'utiliser des crochets. Un exemple serait en utilisant l'une des fonctionnalités principales de Next.js est son support API intégré. Dans ce contexte, au lieu d'utiliser useMessage , nous pouvons simplement utiliser getMessages tout en spécifiant l'argument locale .
Comme mentionné précédemment, il existe une clé spéciale pour pages , où l' id est slug . Contrairement aux limaces traditionnelles qui ressemblent à this-is-a-page , nous vous demandons d'écrire la limace comme une phrase normale et lisible humaine, afin qu'elle puisse être traduite comme n'importe quelle autre chaîne. Cela évite d'avoir des processus spéciaux pour les limaces qui peuvent être coûteuses et complexes à gérer dans plusieurs langues.
Fondamentalement, la slug est la "courte description" lisible par l'homme de votre page, et représente un segment (partie entre / ou à la fin du chemin) d'une URL. Lorsqu'il est utilisé comme segment URL, la transformation suivante est appliquée:
- Par exemple, About Us deviendra about-us .
Pour la page d'accueil, l'URL sera toujours / ce qui signifie que les touches slug ne seront pas utilisées pour créer des segments URL localisés.
N'oubliez pas, les limaces doivent être écrites comme une brève description normale, ce qui signifie sauter des mots pour le garder plus court pour le référencement est découragé. La raison principale à cela, c'est que si vous écrivez "un tas de mots clés", un linguiste qui ne connaît pas le référencement pourrait avoir du mal à traduire ce message. Le fait d'avoir des spécialistes du référencement dans de nombreuses langues serait également très coûteux et difficile à évoluer. Dans un scénario idéal, les pages de référencement spécifiques au marché devraient probablement être rédigées et optimisées dans les langues maternelles, mais cela ne fait plus partie du processus de traduction. L'objectif du next-multilingual est de fournir une solution facile et rationalisée pour localiser les URL dans de nombreuses langues.
La touche slug sera également utilisée comme secours de la touche title lors de l'utilisation de l'API getTitle fournie dans next-multilingual/messages . Cette API facilite la personnalisation des titres lorsqu'une limace semble insuffisante.
️ Notez que la modification d'une valeurslugsignifie qu'une URL changera. Étant donné que ces modifications se produisent dansnext.config.js, comme tout changement de configuration Next.js, le serveur doit être redémarré pour voir les changements en vigueur. Il en va de même si vous modifiez la structure du dossier, car la configuration sous-jacente s'appuie sur cela.
Si vous souhaitez avoir un répertoire sans pages, vous pouvez toujours le localiser en créant un fichier index.<locale>.properties (où locale sont les lieux que vous prenez en charge). Bien que cette option soit prise en charge, nous ne vous recommandons pas de l'utiliser car cela augmentera les chemins d'URL, ce qui va à l'encontre des meilleures pratiques SEO.
Par défaut, next-multilingual exclura certains fichiers comme les pages d'erreur personnalisées, ou tout itinéraire API sous le répertoire /api . Vous pouvez toujours utiliser des touches slug lorsque vous utilisez des messages pour ces fichiers, mais ils ne seront pas utilisés pour créer des URL localisées.
Vous pouvez toujours examiner l'exemple pour voir les fichiers de message en action, mais voici un échantillon qui pourrait être utilisé sur la page d'accueil:
# Homepage title
exampleApp.homepage.title = Homepage
# Homepage headline
exampleApp.homepage.headline = Welcome to the homepage Maintenant que nous avons appris à créer la page d'accueil et certains détails sur le fonctionnement des choses, nous pouvons facilement créer d'autres pages. Nous créons de nombreuses pages dans l'exemple, mais voici un échantillon de ce à quoi about-us.jsx pourrait ressembler:
import { NextPage } from 'next'
import { getTitle , useMessages } from 'next-multilingual/messages'
import Layout from '@/layout'
import styles from './index.module.css'
const AboutUs : NextPage = ( ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
return (
< Layout title = { title } >
< h1 className = { styles . headline } > { title } </ h1 >
< p > { messages . format ( 'details' ) } </ p >
</ Layout >
)
}
export default AboutUs Et bien sûr, vous auriez ce fichier de message about-us.en-US.properties :
# Page localized URL segment (slug) in (translatable) human readable format.
# This key will be "slugified" (e.g, "About Us" will become "about-us"). All non-alphanumeric characters will be replaced by "-".
exampleApp.aboutUsPage.slug = About Us
# Page details.
exampleApp.aboutUsPage.details = This is just some english boilerplate text. next-multilingual est livré avec son propre composant <Link> qui permet le rendu côté client et le côté serveur de l'URL localisée. Son utilisation est simple, cela fonctionne exactement comme Next.js ' <Link> .
La seule chose importante à retenir est que l'attribut href doit toujours contenir l'URL suivante. Cela signifie que la structure de fichiers dans le dossier pages doit être ce qui est utilisé et non les versions localisées.
En d'autres termes, la structure de fichiers est considérée comme la représentation URL "non localisée", et <Link> s'occupera de remplacer les URL par les versions localisées (à partir des fichiers de messages), s'ils diffèrent de la structure.
L'API est disponible sous next-multilingual/link et vous pouvez l'utiliser comme ceci:
import Link from 'next-multilingual/link'
import { useMessages } from 'next-multilingual/messages'
const Menu : React . FC = ( ) => {
const messages = useMessages ( )
return (
< nav >
< Link href = "/" >
< a > { messages . format ( 'home' ) } </ a >
</ Link >
< Link href = "/about-us" >
< a > { messages . format ( 'aboutUs' ) } </ a >
</ Link >
< Link href = "/contact-us" >
< a > { messages . format ( 'contactUs' ) } </ a >
</ Link >
</ nav >
)
}
export default Menu Chacun de ces liens sera automatiquement localisé lorsque la touche slug est spécifiée dans le fichier de messages de cette page. Par exemple, en anglais américain, le chemin d'URL «Contactez-nous» sera /en-us/contact-us tandis que dans le français canadien, il sera /fr-ca/nous-joindre .
Étant donné que les données de ce mappage ne sont pas immédiatement disponibles lors du rendu, next-multilingual/link/ssr s'occupera du rendu côté serveur (SSR). En utilisant getConfig de next-multilingual/config , la configuration de WebPack sera ajoutée automatiquement. Si vous utilisez la méthode Config avancée, cela explique pourquoi la configuration spéciale Webpack est requise dans l'exemple fourni précédemment.
Toutes les URL localisées n'utilisent pas le composant <Link> et c'est aussi pourquoi Next.js a la méthode router.push qui peut être utilisée par de nombreux autres cas d'utilisation. next-multilingual peut prendre en charge ces cas d'utilisation avec le crochet useLocalizedUrl qui renverra une URL localisée, utilisable par tous les composants. Voici un exemple sur la façon dont il peut être exploité:
import { NextPage } from 'next'
import { useMessages } from 'next-multilingual/messages'
import { useLocalizedUrl } from 'next-multilingual/url'
import router from 'next/router'
const Tests : NextPage = ( ) => {
const messages = useMessages ( )
const localizedUrl = useLocalizedUrl ( '/about-us' )
return < button onClick = { ( ) => router . push ( localizedUrl ) } > { messages . format ( 'clickMe' ) } </ button >
}
export default Tests Si vous préférez définir vos URL en ligne à la place qu'en haut du composant, ou si vous devez effectuer des manipulations URL plus avancées, vous pouvez également utiliser le crochet useGetLocalizedUrl qui renvoie une fonction pour obtenir des URL:
import { NextPage } from 'next'
import { useMessages } from 'next-multilingual/messages'
import { useGetLocalizedUrl } from 'next-multilingual/url'
import router from 'next/router'
const Tests : NextPage = ( ) => {
const messages = useMessages ( )
const { getLocalizedUrl } = useGetLocalizedUrl ( )
return (
< button onClick = { ( ) => router . push ( getLocalizedUrl ( '/about-us' ) ) } >
{ messages . format ( 'clickMe' ) }
</ button >
)
}
export default TestsSoyez prudent, si vous souhaitez utiliser la valeur de chaîne de l'URL à l'intérieur des éléments REACT, vous aurez des erreurs car les URL diffèrent entre le pré-rendu et le navigateur. La raison en est que sur le client, sur First Render, Next.js n'a pas accès aux données de réécriture, et utilise donc des chemins URL "semi-localisés" (par exemple
/fr-ca/about-us). Puisqu'il s'agit d'un comportement natif Suivant.js, la façon la plus simple de contourner cela pour l'instant est d'ajoutersuppressHydrationWarning={true}à votre élément. Pour contourner cela,useGetLocalizedUrlrenvoie également une propriétéisLoadingqui peut être utilisée pour suivre lorsque les URL localisées sont disponibles à utiliser sur le client.
Vous pouvez rencontrer une situation où vous devez également obtenir une URL localisée, mais l'utilisation d'un crochet n'est pas une option. C'est là que getLocalizedUrl dans next-multilingual/url entre en jeu. Il agit de la même manière que useLocalizedUrl mais son argument locale est obligatoire.
Imaginez l'utilisation de l'API de Next.js pour envoyer des e-mails transactionnels et vouloir tirer parti des URL localisées de next-multilingual sans avoir à les cocoder dans une configuration. Voici un exemple de la façon dont il peut être utilisé:
import type { NextApiRequest , NextApiResponse } from 'next'
import { isLocale } from 'next-multilingual'
import { getMessages } from 'next-multilingual/messages'
import { getLocalizedUrl } from 'next-multilingual/url'
import { sendEmail } from 'send-email'
/**
* The "/api/send-email" handler.
*/
const handler = ( request : NextApiRequest , response : NextApiResponse ) : Promise < void > => {
const locale = request . headers [ 'accept-language' ]
let emailAddress = ''
try {
emailAddress = JSON . parse ( request . body ) . emailAddress
} catch ( error ) {
response . status ( 400 )
return
}
if ( locale === undefined || ! isLocale ( locale ) || ! emailAddress . length ) {
response . status ( 400 )
return
}
const messages = getMessages ( locale )
sendEmail (
emailAddress ,
messages . format ( 'welcome' , { loginUrl : await getLocalizedUrl ( '/login' , locale , true ) } )
)
response . status ( 200 )
}
export default handler La création de composants est la même que les pages, mais elles vivent en dehors du répertoire pages . De plus, la touche slug (si utilisée) n'aura aucun impact sur les URL. Nous avons quelques exemples de composants qui devraient être explicites, mais voici un exemple de composant Footer.tsx :
import { useMessages } from 'next-multilingual/messages'
const Footer : React . FC = ( ) => {
const messages = useMessages ( )
return < footer > { messages . format ( 'footerMessage' ) } </ footer >
}
export default FooterEt son fichier de messages:
# This is the message in the footer at the bottom of pages
exampleApp.footerComponent.footerMessage = © FooterAssurez-vous également d'examiner l'exemple de composant de commutateur de langue qui est un must dans toutes les applications multilingues.
Nous avons été clairs que le partage des messages est une mauvaise pratique depuis le début, alors de quoi parlons-nous ici? En fait, partager les messages en soi n'est pas mauvais. Ce qui peut causer des problèmes, c'est lorsque vous partagez des messages dans différents contextes. Par exemple, vous pourriez être tenté de créer un fichier de message partagé Button.ts contenant yesButton , noButton Keys - mais ce serait mal. Dans de nombreuses langues, des mots simples tels que "oui" et "non" peuvent avoir des orthographes différentes en fonction du contexte, même s'il s'agit d'un bouton.
Quand est-il bon de partager des messages? Pour les listes d'éléments.
Par exemple, pour garder votre processus de localisation simple, vous souhaitez éviter de stocker des chaînes localisables dans votre base de données (plus de détails sur les pourquoi dans le document de décision de conception). Dans votre base de données, vous identifieriez le contexte à l'aide d'identifiants uniques et vous stockeriez vos messages dans des fichiers de messages partagés, où les identificateurs de votre clé correspondraient à ceux de la base de données.
Pour illustrer cela, nous avons créé un exemple à l'aide de fruits. Tout ce que vous avez à faire est de créer un crochet qui appelle useMessages comme celle-ci:
export { useMessages as useFruitsMessages } from 'next-multilingual/messages'❗ Si vous devez accéder à vos messages en dehors des crochets, vous devez également exporter
getMessages.
Bien sûr, vous aurez vos fichiers de messages dans le même répertoire:
exampleApp.fruits.banana = Banana
exampleApp.fruits.apple = Apple
exampleApp.fruits.strawberry = Strawberry
exampleApp.fruits.grape = Grape
exampleApp.fruits.orange = Orange
exampleApp.fruits.watermelon = Watermelon
exampleApp.fruits.blueberry = Blueberry
exampleApp.fruits.lemon = LemonEt pour l'utiliser, importer simple ce crochet de partout où vous pourriez avoir besoin de ces valeurs:
import { useFruitsMessages } from '../messages/useFruitsMessages'
const FruitList : React . FC ( ) => {
const fruitsMessages = useFruitsMessages ( )
return (
< >
{ fruitsMessages
. getAll ( )
. map ( ( message ) => message . format ( ) )
. join ( ', ' ) }
</ >
)
}
export default FruitListVous pouvez également appeler des messages individuels comme ceci:
fruitsMessages . format ( 'banana' )L'idée de partager ces listes d'articles est que vous pouvez avoir une expérience cohérente sur différents composants. Imaginez une liste déroulante avec une liste de fruits dans une page, et dans une autre page une entrée automatique. Mais la partie importante à retenir est que la liste doit toujours être utilisée dans le même contexte, et non pour réutiliser certains des messages dans un contexte différent.
L'utilisation des espaces réservés dans les messages est une fonctionnalité critique car tous les messages ne contiennent pas de texte statique. next-multilingual prend en charge la syntaxe MessageFormat de l'USI hors de la boîte, ce qui signifie que vous pouvez utiliser le message suivant:
exampleApp.homepage.welcome = Hello {name}!Et injecter les valeurs en utilisant:
messages . format ( 'welcome' , { name : 'John Doe' } ) format Il existe quelques règles simples à garder à l'esprit lors de l'utilisation format :
values lors de la mise en forme du message, il publiera simplement le message en tant que texte statique.values lors de la mise en forme du message, vous devez inclure les valeurs de tous les espaces réservés à l'aide de la syntaxe {placeholder} dans votre message. Sinon, le message ne sera pas affiché.values qui ne sont pas dans votre message, elles seront ignorées silencieusement. L'un des principaux avantages de MessageFormat en USI est d'utiliser les outils et les normes d'Unicode pour permettre aux applications de paraître couramment la plupart des langues. Beaucoup d'ingénieurs pourraient croire qu'en ayant 2 messages, un pour le singulier et un pour le pluriel suffit pour rester couramment dans toutes les langues. En fait, Unicode a documenté les règles plurielles de plus de 200 langues et certaines langues comme l'arabe peuvent avoir jusqu'à 6 formes plurielles.
Pour vous assurer que votre phrase restera couramment dans toutes les langues, vous pouvez utiliser le message suivant:
exampleApp.homepage.mfPlural = {count, plural, =0 {No candy left.} one {Got # candy left.} other {Got # candies left.}}Et la forme plurielle correcte sera choisie, en utilisant les catégories plurielles correctes définies par Unicode:
messages . format ( 'mfPlural' , { count } )Il y a beaucoup à apprendre sur ce sujet. Assurez-vous de lire la documentation UNICODE et essayez vous-même la syntaxe pour vous familiariser avec cette capacité I18N sous-hypothèse.
Dans un cas rare où vous devrez utiliser les deux espaces réservés à l'aide de la syntaxe {placeholder} et afficher également les caractères { et } dans un message, vous devrez les remplacer par le { (pour { ) et } (pour } ) entités HTML qui sont reconnues par des outils de traduction comme celle-ci:
exampleApp.debuggingPage.variableInfo = Your variable contains the following values: & # x7b;{values}} Si vous avez un message sans valeurs (espaces réservés), échapper à { et } avec des entités HTML n'est pas requis et affichera les entités en tant que texte statique.
C'est une situation très courante que nous devons avoir HTML en ligne, à l'intérieur d'un seul message. Une façon de le faire serait:
# Bad example, do not ever do this!
exampleApp.homepage.createAccount1 = Please
exampleApp.homepage.createAccount2 = create your account
exampleApp.homepage.createAccount3 = today for free.Et puis:
< div >
{ messages . format ( 'createAccount1' ) }
< Link href = "/sign-up" > { messages . format ( 'createAccount2' ) } </ Link >
{ messages . format ( 'createAccount3' ) }
</ div >Il y a 2 problèmes avec cette approche:
Il s'agit en fait d'un anti-motif appelé concaténation et doit toujours être évité. C'est la bonne façon de le faire, en utilisant formatJsx :
exampleApp.homepage.createAccount = Please <link>create your account</link> today for free.Et puis:
< div > { messages . formatJsx ( 'createAccount' , { link : < Link href = "/sign-up" > </ Link > } ) } </ div > formatJsx formatJsx prend en charge les espaces réservés et les éléments JSX en tant que values , ce qui signifie que vous pouvez bénéficier des fonctionnalités format standard (par exemple, pluriels) tout en injectant des éléments JSX.
Il existe quelques règles simples à garder à l'esprit lors de l'utilisation format :
formatJsx .<link> xml, l'élément JSX doit être fourni à l'aide link: <Link href="/"></Link> .<i> plusieurs fois dans une phrase, vous devrez créer des balises uniques comme <i1> , <i2> , etc. et transmettre leurs valeurs en argument sous forme d'éléments JSX.Hello <name>{name}</name> ) n'est pas pris en charge..properties .<Link href="/contact-us><a id="test"></a></Link> est valide mais <div><span1></span1><span2></span2></div> est invalide. Au lieu de cela, vous devez utiliser le balisage XML de même niveau dans le fichier .properties . > < Lorsque vous utilisez formatJsx vous devrez toujours échapper aux supports bouclés si vous souhaitez les afficher sous forme de texte. De plus, comme nous utiliserons XML dans les messages formatJsx , des règles > s'appliqueront à < qui sont utilisées pour identifier les balises.
Dans un cas rare où vous devrez injecter JSX dans un message à l'aide de la syntaxe <element></element> (XML) et affiche également les caractères < dans un message, vous devrez les remplacer par le > < (pour < ) et > (pour > ) des entités HTML qui sont reconnues par des outils de traduction comme celui-ci:
exampleApp.statsPage.targetAchieved = You achieved your weekly target (& # x3c;5) and are eligible for a <link>reward</link>.Les liens d'ancrage sont des liens qui vous emmènent à un endroit particulier dans un document plutôt qu'en haut de celui-ci.
L'une des fonctionnalités de base de next-multilingual est la prise en charge des URL localisées. Notre conception a été construite en utilisant des phrases normales faciles à localiser puis transformées en limaces SEO. Nous pouvons utiliser la même fonction pour éliminer les liens d'ancrage, de sorte qu'au lieu d'avoir /fr-ca/nous-joindre#our-team vous pouvez avoir /fr-ca/nous-joindre#notre-équipe .
Il existe deux types de liens d'ancrage:
Si les liens d'ancrage sont sur la même page, et non référé sur d'autres pages, vous pouvez simplement les ajouter dans le fichier .properties Associé à cette page comme celle-ci:
# Table of content header
exampleApp.longPage.tableOfContent = Table of Content
# This key will be used both as content and "slugified". Make sure when translating that its value is unique.
exampleApp.longPage.p1Header = Paragraph 1
# "Lorem ipsum" text to make the (long) page scroll
exampleApp.longPage.p1 = Lorem ipsum dolor sit amet... Et puis la page peut utiliser la fonction slugify pour se lier à l'identifiant unique associé à l'élément que vous souhaitez pointer du fragment URL vers:
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { slugify , useMessages } from 'next-multilingual/messages'
import { useRouter } from 'next/router'
const LongPage : NextPage = ( ) => {
const messages = useMessages ( )
const { locale } = useRouter ( )
return (
< div >
< div >
< h2 > { messages . format ( 'tableOfContent' ) } </ h2 >
< ul >
< li >
< Link href = { `# ${ slugify ( messages . format ( 'p1Header' ) , locale ) } ` } >
{ messages . format ( 'p1Header' ) }
</ Link >
</ li >
</ ul >
</ div >
< div >
< h2 id = { slugify ( messages . format ( 'p1Header' ) , locale ) } > { messages . format ( 'p1Header' ) } </ h2 >
< p > { messages . format ( 'p1' ) } </ p >
</ div >
</ div >
)
}
export default LongPage Il est également courant d'utiliser des liens d'ancrage entre les pages, de sorte que lorsque vous cliquez sur un lien, votre navigateur affiche directement le contenu pertinent de cette page. Pour ce faire, vous devez rendre le message de votre page à la disposition d'autres pages en ajoutant cette exportation simple qui agira comme des "messages partagés":
export const useLongPageMessages = useMessagesEt puis vous pouvez utiliser ce crochet à partir d'une autre page comme celle-ci:
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { slugify , useMessages } from 'next-multilingual/messages'
import { useRouter } from 'next/router'
import { useLongPageMessages } from './long-page'
const AnchorLinks : NextPage = ( ) => {
const messages = useMessages ( )
const { locale , pathname } = useRouter ( )
const longPageMessages = useLongPageMessages ( )
return (
< div >
< div >
< Link
href = { ` ${ pathname } /long-page# ${ slugify ( longPageMessages . format ( 'p3Header' ) , locale ) } ` }
>
{ messages . format ( 'linkAction' ) }
</ Link >
</ div >
</ div >
)
}
export default AnchorLinksCe modèle fonctionne également pour les composants. L'avantage de le faire est que si vous supprimez ou refactez la page, les liens d'ancrage qui y sont associés resteront toujours avec la page.
Vous pouvez créer un composant de message partagé séparé uniquement pour les liens d'ancrage, mais cela casserait le principe de proximité.
Un exemple complet de liens d'ancrage peut être trouvé dans l'exemple d'application.
Une fonctionnalité qui manque à Next.js consiste à gérer des balises HTML importantes utilisées pour le référencement. Nous avons ajouté le composant <Head> pour gérer deux balises très importantes qui vivent dans le HTML <head> :
<link rel=canonical> ): Cela indique que les moteurs de recherche que la source de vérité pour la page par la navigation est cette URL. Très important pour éviter d'être pénalisé pour un contenu en double, d'autant plus que les URL sont insensibles aux cas, mais Google les traite comme sensibles à la casse.<link rel=alternate> ): Cela indique que les moteurs de recherche que la page en cours de navigation est également disponible dans d'autres langues et facilite la rampe du site. L'API est disponible sous next-multilingual/head et vous pouvez l'importer comme ceci:
import Head from 'next-multilingual/head' Tout comme <Link> , <Head> est censé être un remplacement de dépôt pour le composant <js ' <Head> . Dans notre exemple, nous l'utilisons dans le composant de mise en page, comme ceci:
< Head >
< title > { title } </ title >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" > </ meta >
</ Head > Tout cela est d'insérer les liens canoniques et alternatifs afin que les moteurs de recherche puissent mieux faire de votre application. Par exemple, si vous êtes sur la page /en-us/about-us , le HTML suivant sera ajouté automatiquement sous votre balise HTML <head> :
< link rel =" canonical " href =" http://localhost:3000/en-us/about-us " />
< link rel =" alternate " href =" http://localhost:3000/en-us/about-us " hreflang =" en-US " />
< link rel =" alternate " href =" http://localhost:3000/fr-ca/%C3%A0-propos-de-nous " hreflang =" fr-CA " /> Pour bénéficier pleinement du balisage du référencement, <Head> doit être inclus sur toutes les pages. Il existe plusieurs façons d'y parvenir, mais dans l'exemple, nous avons créé un composant <Layout> qui est utilisé sur toutes les pages.
Comme la plupart des sites, vous voudrez tirer parti de la capacité de pages d'erreur personnalisée de Next.js. Avec useMessages() , c'est tout aussi facile que de créer d'autres pages. Par exemple, pour une erreur 404 , vous pouvez créer votre 404.tsx :
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { getTitle , useMessages } from 'next-multilingual/messages'
import Layout from '@/layout'
const Error404 : NextPage = ( ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
return (
< Layout title = { title } >
< h1 > { title } </ h1 >
< Link href = "/" >
< a > { messages . format ( 'goBack' ) } </ a >
</ Link >
</ Layout >
)
}
export default Error404 Et bien sûr, vos messages, par exemple 404.en-US.properties :
# Page title
exampleApp.pageNotFoundError.title = 404 - Page Not Found
# Go back link text
exampleApp.pageNotFoundError.goBack = Go back homeLes API doivent souvent être localisées. Voici un exemple "Hello API":
import type { NextApiRequest , NextApiResponse } from 'next'
import { getMessages } from 'next-multilingual/messages'
/**
* Example API schema.
*/
type Schema = {
message : string
}
/**
* The "hello API" handler.
*/
const handler = ( request : NextApiRequest , response : NextApiResponse < Schema > ) : void => {
const locale = request . headers [ 'accept-language' ]
if ( locale === undefined || ! isLocale ( locale ) ) {
response . status ( 400 )
return
}
const messages = getMessages ( locale )
response . status ( 200 ) . json ( { message : messages . format ( 'message' ) } )
} Ceci est très similaire à l'API implémenté dans l'exemple d'application. Nous utilisons l'en-tête HTTP Accept-Language pour dire à l'API dans lequel les paramètres régionaux que nous voulons que sa réponse soit. Contrairement au crochet useMessages qui a le contexte des paramètres régionaux actuels, nous devons indiquer getMessages dans lesquels le lieu renvoie des messages.
Les fichiers de messages se comportent exactement de la même manière qu'avec useMessages vous avez simplement besoin d'en créer un à côté du fichier de l'itinéraire API, dans notre cas hello.en-US.properties :
# API message
exampleApp.helloApi.message = Hello, from API.Vous pouvez l'implémenter dans toutes les pages, comme tout autre appel API basé sur React, comme ceci:
const SomePage : NextPage = ( ) => {
const [ apiError , setApiError ] = useState ( null )
const [ isApiLoaded , setApiIsLoaded ] = useState ( false )
const [ apiMessage , setApiMessage ] = useState ( '' )
useEffect ( ( ) => {
setApiIsLoaded ( false )
const requestHeaders : HeadersInit = new Headers ( )
requestHeaders . set ( 'Accept-Language' , normalizeLocale ( router . locale as string ) )
fetch ( '/api/hello' , { headers : requestHeaders } )
. then ( ( result ) => result . json ( ) )
. then (
( result ) => {
setApiIsLoaded ( true )
setApiMessage ( result . message )
} ,
( apiError ) => {
setApiIsLoaded ( true )
setApiError ( apiError )
}
)
} , [ router . locale ] )
const showApiMessage : React . FC = ( ) => {
if ( apiError ) {
return (
< >
{ messages . format ( 'apiError' ) }
{ ( apiError as Error ) . message }
</ >
)
} else if ( ! isApiLoaded ) {
return < > { messages . format ( 'apiLoading' ) } </ >
} else {
return < > { apiMessage } </ >
}
}
return (
< div >
< h2 > { messages . format ( 'apiHeader' ) } </ h2 >
< div > { showApiMessage ( { } ) } </ div >
</ div >
)
} Le normalizeLocale n'est pas obligatoire mais une convention ISO 3166 recommandée. Étant donné que Next.js utilise les lieux comme préfixes d'URL, ils sont inférieurs à la configuration et peuvent être re-normalisés au besoin.
❗ Les routes dynamiques sont complexes et les localiser ajoutent encore plus de complexité. Assurez-vous que vous êtes familier avec le fonctionnement de cette fonctionnalité Next.js avant d'essayer d'ajouter la localisation.
Les itinéraires dynamiques sont très courants et pris en charge hors de la boîte par suivant.js. Depuis la version 3.0, next-multilingual fournit les mêmes supports que Next.js en termes de routes dynamiques. Pour que les itinéraires dynamiques fonctionnent avec next-multilingual nous avons quelques modèles à suivre:
href de <Link> et l'argument useLocalizedUrl / useGetLocalizedUrl / getLocalizedUrl url acceptent uniquement les URL de chaîne.<Link> component which accepts a UrlObject , we preferred to streamline our types since urlObject.href can easily be used instead.userRouter().asPath (most common scenario) by providing localized parameters directly in the URL. By using asPath you are using the localized URL which means that the URL you will use will be fully localized.userRouter().pathname is conjunction with hydrateRouteParameters by providing localized parameters. By using pathname you are using the non-localized URL which means that the URL you will use might be a mix of non-localized segments plus the localized parameters. This can be useful in cases where you have nested dynamic routes.We provided several examples of on on to use dynamic routes in our dynamic route test pages.
The main challenge with dynamic routes, is that if the value of the parameter needs to be localized, we need to keep a relation between languages so that we can correctly switch languages. next-multilingual solves this problem with its getLocalizedRouteParameters API that creates a LocalizedRouteParameters object used as a page props. This can work both with getStaticProps and getServerSideProps .
getStaticProps First you need to tell Next.js which predefined paths will be valid by using getStaticPaths (all imports are added in the first example):
import { getCitiesMessages } from '@/messages/cities/citiesMessages'
import { GetStaticPaths } from 'next'
import { slugify } from 'next-multilingual/messages'
export const getStaticPaths : GetStaticPaths = async ( context ) => {
const paths : MultilingualStaticPath [ ] = [ ]
const { locales } = getStaticPathsLocales ( context )
locales . forEach ( ( locale ) => {
const citiesMessages = getCitiesMessages ( locale )
citiesMessages . getAll ( ) . forEach ( ( cityMessage ) => {
paths . push ( {
params : {
city : slugify ( cityMessage . format ( ) , locale ) ,
} ,
locale ,
} )
} )
} )
return {
paths ,
fallback : false ,
}
} Then you have to pre-compute the localized route parameters and return them as props using getStaticProps and getLocalizedRouteParameters :
export type CityPageProps = { localizedRouteParameters : LocalizedRouteParameters }
export const getStaticProps : GetStaticProps < CityPageProps > = async ( context ) => {
const localizedRouteParameters = getLocalizedRouteParameters (
context ,
{
city : getCitiesMessages ,
} ,
import . meta . url
)
return { props : { localizedRouteParameters } }
}If you are using a catch-all dynamic route, you will need to pass your parameters as an array, for each URL segment that you want to support. For example, if you want to support 2 levels:
const localizedRouteParameters = getLocalizedRouteParameters ( context , {
city : [ getCitiesMessages , getCitiesMessages ] ,
} ) Note that since we need to use the getMessages API instead of the useMessages hook, you will also need to export it in the message file:
export {
getMessages as getCitiesMessages ,
useMessages as useCitiesMessages ,
} from 'next-multilingual/messages'Finally you have to pass down your localized route parameters down to your language switcher component when you create your page:
const CityPage : NextPage < CityPageProps > = ( { localizedRouteParameters } ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
const { query } = useRouter ( )
return (
< Layout title = { title } localizedRouteParameters = { localizedRouteParameters } >
< h1 > { query [ 'city' ] } < / h1>
< / Layout>
)
}
export default CityPage The only part missing now is the language switcher which needs to leverage the localized route parameters by using getLanguageSwitcherUrl :
import { normalizeLocale , setCookieLocale } from 'next-multilingual'
import Link from 'next-multilingual/link'
import { KeyValueObject } from 'next-multilingual/messages'
import { LocalizedRouteParameters , useRouter } from 'next-multilingual/router'
import { getLanguageSwitcherUrl } from 'next-multilingual/url'
import { ReactElement } from 'react'
// Locales don't need to be localized.
const localeStrings : KeyValueObject = {
'en-US' : 'English (United States)' ,
'fr-CA' : 'Français (Canada)' ,
}
type LanguageSwitcherProps = {
/** Route parameters, if the page is using a dynamic route. */
localizedRouteParameters ?: LocalizedRouteParameters
}
export const LanguageSwitcher : React . FC < LanguageSwitcherProps > = ( { localizedRouteParameters } ) => {
const router = useRouter ( )
const { pathname , locale : currentLocale , locales , defaultLocale , query } = useRouter ( )
const href = getLanguageSwitcherUrl ( router , localizedRouteParameters )
return (
< div id = "language-switcher" >
< ul >
{ locales
. filter ( ( locale ) => locale !== currentLocale )
. map ( ( locale ) => {
return (
< li key = { locale } >
< Link href = { href } locale = { locale } >
< a
onClick = { ( ) => {
setCookieLocale ( locale )
} }
lang = { normalizeLocale ( locale ) }
>
{ localeStrings [ normalizeLocale ( locale ) ] }
< / a>
< / Link>
< / li >
)
} ) }
< / ul>
< / div >
)
}Check out our fully working examples:
Our ideal translation process is one where you send the modified files to your localization vendor (while working in a branch), and get back the translated files, with the correct locale in the filenames. Once you get the files back you basically submit them back in your branch which means localization becomes an integral part of the development process. Basically, the idea is:
We don't have any "export/import" tool to help as at the time of writing this document.
next-multilingual ? ?️Why did we put so much effort into these details? Because our hypothesis is that it can have a major impact on:
More details can be found on the implementation and design decision in the individual README files of each API and in the documentation directory.