Tutorial
First steps
You need to provide your translations to svelte-fluent
by adding the FluentProvider
component in your component hierarchy.
In the most basic setup those translations can be defined directly in the code like this:
<script>
import { FluentBundle, FluentResource } from '@fluent/bundle';
import { FluentProvider, Localized } from '@nubolab-ffwd/svelte-fluent';
const translations = 'hello = Hello, world!';
const bundle = new FluentBundle('en');
bundle.addResource(new FluentResource(translations));
</script>
<FluentProvider bundles={[bundle]}>
<Localized id="hello" />
</FluentProvider>
Load translations from files
Managing translations directly in the code can get messy.
A better way is to load translations from .ftl
files.
The bundler must support importing
.ftl
files as strings:
- For Vite you can add
?raw
to your import like in the example below.- For Rollup you can add rollup-plugin-string to your configuration.
<script>
import { FluentBundle, FluentResource } from '@fluent/bundle';
import { FluentProvider, Localized } from '@nubolab-ffwd/svelte-fluent';
import translationsEn from './en.ftl?raw';
const resource = new FluentResource(translationsEn);
const bundle = new FluentBundle('en');
bundle.addResource(resource);
</script>
<FluentProvider bundles={[bundle]}>
<Localized id="hello" />
</FluentProvider>
hello = Hello, world!
Multiple languages
With the basics in place we can now extend this to multiple languages. This example shows selection of the desired language and provides a fallback to the auto-detected language from 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.
<script>
import { FluentBundle, FluentResource } from '@fluent/bundle';
import { FluentProvider, Localized } from '@nubolab-ffwd/svelte-fluent';
import { negotiateLanguages } from '@fluent/langneg';
import translationsEn from './en.ftl?raw';
import translationsDe from './de.ftl?raw';
import translationsFr from './fr.ftl?raw';
// this could be stored in a user profile or browser localStorage
export let selectedLocale = '';
const defaultLocale = 'en';
const resources = {
en: new FluentResource(translationsEn),
fr: new FluentResource(translationsFr),
de: new FluentResource(translationsDe)
};
const supportedLocales = Object.keys(resources);
function generateBundles(userLocales) {
// 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;
});
}
</script>
<FluentProvider bundles={generateBundles(selectedLocale ? [selectedLocale] : navigator.languages)}>
<Localized id="hello" />
</FluentProvider>
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, FluentResource } from '@fluent/bundle';
import { FluentProvider, Localized } from '@nubolab-ffwd/svelte-fluent';
import translationsEn from './en.ftl?raw';
const resource = new FluentResource(translationsEn);
const bundle = new FluentBundle('en');
bundle.addResource(resource);
</script>
<FluentProvider bundles={[bundle]}>
<Localized id="hello" args={{ name: 'everyone' }} />
</FluentProvider>
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, FluentResource } from '@fluent/bundle';
import { FluentProvider, Localized } from '@nubolab-ffwd/svelte-fluent';
import translationsEn from './en.ftl?raw';
const resource = new FluentResource(translationsEn);
const bundle = new FluentBundle('en');
bundle.addResource(resource);
</script>
<FluentProvider bundles={[bundle]}>
<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>
</FluentProvider>
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,
FluentResource
} from '@fluent/bundle';
import { FluentProvider, Localized } from '@nubolab-ffwd/svelte-fluent';
import translationsEn from './en.ftl?raw';
function values(opts, allowed) {
const unwrapped = Object.create(null);
for (const [name, opt] of Object.entries(opts)) {
if (allowed.includes(name)) {
unwrapped[name] = opt.valueOf();
}
}
return unwrapped;
}
const WEEKDAY_ALLOWED = ['weekday'];
function WEEKDAY(args, opts) {
const arg = args[0];
if (arg instanceof FluentNone) {
return new FluentNone(`WEEKDAY(${arg.valueOf()})`);
}
if (arg instanceof FluentDateTime) {
return new FluentDateTime(arg.valueOf(), {
...values(arg.opts, WEEKDAY_ALLOWED),
...values(opts, WEEKDAY_ALLOWED)
});
}
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',
...values(opts, WEEKDAY_ALLOWED)
});
}
throw new TypeError('Invalid argument to WEEKDAY');
}
const resource = new FluentResource(translationsEn);
const bundle = new FluentBundle('en', { functions: { WEEKDAY } });
bundle.addResource(resource);
</script>
<FluentProvider bundles={[bundle]}>
<Localized id="weekday-is" args={{ weekday: 2 }} />
</FluentProvider>
weekday-is = Weekday { $weekday } is { WEEKDAY($weekday) }