Nuxt.js internationalization: i18n vs automatic translation (2026)
Nuxt.js has two credible paths to a multilingual site: the official @nuxtjs/i18n module, which wires Vue Router, locale files, and slug routing together, and DOM-based automatic translation via a single script tag. Both work — but they solve different problems and suit very different teams. Here is the honest trade-off.
What @nuxtjs/i18n actually does
@nuxtjs/i18n (nuxt-i18n) is the official Nuxt module for internationalization. It wraps vue-i18nand integrates tightly with Nuxt's file-based routing and Vue Router. What it gives you:
- Locale-prefixed URL routing —
/fr/about,/de/pricing— handled automatically via the Nuxt routing layer. - Browser locale detection from the
Accept-Languageheader (server-side) ornavigator.language(client-side). - Auto-generated
hreflangtags for each language version of a page — important for SEO. - The
$t()composable anduseI18n()hook for translating strings inside templates and<script setup>.
What it does not do: translate any of your content. Every string in every locale file must be written by a human (or fed through a translation service manually). The module manages routing and lookup; filling in the actual translations is your responsibility.
Setting up @nuxtjs/i18n: what is actually involved
A minimal nuxt-i18n setup for three languages requires these pieces:
- 1Install the module and register it in
nuxt.config.tsundermodules. - 2Declare locales, the default locale, and the lazy-loading strategy in the
i18nconfig block. - 3Create per-locale JSON files:
locales/en.json,locales/fr.json,locales/de.json— one per supported language, updated with every new UI string. - 4Replace every hardcoded string in every component with a
$t('key')ort('key')call — touching every.vuefile in the project. - 5Add a locale-switcher component to the navigation; update routing logic for any custom slug pages (e.g. a blog post at
/blog/[slug]becomes/fr/blog/[slug]with Nuxt i18n routing).
A minimal nuxt.config.ts i18n block:
And a corresponding locales/en.json:
Every key must be duplicated and translated in fr.json and de.json — and kept in sync forever as the product evolves.
The compounding maintenance cost
The per-string overhead is small in isolation. The aggregate cost across a real product compounds quickly:
Content–code coupling
Every new UI string requires a key in the source locale file and a matching entry in every target locale file. A feature shipped on Monday means the French and German files need updating before French and German users see anything other than a key fallback.
Component refactoring touches every file
Adding nuxt-i18n to an existing project means touching every .vue file that renders text — wrapping strings in $t(), extracting values into locale files, and verifying nothing is missed. On a project with 50+ components, this is a multi-day refactor.
Locale file drift
Without tooling like @nuxtjs/i18n's missing key detection or a third-party script, locale files silently diverge. The English file gains keys that the French file lacks, and fallback rendering quietly shows English text to French users.
RTL requires separate layout work
nuxt-i18n translates strings; it does not flip layout direction. Supporting Arabic or Hebrew means setting dir="rtl" on the root element, auditing every component for physical CSS properties, and testing the entire layout in RTL mode — a separate engineering effort.
When @nuxtjs/i18n is the right choice
There are specific situations where the full module stack is clearly justified:
SEO-critical content sites with per-language URLs
When every language version must be a distinct, indexable URL with its own canonical and hreflang — and you have the developer resources to build and maintain it — nuxt-i18n delivers exactly that.
Open-source apps with community translations
When translations are community-contributed via pull requests on GitHub, locale JSON files are the right contributor interface. Crowdin, Weblate, and similar platforms integrate directly with this format.
Compliance-regulated content
Legal, medical, or financial applications where every string must be explicitly reviewed and approved before shipping. nuxt-i18n makes every string an auditable discrete artifact with a version history.
Offline-capable PWAs
Nuxt apps that must function offline need all locale strings bundled at build time. nuxt-i18n locale JSON files are included in the build; DOM-based translation cannot work without a network connection.
Adding Lingvit to a Nuxt 3 app
The alternative is a script tag — no module installation, no locale files, no component changes. For Nuxt 3, add the script via the app.head property in nuxt.config.ts:
Alternatively, use the useHead composable inside your root app.vue if you prefer component-level control:
That is the entire integration. Lingvit's widget uses a MutationObserver to watch the DOM after Nuxt renders and hydrates. It handles:
- Server-rendered HTML streamed from Nuxt's SSR engine — translated immediately once hydration is complete.
- Reactive Vue updates — content revealed by
v-if, rendered byv-for, or loaded via async data fetching is translated as new nodes appear in the DOM. - Vue Router navigation — Nuxt's client-side route transitions trigger the observer for the newly rendered page content.
- RTL layout —
document.documentElement.diris set automatically when switching to Arabic, Hebrew, or another RTL language.
Setup comparison
@nuxtjs/i18n setup
- 1.Install
@nuxtjs/i18nand register innuxt.config.ts - 2.Configure locales, strategy, and lazy-loading in the
i18nblock - 3.Create
locales/en.json,fr.json, etc. - 4.Wrap every template string with
$t()across all.vuefiles - 5.Add locale-switcher UI component
- 6.Fill in translation files per language
Lingvit setup
- 1.Add one
scriptentry tonuxt.config.tsapp.head - 2.Select target languages in the Lingvit dashboard
When automatic translation is enough
For many Nuxt projects, DOM-based translation is not a compromise — it is the right engineering decision:
Marketing and content sites
A Nuxt marketing site or blog has no complex pluralization, no offline requirement, and changes copy frequently. Automatic translation handles every new paragraph on first render with no developer action.
SaaS dashboards adding languages quickly
International users mean international revenue. Going live in French via a script tag takes an afternoon. Retrofitting nuxt-i18n onto an existing SaaS dashboard takes weeks and touches every component.
Rapid product iteration
When UI copy changes weekly, locale file maintenance becomes a drag on velocity. Every copy update requires touching every locale file before translators can review it. Automatic translation handles new strings on first render without any developer action.
Existing Nuxt apps without i18n
Adding nuxt-i18n to a project that was not built with it means touching every component that renders text. A DOM-based widget requires zero component changes — add the script tag, select languages, done.
What automatic translation does not cover
DOM-based translation has real limits. Understand them before choosing:
Server-only strings
Strings used in server-side email templates, API response bodies, or Nuxt server routes are never rendered to a browser DOM and will not be translated.
Complex ICU pluralization
Applications that need grammatically correct pluralization for languages with complex plural rules (Arabic: six forms, Polish: four forms) for dynamically counted values require ICU message format support — a library feature, not DOM-level translation.
Offline / PWA requirement
If your Nuxt app must function fully offline, automatic translation is not an option — it requires a network call to fetch translations. Use locale JSON files bundled at build time.
Frequently asked questions
- Does Lingvit work with Nuxt 3 and Nuxt 4?
- Yes. Lingvit is framework-agnostic and operates at the DOM level. Both Nuxt 3 and Nuxt 4 produce standard HTML that the widget observes via MutationObserver. Add the script via app.head.script in nuxt.config.ts with tagPosition: 'bodyClose' and it works out of the box.
- Does Lingvit work with Nuxt SSR?
- Yes. Nuxt's server-rendered HTML is sent to the browser, then Vue hydrates it. Lingvit initialises after hydration is complete and picks up all text nodes from the rendered DOM — whether they came from the server or from client-side state changes.
- Will Lingvit interfere with Vue hydration?
- No. The Lingvit script loads asynchronously after the DOM is ready and waits for the page to stabilise before translating. It does not touch the virtual DOM, does not modify component state, and does not trigger Vue reactivity — it only modifies rendered text nodes.
- Can I use @nuxtjs/i18n alongside Lingvit?
- Yes. If you already have @nuxtjs/i18n for some content and want automatic translation for the rest, the two can coexist. Lingvit translates whatever text nodes are in the DOM at render time, including strings already translated by nuxt-i18n.
Related guides
Add languages to your Nuxt app
Works with Nuxt 3 and Nuxt 4, SSR and static. One config entry, no component changes.