@intlify/core-base review
@intlify/core-base 11.4.10 is the framework-independent engine used beneath Vue I18n. It creates a mutable localization context and exports low-level functions for translation, plural choice, locale fallback, number formatting, and date formatting. It does not install into Vue, make locale state reactive, fetch catalogs, or supply components. Direct consumers must wire a message compiler, nested-key resolver, and fallback strategy. The current release stops devtools grouping from throwing when a message key is an AST, while 11.4.9 fixed stale locale-chain caches after fallback changes. Our measured full import of 11.4.8 was 38.4 KB minified and 13.6 KB gzipped.
Our @intlify/core-base 11.4.8 install took 3.1 seconds, used 2 MB across 5 packages, and produced a 13.6 KB gzipped browser bundle with no audit findings. Install the current 11.4.10 release when you are building an Intlify-compatible adapter; application teams should usually choose `vue-i18n` or a higher-level framework-neutral package.
We installed it
| Install | ✓ · 3.1s | 5 packages on disk · 2 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package with exports map |
| Browser | 13.6 KB | gzipped (38.4 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does @intlify/core-base install cleanly?
Yes. In a fresh container with an empty cache, npm install @intlify/core-base finished in 3 seconds, leaving 5 packages and 2 MB on disk. npm audit reported no known vulnerabilities.
How much does @intlify/core-base add to a browser bundle?
13.6 KB gzipped (38.4 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does @intlify/core-base work with both ESM and CommonJS?
Yes. Both import '@intlify/core-base' and require('@intlify/core-base') worked in Node 22 in our run. The package is published as CommonJS with an exports map.
Does @intlify/core-base include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
@intlify/core-base or @formatjs/intl: which should you use?
@formatjs/intl: Choose it for a framework-neutral formatter tied to ICU messages and FormatJS extraction tooling. Our @intlify/core-base 11.4.8 install took 3.1 seconds, used 2 MB across 5 packages, and produced a 13.6 KB gzipped browser bundle with no audit findings.
When should you not use @intlify/core-base?
This is an ordinary Vue application. vue-i18n adds reactivity, useI18n, components, app installation, and documented catalog workflows around this engine.
Use it if
- You are writing an i18n adapter for another framework and need translation behavior compatible with the Intlify family.
- Your runtime needs direct control of message compilation, nested key resolution, missing-key hooks, and regional fallback chains.
- Locale-aware text, numbers, and dates are required without importing Vue itself.
- Your build already emits Intlify message functions or AST resources and needs the matching execution layer.
- This is an ordinary Vue application. `vue-i18n` adds reactivity, `useI18n`, components, app installation, and documented catalog workflows around this engine.
- You expect `createCoreContext()` to recreate Vue I18n defaults by itself. A bare context has no string compiler, uses flat key lookup, and selects the simpler fallback routine.
- Your install or server runs below Node 22. The current package declares Node 22 or newer.
- A task-focused public SDK guide is required. The package README is a single-line description, leaving behavior to declarations, source, and tests.
- Catalogs must follow standard ICU MessageFormat across several language stacks. Intlify has its own message syntax and compiler, so interchange needs proof before adoption.
Setup reality
We installed @intlify/core-base 11.4.8 in 3.1 seconds in a fresh Node 22 Bookworm sandbox on August 22, 2026. That measured install left 5 packages and 2 MB on disk. The package was 632 KB unpacked, declared 3 direct dependencies and no peers, and npm audit found 0 known vulnerabilities. It included TypeScript declarations. CommonJS require() and ESM import both worked through the exports map, and our browser build measured 38.4 KB minified and 13.6 KB gzipped.
The current registry release is 11.4.10, so those install numbers describe 11.4.8 rather than a fresh measurement of the two later patches. All three exact-version dependencies belong to Intlify. Node 22 or newer is required. There are no credentials or native compilation steps. The configuration cost is in code: pass compile, resolveValue, and fallbackWithLocaleChain if you want compiled strings, nested paths, and the regional chain behavior associated with Vue I18n.
A core context is mutable and non-reactive. Updating ctx.locale or ctx.messages will not notify UI code, load a file, save a preference, or update the document language. Global registration functions change defaults for later contexts across the process, which can leak between tests or tenants. Explicit per-context options are easier to isolate. Create one context per request when locale and catalogs differ across concurrent server work.
Runtime string compilation spends work during translation; precompiled functions or AST messages require a matching build pipeline. Number and date output comes from the host's Intl data. escapeParameter defaults to false, and an HTML warning does not sanitize returned markup. Version 11.4.9 matters for live fallback changes because it invalidates the locale-chain cache and corrects simple fallback maps. Version 11.4.10 then prevents AST message keys from breaking devtools group IDs.
Patterns
Create a context with compiler and fallback behavior create-core-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,
})A context without these 3 helpers cannot compile string messages and falls back to flat-key lookup plus the simple locale routine.
Insert a named value into a message interpolate-name
import { translate } from '@intlify/core-base'
const text = translate(ctx, 'greeting', { name: 'Ada' })
console.log(text)The string is compiled because `ctx` was created with `messageCompiler: compile`. A bare context does not turn `{name}` into a placeholder.
Fill positional placeholders interpolate-list
const ctx = createCoreContext({
locale: 'en',
messages: { en: { status: '{0} of {1} complete' } },
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
console.log(translate(ctx, 'status', [3, 10]))List placeholders start at 0. Named placeholders are usually clearer to translators when the order can change between languages.
Select a plural branch from a count choose-plural-form
const ctx = createCoreContext({
locale: 'en',
messages: { en: { apples: 'no apples | one apple | {count} apples' } },
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
console.log(translate(ctx, 'apples', 0))
console.log(translate(ctx, 'apples', 1))
console.log(translate(ctx, 'apples', 12))The numeric choice also provides `count` and `n`. Locale selection uses `Intl.PluralRules` unless the context supplies a custom rule.
Look up a nested catalog path resolve-nested-message
import { resolveValue } from '@intlify/core-base'
const messages = {
checkout: { payment: { failed: 'Payment failed' } },
}
console.log(resolveValue(messages, 'checkout.payment.failed'))
console.log(resolveValue(messages, 'checkout.payment.missing'))`resolveValue` traverses object paths and array indexes, returning null for a miss. The default `resolveWithKeyValue` treats this dotted key as one flat property.
Inspect fallback order for a regional locale build-locale-chain
import { fallbackWithLocaleChain } from '@intlify/core-base'
const ctx = createCoreContext({
locale: 'de-CH',
fallbackLocale: {
'de-CH': ['fr', 'it'],
default: ['en'],
},
localeFallbacker: fallbackWithLocaleChain,
})
console.log(fallbackWithLocaleChain(ctx, ctx.fallbackLocale, 'de-CH'))The locale-chain routine understands decision maps and implicit regional parents. A trailing `!` on the starting locale suppresses parent fallback.
Report and replace a missing message replace-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,
})
console.log(translate(ctx, 'account.title'))The missing hook runs inside translation and may return replacement text. Keep its reporting path fast because rendering can call it repeatedly.
Escape values inserted into a translation escape-parameters
const ctx = createCoreContext({
locale: 'en',
messages: { en: { hello: 'Hello, {name}!' } },
escapeParameter: true,
messageCompiler: compile,
messageResolver: resolveValue,
localeFallbacker: fallbackWithLocaleChain,
})
console.log(translate(ctx, 'hello', {
name: '<img src=x onerror=alert(1)>',
}))`escapeParameter` is false unless enabled. Escaped placeholders do not make arbitrary HTML stored in a catalog safe to inject into a page.
Trim each translated string after resolution post-process-result
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,
})
console.log(translate(ctx, 'padded'))`postTranslation` runs on every successful result. Slow I/O here adds latency to every call and can multiply during one render.
Format money with named number rules format-currency
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,
})
console.log(number(ctx, 10100, 'money'))
console.log(number(ctx, 10100, 'money', 'ja-JP'))Formatting support and exact output come from `Intl.NumberFormat` in the running JavaScript engine. `part: true` returns parts instead of a final string.
Render a date in an explicit time zone format-date
import { datetime } from '@intlify/core-base'
const ctx = createCoreContext({
locale: 'en-US',
datetimeFormats: {
'en-US': { short: { year: 'numeric', month: 'short', day: 'numeric' } },
},
localeFallbacker: fallbackWithLocaleChain,
})
console.log(datetime(ctx, new Date('2026-08-08T12:00:00Z'), {
key: 'short',
timeZone: 'UTC',
}))Without `timeZone`, the host machine decides the zone and may render a different calendar date. Invalid dates warn and return an empty string.
Replace locale and catalog on one context change-locale
ctx.locale = 'ja-JP'
ctx.messages['ja-JP'] = {
greeting: 'こんにちは、{name}さん',
}
const text = translate(ctx, 'greeting', { name: 'Ada' })
console.log(text)These assignments mutate plain context state. They do not notify a component tree, download messages, save the choice, or alter `<html lang>`.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @formatjs/intl | npm | Choose it for a framework-neutral formatter tied to ICU messages and FormatJS extraction tooling. |
| intl-messageformat | npm | Choose it when compiling and formatting individual ICU messages is enough and no catalog context is needed. |
| @messageformat/core | npm | Choose it for ICU-style compilation with explicit runtime and build-time paths. |
| i18next | npm | Choose it for a documented general-purpose i18n system with loaders, plugins, and framework adapters. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

