Framework
Svelte
The official Svelte 5 package for Verbaly. Idiomatic stores over the same reactive core, store factories for app-level use, and a <Trans> component for rich text.
What you can accomplish:
- Translate with stores:
$tre-renders on every locale switch, no wiring - Bind a language switcher in one line:
bind:valueon the locale store just works - Use context or store factories, with or without a component tree
- Map a tag in a message to your own component: <Trans> gains the components prop in 0.23.0
Installation
Install the package with your preferred package manager.
pnpm add @verbaly/svelteSetup & stores
<script>
// root component / layout:
import { provideVerbaly } from '@verbaly/svelte';
import { verbaly } from 'virtual:verbaly';
provideVerbaly(verbaly);
</script>
<script>
// any child component:
import { useT, useLocale } from '@verbaly/svelte';
const t = useT();
const locale = useLocale(); // writable store
</script>
<p>{$t('inbox', { count: 3 })}</p>
<select bind:value={$locale}>…</select>- Idiomatic stores:
$tre-renders on every locale switch;bind:value={$locale}on a select just works. - No component tree?
tStore(instance)/localeStore(instance)build the same stores without context, as app-level singletons. - Rich text: the core's DOM interpreter works in any Svelte app via
data-verbaly-rich+bindDom(see Plain HTML → Rich text).
Write the text in place
Since 0.20.0 the compiler reads your .svelte files too: write the source text with the store tag, in markup or script, and it is extracted, keyed and typed on save. No key to invent, no catalog to touch.
<h1>{$t`Hola ${name}, tienes ${count} mensajes`}</h1>
<!-- extracted on save; the build rewrites it to: -->
<h1>{$t('x7Ka9q2f', { name, count })}</h1>Prefer readable keys? $t.id('inbox.title') before the template extracts under your id instead of a content hash.
Rich text with <Trans>
Since 0.10.0 Svelte has a real component too: import it from the Trans.svelte subpath (raw component, compiled by your bundler):
<script>
import Trans from '@verbaly/svelte/Trans.svelte';
import DocsLink from './DocsLink.svelte';
</script>
<Trans id='home.title' />
<Trans id='greet' values={{ name: 'Aron' }} />
<!-- message: 'Read the <docs>guide</docs>' → your component wraps the tag content -->
<Trans id='cta' components={{ docs: DocsLink }} />- Message tags (
The <em>build</em> gate) render as real elements against the same phrasing whitelist asdata-verbaly-rich. Unknown tags unwrap to inert text,richTagsoverrides. - Uses the instance from
provideVerbaly; passinstance={verbaly}explicitly when there's no context. Re-renders on every locale switch. - Named links (
0.11.0): thelinksprop renders named tags as real<a>elements, with hrefs from your code, never from messages;javascript:blocked. - Your own components (
0.23.0): thecomponentsprop maps a tag to a Svelte component that receives the tag content as children, and wins over links and the whitelist. Needs Svelte 5; on Svelte 4 stay on0.22.0.
SvelteKit
Server-rendered pages arrive already translated in the visitor's language, and the client hydrates with the same locale and catalog: no flash of untranslated text, no hydration mismatch.
- Every page renders in the visitor's language, chosen per request from their cookie or
Accept-Languageheader - No flash of untranslated text: the HTML the server sends and the page after hydration are identical, catalog included
- Each request gets its own instance, so simultaneous visitors never see each other's language
- A one-call language switcher:
switchLocaleloads the catalog, then persists the choice for the next server render
SvelteKit needs the SSR package (@verbaly/sveltekit) alongside the Svelte bindings:
pnpm add verbaly @verbaly/sveltekit @verbaly/svelte @verbaly/viteWire it up
Three wires on the server, one on the client. First the Vite plugin and the lang placeholder:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import verbaly from '@verbaly/vite';
export default { plugins: [verbaly(), sveltekit()] };
<!-- src/app.html -->
<html lang="%verbaly.lang%">The server hook resolves the locale per request (cookie, then Accept-Language, then your fallback) and fills the placeholder:
// src/hooks.server.ts
import { verbalyHandle } from '@verbaly/sveltekit';
import { locales } from 'virtual:verbaly';
export const handle = verbalyHandle({ locales });
// src/app.d.ts
declare namespace App {
interface Locals { verbalyLocale: string }
}Hand the locale to the client, then build one instance per render with the catalog awaited, the no-flash guarantee:
// +layout.server.ts
export const load = ({ locals }) => ({ locale: locals.verbalyLocale });
// +layout.ts
import { createRequestInstance } from 'virtual:verbaly';
export const load = async ({ data }) => ({
...data,
verbaly: await createRequestInstance(data.locale), // catalog ready BEFORE render
});
<!-- +layout.svelte -->
<script>
import { provideVerbaly } from '@verbaly/svelte';
let { data, children } = $props();
provideVerbaly(data.verbaly);
</script>
{@render children()}From here it's plain @verbaly/svelte: useT() / useLocale() stores and Trans.svelte work unchanged in every component.
Switching languages
switchLocale loads the catalog first, switches live, then writes the cookie the server hook reads, so the next SSR request already matches:
<script>
import { switchLocale } from '@verbaly/sveltekit';
import { useVerbaly } from '@verbaly/svelte';
const verbaly = useVerbaly();
</script>
<button onclick={() => switchLocale(verbaly, 'es')}>Español</button>- The cookie defaults to
verbaly-locale(same identity as the core's storage key);cookie: falseon either side disables it, for pureAccept-Language. fallbackdefaults to your first locale; negotiation understands q-values and narrows regions (es-PEmatcheses).- No dependency on SvelteKit itself: the hook is typed structurally and stays compatible across Kit 2.x.