A straightforward internationalization (i18n) library for Svelte 5, leveraging Runes for reactivity and supporting the ICU Message Format for powerful localization features.
$state
, $derived
).t
, setLocale
) and reactive state (locale
).Install with NPM
npm i @svelte-intl/svelte-intl
Install with Yarn
yarn add @svelte-intl/svelte-intl
Create Translation Files: Define your messages using ICU syntax in separate files per locale.
src/lib/translations/en-us.json
:
{
"hello": "Hello!",
"seeProfile": "{name}, Click to see <link>{gender, select, male {his} female {her} other {their}} profile</link>.",
// ... other messages
};
src/lib/translations/es-la.json
:
{
"hello": "¡Hola!",
"seeProfile": "{name}, Haz clic para ver <link>su perfil</link>",
// ... other messages
};
Configure svelte-intl
:
Create an entry point for your i18n configuration, typically in $lib/i18n/index.ts
. This file will initialize the library and export the necessary functions and state.
src/lib/i18n/index.ts
:import enUs from "../translations/en-us.json";
import esLa from "../translations/es-la.json";
import { makeI18n } from "./i18n.svelte";
const { t, setLocale, locale, format } = makeI18n({
"en-us": en_us,
"es-la": es_la,
}, "en-us");
export { t, setLocale, locale, format };
vite.config.(ts|js)
// ...
import { svelteIntl } from '@svelte-intl/svelte-intl/vite';
export default defineConfig(({ mode }) => {
return {
plugins: [
// ...
svelteIntl({
localesDir: './messages',
defaultLocale: 'en-us',
}),
],
// ...
}
})
Import the exported functions and state into your Svelte components.
<script lang="ts">
import { t, setLocale, type Locale, locale } from "$lib/i18n"; // Adjust path if needed
import type { Snippet } from "svelte";
// Example data for rich text
let name = $state("Martin");
let gender = $state<"male" | "female" | "other">("male");
// Snippet definition for rich text injection
// This snippet will be rendered inside the <link></link> placeholder in the ICU message
{#snippet link(content: Snippet)}
<a href="/user/{name}" class="text-blue-600 hover:underline">
{@render content()}
</a>
{/snippet}
</script>
<label>
Locale:
<select
class="border p-1 rounded"
value={locale.current}
onchange={(e) => {
// Update the locale using setLocale
// The 't' function and 'locale.current' will reactively update
setLocale((e.target as HTMLSelectElement)?.value as Locale);
}}
>
{#each locale.list as l}
<option value={l}>{l}</option>
{/each}
</select>
</label>
<hr class="my-4">
<h1>{t("hello")}</h1>
<p>
{@render t.rich("seeProfile", {
name: name, // Pass data for placeholders
gender: gender,
link: link // Pass the defined Svelte snippet
})}
</p>
<h2 class="mt-4">Current Locale: {locale.current}</h2>
<div class="mt-4">
<button onclick={() => { name = "Alice"; gender = "female"; }} class="p-1 border rounded bg-gray-100">
Change to Alice
</button>
<button onclick={() => { name = "Martin"; gender = "male"; }} class="p-1 border rounded bg-gray-100 ml-2">
Change to Martin
</button>
</div>
t
, setLocale
, locale
, and the Locale
type from your setup file ("$lib/i18n"
).t
): Use the t
function (which behaves like a Rune) directly in your template: {t("key")}
. It automatically updates when the locale changes via setLocale
.locale.list
to iterate over available locales (e.g., in a dropdown).locale.current
to display the currently active locale or bind it to the switcher's value.setLocale("new-locale")
to change the application's language. The t
function and locale.current
will react automatically.<link />
).{#snippet link(content: Snippet)}
). This snippet receives the text content designated for the link from the ICU message structure (e.g., the words "Click here" if your ICU was Click <link>here</link>
). Correction based on user's example: The ICU string shown is more complex ({gender, select, ... <link>...</link> ...}
), the snippet replaces the <link>...</link>
part.{@render t.rich("key", { dataPlaceholder: value, snippetPlaceholderName: snippetName })}
. Pass regular data and the defined snippet itself as values in the options object. The library renders the translated string, injecting the rendered snippet into the appropriate placeholder.makeI18n(translations: Record<string, Record<string, string>>, defaultLocale: string)
: Factory function to initialize the i18n system. Requires an object mapping locale codes to translation dictionaries and the initial locale code.t(key: string, options?: Record<string, string | number>)
: Reactive Rune. Returns the translated string for the given key in the current locale. Supports basic ICU placeholder replacement (not rich text).t.rich(key: string, options: Record<string, any>)
: Function to handle translations containing rich text (HTML). Returns a renderable object. Options should include data for ICU placeholders and Svelte Snippets matching named placeholders in the ICU string. Use with {@render ...}
.setLocale(newLocale: Locale)
: Function to change the active locale. Triggers reactivity for t
and locale
.locale
: Reactive Rune. Provides an object { current: string, list: string[] }
containing the currently active locale code and a list of all available locale codes.Locale
: Exported TypeScript type representing the union of available locale strings (e.g., "en-us" | "es-la"
).