Skip to content
Comvi

@comvi/core

View .md

createI18n() returns the i18n instance: text with {param} interpolation, the cache, events and default params. The capabilities attach with .use(installer) from a subpath, /loader, /plugins and /devtools; /icu is the compiler option and /rich-text and /tags are the rich-text helpers.

  • Values: createI18n, I18n, MessagesCache, flattenCatalog, createBoundTranslation, translationResultToString, subscribeToRevision, REVISION_EVENTS, missingCapability, hasLoaderApi, hasPluginApi, formatNumber, formatDate, formatCurrency, formatRelativeTime, getTextDirection, createElement, isVirtualNode.
  • Types: I18nOptions, I18nEvent, I18nEventData, TranslationKeys, TranslationParams, TranslationResult, VirtualNode, ElementNode, TextNode, FragmentNode, MessageCompiler, SyntaxExtension, IcuSyntaxError, LocaleSource, RevisionEvent, RevisionEventSource, CapabilityName, I18nPlugin, I18nPluginFactory, PluginOptions, ErrorReportContext, I18nBaseOptions, TranslationValue, MessagesMap, FlattenedMessages, LoaderFn, LoaderResult, MissingKeyInfo, PostProcessFn, TagInterpolationOptions, TagCallbackParams, TranslationParamValue, DefaultTranslationParams, DefaultTranslationParamValue, SetDefaultParamsArg, DefaultParamsSnapshot, HasTranslationKeys, ExtractNamespaces, the instance shapes and the key helpers listed under Types.
function createI18n<D extends DefaultTranslationParams = {}>(options: I18nOptions<D>): I18n<D>
const I18n: new <D>(options: I18nOptions<D>) => I18n<D>

createI18n(options) is new I18n(options). It throws when locale is missing or messages is not an object; in development it also checks that every per-locale value is one.

src/i18n.ts
import { createI18n } from '@comvi/core';
export const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: { en: { greeting: 'Hello, {name}!' } },
});
await i18n.init();
Option Type Default Meaning
locale string required Initial active locale
defaultNs string 'default' Namespace for a call that passes no ns param
ns string[] [defaultNs] Namespaces init() loads; [] skips initial loading. Needs the loader capability, otherwise init() warns once in development
messages Record<string, Record<string, TranslationValue>> none Inline catalogs keyed 'locale' or 'locale:namespace', flat or nested
compiler MessageCompiler simple compiler Pass icuCompiler for ICU plural, select and selectordinal
fallbackLocale string | readonly string[] [] Locales consulted, in order, when a key is missing or empty; with a loader, init() and each namespace activation load them too
missingParam 'literal' | 'drop' 'literal' Render an absent {name} as itself or as an empty string; null always renders empty
defaultParams DefaultTranslationParams none Non-nullish interpolation defaults merged under every call; locale, ns, fallback, markers, tagInterpolation are rejected
postProcess PostProcessFn none One post-processor, registered first
onMissingKey (info: MissingKeyInfo) => TranslationResult | void none Miss hook, consulted after the fallback chain when neither params.fallback nor a plugin callback supplied a value
strict 'dev' | 'off' 'off' 'dev' warns on every miss in development builds
apiKey string none Read back by plugins as i18n.apiKey
collectContext boolean true false opts out of in-context editor context collection, even when the Chrome extension enables the editor
tagInterpolation TagInterpolationOptions none basicHtmlTags, strict, onTagWarning, extensions
devMode boolean build flag Defaults to the build flavour of the dist the bundler resolved: true from the development export condition, false from the default build. Plugins read it
onError (error: Error, context?: ErrorReportContext) => void none Global error sink
exposeGlobal boolean none Ignored by a bare instance, even one with .use(devtools()); init() warns once in development when true. Pass it to devtools({ exposeGlobal }) instead
instanceId string none Same: pass it to devtools({ instanceId }) instead
use<T extends this>(installer: (instance: this) => T): T

i18n.use(f) calls f(i18n) and returns the widened instance. The installer has to return the instance it was given; anything else throws with code: 'E_USE_NOT_INSTALLER'. A raw plugin goes through definePlugin(fn) or registerPlugin(fn) instead.

src/compose.ts
import { createI18n } from '@comvi/core';
import { loader } from '@comvi/core/loader';
import { plugins } from '@comvi/core/plugins';
export const i18n = createI18n({ locale: 'en' })
.use(loader({ uk: () => import('./locales/uk.json') }))
.use(plugins());
Property Type Meaning
locale string (get/set) The setter is fire-and-forget; a rejected switch emits loadError with namespace: 'locale-change'
isLoading boolean A load is in flight
isInitializing boolean init() is running
isInitialized boolean init() completed
apiKey string | undefined Readonly, from the option
collectContext boolean | undefined Readonly, from the option
devMode boolean Readonly
instanceId string | undefined Assigned by the devtools capability
messagesCache MessagesCache Readonly, see MessagesCache
defaultParams DefaultParamsSnapshot<D> Readonly shallow snapshot, copied on every read
configRevision number Bumps on every configChanged
localeRevision number For framework bindings: how many times the locale has changed, bumped before localeChanged listeners run

isInitializing belongs to init() alone: a locale detector that calls setLocale() mid-init flips isLoading, never isInitializing. The flag clears when init() settles, whether it resolved or threw.

init(): Promise<this>
t(key, params?): string
tRaw(key, params?): TranslationResult
addMessages(messages: MessagesMap): void
getMessages(locale?: string, namespace?: string): FlattenedMessages
hasLocale(locale: string, namespace?: string): boolean
hasTranslation(key: string, locale?: string, namespace?: string, checkFallbacks?: boolean): boolean
setLocale(value: string): Promise<void>
setFallbackLocale(fallback: string | readonly string[]): void
setDefaultParams(params: SetDefaultParamsArg<D>): void
setDefaultNamespace(namespace: string): void
getDefaultNamespace(): string
getActiveNamespaces(): string[]
getFallbackLocales(): string[]
getLoadedLocales(): string[]
withMessages(catalogs: Readonly<Record<string, FlattenedMessages>>): I18nMessagesView<D>
clearMessages(locale?: string, namespace?: string): void
on<E extends I18nEvent>(event: E, callback: (data: I18nEventData[E]) => void): () => void
reportError(error: unknown, context?: ErrorReportContext): void
destroy(): Promise<void>
Method Meaning
init() Runs plugin init hooks and the locale detector (plugin capability), then loads the initial namespaces. Rejects after destroy()
t(key, params?) Translate to a string; t(null) returns ''. One call signature: the key union is AcceptedCoreKey, params dispatch through ArgsFor
tRaw(key, params?) Same lookup, returns string | Array<string | VirtualNode>
addMessages(t) Merge catalogs keyed 'locale' or 'locale:namespace', flat or nested; every namespace carried is activated and emits namespaceLoaded
getMessages(l?, ns?) Defaults to the current locale and default namespace
hasLocale(l, ns?) Cache check, no fallback chain
hasTranslation(key, l?, ns?, fb?) Key check; an empty value is not a translation; checkFallbacks (default false) walks the chain
setLocale(l) Synchronous with nothing composed; the loader capability overrides it to load first. A rapid second call supersedes the first silently
setFallbackLocale(fb) Emits configChanged with source 'fallbackLocale'
setDefaultParams(p) Replaces defaults, emits configChanged with source 'defaultParams'; constructor-guaranteed keys cannot be removed
setDefaultNamespace(ns) Emits defaultNamespaceChanged
getDefaultNamespace(), getActiveNamespaces(), getFallbackLocales(), getLoadedLocales() Snapshots, not live
withMessages(catalogs) A read view with three methods and nothing else, t, tRaw and hasTranslation, which read each key from catalogs first and from the cache otherwise: the merge addMessages(catalogs) would make, without making it. The view never writes the instance (no cache entry, no locale, no active namespace) and never emits namespaceLoaded or localeChanged, so a dropped view leaves nothing behind. Diagnostics are not publication: a missing key emits missingKey and a throwing post-processor reaches onError, as for any t(). A later addMessages() reaches the view through the cache for every key catalogs does not hold. catalogs is keyed 'locale:namespace' and holds flat catalogs, as flattenCatalog returns them
clearMessages(l?, ns?) Clears content only, emits messagesCleared; deactivation is removeActiveNamespace() on the loader capability
on(event, cb) Returns an unsubscribe function
reportError(e, ctx?) Routes to onError; the same Error object is reported once per instance
destroy() Idempotent. Removes discovery, runs plugin cleanups in LIFO order, emits destroyed, clears subscriptions, active namespaces, post-processors, cache, loader and detector

A destroy() that lands while init() is awaiting makes init() resolve quietly with isInitialized still false: no initialized event, no error report. That is what happens when a component unmounts with a fire-and-forget init() in flight.

Lookup on a miss: emit missingKey, run the plugin onMissingKey callbacks, then return params.fallback if set, else the first defined plugin result, else the constructor onMissingKey result, else the key itself.

Event Payload Fires when
initialized void init() finished
destroyed void destroy() finished
localeChanged { from: string; to: string } the locale changed
defaultNamespaceChanged { from: string; to: string } the default namespace changed
messagesCleared { locale?: string; namespace?: string } clearMessages() ran
loadingStateChanged { isLoading: boolean; isInitializing: boolean } a loading flag flipped
namespaceLoaded { namespace: string; locale: string } a catalog landed
missingKey { key: string; locale: string; namespace: string } a lookup missed
loadError { locale: string; namespace: string; error: Error } a load failed
configChanged { source: 'fallbackLocale' | 'namespaceActivated' | 'namespaceDeactivated' | 'defaultParams' } config changed

subscribeToRevision(i18n, cb) subscribes to the seven events in REVISION_EVENTS (localeChanged, namespaceLoaded, loadingStateChanged, initialized, messagesCleared, defaultNamespaceChanged, configChanged) and returns one unsubscribe.

function formatNumber(i18n: LocaleSource, value: number, options?: Intl.NumberFormatOptions, locale?: string): string
function formatDate(i18n: LocaleSource, value: Date | number, options?: Intl.DateTimeFormatOptions, locale?: string): string
function formatCurrency(i18n: LocaleSource, value: number, currency: string, options?: Intl.NumberFormatOptions, locale?: string): string
function formatRelativeTime(i18n: LocaleSource, value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions, locale?: string): string
function getTextDirection(locale: string): 'ltr' | 'rtl'

Free functions, not methods. LocaleSource is { locale: string }, so any i18n instance qualifies as the first argument. getTextDirection takes a locale string. Formatters are cached per locale and options.

function flattenCatalog(nested: Record<string, any>): FlattenedMessages
function createBoundTranslation<D, NS>(i18n: { t; tRaw? }, ns?: NS): NamespaceBoundTranslateFn<NS, D, TranslationResult> // any object with the instance's t(), such as the instance or a withMessages() view
function translationResultToString(result: TranslationResult): string
function subscribeToRevision(i18n: RevisionEventSource, callback: (event: RevisionEvent) => void): () => void
function missingCapability(name: 'loader' | 'plugins'): Error
function hasLoaderApi(instance: WrapperI18nInstance): instance is WrapperI18nInstance & I18nLoaderApi
function hasPluginApi(instance: WrapperI18nInstance): instance is WrapperI18nInstance & I18nPluginApi
function createElement(tag: string, props?: Record<string, unknown>, children?: Array<VirtualNode | string>): ElementNode
function isVirtualNode(value: unknown): value is VirtualNode

flattenCatalog is optional: the instance flattens nested catalogs itself. Non-string leaves are coerced with String(), null and undefined leaves are dropped with a development warning.

hasLoaderApi and hasPluginApi are runtime guards: each checks that every member of the capability is a real function, so the development stand-ins attachPlugins installs for loader members do not count.

new MessagesCache(options?: { defaultNs?: string })
get(locale: string, namespace?: string): FlattenedMessages | undefined
set(locale: string, namespace: string, translations: FlattenedMessages): void
has(locale: string, namespace?: string): boolean
delete(locale: string, namespace?: string): void
clear(): void
keys(): IterableIterator<string> // 'locale:namespace'
getLocales(): string[]
getRevision(): number
clone(): Map<string, FlattenedMessages>
getInternalMap(): ReadonlyMap<string, FlattenedMessages>
get size(): number
Type Meaning
TranslationKeys Augment it to type t(); see Type-check translation keys
InferKeys<T, NS?, DefaultNS?> Keys inferred from a JSON catalog object, prefixed NS: unless NS equals DefaultNS; params are never
DefaultNsKeys, Namespaces, NamespacedKeys<NS>, NamespacedKeyParams<NS, K>, PermissiveKey Key helpers derived from TranslationKeys
ParamsArg, NamespacedParamsArg, NamespaceBoundParamsArg, AcceptedKey, AcceptedCoreKey, ArgsFor The single call signature’s parts
TranslateFn<D, R>, NamespaceBoundTranslateFn<NS, D, R> The t / tRaw shapes bindings return
I18nCoreInstance<D> Lookup, cache and events surface
I18nCoreExtraApi init, destroy, setLocale, instanceId, withMessages, the snapshot getters
MessagesMap Record<string, Record<string, TranslationValue>>: catalogs keyed 'locale' or 'locale:namespace', the shape addMessages() and the messages option take. Re-exported by @comvi/react, @comvi/next and @comvi/nuxt
I18nMessagesView<D> What withMessages() returns: t, tRaw and hasTranslation of I18nCoreInstance
WrapperI18nInstance<D> I18nCoreInstance & I18nCoreExtraApi, what every binding requires and a bare instance satisfies
AnyI18nInstance WrapperI18nInstance with setDefaultParams and init widened, what providers accept
I18nLoaderApi, I18nPluginApi The two capability member sets
PluginI18nInstance<D> WrapperI18nInstance & I18nLoaderApi & I18nPluginApi, what a plugin may rely on
I18nInstance<D> A composed instance: I18nCoreInstance plus reloadMessages, setPluginData, getPluginData
VirtualNode ElementNode | TextNode | FragmentNode; TranslationResult is string | Array<string | VirtualNode>
TagCallback (params: { children: TranslationResult; name: string }) => VirtualNode | string
IcuSyntaxError Error & { code: 'E_ICU_SYNTAX'; argumentType: string }
ErrorReportContext { source; pluginName?; tagName?; key?; locale?; namespace?; event? } with source one of plugin, plugin-cleanup, request-cleanup, init, translation, namespace-load, post-processor, event, setLocale, compile
ComviQueue, ComviQueueEntry, ComviHook The window.__COMVI__ protocol, see /devtools