Localization for SvelteKit with Svelte 5 reactivity.
Not all tested thoroughly.
src/lib/localization
index.ts
import { browser, dev } from '$app/environment';
import { LocalizationFactory, type ImportLoads } from 'svelte5kit-localization';
// Place your jsons in the folder below
// e.g. /src/lib/localization/locales/en-US/navigation.json
const importDirPath = '/src/lib/localization/locales';
const importLoads = import.meta.glob(['/src/lib/localization/locales/**/*']) as ImportLoads;
// Configure the factory
LocalizationFactory.configure({
browser,
contextName: 'i18n',
importDirPath,
importLoads
});
// Now configure the service
const importLoaderFactory = LocalizationFactory.importLoaderFactory();
LocalizationFactory.setCommonServiceConfig({
availableLocales: ['en'],
// Set prepare on the top level
// IMPL: const prepare = config.prepare ?? prepareNamedFormat
// Use this option for ICU format, so the localizations are preparsed (see impl below)
// prepare: prepareICUFormat
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);
}
+layout.server.ts
import { extractLocales } from '$lib/localization';
load(...
...
...
return {
...
// extractLocales uses event.untrack to extract data, so it won't trigger reload
// Default search options for the active locale
// The requested locales are extracted from the headers on the server side or from navigator.languages on the client side
// const DefaultActiveLocaleSearchOptions: ActiveLocaleSearchOptions = {
// params: ['lang', 'locale', 'language'],
// searchParams: ['lang', 'locale', 'language'],
// cookies: ['lang', 'locale', 'language']
// };
i18n: extractLocales(event),
};
+layout.ts
import { initialLoadLocalizations } from '$lib/localization';
load(...
...
// Get url path without tracking
// Otherwise this will trigger on every navigation
const urlpathname = event.untrack(() => event.url.pathname);
const i18n = await initialLoadLocalizations(
urlpathname,
{
activeLocale: event.data.i18n.activeLocale
},
);
...
return {
...
i18n
}
+layout.svelte
import { loadLocalizations, setLocalizationContextService } from '$lib/localization';
...
// Set Localization Context Service
setLocalizationContextService(data.i18n);
beforeNavigate((navigation) => {
if (navigation.to?.url.pathname) {
loadLocalizations(navigation.to.url.pathname);
}
});
...
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('prefix.my.some.suffix')
// default fallback
// Avoid using with the ICU format, because this will cause additional parsing on the request
text('my.some.key.maybeformat', {param1: "whatever", default:"Hi, {name}!"})
// Will trigger loading (if not loaded already) for the last pathname, which was loaded
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>
export function namedFormat(
str: string,
replacements?: Record<string, string | undefined>
): string {
return str.replace(/{([^{}]*)}/g, function (match, key) {
return replacements?.[key] || match;
});
}
export const prepareNamedFormat: PrepareFunction = (_: string, value: string) => {
return function (params?: FormatParams) {
return namedFormat(value, params);
};
};
export const prepareICUFormat: PrepareFunction = (locale: string, value: string) => {
const msg = new IntlMessageFormat(value, locale);
return function (params?: FormatParams) {
return msg.format(params);
};
};