Tutorial
First steps
You need to provide your translations to svelte-fluent
by creating a SvelteFluent object
and initializing the FluentContext.
In the most basic setup those translations can be defined directly in the code like this:
<script>
import { FluentBundle, FluentResource } from '@fluent/bundle';
import { createSvelteFluent, initFluentContext, Localized } from '@nubolab-ffwd/svelte-fluent';
const translations = 'hello = Hello, world!';
const bundle = new FluentBundle('en');
bundle.addResource(new FluentResource(translations));
initFluentContext(() => createSvelteFluent([bundle]));
</script>
<Localized id="hello" />
Load translations from files
Managing translations directly in the code can get messy.
A better way is to load translations from .ftl
files.
<script>
import { FluentBundle } from '@fluent/bundle';
import { createSvelteFluent, initFluentContext, Localized } from '@nubolab-ffwd/svelte-fluent';
import resourceEn from './en.ftl';
const bundle = new FluentBundle('en');
bundle.addResource(resourceEn);
initFluentContext(() => createSvelteFluent([bundle]));
</script>
<Localized id="hello" />
hello = Hello, world!
Multiple languages
We can now extend this to multiple languages. Let’s add a selection for the desired language and fallback to the auto-detected language of the browser.
This example will fail when used with server-side rendering (SSR) because during SSR the component cannot use browser-only globals like
navigator.languages
.As an alternative that works in SSR you can parse the HTTP Accept-Language header and replace
navigator.languages
with the resulting list of locale identifiers.Check out the SvelteKit integration guide for an example how to do this.
<script>
import { FluentBundle } from '@fluent/bundle';
import { createSvelteFluent, initFluentContext, Localized } from '@nubolab-ffwd/svelte-fluent';
import { negotiateLanguages } from '@fluent/langneg';
import resourceEn from './en.ftl';
import resourceDe from './de.ftl';
import resourceFr from './fr.ftl';
// this could be stored in a user profile or browser localStorage
let { selectedLocale = '' } = $props();
const defaultLocale = 'en';
const resources = {
en: resourceEn,
fr: resourceFr,
de: resourceDe
};
const supportedLocales = Object.keys(resources);
const bundles = $derived.by(() => {
const userLocales = selectedLocale ? [selectedLocale] : navigator.languages;
// Choose locales that are best for the user.
const selectedLocales = negotiateLanguages(userLocales, supportedLocales, {
defaultLocale,
strategy: 'lookup'
});
// prepare fluent bundles
return selectedLocales.map((locale) => {
const bundle = new FluentBundle(locale);
bundle.addResource(resources[locale]);
return bundle;
});
});
initFluentContext(() => createSvelteFluent(bundles));
</script>
<Localized id="hello" />
hello = Hello, world!
hello = Salut le monde !
hello = Hallo Welt!
Interpolation
You can insert variables into your translated text by using Fluent Placeables.
Values for those variables are provided via the args
prop of the Localized and Overlay components.
<script>
import { FluentBundle } from '@fluent/bundle';
import { createSvelteFluent, initFluentContext, Localized } from '@nubolab-ffwd/svelte-fluent';
import resourceEn from './en.ftl';
const bundle = new FluentBundle('en');
bundle.addResource(resourceEn);
initFluentContext(() => createSvelteFluent([bundle]));
</script>
<Localized id="hello" args={{ name: 'everyone' }} />
hello = Hello, { $name }!
Interpolation formatting
Fluent outputs interpolations in a human readable format appropriate to the currently used locale. You can customize the formatting by using Fluent Functions.
Formatting parameters listed in “Parameters” in the Fluent Functions documentation can be set both in the .ftl
files or in the JS source.
Parameters listed in “Developer parameters” can only be set in JS code.
<script>
import { FluentBundle, FluentNumber } from '@fluent/bundle';
import { createSvelteFluent, initFluentContext, Localized } from '@nubolab-ffwd/svelte-fluent';
import resourceEn from './en.ftl';
const bundle = new FluentBundle('en');
bundle.addResource(resourceEn);
initFluentContext(() => createSvelteFluent([bundle]));
</script>
<div><Localized id="dpi-ratio" args={{ ratio: 16 / 9 }} /></div>
<div>
<Localized
id="balance"
args={{ balance: new FluentNumber(1234.56, { style: 'currency', currency: 'USD' }) }}
/>
</div>
<div><Localized id="today-is" args={{ date: new Date() }} /></div>
dpi-ratio = Your DPI ratio is { NUMBER($ratio, minimumFractionDigits: 2) }
balance = Your account balance is { $balance }
today-is = Today is { DATETIME($date, month: "long", year: "numeric", day: "numeric") }
Custom functions
You can extend the default Fluent Functions with custom formatting functions by adding them to the functions
option of FluentBundle
.
You can also check out the code of the built-in Fluent Functions for more examples.
<script>
import { FluentBundle, FluentDateTime, FluentNone, FluentNumber } from '@fluent/bundle';
import { createSvelteFluent, initFluentContext, Localized } from '@nubolab-ffwd/svelte-fluent';
import resourceEn from './en.ftl';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function WEEKDAY(args, opts) {
const arg = args[0];
if (arg instanceof FluentNone) {
return new FluentNone(`WEEKDAY(${arg.valueOf()})`);
}
if (arg instanceof FluentNumber) {
const date = new Date(Date.now());
const offset = arg.valueOf() - date.getDay();
date.setDate(date.getDate() + offset);
return new FluentDateTime(date.valueOf(), {
weekday: 'long'
});
}
if (arg instanceof FluentDateTime) {
return new FluentDateTime(arg.valueOf(), {
weekday: 'long'
});
}
throw new TypeError('Invalid argument to WEEKDAY');
}
const bundle = new FluentBundle('en', { functions: { WEEKDAY } });
bundle.addResource(resourceEn);
initFluentContext(() => createSvelteFluent([bundle]));
</script>
<div>
<Localized id="weekday-number" args={{ weekday: 2 }} />
</div>
<div>
<Localized id="weekday-date" args={{ date: new Date(2024, 1, 6) }} />
</div>
weekday-number = Weekday { $weekday } is { WEEKDAY($weekday) }
weekday-date = Weekday of { $date } is { WEEKDAY($date) }