This library provides FormatJS internationalization bindings for Svelte 5.

Installation

npm i -S @formatjs/svelte-intl

Usage

Create an intl instance and provide it to the component tree using Svelte's Context API.

Provider setup

In your root component (e.g. App.svelte):

<script>
  import {createIntl, provideIntl} from '@formatjs/svelte-intl'

  const intl = createIntl({
    locale: 'en',
    defaultLocale: 'en',
    messages: {
      greeting: 'Hello, {name}!',
    },
  })

  provideIntl(intl)
</script>

<slot />

Consuming intl

In any descendant component, use useIntl() to access the intl object:

<script>
  import {useIntl} from '@formatjs/svelte-intl'

  const intl = useIntl()
</script>

<p>
  {intl.formatMessage({id: 'greeting'}, {name: 'World'})}
</p>
<p>
  {intl.formatNumber(1000, {style: 'currency', currency: 'USD'})}
</p>
<p>
  {intl.formatDate(new Date(), {dateStyle: 'full'})}
</p>

See @formatjs/intl for the full list of IntlShape methods.

Standalone usage (without context)

You can also use createIntl directly without Svelte context, useful for utility files or server-side code:

import {createIntl, createIntlCache} from '@formatjs/svelte-intl'

const cache = createIntlCache()
const intl = createIntl(
  {
    locale: 'en',
    defaultLocale: 'en',
    messages: {greeting: 'Hello, {name}!'},
  },
  cache
)

intl.formatMessage({id: 'greeting'}, {name: 'World'})

Rich Text / HTML Formatting

Unlike react-intl which uses React elements for rich text, Svelte does not have a VNode-like type. There are two approaches:

Using {@html} (simple)

Use {@html} to render HTML returned by formatMessage:

<script>
  import {useIntl} from '@formatjs/svelte-intl'

  const intl = useIntl()
  // Message: "Welcome, <b>{name}</b>!"
</script>

<p>
  {@html intl.formatMessage(
    {id: 'welcome', defaultMessage: 'Welcome, <b>{name}</b>!'},
    {name: userName}
  )}
</p>

Using rich text callbacks (safe)

For XSS-safe rich text, use formatMessage's tag callbacks to split the message into parts, then render each part with Svelte markup:

<script>
  import {useIntl} from '@formatjs/svelte-intl'

  const intl = useIntl()
  // Message: "Read the <link>docs</link> for more info."

  const parts = intl.formatMessage(
    {
      id: 'info',
      defaultMessage: 'Read the <link>docs</link> for more info.',
    },
    {
      link: (chunks) => `<a href="/docs">${chunks}</a>`,
    }
  )
</script>

<p>{@html parts}</p>

Since T = string in @formatjs/svelte-intl, rich text tag callbacks receive and return strings. You still need {@html} to render the result, but the message structure is preserved for translators.

Message Declaration

Use defineMessages and defineMessage for static analysis and extraction:

<script>
  import {defineMessages, useIntl} from '@formatjs/svelte-intl'

  const messages = defineMessages({
    greeting: {
      id: 'app.greeting',
      defaultMessage: 'Hello, {name}!',
      description: 'Greeting shown on homepage',
    },
    farewell: {
      id: 'app.farewell',
      defaultMessage: 'Goodbye!',
    },
  })

  const intl = useIntl()
</script>

<h1>{intl.formatMessage(messages.greeting, {name: 'World'})}</h1>
<p>{intl.formatMessage(messages.farewell)}</p>

API Reference

ExportDescription
provideIntlSet intl context via Svelte's setContext. Call in a parent component.
useIntlGet intl context via Svelte's getContext. Call in a descendant component.
intlKeyThe Symbol used as the context key.
createIntlCreate an IntlShape instance. Re-exported from @formatjs/intl.
createIntlCacheCreate a cache for createIntl. Re-exported from @formatjs/intl.
defineMessagesIdentity function for message extraction tooling.
defineMessageIdentity function for single message extraction.

All types and error classes from @formatjs/intl are also re-exported.

Tooling

formatjs toolchain supports .svelte files:

Extracting messages

formatjs extract "src/**/*.{ts,svelte}" --out-file lang.json

The CLI extracts messages from:

  • <script> and <script module> blocks
  • Template expressions: {intl.formatMessage(...)}, attribute bindings, {#if}, {#each}, {@const}, etc.

Typed messages and migration

Version 2 requires TypeScript 5.4 or newer. Message helpers now share the @formatjs/intl contracts. Returned descriptors and catalog entries are readonly; construct a new descriptor instead of mutating one.

import {defineMessage} from '@formatjs/svelte-intl'

const message = defineMessage<{readonly count: number}>(
  {id: 'items', defaultMessage: '{count, number} items'},
  {typed: true}
)
// intl.formatMessage(message, {count: 3})

defineMessages<Contracts>(catalog, {typed: true}) attaches a separate values contract to each catalog entry. Helpers return the original objects at runtime. The generic declares the contract; it does not parse ICU literals.

For formatMessage and $t, the first generic is the values contract and the second is the rich output type. Move an existing output generic to the second position. Without generics, typed descriptors retain their checks; ordinary unregistered descriptors remain permissive. Vue uses its configured VNode output; Svelte uses strings.