Ask your question and get a summary of the document by referencing this page and the AI provider of your choice
The content of this page was translated using an AI.
See the last version of the original content in EnglishIf you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
vue-i18n VS Intlayer | Vue Internationalisation (i18n) Benchmark
vue-i18n is the reference i18n library for Vue. Intlayer is a compiler-based, component-scoped alternative with a Vue integration (vue-intlayer). We already compared their features and developer experience. This article looks at what each one costs once the app is built.
The data comes from Benchmark Bloom, an open-source suite that builds the same application with each library and records what the browser actually downloads and executes.
tl;dr: On the same Vite + Vue 3 app,vue-i18nships 134.9 KB of gzipped JavaScript per page against 41.3 KB for the app without i18n. Intlayer ships 57.1 KB. Thevue-i18nruntime alone weighs 24.3 KB gzip (6x Intlayer's 3.9 KB), every page carries 90% of foreign-page strings, and a component compiled in isolation drags in 196 KB because it is bound to the global message tree. The@intlayer/vue-i18nadapter keeps thevue-i18nAPI and measured 47.0 KB per page.
In short
- vue-i18n - The de-facto i18n library for Vue 2 / Vue 3 and the core of
@nuxtjs/i18n. ICU-style messages, SFC<i18n>blocks,v-tdirective,d()/n()formatters, large ecosystem. Messages are registered on a global instance atcreateI18n(); lazy loading per locale is a manualsetLocaleMessage()pattern, and per-route splitting is yours to build. - Intlayer - Component-centric content model.
.content.tsdictionaries sit next to the component they serve, a build-time compiler (vite-intlayer) tree-shakes and lazy-loads them per component and per locale, strict TypeScript types are generated from your content, and missing translations fail at build time. Ships router / SEO helpers, a Visual Editor / CMS and AI-assisted translation.
Open the table in a modal to view all data content clearly
Badges update automatically. Snapshots will vary over time.
Side-by-side feature comparison
Open the table in a modal to view all data content clearly
| Feature | vue-intlayer (Intlayer) | vue-i18n |
|---|---|---|
| Translations near components | ✅ Yes, .content.ts collocated with each component | ✅ Via SFC <i18n> blocks (optional); global catalogues are the common setup |
| TypeScript integration | ✅ Strict types auto-generated from content | ✅ Good typings; strict key safety needs schema typing and discipline |
| Missing translation detection | ✅ TypeScript error + build-time error/warning | ⚠️ Runtime fallback + console warning |
| Rich content (components / Markdown) | ✅ Direct support | ⚠️ <i18n-t> component interpolation; Markdown via external plugins |
| ICU support | ⚠️ WIP | ✅ Yes |
| Formatting (dates, numbers, currencies) | ✅ Intl-based formatters | ✅ d() / n() with datetimeFormats / numberFormats |
| Localised routing | ✅ Helpers for Vue Router / Nuxt, getMultilingualUrls | ⚠️ Not core (@nuxtjs/i18n or custom router setup) |
| SEO helpers (hreflang, sitemap, robots) | ✅ Built-in helpers | ❌ Not core |
| Tree-shaking (ship only used content) | ✅ Per component, per locale, automated by the compiler | ⚠️ Manual: split catalogues, setLocaleMessage() per route |
| Lazy loading | ✅ importMode: 'dynamic' (one line of config) | ✅ Manual import() + setLocaleMessage() |
| Purge unused content | ✅ Dead dictionaries are dropped at build time | ❌ Not built-in |
| Testing missing translations (CLI / CI) | ✅ npx intlayer content test | ⚠️ Third-party (vue-i18n-extract) |
| AI-powered translation | ✅ Built-in, uses your own provider keys | ❌ No |
| Visual Editor / CMS | ✅ Free Visual Editor + optional CMS | ❌ No (external localisation platforms) |
| MCP server & Agent Skills | ✅ Yes | ❌ No |
| Ecosystem / community | ⚠️ Smaller but growing fast | ✅ Large and mature in the Vue ecosystem |
The benchmark
What was measured
The Benchmark Bloom suite builds the same Vite + Vue 3 application with each library: 10 pages (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locales (en, fr, es, de, it, pt, zh, ja, ko, ru), identical components and identical content. Pages are measured in en and fr.
Both libraries were tested in the static configuration, the one most Vue projects ship: for vue-i18n, every locale's JSON imported and passed to createI18n({ messages }); for Intlayer, the default importMode: 'static'. In that mode Intlayer bundles every locale too, but the compiler still scopes content per component, so a page only carries the dictionaries of the components it renders.
For each build, the suite records:
- Lib size: gzip size of an empty component that only imports the i18n library. The fixed cost of the runtime.
- Page JS: gzip JavaScript downloaded per page, averaged over all pages and locales.
- Locale leak %: share of translated strings found in the downloaded JS that belong to a locale the user is not viewing (fingerprinted on
enandfr, so 50% means "the other measured locale is fully present"; with 10 locales bundled, the real waste is higher). - Page leak %: share of translated strings found in the downloaded JS that belong to a page the user is not on.
- Component avg: average gzip size of each component compiled in isolation. Shows how much i18n runtime and catalogue a single component drags in.
- E2E reactivity: wall-clock time between selecting a new locale and
html[lang]updating in the DOM (Playwright, 5 iterations). - Page load:
PerformanceNavigationTiming.duration.
Numbers below come from the run dated 2026-09-12 withvue-i18n11.4.0 andintlayer9.5.0 / 9.5.1. The test application is deliberately small (a few dozen strings per locale), so leakage percentages describe a pattern: they grow with your content whilst the runtime cost stays fixed.
Results on Vite + Vue 3
Open the table in a modal to view all data content clearly
| Library | Strategy | Lib size (gz) | Lib size (min) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Page load |
|---|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 0.0 KB | 41.3 KB | 0.0% | - | 1.1 KB | 1.8 ms | 10.8 ms |
vue-i18n | static | 24.3 KB | 83.2 KB | 134.9 KB | 50.0% | 90.0% | 196.0 KB | 2.8 ms | 13.6 ms |
vue-intlayer | static | 3.9 KB | 11.1 KB | 57.1 KB | 56.8% | 0.0% | 7.7 KB | 4.5 ms | 13.8 ms |
@intlayer/vue-i18n (compat) | static | 7.9 KB | 23.2 KB | 47.0 KB | 15.0% | 0.0% | 8.4 KB | 1.5 ms | 9.3 ms |
The base app's page-leak column is left blank: with no i18n library, the fingerprinting picks up hard-coded strings in shared chunks and the number is not meaningful.
How to read it
- Runtime cost.
vue-i18nis one of the heaviest runtimes in the whole benchmark: 24.3 KB gzip / 83.2 KB minified for an empty component that only imports it.vue-intlayercosts 3.9 KB gzip. That gap is paid on every page regardless of how many strings you have. - Per-page JavaScript. The app without i18n weighs 41.3 KB.
vue-i18nmore than triples it to 134.9 KB; Intlayer lands at 57.1 KB, +15.8 KB, most of which is the ten bundled locales (see the next point). - Leakage. With
createI18n({ messages: { en, fr, ... } }), every page ships every locale and every page's strings: 50% locale leakage (on the two fingerprinted locales) and 90% page leakage. Intlayer'sstaticmode also bundles every locale (hence the comparable locale-leak figure) but has 0% page leakage: a page only pulls the dictionaries of the components it renders. Switching toimportMode: 'dynamic'removes the locale leakage as well; that configuration was not part of this Vue run. - Component size is where the architecture shows. A component calling
useI18n()compiles to 196 KB on average, becauset()is bound to the global instance that holds every message of every locale. The same component withuseIntlayer()compiles to 7.7 KB: it only reaches its own dictionary. - Reactivity is a non-issue for both (2-5 ms). Vue's reactivity system makes locale switching cheap once the messages are in memory.
@intlayer/vue-i18n, the drop-in adapter, keeps thevue-i18nAPI and measured 47.0 KB per page and 8.4 KB per component, with the application code untouched.
For reference, the same run measured fluent-vue at 171.8 KB per page, 29.7 KB of runtime and 217 KB per component.
Why the gap? Global instance vs. compiled dictionaries
vue-i18n is a runtime. createI18n() builds a global instance holding a message tree per locale; useI18n() binds each component to it; t("footer.github") looks the key up at render time. This is what makes SFC <i18n> blocks, v-t, and runtime message loading possible, and it is also why every component's dependency graph includes the whole tree:
Copy the code to the clipboard
Optimising means you split en.json into per-route files, you call setLocaleMessage() in a router guard, and you keep the route-to-file map correct as components move. The runtime can't do it for you because it has no idea which keys a component will ask for.
Intlayer moves that knowledge to the build. Content is declared next to the component, and vite-intlayer resolves which component imports which dictionary:
Copy the code to the clipboard
The compiler emits, per dictionary and per locale, exactly the JSON that component needs, and drops dictionaries nothing imports. Per-route scoping is a consequence of per-component scoping, not a task.
To also drop the unused locales, setdictionary.importMode: 'dynamic'inintlayer.config.ts. See the bundle optimisation doc.
Developer experience
Setup
vue-i18n
Copy the code to the clipboard
Copy the code to the clipboard
Intlayer
Copy the code to the clipboard
Copy the code to the clipboard
Copy the code to the clipboard
Component
vue-i18n
Copy the code to the clipboard
Copy the code to the clipboard
t('counter.label') is a string until you type the message schema yourself; a typo renders the key.
Intlayer
Copy the code to the clipboard
Comments
No comments yet. Be the first to share your thoughts.
