SvelteKit
@transglot/sveltekit: load a published bundle in a load function and seed the Svelte context, so server-rendered markup is translated.
@transglot/sveltekit fetches a locale's published bundle in a SvelteKit load function and seeds the @transglot/svelte context from it synchronously. Server-rendered markup contains translations, and hydration does not fetch the seeded locale again.
It needs SvelteKit 2 and Svelte 5.
Install
npm install @transglot/sveltekit@transglot/svelte and @transglot/runtime are installed with it, and the package re-exports what you need from both, so you import everything from @transglot/sveltekit.
Setup
Add your CDN key to .env as TRANSGLOT_CDN_KEY, then load the snapshot in a server load function:
import { TRANSGLOT_CDN_KEY } from '$env/static/private';
import { loadTranslations } from '@transglot/sveltekit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async (event) => ({
snapshot: await loadTranslations(event, {
baseUrl: 'https://api.transglot.ai',
project: 42,
cdnKey: TRANSGLOT_CDN_KEY,
locale: event.params.locale,
}),
});Seed the context in the matching layout component:
<script lang="ts">
import { setTransglotContextFromSnapshot } from '@transglot/sveltekit';
let { data, children } = $props();
setTransglotContextFromSnapshot({ snapshots: data.snapshot });
</script>
{@render children()}Translate in any component below it:
<script lang="ts">
import { useTranslations } from '@transglot/sveltekit';
const { t } = useTranslations();
</script>
<h1>{$t('home.title')}</h1>
<p>{$t('home.greeting', { name: 'Ada' })}</p>The CDN key stays on the server: the snapshot carries the bundle and its headers, not the key.
Use a server load function
Load the snapshot in +layout.server.ts or +page.server.ts. In a universal +layout.ts, the key ends up in your client code, the load runs in the browser on client-side navigation (where the CDN's CORS rules apply, see In a browser), and during server rendering SvelteKit refuses to let the loader read the etag, x-cdn-format and x-cdn-version response headers unless your handle hook allows them with filterSerializedResponseHeaders.
Switching locale
setTransglotContextFromSnapshot runs once, when the layout component initializes. SvelteKit keeps a layout mounted when only its route parameters change, so navigating from /en to /fr does not reseed it. Either:
- reload the page on a locale change, for example with
data-sveltekit-reloadon your language links, or - seed every locale you serve and switch with
setLocale, which needs no network request between seeded locales:
import { TRANSGLOT_CDN_KEY } from '$env/static/private';
import { loadTranslations } from '@transglot/sveltekit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async (event) => ({
snapshots: await Promise.all(
['en', 'fr', 'de'].map((locale) =>
loadTranslations(event, {
baseUrl: 'https://api.transglot.ai',
project: 42,
cdnKey: TRANSGLOT_CDN_KEY,
locale,
}),
),
),
});<script lang="ts">
import { setTransglotContextFromSnapshot } from '@transglot/sveltekit';
let { data, children } = $props();
const { locale, setLocale } = setTransglotContextFromSnapshot({
snapshots: data.snapshots,
locale: 'en',
});
</script>
<button onclick={() => setLocale($locale === 'en' ? 'fr' : 'en')}>{$locale}</button>
{@render children()}Switching to a locale that was not seeded, without a loadSnapshot, keeps the current locale and logs a dev warning.
Freshness
The seeded client does not poll or revalidate. A new publish reaches users when a load function runs loadTranslations again, which happens on each server render of the route unless you cache it.
API
loadTranslations(event, options)
loadTranslations(event: LoadEventLike, options: LoadTranslationsOptions): Promise<TranslateSnapshot>
event is the load event, or any object with a fetch function; the loader uses event.fetch. It fetches one locale with the runtime client (the same URL, authentication, 429 retries and error mapping) and returns the raw body with its headers.
Prop
Type
TranslateSnapshot has the same fields as in the Next.js package: project, locale, baseUrl, format, version, etag and bundle. See TranslateSnapshot.
setTransglotContextFromSnapshot(options)
setTransglotContextFromSnapshot(options: CreateSeededClientOptions): TranslationStore
Call it while a component initializes. It builds a seeded client, sets the @transglot/svelte context with it, and returns the store ({ t, locale, setLocale, client }).
Prop
Type
createSeededClient(options)
createSeededClient(options: CreateSeededClientOptions): RuntimeClient returns the seeded client on its own, with the same options. Its refresh() does nothing for a locale it holds, hydrate(locale?) resolves true for a seeded locale, and stop() does nothing.
Re-exports
- From
@transglot/svelte:setTransglotContext,useTranslations,createTranslationStore, and the typesTransglotSource,TranslationStore,TranslateFn. See Svelte. - From
@transglot/runtime:createClient,RuntimeError,parseBundle,interpolate, and the typesRuntimeClient,ClientOptions. - Types:
LoadTranslationsOptions,LoadEventLike,TranslateSnapshot,CreateSeededClientOptions.
Errors
loadTranslations rejects with the runtime's RuntimeError; the slugs are listed on the JavaScript runtime page. Map it to a SvelteKit error in your load function:
import { TRANSGLOT_CDN_KEY } from '$env/static/private';
import { RuntimeError, loadTranslations } from '@transglot/sveltekit';
import { error } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async (event) => {
try {
return {
snapshot: await loadTranslations(event, {
baseUrl: 'https://api.transglot.ai',
project: 42,
cdnKey: TRANSGLOT_CDN_KEY,
locale: event.params.locale,
}),
};
} catch (err) {
if (err instanceof RuntimeError && err.slug === 'not-found') {
error(404, 'Unknown locale');
}
throw err;
}
};Seeding throws Error('[transglot] createSeededClient() requires at least one snapshot.') for an empty array, and a RuntimeError with the slug unsupported-format when the project's delivery format is not one of json_flat, json_nested, laravel_json or flutter_arb.