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-i18n is the reference i18n library for Vue. Intlayer is a compiler-based, component-scoped alternative with a Vue integration (vue-intlayer). 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
Pick the metrics and the libraries you care about:
Metric
Dynamic JSON loading
Lazy-loads translations at runtime
Scoped JSON (namespacing)
Per-page translation namespaces
What is this metric?
The total gzip-compressed size of the internationalisation library bundle. It only includes the provider and content retrieval logic after tree-shaking and minification.
Why is it important?
A smaller library size reduces the initial JavaScript payload, leading to faster download and execution times on the client.
View as
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.
Full table, every library and every strategy, in the Vue benchmark report.
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
Copy the code to the clipboard
label and increment are typed; a typo is a TypeScript error, a missing French value is a build error.
Lazy loading per locale
vue-i18n
Copy the code to the clipboard
Then call loadLocaleMessages() from a router guard, and split locales/{locale}.json by route yourself if you want per-page scoping.
Intlayer
Copy the code to the clipboard
Keep the vue-i18n API, get Intlayer's output
@intlayer/vue-i18n is a drop-in adapter: useI18n(), t(), d(), n(), {name} and {0} interpolation, pipe plurals ("car | cars"), v-t and i18n.global.locale keep working, served from Intlayer dictionaries compiled by vite-intlayer.
Copy the code to the clipboard
In the benchmark, the compat build of the same app went from 134.9 KB to 47.0 KB per page and from 196 KB to 8.4 KB per component, with the components untouched. Your existing locales/{locale}.json can stay the source of truth through the JSON sync plugin.
See the vue-i18n migration guide and the compatibility doc. Nuxt users have the same path through @nuxtjs/i18n compatibility.
When to choose which?
- Choose vue-i18n if you want the standard Vue approach, you rely on ICU messages or SFC
<i18n>blocks, you already use@nuxtjs/i18n, or a translation platform expects centralised JSON. Budget the time to split catalogues and lazy-load per route if bundle size matters. - Choose Intlayer if you want component-scoped content, strict TypeScript, build-time missing-key errors, zero-effort tree-shaking and lazy loading, and built-in editorial tooling (Visual Editor, CMS, AI translation, MCP server). Especially relevant for large, modular Vue / Nuxt codebases and design systems.
- Choose
@intlayer/vue-i18nif you are already onvue-i18nand want the bundle gains without a rewrite.
FAQ
Both, depending on how you adopt it. vue-intlayer is a native runtime with its own useIntlayer() composable. @intlayer/vue-i18n is a compat adapter that keeps the vue-i18n API and swaps what it is bound to, so you can migrate without touching components and move file by file afterwards.
The adapter does not read them. Move those messages into your locale JSON, or into a .content.ts next to the component, which is the same idea with generated types. That is the one vue-i18n feature that does not carry over.
Yes. Intlayer with Nuxt covers multilingual routing, locale detection middleware and sitemap generation. If you are on @nuxtjs/i18n, the Nuxt i18n compat adapter is the migration path.
Yes. The JSON sync plugin reads them with the vue-i18n dialect ({name}, {0}, "car | cars" pipe plurals) and writes translations back when the CLI or the CMS updates them.
Native ICU support is a work in progress. The @intlayer/vue-i18n adapter resolves vue-i18n's own message syntax, including pipe plurals and named and list interpolation. For Intlayer's pluralization model, see enumeration content.
Related comparisons
Reference docs:
Benchmark reports:
To understand where these libraries come from, read the history of JavaScript i18n.
GitHub STARs
GitHub stars are a strong indicator of a project's popularity, community trust, and long-term relevance. While not a direct measure of technical quality, they reflect how many developers find the project useful, follow its progress, and are likely to adopt it.
Commit activity
Stars show popularity. Commits show how much work goes into a project. At the time of writing, Intlayer counts about 7,500 commits, more than most libraries compared here, and about 5 times more than next-intl or next-i18next.
- intlify/vue-i18n
- aymericzip/intlayer
Commits on the default branch, source: GitHub API.
Intlayer is a monorepo, so its count includes every framework package, the CLI and the docs. Read commits as a signal of activity, not of quality.
npm downloads
- vue-i18n
- vue-intlayer
Source: npm registry downloads API.
Downloads reward the oldest solutions, not the best ones. A library released years ago is still installed by every project that chose it back then, by every CI run and by every package that depends on it. The count measures inertia more than a fresh choice.
AI assistants amplify the effect. next-intl, i18next and vue-i18n are everywhere in the code they were trained on, so they suggest them by default, without comparing the alternatives. Each suggestion adds downloads, which feeds the next suggestion. Compare on the benchmark rather than on the download count.
Conclusion
vue-i18n is mature, flexible and deeply integrated with Vue. The benchmark shows what its runtime-first design costs on a Vite build: a 24 KB gzip runtime, 134.9 KB per page for an app that weighs 41 KB without i18n, 90% foreign-page content on every page, and components that each reach 196 KB because they hang off the global message tree.
Intlayer moves the work into the compiler. Per-component dictionaries and dead-content purging are build outputs, not conventions. On the same app: 3.9 KB runtime, 57.1 KB per page, 0% page leakage, components 25x smaller. And if a rewrite isn't on the table, @intlayer/vue-i18n gets most of the way there with the components untouched.
All the raw data, the test apps and the scripts are in the Benchmark Bloom repository. Run it yourself.
Refer to the 'Why Intlayer?' doc for more details.
Comments
No comments yet. Be the first to share your thoughts.
