Svelte i18n Integration
Comvi’s Svelte integration uses Svelte’s native store system for reactive translations. It supports both Svelte 4 (reactive $: declarations) and Svelte 5 (runes), and works with SvelteKit for SSR.
Installation
Section titled “Installation”pnpm add @comvi/svelte @comvi/core @comvi/plugin-fetch-loader-
Create the i18n instance
src/lib/i18n.ts import { createI18n } from '@comvi/core';import { FetchLoader } from '@comvi/plugin-fetch-loader';export const i18n = createI18n({locale: 'en',fallbackLocale: 'en',}).use(FetchLoader({cdnUrl: 'https://cdn.comvi.io/your-distribution-id',})); -
Set up the context in your root layout
src/routes/+layout.svelte <script lang="ts">import { setI18nContext } from '@comvi/svelte';import { i18n } from '$lib/i18n';setI18nContext(i18n);</script><slot /> -
Use in components
src/routes/+page.svelte <script lang="ts">import { useI18n } from '@comvi/svelte';const { t, locale } = useI18n();</script><h1>{$t('hello.world')}</h1><p>{$t('welcome.message', { name: 'Alice' })}</p><p>Current locale: {$locale}</p>
Context Setup
Section titled “Context Setup”Comvi uses Svelte’s context API to make the i18n instance available to all child components. Call setI18nContext once in your root layout. All descendant components can then access translations with useI18n() or getI18nContext().
<script lang="ts"> import { setI18nContext } from '@comvi/svelte'; import { i18n } from '$lib/i18n';
setI18nContext(i18n);</script>
<slot />Reading the Context Directly
Section titled “Reading the Context Directly”If you need the raw i18n instance (for example, in a utility function), use getI18nContext():
<script lang="ts"> import { getI18nContext } from '@comvi/svelte';
const i18n = getI18nContext();</script>useI18n() Store
Section titled “useI18n() Store”The primary function for accessing translations and locale state. It returns Svelte stores that you subscribe to with the $ prefix.
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { t, locale, isLoading, setLocale } = useI18n();</script>
{#if $isLoading} <p>Loading translations...</p>{:else} <h1>{$t('page.title')}</h1> <p>Current locale: {$locale}</p>{/if}Returned values:
| Property | Type | Description |
|---|---|---|
t | Readable<(key, params?) => string> | Reactive translation function (use as $t(...)) |
tRaw | Readable<(key, params?) => TranslationResult> | Structured output for advanced renderers (use as $tRaw(...)) |
locale | Readable<string> | Current language (read-only store, use setLocale() to change) |
isLoading | Readable<boolean> | Whether translations are being loaded |
setLocale | (lang: string) => Promise<void> | Switch to a different language |
Changing Language
Section titled “Changing Language”Use setLocale() to switch languages. The locale store is read-only:
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { locale, setLocale } = useI18n();</script>
<select value={$locale} on:change={(e) => setLocale(e.target.value)}> <option value="en">English</option> <option value="de">Deutsch</option> <option value="fr">Francais</option></select><script lang="ts"> import { useI18n } from '@comvi/svelte';
const { locale, setLocale } = useI18n();</script>
<button on:click={() => setLocale('de')}> Switch to German</button><p>Current: {$locale}</p>The <T> Component
Section titled “The <T> Component”For translations containing HTML or child components, use the <T> component for safe interpolation:
<script lang="ts"> import { T } from '@comvi/svelte';</script>
<!-- Translation: "Read our <link>terms of service</link>" --><T i18nKey="legal.tos" components={{ link: { tag: 'a', props: { href: '/terms' } }, }}/>Namespaces
Section titled “Namespaces”Load translations from a specific namespace:
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { t } = useI18n('dashboard');</script>
<h1>{$t('page.title')}</h1>Svelte 5 Runes
Section titled “Svelte 5 Runes”Comvi works with Svelte 5 runes. The stores returned by useI18n() are compatible with the $ rune syntax:
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { t, locale, setLocale } = useI18n();</script>
<h1>{$t('page.title')}</h1>
<select value={$locale} onchange={(e) => setLocale(e.target.value)}> <option value="en">English</option> <option value="de">Deutsch</option></select>Derived State with Runes
Section titled “Derived State with Runes”In Svelte 5, use $derived for computed translations:
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { t } = useI18n();
let count = $state(0); let cartMessage = $derived($t('cart.items', { count }));</script>
<p>{cartMessage}</p><button onclick={() => count++}>Add item</button>Svelte 4 Reactive Declarations
Section titled “Svelte 4 Reactive Declarations”In Svelte 4, use $: reactive declarations for derived translations:
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { t } = useI18n();
let count = 0; $: cartMessage = $t('cart.items', { count });</script>
<p>{cartMessage}</p><button on:click={() => count++}>Add item</button>SvelteKit SSR
Section titled “SvelteKit SSR”Loading Translations Server-Side
Section titled “Loading Translations Server-Side”Initialize translations on the server so they are included in the initial HTML response:
import { i18n } from '$lib/i18n';import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ request }) => { const acceptLanguage = request.headers.get('accept-language'); // Parse the Accept-Language header to find a supported language const locale = acceptLanguage?.split(',')[0]?.split('-')[0] || 'en';
i18n.locale = locale; await i18n.init();
return { locale, translations: i18n.getTranslations(locale), };};Hydrating on the Client
Section titled “Hydrating on the Client”Pass server-loaded translations to the client layout to avoid a duplicate fetch:
<script lang="ts"> import { setI18nContext } from '@comvi/svelte'; import { i18n } from '$lib/i18n'; import type { LayoutData } from './$types';
export let data: LayoutData;
// Hydrate with server-loaded translations i18n.addTranslations({ [data.locale]: data.translations }); i18n.locale = data.locale;
setI18nContext(i18n);</script>
<html lang={data.locale}> <body> <slot /> </body></html>SEO with <svelte:head>
Section titled “SEO with <svelte:head>”Add language metadata for search engines:
<script lang="ts"> import { useI18n } from '@comvi/svelte';
const { locale } = useI18n();</script>
<svelte:head> <html lang={$locale} /> <link rel="alternate" hreflang="en" href="https://yoursite.com/en" /> <link rel="alternate" hreflang="de" href="https://yoursite.com/de" /></svelte:head>TypeScript
Section titled “TypeScript”Enable type-safe translations with auto-generated types. The Comvi CLI emits a .d.ts that augments the TranslationKeys interface in @comvi/core for APIs that expose the typed core translation function.
import { createI18n } from '@comvi/core';// Side-effect import — augments @comvi/core's TranslationKeys interfaceimport './types/i18n';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en',});The current Svelte $t store is typed as (key: string, params?: TranslationParams) => string, so it remains permissive even when generated types are imported. Use the generated types with direct core APIs or other bindings that expose typed t() overloads. See Type-Safe Translations for the full setup.