Svelte i18n vs automatic translation: which should you use?
SvelteKit doesn't have built-in i18n beyond basic URL routing. The two main options are a string-extraction library — svelte-i18n or Paraglide-JS — or DOM-level automatic translation. The right choice depends on your project's timeline, team size, and content update cadence.
The string-extraction approach: svelte-i18n and Paraglide-JS
svelte-i18n is the most widely used i18n library in the Svelte ecosystem. It works with a familiar message-file model: you store translations in JSON files keyed by locale, then reference strings in templates using the $_() syntax backed by a reactive Svelte store.
// src/lib/i18n.js
import { addMessages, init, getLocaleFromNavigator } from 'svelte-i18n'
import en from './locales/en.json'
import fr from './locales/fr.json'
addMessages('en', en)
addMessages('fr', fr)
init({ fallbackLocale: 'en', initialLocale: getLocaleFromNavigator() })
In Svelte templates you import the $_ store and call it as a function:
<script>
import { _ } from 'svelte-i18n'
</script>
<h1>{$_('home.title')}</h1>
<p>{$_('home.description')}</p>
Paraglide-JSby inlang takes a different approach: it is compile-time, typesafe, and generates fully typed message functions from your locale files at build time. This means you get IDE autocomplete on every translation key and a build error if you reference a key that doesn't exist. It's newer but gaining traction quickly in the SvelteKit community as an alternative for teams that want stronger guarantees.
Both libraries share the same fundamental workflow: extract every string from your components into locale JSON files, maintain one file per language, update those files whenever copy changes, and handle plurals and interpolation explicitly in message syntax. For a mid-size SvelteKit app, initial setup typically takes one to three days — and that's before filling in the translations themselves.
The automatic translation approach
The alternative is to operate at the DOM level rather than the component level. You add one script tag to src/app.html before </body>, and Lingvit handles the rest:
<!-- src/app.html -->
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
<script src="https://cdn.lingvit.com/widget.bundle.js"
data-project-id="YOUR_PROJECT_ID"></script>
</body>
Lingvit's MutationObserver watches for DOM changes and translates text nodes as they appear. This covers the full range of SvelteKit's reactivity model:
- →Route transitions.SvelteKit's client-side navigation replaces the page body in the DOM. The MutationObserver detects the new content immediately and translates it before the user sees it.
- →Reactive store updates. When a Svelte store changes and causes a DOM update — a counter, a fetched list, a conditional block — the new nodes are translated automatically.
- →
$derivedvalues. Svelte 5's rune-based reactivity still writes to the DOM. Lingvit observes the resulting mutations — the reactive primitive is irrelevant. - →No string extraction, no locale files. New content is translated the moment it appears in the DOM. There is nothing to update in source control when copy changes.
Editorial overrides are available from the dashboard when you want a human-reviewed translation for a specific string without touching code.
How they compare
The two approaches differ across every dimension that matters in practice:
Setup time
Adding svelte-i18n or Paraglide-JS to an existing SvelteKit app means touching every component that renders text — replacing literal strings with $_('key') calls and building out locale files. For a mid-size app this is a multi-day effort. Adding Lingvit uses a single script tag and a guided setup flow; actual setup time varies by app.
Ongoing maintenance
With string extraction, every copy change means updating every locale file. Miss one and a language falls back to English silently. Automatic translation has zero ongoing maintenance: new strings are handled on first render with no developer action required.
Developer experience
Paraglide-JS gives you type-safe translation keys with IDE autocomplete — a genuinely good DX if you're building a product where every string is planned. For marketing sites or rapidly-changing SaaS UIs, replacing literal strings with function calls everywhere is friction that slows iteration. Lingvit requires no changes to Svelte components at all.
SEO
Both approaches can support SEO-friendly locale URLs (/fr/, /de/). Lingvit handles dynamically rendered content automatically — translated text that SvelteKit inserts via client-side navigation or async data is picked up without any extra configuration.
Control
String extraction gives per-key control: every translation is an explicit decision stored in source control. Automatic translation gives dashboard-level review: you can inspect and override individual strings through the Lingvit interface without touching code. Which level of control you need depends on your content's risk profile.
When to choose svelte-i18n or Paraglide-JS
You need compile-time type safety
Paraglide-JS generates typed message functions from your locale files. If your team values catching missing translation keys at build time and wants IDE autocomplete on every key, the compile-time approach is a genuine productivity win — especially on larger teams where multiple developers touch copy.
Your translators work in locale files
If you have an established translation workflow — translators using Crowdin, Weblate, or a TMS that integrates with JSON locale files — svelte-i18n fits naturally. The locale JSON format is a lingua franca for professional translation tooling.
Every string must be reviewed before going live
Legal, medical, or government applications often require explicit approval of every translated string before it reaches production. String extraction gives you that explicit, per-key audit trail — a translation only ships when a human has approved the locale file entry.
You have dedicated i18n engineering time
Maintaining locale files accurately across multiple languages as the product evolves requires ongoing engineering attention. If you have someone whose job includes keeping translations current, the overhead is manageable. Without that person, locale files tend to drift behind the product.
When to choose automatic translation
You need multilingual support today
Retrofitting svelte-i18n onto an existing SvelteKit application means touching every component that renders text. A DOM-based translation widget requires a single script tag change — no component rewrites, no locale file bootstrapping, no build pipeline changes.
Your content updates frequently
Blogs, marketing sites, and SaaS UIs that change weekly are poorly served by string extraction — every content change means updating every locale file, or accepting that some languages show stale copy. Automatic translation handles new content on first render with no developer action.
You don't have dedicated i18n resources
Most product teams don't have an i18n engineer. Without one, locale files accumulate debt fast: missing keys, outdated strings, untranslated placeholders. Automatic translation removes the maintenance burden entirely — the translation layer keeps itself current as the DOM changes.
You're adding languages to an existing SvelteKit app
If your app already exists and you're adding multilingual support as a new requirement, the cost of retrofitting string extraction is proportional to the number of components you've already written. Lingvit requires zero changes to existing Svelte components — add the script tag and configure languages in the dashboard.
Frequently asked questions
- Does automatic translation work with SvelteKit's file-based routing?
- Yes. SvelteKit's client-side navigation replaces page content in the DOM, which Lingvit's MutationObserver detects immediately. Every route change triggers a fresh translation pass on the new content.
- Can I use svelte-i18n for URL routing and automatic translation for content?
- Yes. The two approaches operate at different layers. You can use svelte-i18n or Paraglide-JS purely for locale-aware URL routing (/fr/, /de/) while Lingvit handles all text content translation.
- Does it handle Svelte store-derived content?
- Yes. When a Svelte store update causes reactive DOM mutations, Lingvit observes the resulting DOM changes and translates the new text nodes.
- How does automatic translation handle plurals and interpolated values?
- Lingvit translates the final rendered string — after Svelte has interpolated variables and resolved plurals. You don't need to write translation keys for dynamic strings; the fully rendered text is what gets translated.
Related guides
Add languages to your SvelteKit app
Works with SvelteKit, Svelte 5 runes, and reactive stores. One script tag, no component rewrites.