
Summary
Localization for SvelteKit with Svelte 5 reactivity.
- SSR enabled
- lazy loading
- some DX improvements
- named format (custom see below)
- ICU format (via 'intl-messageformat'), not tested yet
Not all tested thoroughly.
Localizations folder src/lib/localization
index.ts
import { browser, dev } from '$app/environment';
import { LocalizationFactory, type ImportLoads } from 'svelte5kit-localization';
const importDirPath = '/src/lib/localization/locales';
const importLoads = import.meta.glob(['/src/lib/localization/locales/**/*']) as ImportLoads;
LocalizationFactory.configure({
browser,
contextName: 'i18n',
importDirPath,
importLoads
});
const importLoaderFactory = LocalizationFactory.importLoaderFactory();
LocalizationFactory.setCommonServiceConfig({
availableLocales: ['en'],
loaders: [
{
key: 'navigation',
load: importLoaderFactory('navigation.json')
},
{
key: 'another',
load: importLoaderFactory('another.json'),
routes: ['/onemore']
}
],
logger: dev && browser ? console : undefined
});
export const {
initialLoadLocalizations,
setContextService: setLocalizationContextService,
getContextService: getLocalizationContextService,
extractLocales
} = LocalizationFactory;
export function loadLocalizations(pathname: string) {
return getLocalizationContextService().loadLocalizations(pathname);
}
export function setActiveLocale(locale: string) {
return getLocalizationContextService().setActiveLocale(locale);
}
Root +layout.server.ts
import { extractLocales } from '$lib/localization';
load(...
...
...
return {
...
i18n: extractLocales(event),
};
Root +layout.ts
import { initialLoadLocalizations } from '$lib/localization';
load(...
...
const urlpathname = event.untrack(() => event.url.pathname);
const i18n = await initialLoadLocalizations(
urlpathname,
{
activeLocale: event.data.i18n.activeLocale
},
);
...
return {
...
i18n
}
Root +layout.svelte
import { loadLocalizations, setLocalizationContextService } from '$lib/localization';
...
setLocalizationContextService(data.i18n);
beforeNavigate((navigation) => {
if (navigation.to?.url.pathname) {
loadLocalizations(navigation.to.url.pathname);
}
});
...
Somewhere in your myComponent.svelte
import { getLocalizationContextService } from '$lib/localization';
....
const { text, getPSText, setActiveLocale } = getLocalizationContextService();
...
text('my.some.key.maybeformat', {param1: "whatever"});
const pstext = getPSText('prefix', 'suffix');
pstext('my.some', {param1: "whatever"});
text('my.some.key.maybeformat', {param1: "whatever", default:"Hi, {name}!"})
setActiveLocale('de');
...
<span>{text('my.some.key.maybeformat', {param1: "whatever"})}</span>
<span>{pstext('my.some', {param1: "whatever"})}</span> <!-- same as above -->
<span>{text('my.some.key.maybeformat', {param1: "whatever", default:"Hi, {name}!"})}</span>
Named format
Implementation
export function namedFormat(
str: string,
replacements?: Record<string, string | undefined>
): string {
return str.replace(/{([^{}]*)}/g, function (match, key) {
return replacements?.[key] || match;
});
}
Prepare function
export const prepareNamedFormat: PrepareFunction = (_: string, value: string) => {
return function (params?: FormatParams) {
return namedFormat(value, params);
};
};
ICU format prepare function
export const prepareICUFormat: PrepareFunction = (locale: string, value: string) => {
const msg = new IntlMessageFormat(value, locale);
return function (params?: FormatParams) {
return msg.format(params);
};
};