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
Sélectionnez les métriques et les bibliothèques qui vous intéressent :
Métrique
Chargement JSON dynamique
Charge les traductions à la volée
JSON scopé (espaces de noms)
Espaces de noms de traduction par page
Quelle est cette métrique ?
La taille totale compressée en gzip du bundle de la bibliothèque d’internationalisation. Elle n’inclut que le fournisseur et la logique de récupération de contenu après tree-shaking et minification.
Pourquoi est-ce important ?
Une taille de bibliothèque plus petite réduit la charge utile JavaScript initiale, ce qui accélère le téléchargement et le temps d’exécution sur le client.
Voir comme
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.
Tableau complet, chaque bibliothèque et chaque stratégie, dans le rapport de benchmark Next.js.
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.
Tableau complet dans le rapport de benchmark TanStack Start.
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, et le gaspillage augmente sur deux axes à la fois, les pages et les locales :

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
createNavigation(routing) et createMiddleware(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 configuration routing d'Intlayer. Si vous utilisez les pathnames localisés de next-intl (/about vers /a-propos), l'adaptateur ne les interpole pas ; routing.rewrite d'Intlayer couvre ce cas mais constitue un changement distinct.
La passe d'optimisation a besoin d'un namespace statique pour savoir quel dictionnaire importer. Un appel nu fonctionne toujours, via un registre d'exécution qui référence chaque dictionnaire, ce qui correspond exactement à la fuite que vous essayiez de supprimer. Passez le namespace.
8.0 KB de runtime contre 5.5 KB pour next-intlayer, et +6-7 KB par page par rapport au build natif. Il paie pour la surface d'API de next-intl. Si vous atteignez le point où chaque composant a été migré vers useIntlayer, supprimez l'adaptateur.
Les formateurs s'appuient sur l'API native Intl et seule la locale influence leur résultat. Si vous comptez sur un fuseau horaire forcé ou un now fixe pour des dates stables à l'hydratation, gérez-le au niveau du site d'appel. Consultez formatage de dates, heures et nombres.
Quand utiliser quoi?
Votre application est petite, votre bundle n'est pas une préoccupation, et votre équipe est à l'aise avec la gestion des namespaces et de pick() par page.
Vous utilisez déjà next-intl aujourd'hui et souhaitez bénéficier des gains de bundle, de fuite et d'hydratation, des clés typées et des outils CLI / CMS sans réécriture. C'est le point d'entrée recommandé pour toute codebase next-intl existante.
Pour les nouveaux projets, ou une fois que l'adaptateur a fait son travail. C'est le plus léger des trois (5.5 KB, +0.3 KB par page) et il débloque les composants serveur synchrones, les fichiers .content.ts par composant et l'ensemble des fonctionnalités. Commencez avec Intlayer avec Next.js.
FAQ
Sur Next.js, oui pour les composants : le build de benchmark n'a modifié que next.config.ts et intlayer.config.ts. getRequestConfig dans src/i18n.ts, la prop messages sur le provider et les appels pick() par page deviennent du code mort que vous pouvez supprimer par la suite.
Ils continuent de fonctionner. t("key", { count }), t.rich(), t.markup(), select, selectordinal, # et {ts, date, long} sont résolus par le résolveur ICU d'Intlayer. Consultez format de message ICU.
Il transporte la surface d'API de next-intl au-dessus du cœur d'Intlayer : useFormatter, t.rich, le résolveur ICU, les helpers de navigation. Cela représente 8.0 KB contre 5.5 KB, et +6 KB par page. C'est le pont, pas la destination.
Oui. N'importe quel composant peut passer de useTranslations("about") à useIntlayer("about") avec un fichier .content.ts co-localisé. Les dictionnaires JSON et .content.ts coexistent et fusionnent, il n'y a donc pas de rupture brutale.
Pas via les pathnames de next-intl : l'adaptateur les accepte pour le typage mais ne les interpole pas. Utilisez plutôt routing.rewrite d'Intlayer, qui émet les littéraux localisés dans le registre de types.
Comparaisons connexes
Même série d'adaptateurs :
Les bibliothèques comparées directement :
Documentation de référence :
Pour comprendre d'où viennent ces bibliothèques, lisez l'histoire de l'i18n en JavaScript.
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.
