Posez votre question et obtenez un résumé du document en referencant cette page et le Provider AI de votre choix
Le contenu de cette page a été traduit à l'aide d'une IA.
Voir la dernière version du contenu original en anglaisSi vous avez une idée d’amélioration pour améliorer cette documentation, n’hésitez pas à contribuer en submitant une pull request sur GitHub.
Lien GitHub de la documentationCopier le Markdown du doc dans le presse-papiers
next-intl VS @intlayer/next-intl | Même API, Bundle différent
@intlayer/next-intl est un adaptateur de compatibilité : il expose l'API next-intl (useTranslations, getTranslations, useLocale, t.rich(), pluriels ICU, NextIntlClientProvider...) et la sert à partir de dictionnaires compilés par Intlayer. Le code de l'application ne change pas. Le bundle, lui, change.
Cet article compare les deux sur la même application Next.js, construite une fois avec next-intl et une fois avec l'adaptateur. Les chiffres proviennent de Benchmark Bloom, une suite open-source qui enregistre ce que le navigateur télécharge réellement. Si vous voulez la comparaison next-intl vs Intlayer en tant que bibliothèques, consultez next-intl vs Intlayer. Celui-ci traite de ce que l'adaptateur change lorsque vous conservez vos composants tels qu'ils sont.
tl;dr: Sur la même application Next.js, remplacernext-intlpar@intlayer/next-intla réduit le JavaScript par page de 153.6 KB à 147.5 KB en gzip, le composant moyen de 21.8 KB à 8.1 KB, la fuite de chaînes de pages étrangères de ~90% à 0%, et l'hydratation de 14.7 ms à 12.8 ms, sans modifier aucun composant. Sur TanStack Start, l'équivalentuse-intl(@intlayer/use-intl) a réduit les composants de 76-87 KB à 9-11 KB et le changement de locale de 7-21 ms à 4-9 ms. L'adaptateur consomme 8.0 KB de runtime contre 14.7 KB pournext-intlet 5.5 KB pournext-intlayernatif. La navigation et les middleware sont réimplémentés sur la configuration de routage d'Intlayer; lespathnameslocalisés sont la seule fonctionnalité non reprise.
Qu'est-ce que @intlayer/next-intl
next-intl est un runtime : getRequestConfig charge un messages/{locale}.json par requête, NextIntlClientProvider l'envoie au client, et useTranslations("about") lit les clés de cet objet au moment du rendu. Chaque optimisation (namespaces, pick(messages, [...]) par page, lazy loading) est à votre charge.
@intlayer/next-intl conserve la première et la dernière partie de cette chaîne et remplace celle du milieu. Vos composants appellent toujours useTranslations("about"); ce qu'ils reçoivent provient d'un dictionnaire Intlayer compilé au moment de la construction, limité à ce composant, dans la locale active uniquement.
Trois mécanismes le rendent possible :
- Aliasing d'imports.
createNextIntlPlugin()depuis@intlayer/next-intl/pluginencapsulewithIntlayeret ajoute des alias Webpack / Turbopack pour quenext-intl,next-intl/server,next-intl/navigationetnext-intl/middlewarese résolvent en@intlayer/next-intl. Aucun import dans votre codebase n'est renommé. - JSON comme source de vérité. Le plugin
syncJSONlit votremessages/{locale}.jsonexistant, divise ses clés de niveau supérieur en un dictionnaire par namespace, et réécrit les traductions dans les mêmes fichiers lorsque la CLI ou le CMS les met à jour. Le workflow de vos traducteurs reste inchangé. - Call-site binding. The Intlayer optimize pass (Babel or SWC) rewrites
useTranslations("about")into a call that receives theaboutdictionary directly. The component no longer reaches a global message tree; it reaches its own content.
Copier le code dans le presse-papiers
Copier le code dans le presse-papiers
Cette réécriture explique pourquoi les colonnes taille des composants et fuite de page ci-dessous se déplacent : une page récupère uniquement les dictionnaires des composants qu'elle rend, et uniquement dans la locale servie.
Ce que l'adaptateur conserve, ignore et ne remplace pas
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
API next-intl | Avec @intlayer/next-intl |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ Conservé. Lié au dictionnaire ns au moment de la compilation. Les clés sont typées par rapport à votre contenu. |
getTranslations({ locale, namespace }) | ✅ Conservé |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ Conservé. Les pluriels ICU, select, selectordinal, #, {ts, date, long} s'exécutent via le résolveur ICU d'Intlayer |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ Conservé |
useFormatter() | ✅ Conservé. dateTime, number, relativeTime, list, dateTimeRange font le pont vers Intl natif |
NextIntlClientProvider | ✅ Conservé. Les props messages, timeZone et now sont acceptés mais ignorés (un avertissement dev vous l'indique) |
getMessages() | ✅ Conservé pour la compatibilité; plus nécessaire |
getRequestConfig() in src/i18n.ts | ⚠️ Non nécessaire. Les dictionnaires sont compilés au moment de la construction; il n'y a pas de chargement de messages par requête |
defineRouting() | ✅ Conservé. Les champs omis (locales, defaultLocale, localePrefix) sont lus depuis intlayer.config.ts |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ Conservé. Réimplémenté sur la config de routage d'Intlayer ; l'argument routing est accepté mais ignoré |
pathnames (noms de routes localisés) | ❌ Accepté pour le typage, non interpolé. Conservez les chemins simples ou déplacez ce mapping vers Intlayer's rewrite |
createMiddleware() | ✅ Conservé. Retourne le proxy d'Intlayer ; définit le cookie NEXT_LOCALE afin que useLocale() et votre sélecteur de langue continuent de fonctionner |
NEXT_LOCALE cookie | ✅ Lu par défaut (sauf si vous configurez routing.storage vous-même) |
useTranslations() nu sans namespace | ⚠️ Fonctionne, mais le site d'appel n'est pas lié : il se résout via le registre runtime. Passez un namespace pour obtenir les gains de bundle |
Le benchmark
Ce qui a été mesuré
La suite Benchmark Bloom construit la même application avec chaque configuration : 10 pages (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locales (en, fr, es, de, it, pt, zh, ja, ko, ru), des composants identiques et un contenu identique. Les pages sont mesurées en en et fr.
next-intl a été construit avec quatre stratégies de chargement, de la configuration naïve (chargement entier de messages/{locale}.json) à la configuration optimale (un namespace par route + pick() par page). L'adaptateur a été construit sur les mêmes composants que la configuration naïve, avec seulement next.config.ts et intlayer.config.ts modifiés. Il n'a pas de variante "scoped" : le compilateur scopes le contenu par composant, donc ses lignes static et dynamic sont déjà scoped.
Pour chaque build, la suite enregistre :
- Lib size : taille gzip d'un composant vide qui importe uniquement la bibliothèque i18n. Le coût fixe du runtime.
- Page JS : JavaScript gzip téléchargé par page, en moyenne sur toutes les pages et locales.
- Fuite locale % : part des chaînes traduites trouvées dans le JS téléchargé appartenant à une locale que l'utilisateur ne consulte pas.
- Fuite page % : part des chaînes traduites trouvées dans le JS téléchargé appartenant à une page sur laquelle l'utilisateur n'est pas.
- Composant moy : taille gzip moyenne de chaque composant compilé isolément. Montre combien de runtime i18n et de catalogue un seul composant entraîne.
- Réactivité E2E : temps écoulé entre la sélection d'une nouvelle locale et la mise à jour de
html[lang]dans le DOM (Playwright, 5 itérations). - Hydratation : durée de la phase d'hydratation React.
Les chiffres ci-dessous proviennent de l'exécution datée du 2026-09-12 avecnext-intl/use-intl4.14.2 et@intlayer/*9.5.1. L'application de test est volontairement petite (quelques dizaines de chaînes par locale), donc les pourcentages de fuite décrivent un modèle : ils augmentent avec votre contenu tandis que le coût d'exécution reste fixe.
Résultats sur Next.js
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Setup | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
Comment le lire
- Mêmes composants, 6 KB de moins par page. L'adapter build de l'application naïve atterrit à 147.5 KB, sous chaque configuration
next-intly compris la plus optimisée (153.6 KB). Le runtime lui-même est la différence : 8.0 KB versus 14.7 KB, payés sur chaque page. - La fuite passe à 0% sans toucher à un composant. La configuration naïve de
next-intlexpédie ~90% des chaînes de pages étrangères sur chaque page. Atteindre 0% avecnext-intlsignifie les setupsscoped-*: un namespace par route, etpick(messages, [...])dans chaque page. L'adapter atteint 0% à partir du code naïf car la passe d'optimisation lie chaqueuseTranslations("ns")à son propre dictionnaire. - Les composants rétrécissent 2.7x. Un composant compilé en isolation fait en moyenne 21.8 KB avec
next-intl(il atteint le provider et l'arborescence des messages) et 8.1 KB avec l'adapter. Dans le setupscoped-staticdenext-intl, ce nombre monte à 80 KB, car le fichier namespace de chaque route devient accessible à partir de la page qui le sélectionne. - L'hydratation est 2 ms plus rapide (12.8 vs 14.7 ms) : il n'y a pas d'objet de message à désérialiser de la charge utile RSC avant que React puisse hydrater.
- L'adaptateur n'est pas le runtime natif.
next-intlayers'élève à 141.3 KB, +0.3 KB par rapport à l'app de base, avec un runtime de 5.5 KB. L'adaptateur transporte la surface API denext-intl(useFormatter,t.rich, le résolveur ICU) au-dessus du noyau d'Intlayer, d'où 8.0 KB et +6 KB par page. C'est le pont, pas la destination.
Résultats sur TanStack Start (use-intl)
use-intl est le noyau framework-agnostique de next-intl. Son adaptateur, @intlayer/use-intl, suit le même design avec un plugin Vite (@intlayer/use-intl/plugin).
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Configuration | Stratégie | Taille lib (gz) | JS page moy (gz) | Fuite locale | Fuite page | Composant moy (gz) | Réactivité E2E | Hydratation |
|---|---|---|---|---|---|---|---|---|
| base (pas i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
Comment le lire
- Les octets par page sont équivalents à l'
use-intloptimisé.@intlayer/use-intlen modedynamic(129.7 KB) est dans les 1 KB deuse-intl'sscoped-dynamic(128.7 KB), et 10 KB au-dessus dudynamicsimple deuse-intl(119.4 KB). Cette lignedynamicsimple fuit toujours 90 % des chaînes de pages étrangères ; le nombre d'octets est faible car le contenu de l'application de test est petit. Le 0% de l'adapter reste plat à mesure que le contenu augmente. - Les composants sont 7-9x plus petits. Les composants
use-intlfont en moyenne 76-87 KB dans chaque stratégie, caruseTranslationsest lié à l'objet de messages complet du provider. L'adaptateur fait en moyenne 9-11 KB. - Le changement de locale est plus rapide. Les configurations
use-intloptimisées prennent 13-21 ms pour mettre à jourhtml[lang]; l'adaptateur prend 4-9 ms. Moins de composants se re-rendent, et rien n'est re-sélectionné dans un arbre de messages. staticconserve chaque locale. La lignestaticde l'adaptateur affiche une fuite de locale de 49.7%, la même que celle d'Intlayer natif en modestatic: toutes les locales sont bundlées, seuls les dictionnaires de la page le sont. Une ligne de config (importMode: 'dynamic') la supprime.
Pourquoi les nombres changent
Rien dans le composant n'a changé, donc les gains proviennent entièrement de ce à quoi useTranslations est lié.
Avec next-intl, la liaison est le fournisseur. NextIntlClientProvider reçoit l'objet messages complet pour la locale ; chaque useTranslations("about") le lit depuis celui-ci. Le bundler voit un composant important un hook qui lit un contexte, et ne peut pas savoir que seule la branche about est utilisée. Les routes ci-dessous partagent toutes le même objet message, donc la colonne page-leak affiche ~90% jusqu'à ce que vous divisiez le fichier vous-même.
Copier le code dans le presse-papiers
Avec @intlayer/next-intl, la liaison est le dictionnaire. syncJSON transforme messages/en.json en un dictionnaire par clé de niveau supérieur ; le compilateur résout quel composant appelle useTranslations("about") et lui transmet about directement, dans la locale active, en tant qu'import que le bundler peut tracer et diviser.
Copier le code dans le presse-papiers
src/i18n.ts et la prop messages disparaissent. Tout le reste est identique.
Migration en trois étapes
Installer
bashCopier le codeCopier le code dans le presse-papiers
La commande détecte
next-intlet installeintlayer,next-intlayer,@intlayer/next-intlet@intlayer/sync-json-plugin. Gardeznext-intlinstallé : c'est une dépendance pair de l'adaptateur et il fournit les types.Pointez Intlayer vers vos messages
intlayer.config.tsCopier le codeCopier le code dans le presse-papiers
messages/{locale}.jsonreste à sa place. Chaque clé de haut niveau devient un dictionnaire ;useTranslations("about")mappe au dictionnaireabout.Encapsuler next.config.ts
next.config.tsCopier le codeCopier le code dans le presse-papiers
createNextIntlPlugin()composewithIntlayer(surveillance du contenu, compilation des dictionnaires, la passe d'optimisation) et les aliasnext-intl→@intlayer/next-intlpour Webpack et Turbopack. Compilez, et les chiffres dans les tableaux ci-dessus sont les vôtres.
Ce que vous pouvez supprimer ensuite
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Fichier / motif | Raison |
|---|---|
getRequestConfig dans src/i18n.ts | Aucun chargement de message par requête. Conservez le fichier uniquement s'il exporte également des helpers createNavigation |
messages={...} sur NextIntlClientProvider | L'adaptateur lit la sortie compilée ; la prop est ignorée et enregistre un avertissement en développement |
await getMessages() dans les layouts | Même raison |
Per-page pick(messages, [...]) | Le compilateur effectue le picking, par composant |
Ce que vous gagnez au-delà des bytes
- Typed keys.
useTranslations("about")est typé par rapport au dictionnaire compiléabout.t("does.not.exist")est une erreur TypeScript, pas un fallback à l'exécution. npx intlayer testéchoue dans CI quand une locale est manquante d'une clé.npx intlayer filltraduit les clés manquantes avec le fournisseur de votre choix (OpenAI, Anthropic, Mistral, Gemini...) en utilisant votre propre clé, et écrit le résultat dansmessages/{locale}.json.- Visual Editor et CMS fonctionnent sur les mêmes dictionnaires, donc les non-développeurs peuvent éditer
messages/fr.jsonvia une UI et le fichier se met à jour. - Migration progressive vers
.content.ts. N'importe quel composant peut passer deuseTranslations("about")àuseIntlayer("about")avec un fichier de contenu co-localisé, un par un. Les dictionnaires JSON et.content.tscoexistent et fusionnent.
Limites à connaître avant de commencer
- La configuration du routage se déplace vers
intlayer.config.ts.createNavigation(routing)etcreateMiddleware(routing)conservent leur signature mais ignorent l'argument : les locales, la locale par défaut et la stratégie de préfixe proviennent de la configurationroutingd'Intlayer. Si vous utilisez lespathnameslocalisés denext-intl(/about→/a-propos), l'adaptateur ne les interpole pas ;routing.rewrited'Intlayer couvre ce cas mais c'est une modification séparée. useTranslations()sans namespace n'est pas lié. La passe d'optimisation a besoin d'un namespace statique pour savoir quel dictionnaire importer. Un appel nu fonctionne quand même, via un registre runtime qui référence chaque dictionnaire, ce qui est exactement la fuite que vous tentiez d'éliminer. Passez le namespace.- L'adaptateur n'est pas gratuit. 8.0 KB de runtime contre 5.5 KB pour
next-intlayer, et +6-7 KB par page par rapport au build natif. C'est le prix de la surface APInext-intl. Si vous en arrivez au point où chaque composant a été déplacé versuseIntlayer, abandonnez l'adaptateur. messages,timeZone,nowsur le provider sont ignorés. Les formateurs sont soutenus parIntlnatif et seule la locale influence leur sortie; si vous comptiez sur un fuseau horaire forcé ou unnowfixe pour des dates stables à l'hydratation, gérez-le au site d'appel.
Quand utiliser quoi?
- Restez sur
next-intlsi votre app est petite, votre bundle n'est pas une préoccupation, et votre équipe est à l'aise pour posséder les namespaces etpick()par page. - Utiliser
@intlayer/next-intlsi vous êtes actuellement surnext-intlet souhaitez les gains en termes de bundle, de fuite mémoire et d'hydratation, des clés typées et les outils CLI / CMS sans réécriture. C'est le point d'entrée recommandé pour toute base de codenext-intlexistante. - Opter pour la solution native (
next-intlayer) pour les nouveaux projets, ou une fois que l'adapter a rempli son rôle. C'est la plus légère des trois (5,5 KB, +0,3 KB par page) et déverrouille les composants serveur synchrones, les fichiers.content.tspar composant et l'ensemble complet des fonctionnalités.
Comparaisons connexes
- next-intl vs Intlayer (les bibliothèques, même benchmark)
- i18next vs @intlayer/i18next (même série d'adaptateurs)
- Lingui vs @intlayer/lingui (même série d'adaptateurs)
- vue-i18n vs @intlayer/vue-i18n (même série d'adaptateurs)
- Guide de migration : next-intl vers Intlayer
- Référence de l'adaptateur de compatibilité : next-intl
Conclusion
@intlayer/next-intl fait une chose : elle change ce à quoi useTranslations est lié, d'un provider contenant chaque message à un dictionnaire compilé pour ce composant. Sur la même application Next.js, cela représente 6 KB par page, 2,7x plus petits composants, 0% de fuite et 2 ms d'hydratation, avant même que quelqu'un n'ouvre un fichier de composant. La navigation et les middlewares conservent leur API au-dessus de la configuration de routage d'Intlayer, et le runtime natif next-intlayer reste encore plus léger.
Toutes les données brutes, les applications de test et les scripts se trouvent dans le référentiel Benchmark Bloom. Exécutez-le vous-même.
Reportez-vous à la documentation « Why Intlayer? » pour plus de détails.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à partager vos pensées.
