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
Lingui VS Intlayer | Benchmark d'Internationalization (i18n) React & Next.js
Lingui et Intlayer sont les deux bibliothèques de ce benchmark qui s'appuient sur un compilateur plutôt que sur un runtime pur. Lingui extrait les messages des macros au moment de la compilation et compile les catalogues par locale. Intlayer compile les dictionnaires par composant et les tree-shake par locale. Sur le papier, ils devraient être proches. Les chiffres montrent où ils divergent.
Les données proviennent de Benchmark Bloom, une suite open-source qui construit la même application avec chaque bibliothèque et enregistre ce que le navigateur télécharge et exécute réellement.
tl;dr: Lingui est le plus proche d'Intlayer en termes de JavaScript brut par page : 115-120 KB vs 118.6 KB sur TanStack Start une fois le chargement différé configuré, 148.6 KB vs 141.3 KB sur Next.js. L'écart s'élargit ailleurs : un composant Lingui compilé isolément pèse 58-153 KB contre 6-8 KB pour Intlayer, l'hydratation prend 28-34 ms contre 11-14 ms, le fallback pour la locale source fuit 3-15% des chaînes de caractèresendans les pagesfrdans chaque setup optimisé, et atteindre ce setup optimisé signifie extraire, compiler et sélectionner manuellement les catalogs par route. Intlayer y arrive sans aucune configuration.
En résumé
- Lingui - Basé sur des macros (
t`...`,<Trans>,msg), ICU MessageFormat, catalogues.po/ JSON, workflowlingui extract+lingui compile. Compile les IDs de message en hashes courts, supporte le chargement dynamique de catalogues par locale. Bien établi, framework-agnostique, forte histoire de tooling pour traducteurs autour de.po. - Intlayer - Modèle de contenu centré sur les composants. Les dictionnaires
.content.tsse situent à côté du composant qu'ils servent, un compilateur au moment du build tree-shake et lazy-load par composant et par locale, les types TypeScript stricts sont générés à partir de votre contenu, et les traductions manquantes échouent au moment du build. Livré avec middleware, helpers SEO, un Éditeur Visuel / CMS et traduction assistée par IA.
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
Les badges se mettent à jour automatiquement. Les snapshots varieront au fil du temps.
Comparaison des fonctionnalités côte à côte
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Fonctionnalité | Intlayer (react-intlayer / next-intlayer) | Lingui (@lingui/core / @lingui/react) |
|---|---|---|
| Traductions près des composants | ✅ Oui, .content.ts colocalisé avec chaque composant | ⚠️ Chaînes de caractères sources inline dans JSX via macros; traductions dans les catalogues .po centralisés |
| Intégration TypeScript | ✅ Types stricts auto-générés à partir du contenu | ⚠️ Les macros sont typées ; les IDs de message ne le sont pas, les entrées de catalogue manquantes ne sont pas détectées |
| Détection des traductions manquantes | ✅ Erreur TypeScript + erreur/avertissement au moment de la compilation | ⚠️ lingui extract rapporte les statistiques ; le fallback à l'exécution utilise la chaîne source |
| Contenu riche (JSX / Markdown / composants) | ✅ Support direct | ✅ <Trans> avec composants imbriqués |
| Support ICU | ⚠️ WIP | ✅ Oui (macros plural, select, selectOrdinal) |
| Formatage (dates, nombres, devises) | ✅ useNumber, useDate, ... (Intl sous le capot) | ✅ i18n.date(), i18n.number() |
| Routage localisé et middleware | ✅ Proxy/middleware intégré, getMultilingualUrls | ❌ Pas dans le cœur |
| SEO helpers (hreflang, sitemap, robots) | ✅ Built-in helpers | ❌ Manual |
| Synchronous server components | ✅ useIntlayer from next-intlayer/server works in any child server component | ⚠️ Needs an I18n instance per request, passed down or set via setI18n |
| Tree-shaking (ship only used content) | ✅ Per component, per locale, automated by the compiler | ⚠️ Per locale via lingui compile; per route needs manual catalog splitting |
| Chargement lazy | ✅ importMode: 'dynamic' (une ligne de config) | ⚠️ import() manuel des catalogues compilés + i18n.load() / i18n.activate() |
| Purger le contenu inutilisé | ✅ Les dictionnaires morts sont supprimés au moment du build | ✅ lingui extract --clean supprime les messages obsolètes |
| Tester les traductions manquantes (CLI/CI) | ✅ npx intlayer content test | ⚠️ Statistiques de lingui extract (pas de code de sortie d'erreur par défaut) |
| Build pipeline | ✅ Un plugin (@intlayer/swc / @intlayer/babel / vite-intlayer) | ⚠️ Macro plugin (Babel ou SWC) + extract + compile étapes |
| AI-powered translation | ✅ Intégré, utilise vos propres clés de provider | ❌ Non |
| Visual Editor / CMS | ✅ Visual Editor gratuit + CMS optionnel | ❌ Non (.po fonctionne avec un TMS externe) |
| Serveur MCP & Compétences Agent | ✅ Oui | ❌ Non |
| Écosystème / communauté | ⚠️ Plus petit mais en croissance rapide | ✅ Établi, indépendant du framework |
Le benchmark
Ce qui a été mesuré
La suite Benchmark Bloom construit la même application avec chaque bibliothèque : 10 pages (accueil, à propos, blog, carrières, contact, FAQ, tarification, produits, paramètres, équipe), 10 locales (en, fr, es, de, it, pt, zh, ja, ko, ru), composants identiques et contenu identique. Les pages sont mesurées en en et fr. Chaque bibliothèque est implémentée dans jusqu'à quatre stratégies de chargement, de la configuration naïve à la configuration optimale :
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Stratégie | Description | Qui fait cela |
|---|---|---|
| static | Tous les catalogues compilés des locales importés et chargés à l'avance | Prototypes rapides, code généré par IA |
| dynamic | Seul le catalogue de la locale active est import()é, mais il contient toutes les pages | La plupart des projets |
| scoped-static | Un catalogue par route, tous bundlés à l'avance | Rare |
| scoped-dynamic | Un catalogue par route + lazy import(). Seulement la page actuelle, la locale actuelle | Applications avec un budget de performance strict |
Intlayer n'a pas de variante "scoped" : le compilateur scope le contenu par composant automatiquement, donc ses lignes static et dynamic sont déjà scopées.
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, moyenné sur toutes les pages et locales.
- Locale leak %: part des chaînes traduites trouvées dans le JS téléchargé qui appartiennent à une locale que l'utilisateur n'est pas en train de consulter (empreinte digitale sur
enetfr, donc 50% signifie "l'autre locale mesurée est entièrement présente"; avec 10 locales bundlées, le gaspillage réel est plus élevé). - Page leak %: part des chaînes traduites trouvées dans le JS téléchargé qui appartiennent à une page sur laquelle l'utilisateur n'est pas.
- Component avg: taille gzip moyenne de chaque composant compilé isolément. Montre combien de runtime i18n et de catalog un seul composant entraîne.
- E2E réactivité : 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 de React.
Les chiffres ci-dessous proviennent de l'exécution datée du 12-09-2026 avec@lingui/react6.6.0 etintlayer9.5.1. L'application de test est volontairement petite (quelques dizaines de chaînes par locale), donc les pourcentages de fuite décrivent un motif : 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
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (sans i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
| Lingui | static | 11.9 KB | 207.4 KB | 50.0% | 90.0% | 73.3 KB | 15.3 ms | 15.2 ms |
| Lingui | dynamic | 11.9 KB | 145.4 KB | 2.8% | 89.9% | 19.9 KB | 15.7 ms | 12.7 ms |
| Lingui | scoped-static | 11.9 KB | 148.2 KB | 2.7% | 89.1% | 20.4 KB | 15.1 ms | 13.1 ms |
| Lingui | scoped-dynamic | 11.9 KB | 148.6 KB | 14.8% | 0.0% | 152.6 KB | 16.1 ms | 14.8 ms |
next-intlayer | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
Comment le lire
- Coût d'exécution. Un composant vide coûte 11.9 KB gzip avec Lingui, 5.5 KB avec Intlayer. Sur la page complète, la meilleure configuration de Lingui se situe à +7.3 KB par rapport à Intlayer (148.6 vs 141.3 KB) ; Intlayer se situe à +0.3 KB par rapport à l'app de base.
- La configuration naïve est coûteuse. Charger chaque catalog compilé en avant donne 207.4 KB par page, +66 KB par rapport à l'app de base. La moitié des chaînes empreintes appartiennent à la mauvaise locale, 90% à la mauvaise page.
- Le chargement dynamique corrige la locale, pas la page. Avec un catalog par locale, la fuite de page reste à ~90% : l'intégralité du catalog
frse charge sur chaque page française. Atteindre 0% de fuite de page requiert la configurationscoped-dynamic: un catalog par route, extrait et compilé séparément, sélectionné manuellement dans chaque page. - La source-locale fallback fuit. Même dans les configurations optimisées, 3-15% des chaînes
ensont incluses dans les pagesfr. Les macros Lingui conservent le message source disponible comme fallback, ce qui se retrouve dans le bundle à côté de la traduction. Intlayer résout les fallbacks au moment de la compilation et ne livre que la locale active. - La taille du composant explose en
scoped-dynamic. Chaque composant compilé isolément fait en moyenne 152.6 KB, car le catalog de chaque route est accessible depuis le composant qui l'importe. Le même composant avecuseIntlayer()fait en moyenne 6.9 KB.
Résultats sur TanStack Start
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
| Lingui | static | 11.2 KB | 152.2 KB | 50.0% | 90.0% | 58.0 KB | 3.9 ms | 19.9 ms |
| Lingui | dynamic | 11.2 KB | 115.2 KB | 9.3% | 0.0% | 85.5 KB | 5.9 ms | 28.0 ms |
| Lingui | scoped-static | 11.2 KB | 120.8 KB | 4.0% | 0.0% | 147.9 KB | 7.1 ms | 33.9 ms |
| Lingui | scoped-dynamic | 11.2 KB | 120.2 KB | 8.6% | 0.0% | 83.7 KB | 42.1 ms | 32.9 ms |
intlayer | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
@intlayer/lingui (compat) | dynamic | 10.3 KB | 137.0 KB | 9.9% | 0.0% | 12.8 KB | 2.9 ms | 19.7 ms |
Comment le lire
- Sur le JavaScript par page, Lingui gagne de justesse. Le
dynamicde Lingui s'élève à 115.2 KB, 3.4 KB sous les 118.6 KB d'Intlayer. Les catalogues compilés de Lingui avec des IDs hachés sont compacts, et le routeur TanStack Start divise les routes suffisamment bien pour que la fuite de page soit déjà à 0% dans la lignedynamic. - Tout ce qui concerne la taille de la page va dans l'autre sens. L'hydratation prend 28-34 ms avec Lingui contre 11-14 ms avec Intlayer :
i18n.load()+i18n.activate()s'exécutent sur le client avant que React puisse hydrater. Les composants compilés isolément pèsent 58-148 KB contre 6-8 KB. La fuite de locale n'atteint jamais 0% (4-9%) en raison du fallback de locale source. - Le changement de locale dans la configuration optimisée est lent.
scoped-dynamicLingui prend 42 ms pour mettre à jourhtml[lang]: le nouveau catalogue de route doit être récupéré, chargé et activé avant que le changement soit visible. Intlayer bascule en 3-4 ms dans les deux modes. - La ligne
staticd'Intlayer a déjà 0% de fuite de page car seuls les dictionnaires importés par les composants de la page sont regroupés. Une ligne de config (importMode: 'dynamic') supprime aussi la fuite de locale. @intlayer/linguiconserve la syntaxe des macros de Lingui et les sert à partir des dictionnaires Intlayer. Il échange une taille de page plus importante (137 KB, puisque le runtime des macros reste) pour des composants plus petits (12.8 KB) et une hydratation plus rapide que Lingui natif. C'est une étape de migration, pas la destination.
Pourquoi l'écart ? Deux compilateurs, deux unités de travail
Les deux bibliothèques compilent. La différence est ce qu' elles compilent.
Lingui compile des catalogues. Les macros dans votre source sont extraites dans un fichier .po par locale, puis compilées dans un module JS par locale. L'unité est la locale. Diviser davantage, par route ou par composant, signifie créer plusieurs catalogues, configurer lingui.config.ts pour extraire chacun d'un ensemble différent de fichiers, et charger le bon dans chaque route. L'instance I18n au runtime est globale ; chaque appel useLingui() souscrit le composant à celle-ci.
Copier le code dans le presse-papiers
Intlayer compile les dictionnaires. Chaque fichier .content.ts est un dictionnaire lié à une clé ; le compilateur résout quel composant importe quelle clé et émet, par dictionnaire et par locale, exactement le JSON dont ce composant a besoin. L'unité est le composant. La portée de la route est une conséquence : une page n'importe que les dictionnaires des composants qu'elle rend.
Copier le code dans le presse-papiers
C'est pourquoi le pattern scoped-dynamic est une sortie de build pour Intlayer et une configuration de projet pour Lingui.
Pour obtenir les numéros de la lignedynamic, définissezdictionary.importMode: 'dynamic'dansintlayer.config.ts. Voir la documentation d'optimisation du bundle.
Expérience développeur
Configuration
Lingui
Copier le code dans le presse-papiers
Copier le code dans le presse-papiers
Puis ajoutez @lingui/babel-plugin-lingui-macro (ou @lingui/swc-plugin) au bundler, exécutez lingui extract après avoir modifié la source, lingui compile avant de construire, et enveloppez l'arborescence dans <I18nProvider i18n={i18n}>.
Intlayer
Copier le code dans le presse-papiers
Ajoutez intlayer() à vite.config.ts (ou withIntlayer() à next.config.ts) et enveloppez l'arborescence dans <IntlayerProvider>. Aucune étape d'extraction ou de compilation : les dictionnaires sont construits lors de l'exécution du bundler.
Composant
Lingui
Copier le code dans le presse-papiers
Le texte anglais se trouve dans le composant ; le texte français se trouve dans src/locales/fr/messages.po sous un ID haché, après l'exécution de lingui extract. Oublier de l'exécuter ou d'exécuter compile bascule silencieusement vers l'anglais.
Intlayer
Copier le code dans le presse-papiers
Copier le code dans le presse-papiers
Les deux locales se trouvent dans un seul fichier à côté du composant. Une valeur fr manquante est une erreur de build, une clé incorrecte est une erreur TypeScript.
En dehors des composants
Métadonnées, loaders, fonctions serveur : partout sans arborescence React.
Lingui
Copier le code dans le presse-papiers
Une nouvelle instance I18n par appel, le bon catalogue chargé manuellement, et msg + i18n._() au lieu de t. Comme le benchmark le note, savoir quand utiliser t, t` ` , i18n.t(), msg ou <Trans> « n'est pas intuitif ».
Intlayer
Copier le code dans le presse-papiers
Conservez les macros Lingui, obtenez les dictionnaires Intlayer
@intlayer/lingui est un adaptateur prêt à l'emploi pour @lingui/core et @lingui/react. Les macros continuent de se compiler comme avant ; le runtime i18n._() qu'elles compilent est servi à partir des dictionnaires Intlayer, avec des plugins de synchronisation .po qui maintiennent vos catalogues existants comme source de vérité. Les pluriels et sélections ICU s'affichent de manière identique.
Copier le code dans le presse-papiers
Conservez @lingui/babel-plugin-lingui-macro / @lingui/swc-plugin dans la build, exécutées avant le compilateur Intlayer. Consultez la documentation de compatibilité Lingui.
Quand choisir quoi ?
- Choisir Lingui si vous voulez ICU MessageFormat avec des macros typées, vos traducteurs travaillent en
.poavec un pipeline TMS existant, vous préférez les chaînes source inline en JSX, et votre équipe est à l'aise avec la gestion du workflow extract / compile / catalog-splitting. Son JS par page est compétitif une fois le lazy loading configuré. - Choisir Intlayer si vous voulez du contenu scoped au composant, du TypeScript strict, des erreurs de clés manquantes au build-time, du tree-shaking et lazy loading sans effort, de petits composants, une hydratation rapide, un changement de locale instantané, et des outils éditoriaux intégrés (Visual Editor, CMS, traduction IA, serveur MCP). Particulièrement pertinent pour les grandes codebases modulaires et les design systems.
- Choisir
@intlayer/linguisi vous êtes sur Lingui et souhaitez migrer progressivement vers les dictionnaires Intlayer sans modifier les macros.
Comparaisons associées
- next-intl vs Intlayer (même benchmark)
- i18next vs Intlayer (même benchmark)
- vue-i18n vs Intlayer benchmark (même benchmark)
- Compiler vs declarative i18n
Étoiles GitHub
Les stars GitHub sont un indicateur fort de la popularité d'un projet, de la confiance de la communauté et de sa pertinence à long terme. Bien que ce ne soit pas une mesure directe de la qualité technique, ils reflètent le nombre de développeurs qui trouvent le projet utile, suivent sa progression et sont susceptibles de l'adopter.
Conclusion
Lingui est la plus forte bibliothèque runtime-plus-compiler dans ce benchmark. Ses catalogs compilés et hashés lui donnent du JavaScript par page dans quelques KB de Intlayer, et même légèrement en dessous sur TanStack Start. Si les bytes par page étaient la seule métrique, ce serait une égalité.
Ce n'est pas le cas. Le compilateur de Lingui s'arrête à la locale ; tout ce qui est en dessous (catalogs par route, chargement différé, exclusion de la fallback du bundle) est configuration, et le benchmark montre le coût de cette limite : composants 10-20x plus volumineux, hydratation 2-3x plus lente, 3-15% de fuite de locale qui ne disparaît jamais, et un changement de locale de 42 ms dans la configuration optimisée. Le compilateur d'Intlayer fonctionne au niveau du composant, donc ces chiffres sont 6-8 KB, 11-14 ms, 0% et 3-4 ms sans aucune configuration.
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.
Consultez le document 'Pourquoi Intlayer ?' pour plus de détails.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à partager vos pensées.
