Skip to content

Integrations

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:

Installation

Install the package with your preferred package manager.

 pnpm add @verbaly/svelte

Setup & stores

+layout.svelte · Inbox.svelte
<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>

Write the text in place

The compiler reads your .svelte files too: write the source text with the store tag, in a markup expression or in the script, and it is extracted, keyed and typed on save. Plain display text is never extracted, so a page can show t`…` as an example safely. No key to invent, no catalog to touch.

Inbox.svelte
<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>

Svelte has a real component too: import it from the Trans.svelte subpath (raw component, compiled by your bundler):

Title.svelte
<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 }} />

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.

SvelteKit needs the SSR package (@verbaly/sveltekit) alongside the Svelte bindings:

 pnpm add verbaly @verbaly/sveltekit @verbaly/svelte @verbaly/vite

Wire it up

Three wires on the server, one on the client. First the Vite plugin and the lang placeholder:

vite.config.ts · src/app.html
// 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%" dir="%verbaly.dir%">

The server hook resolves the locale per request and fills the placeholder. Pass routing and the address decides when it carries the language, falling back to the saved choice, then Accept-Language, then your fallback:

src/hooks.server.ts · src/app.d.ts
// src/hooks.server.ts
import { verbalyHandle } from '@verbaly/sveltekit';
import { locales, routing } from 'virtual:verbaly';

export const handle = verbalyHandle({ locales, routing });

// 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:

src/routes/+layout.server.ts · +layout.ts · +layout.svelte
// +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.

$t is typed by your catalog: the types go to src/verbaly.d.ts, the folder SvelteKit's tsconfig reads, so svelte-check sees them with nothing to configure and fails on a key no catalog has.

One route tree for every language

If your pages live at /es/docs, you do not need a route folder per language. One universal hook makes /es/docs render through your /docs route, so your routes stay exactly as they were:

src/hooks.ts
import { verbalyReroute } from '@verbaly/sveltekit';
import { locales } from 'virtual:verbaly';

export const reroute = verbalyReroute({ locales });

It goes in hooks.ts and not hooks.server.ts, because moving between pages in the browser has to resolve the same way the server did.

Switching languages

switchLocale loads the catalog first, switches live, then writes the cookie the server hook reads, so the next SSR request already matches:

LangPicker.svelte
<script>
  import { switchLocale } from '@verbaly/sveltekit';
  import { useVerbaly } from '@verbaly/svelte';

  const verbaly = useVerbaly();
</script>

<button onclick={() => switchLocale(verbaly, 'es')}>Español</button>
↑↓to moveEnterto open
Copied to clipboard