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:
initIntl()creates/replaces the package default runtime (global fallback for helpers and unscoped components).createIntl()creates a separate runtime object that you pass around explicitly.
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
1function createIntl(options?: IntlRuntimeOptions): IntlRuntimeIf called with no options, it creates a runtime using defaults and lazy message loading settings.
Runtime shape
IntlRuntime exposes:
locale/fallbackLocalemessages/fallbackMessagesdirectionloadedLocalesstatus(idle|loading|ready|error)errorsnapshot()setLocale(locale)loadLocale(locale?)setMessages(messages, locale?)setFallbackMessages(messages, locale?)getMessage(key)subscribe(listener)destroy()
Use these methods directly when you need isolation and deterministic control.
Core behavior to understand
messagesandfallbackMessagesare merged withparentScopeif present.- inline
messagesfor the configured locale are loaded into memory immediately. - if
srcorsrcDiris configured, locale fetching happens when needed. - switching locale on this runtime via
setLocale()keeps isolation from the default runtime unless you useinitIntl().
1const runtime = createIntl({ locale: 'en-US', srcDir: '/locales' })2await runtime.setLocale('fr-FR')Options deep dive
locale
- Default:
document.documentElement.langif present, otherwise inherited parent locale or'en'. - If missing/empty,
getLocaleresolution still falls back to defaults. - If
parentScopeexists, it inherits locale unless you provide one.
fallbackLocale
- Default:
enif not provided; inherited fromparentScopeif available. - Used when active-locale keys are missing.
messages
- Inline messages for the active locale.
- Useful for SSR snapshots, integration tests, and no-network bootstraps.
1createIntl({2 locale: 'en',3 messages: {4 nav: { home: 'Home', checkout: 'Checkout' },5 },6})fallbackMessages
- Inline fallback messages keyed by
fallbackLocale. - Good for bootstrapping critical copy while still loading remote locale bundles.
src vs srcDir
Use exactly one of them per runtime in normal setups:
src: one exact endpointsrcDir: auto-load using${srcDir}/${locale}.json
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.
1createIntl({ locale: 'en', src: './locales/en.json', baseUrl: 'https://cdn.example.com' })loader
Custom loader is used for all locale fetches.
Signature:
1(locale: string, signal?: AbortSignal) => Promise<IntlMessages> | IntlMessages1const 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:
- supports authenticated endpoints
- lets you add response transforms/caching
- receives
AbortSignalso rapid language switches don’t accumulate stale requests
parentScope
Child runtimes inherit parent messages and configuration, then apply local overrides.
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.
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.
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.
1const runtime = createIntl({2 locale: 'en-US',3 messages: { title: 'Home' },4})5 6runtime.getMessage('title') // "Home"7runtime.destroy()Runtime methods in practice
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
setLocale(locale)→Promise<IntlRuntimeSnapshot>loadLocale(locale?)→Promise<IntlRuntimeSnapshot>setMessages(...)andsetFallbackMessages(...)→IntlRuntimeSnapshotgetMessage(key)→ message value orundefinedsnapshot()→ normalized snapshot includingloadedLocalesanderror
Error and lifecycle notes
snapshot().statusis your source of truth:idle: no remote load has startedloading: a load is in-flightready: locale messages are readyerror: load failed
destroy()clears caches, listeners, and loaded data for that runtime.destroy()does not mutate sibling runtimes.
Migration from initIntl to scoped runtimes
If you have one global locale currently, start by moving feature areas one-by-one:
- Keep
initIntl()for app shell - Create
createIntl()for each page section or widget - Pass scoped runtimes into helper calls that need independent state
- Keep existing
<intl-locale>usage where DOM scoping is already clear
Use createIntl() when you want predictable, composable runtime boundaries.