Integration
SvelteKit
Initialize a SvelteKit app
Use npm create to initialize an new SvelteKit project called svelte-fluent-sveltekit.
- Select “Skeleton project” when asked for the template
- Select “Yes, using TypeScript syntax” when asked for typescript type checking
npm create svelte@latest svelte-fluent-sveltekit
cd svelte-fluent-sveltekit
npm install Now install svelte-fluent:
npm install --save-dev @nubolab-ffwd/svelte-fluent
npm install --save jsdom We also need @fluent/bundle to parse the .ftl files:
npm install --save-dev @fluent/bundle We will be using @fluent/langneg for selecting the displayed language based on
the browser’s Accept-Language header. Let’s install it with:
npm install --save-dev @fluent/langneg Some features of svelte-fluent require a vite plugin to function.
Let’s add it to vite.config.ts:
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
+import svelteFluent from '@nubolab-ffwd/svelte-fluent/vite';
export default defineConfig({
- plugins: [sveltekit()]
+ plugins: [svelteFluent(), sveltekit()]
});
Create translation files
Let’s create some translation files that we can use in our application:
# src/translations/en.ftl
welcome = Welcome to svelte-fluent! # src/translations/de.ftl
welcome = Willkommen bei svelte-fluent! Load translations and select language
Now that we have some translation files, we can load them.
We also want our app to respect the browser language settings of our visitors.
Let’s create some helpers for that in src/lib/fluent.ts:
// src/lib/fluent.ts
import { FluentBundle, FluentResource } from '@fluent/bundle';
import { acceptedLanguages, negotiateLanguages } from '@fluent/langneg';
import type { RequestEvent } from '@sveltejs/kit';
import resourcesDe from '../translations/de.ftl';
import resourcesEn from '../translations/en.ftl';
const defaultLocale = 'en';
const resources: Record<string, FluentResource> = {
de: resourcesDe,
en: resourcesEn
};
export function generateBundles(locale: string): FluentBundle[] {
const bundle = new FluentBundle(locale);
bundle.addResource(resources[locale]);
return [bundle];
}
export function negotiateLocale(ev: RequestEvent): string {
const accepted = acceptedLanguages(ev.request.headers.get('accept-language') ?? '');
return (
negotiateLanguages(accepted, Object.keys(resources), {
defaultLocale,
strategy: 'lookup'
}).at(0) ?? defaultLocale
);
} Typescript will complain about the imports of the .ftl files.
Don’t worry, we’ll fix this in a moment.
Add server hook
We need to add a SvelteKit server hook that selects the appropriate locale and creates the SvelteFluent object.
Let’s use the new helpers we created to build the hook:
// src/hooks.server.ts
import { generateBundles, negotiateLocale } from '$lib/fluent';
import type { Handle } from '@sveltejs/kit';
import { createSvelteFluent } from '@nubolab-ffwd/svelte-fluent';
export const handle: Handle = async ({ event, resolve }) => {
event.locals.locale = negotiateLocale(event);
event.locals.fluent = createSvelteFluent(generateBundles(event.locals.locale));
return resolve(event);
}; Now Typescript will complain about event.locals.locale and event.locals.fluent.
To fix this we need to modify our src/app.d.ts:
+import '@nubolab-ffwd/svelte-fluent/types';
+import { SvelteFluent } from '@nubolab-ffwd/svelte-fluent';
+
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
- // interface Locals {}
+ interface Locals {
+ locale: string;
+ fluent: SvelteFluent;
+ }
// interface PageData {}
// interface PageState {}
// interface Platform {} Client integration
On the client side, it’s impossible to access event.locals.locale and event.locals.fluent that we added in the server hook. We need some additional
code to bridge the gap.
Let’s start by adding a src/routes/+layout.server.ts:
// src/routes/+layout.server.ts
export function load(event) {
// expose selected locale from hook to client
return { locale: event.locals.locale };
} This exposes event.locals.locale as event.data.locale on the client.
We also need the SvelteFluent object which we can create in src/routes/+layout.ts:
// src/routes/+layout.ts
import { generateBundles } from '$lib/fluent';
import { createSvelteFluent } from '@nubolab-ffwd/svelte-fluent';
export function load(event) {
return {
...event.data,
fluent: createSvelteFluent(generateBundles(event.data.locale))
};
} Now we can access the SvelteFluent object via event.data.fluent and
use it to initialize the FluentContext in src/routes/+layout.svelte which
is required for using the Localized and Overlay components.
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { initFluentContext } from '@nubolab-ffwd/svelte-fluent';
import type { PageData } from './$types';
import type { Snippet } from 'svelte';
let { data, children }: { data: PageData; children: Snippet } = $props();
initFluentContext(() => data.fluent);
</script>
{@render children()}
Render your first localized message
With all the setup work complete, it’s finally time to render your first
localized message in src/routes/+page.svelte:
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { Localized } from '@nubolab-ffwd/svelte-fluent';
</script>
<h1><Localized id="welcome" /></h1>
Launch the app
Now we have all the pieces in place and can open the app. Run this in a terminal:
npm run dev Open your browser and go to http://localhost:5173 and you should see the app.
Bonus: server-side localization
We want to extend our application with a form. The form should validate inputs and generate localized error messages if validation fails.
Let’s start by adding some additional messages to our translation files:
# src/translations/en.ftl
welcome = Welcome to svelte-fluent!
+
+example-form =
+ .heading = Example form
+ .name-field-label = Name
+ .submit-label = Submit
+ .name-required-error = Name must not be empty
+ .success-message = Form was submitted successfully! # src/translations/de.ftl
welcome = Willkommen bei svelte-fluent!
+
+example-form =
+ .heading = Beispielformular
+ .name-field-label = Name
+ .submit-label = Absenden
+ .name-required-error = Name darf nicht leer sein
+ .success-message = Formular wurde erfolgreich abgeschickt! Next we add a form to src/routes/+page.svelte:
</script>
<h1><Localized id="welcome" /></h1>
+
+<Localized id="example-form">
+ {#snippet children({ attrs })}
+ <h2>{attrs.heading}</h2>
+ <form method="POST">
+ <label>
+ {attrs['name-field-label']}
+ <input name="name" />
+ </label>
+ <button>{attrs['submit-label']}</button>
+ </form>
+ {/snippet}
+</Localized> Also we need to add a SvelteKit form action to src/routes/+page.server.ts:
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions = {
default: async ({ request, locals: { fluent } }) => {
const data = await request.formData();
const name = data.get('name');
if (!name || !name.toString().trim()) {
// render the localized error message
const error = fluent.localize('example-form.name-required-error');
return fail(400, { name, error });
}
return { success: true };
}
} satisfies Actions; And finally, we need to handle the response from the action in our form in src/routes/+page.svelte:
<script lang="ts">
import { Localized } from '@nubolab-ffwd/svelte-fluent';
+ import type { ActionData } from './$types';
+
+ let { form }: { form: ActionData } = $props();
</script>
<Localized id="example-form">
{#snippet children({ attrs })}
<h2>{attrs.heading}</h2>
+
+ {#if form?.success}
+ <p>{attrs['success-message']}</p>
+ {/if}
+
<form method="POST">
+ {#if form?.error}<p class="error">{form.error}</p>{/if}
<label>
{attrs['name-field-label']}
- <input name="name" />
+ <input name="name" value={form?.name ?? ''} />
</label>
<button>{attrs['submit-label']}</button>
</form> Now open your browser and go to http://localhost:5173 and you should see the new form that displays localized error messages when you submit it.
What’s next?
You now have a fully functional application where you can localize messages with svelte-fluent.
You can learn more about how to use svelte-fluent in the Tutorial or check out the Reference for API documentation.

