createIntl

createIntl(options?) creates a standalone localization runtime.

Use it when one part of the app needs its own locale state, loading strategy, or message source while keeping the rest of the app unchanged.

The key difference from initIntl() is scope:

ts
1import { createIntl } from '@beforesemicolon/intl'2 3const checkoutRuntime = createIntl({4  locale: 'en-US',5  fallbackLocale: 'en',6  messages: {7    checkout: {8      title: 'Checkout',9      totalLabel: 'Total',10      actions: {11        primary: 'Place order',12      },13    },14  },15})16 17checkoutRuntime.getMessage('checkout.title') // "Checkout"

Native API: `Intl.Locale`

Signature

ts
1function createIntl(options?: IntlRuntimeOptions): IntlRuntime

If called with no options, it creates a runtime using defaults and lazy message loading settings.

Runtime shape

IntlRuntime exposes:

Use these methods directly when you need isolation and deterministic control.

Core behavior to understand

ts
1const runtime = createIntl({ locale: 'en-US', srcDir: '/locales' })2await runtime.setLocale('fr-FR')

Options deep dive

locale

fallbackLocale

messages

ts
1createIntl({2  locale: 'en',3  messages: {4    nav: { home: 'Home', checkout: 'Checkout' },5  },6})

fallbackMessages

src vs srcDir

Use exactly one of them per runtime in normal setups:

ts
1const exact = createIntl({ locale: 'en', src: '/api/messages/en.json' })2const perLocale = createIntl({ locale: 'fr', srcDir: '/locales' })

baseUrl

Base URL used when paths are relative.

ts
1createIntl({ locale: 'en', src: './locales/en.json', baseUrl: 'https://cdn.example.com' })

loader

Custom loader is used for all locale fetches.

Signature:

ts
1(locale: string, signal?: AbortSignal) => Promise<IntlMessages> | IntlMessages
ts
1const runtime = createIntl({2  locale: 'pt-CV',3  fallbackLocale: 'en',4  loader: (locale, signal) =>5    fetch(`/i18n/messages?locale=${locale}`, {6      method: 'GET',7      headers: { 'Accept': 'application/json' },8      signal,9    }).then((res) => res.json()),10})

Why this matters:

parentScope

Child runtimes inherit parent messages and configuration, then apply local overrides.

ts
1const shell = createIntl({2  locale: 'en-US',3  messages: {4    common: {5      save: 'Save',6      cancel: 'Cancel',7    },8  },9})10 11const modal = createIntl({12  locale: 'fr-FR',13  parentScope: shell,14  messages: {15    common: { save: 'Sauvegarder' },16  },17})18 19modal.getMessage('common.save') // "Sauvegarder"20modal.getMessage('common.cancel') // "Cancel"

Common setup patterns

Isolated UI previews

Keep each preview runtime isolated from production defaults.

ts
1const productCard = createIntl({2  locale: 'en-US',3  messages: {4    product: { cta: 'Add to cart' },5  },6})

Route-level widgets

Each route can own its own runtime for reduced coupling.

ts
1const checkoutRuntime = createIntl({2  locale: 'en-US',3  src: '/locales/en.checkout.json',4})5 6const supportRuntime = createIntl({7  locale: 'en-US',8  src: '/locales/en.support.json',9})

Runtime testing and fixtures

Create and tear down runtimes per test case.

ts
1const runtime = createIntl({2  locale: 'en-US',3  messages: { title: 'Home' },4})5 6runtime.getMessage('title') // "Home"7runtime.destroy()

Runtime methods in practice

ts
1const runtime = createIntl({ locale: 'en-US', srcDir: '/locales' })2 3await runtime.setLocale('fr-FR')4await runtime.loadLocale('es-ES')5 6runtime.setMessages({ checkout: { title: 'Quick checkout' } })7runtime.setFallbackMessages({ common: { cancel: 'Cancel' } })8 9runtime.subscribe((snapshot) => {10  console.log(snapshot.status, snapshot.locale)11})12 13console.log(runtime.snapshot())14runtime.destroy()

Return types that matter

Error and lifecycle notes

Migration from initIntl to scoped runtimes

If you have one global locale currently, start by moving feature areas one-by-one:

  1. Keep initIntl() for app shell
  2. Create createIntl() for each page section or widget
  3. Pass scoped runtimes into helper calls that need independent state
  4. Keep existing <intl-locale> usage where DOM scoping is already clear

Use createIntl() when you want predictable, composable runtime boundaries.

edit this doc