@intlify/core-base
@intlify/core-base is the framework-neutral translation engine underneath Vue I18n. It exposes a mutable core context plus functions for message lookup, interpolation, plural selection, locale fallback, number formatting and date formatting. It does not provide Vue components, composables, reactive state, catalog loading or app installation. Direct users are expected to choose and wire the message compiler, nested-key resolver and fallback algorithm themselves.
A capable engine for authors of i18n integrations, not the package most application teams should install directly. Choose it when matching Intlify behavior is the requirement; otherwise use vue-i18n or a documented framework-neutral library and avoid rebuilding the missing adapter layer.
Use it if
- You are building an i18n adapter for a framework or runtime and want the same message syntax and fallback behavior as Vue I18n
- You need direct control over message compilation, nested-key resolution, fallback chains and missing-key behavior
- You want locale-aware translation, number formatting and date formatting without depending on Vue
- You are maintaining Intlify infrastructure and need to consume its precompiled message functions or AST resources directly
- You are localizing a normal Vue application: vue-i18n supplies this engine plus reactivity, useI18n, components, app installation and documented catalog workflows
- You expect sensible high-level defaults from createCoreContext alone: the shipped implementation defaults to no message compiler, flat key lookup and simple fallback unless helpers are registered or passed explicitly
- Your runtime or build uses Node below 22: version 11.4.8 declares Node 22 or newer
- You need a well-documented public SDK: the package README contains one descriptive sentence, while practical behavior has to be learned from exported types, source and repository tests
- You need standard ICU MessageFormat catalogs shared with non-Intlify tools: this engine uses Intlify message syntax and its own compiler, so confirm translator and backend compatibility before committing
Setup reality
Install @intlify/core-base and make sure the process running npm, tests and server code is on Node >=22. There are no peer dependencies, but the package installs exact-version Intlify companions for shared utilities, devtools types and message compilation. The first surprise is that createCoreContext is intentionally primitive. With no extra options it has no string message compiler, uses resolveWithKeyValue for flat keys, and uses fallbackWithSimple instead of the regional locale-chain algorithm. If you want the behavior developers associate with Vue I18n, pass compile as messageCompiler, resolveValue as messageResolver and fallbackWithLocaleChain as localeFallbacker on every context, or register global defaults before creating contexts. Global registration affects later contexts process-wide, which is awkward in tests and multi-tenant services, so explicit options are easier to reason about. The context is mutable state, not a reactive store: changing ctx.locale or ctx.messages does not notify a UI, load files, persist preferences or update an HTML lang attribute. Number and datetime output relies on the runtime's Intl locale data. Runtime compilation accepts string catalogs but adds compiler work; build-time message functions or AST resources require you to own a matching compilation pipeline. escapeParameter is false by default, HTML in messages only produces a warning, and returning strings does not make later HTML rendering safe. For SSR or concurrent tenants, create a context per request or keep locale state out of shared mutation. TypeScript declarations are extensive, but the many overloads and schema generics are designed for library authors rather than a quick application setup.
Patterns
Create a context with the full Intlify behaviorcreate-full-context
import {
compile,
createCoreContext,
fallbackWithLocaleChain,
resolveValue,
} from '@intlify/core-base'
const ctx = createCoreContext({
locale: 'en-US',
fallbackLocale: 'en',
messages: {
'en-US': { greeting: 'Hello, {name}!' },
en: { greeting: 'Hello, {name}!' },
},
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})Passing all three helpers is deliberate. A bare context has no string compiler and defaults to flat keys plus the simpler fallback algorithm.
Translate with named interpolationtranslate-named-values
import { translate } from '@intlify/core-base'
const text = translate(ctx, 'greeting', { name: 'Ada' })
console.log(text) // Hello, Ada!Named placeholders are compiled only because the context includes messageCompiler: compile; without a compiler the raw string is returned.
Translate with positional interpolationtranslate-list-values
const ctx = createCoreContext({
locale: 'en',
messages: { en: { status: '{0} of {1} complete' } },
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
translate(ctx, 'status', [3, 10])List interpolation uses zero-based placeholders. Keep a consistent placeholder style in catalogs because named values are easier for translators to understand.
Select plural formstranslate-plurals
const ctx = createCoreContext({
locale: 'en',
messages: {
en: { apples: 'no apples | one apple | {count} apples' },
},
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
translate(ctx, 'apples', 0)
translate(ctx, 'apples', 1)
translate(ctx, 'apples', 12)The numeric choice also supplies count and n implicitly. Locale plural selection depends on Intl.PluralRules unless a custom pluralRules entry overrides it.
Resolve nested catalog pathsresolve-nested-key
import { resolveValue } from '@intlify/core-base'
const messages = {
checkout: { payment: { failed: 'Payment failed' } },
}
resolveValue(messages, 'checkout.payment.failed') // Payment failed
resolveValue(messages, 'checkout.payment.missing') // nullresolveValue supports object paths and array indexes. The default resolveWithKeyValue resolver treats the whole key as flat and will not traverse this object.
Inspect a regional fallback chainconfigure-fallback-chain
import { fallbackWithLocaleChain } from '@intlify/core-base'
const ctx = createCoreContext({
locale: 'de-CH',
fallbackLocale: {
'de-CH': ['fr', 'it'],
default: ['en'],
},
localeFallbacker: fallbackWithLocaleChain,
})
const chain = fallbackWithLocaleChain(ctx, ctx.fallbackLocale, 'de-CH')
// ['de-CH', 'fr', 'it', 'en']The locale-chain fallbacker understands regional parents and decision maps. Appending ! to the starting locale suppresses implicit parent fallback.
Provide a missing-key handlerhandle-missing-key
const ctx = createCoreContext({
locale: 'en',
messages: { en: {} },
missing(_ctx, locale, key, type) {
reportMissing({ locale, key, type })
return `[${key}]`
},
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
translate(ctx, 'account.title') // [account.title]The handler runs during resolution and may return replacement text. Keep reporting non-blocking because translation calls often happen during rendering.
Escape untrusted interpolation valuesescape-interpolation
const ctx = createCoreContext({
locale: 'en',
messages: { en: { hello: 'Hello, {name}!' } },
escapeParameter: true,
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
translate(ctx, 'hello', { name: '<img src=x onerror=alert(1)>' })escapeParameter defaults to false. Escaping placeholders is useful defense, but the returned string is not a license to render arbitrary catalog HTML unsafely.
Run a post-translation hookpost-process-translation
const ctx = createCoreContext({
locale: 'en',
messages: { en: { padded: ' ready ' } },
postTranslation(value, key) {
auditTranslation(key)
return typeof value === 'string' ? value.trim() : value
},
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
translate(ctx, 'padded') // readyThis hook runs for every translated result. Avoid expensive I/O and do not use it to hide malformed source catalogs.
Format currency through the core contextformat-number
import { number } from '@intlify/core-base'
const ctx = createCoreContext({
locale: 'en-US',
numberFormats: {
'en-US': { money: { style: 'currency', currency: 'USD' } },
'ja-JP': { money: { style: 'currency', currency: 'JPY' } },
},
localeFallbacker: fallbackWithLocaleChain,
})
number(ctx, 10100, 'money')
number(ctx, 10100, 'money', 'ja-JP')Output and supported options come from Intl.NumberFormat in the host runtime. The function may also return parts when called with part: true.
Format a date through the core contextformat-datetime
import { datetime } from '@intlify/core-base'
const ctx = createCoreContext({
locale: 'en-US',
datetimeFormats: {
'en-US': { short: { year: 'numeric', month: 'short', day: 'numeric' } },
},
localeFallbacker: fallbackWithLocaleChain,
})
datetime(ctx, new Date('2026-08-08T12:00:00Z'), {
key: 'short',
timeZone: 'UTC',
})Without an explicit timeZone, the same timestamp can format as a different calendar day on another machine. Invalid dates return an empty string after warning.
Switch a context's localeswitch-context-locale
ctx.locale = 'ja-JP'
ctx.messages['ja-JP'] = {
greeting: 'こんにちは、{name}さん',
}
const text = translate(ctx, 'greeting', { name: 'Ada' })The context is mutable but not reactive. This assignment does not notify components, persist a preference or load a catalog, so an adapter must provide those behaviors.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @formatjs/intl | npm | You want a framework-neutral formatter built around standard ICU messages and the FormatJS toolchain |
| intl-messageformat | npm | You only need to compile and format ICU MessageFormat strings without a catalog context or fallback system |
| @messageformat/core | npm | You want ICU-style message compilation with explicit build-time and runtime APIs |
| i18next | npm | You want a documented framework-neutral i18n platform with loaders, plugins and adapters already available |