svelte5kit-localization Svelte Themes

Svelte5kit Localization

Localization for SvelteKit with Svelte 5 reactivity

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';

// 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);
}

Root +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),
    };

Root +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
    }

Root +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);
    }
  });
  ...

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('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>

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);
    };
};

Top categories

Loading Svelte Themes