Reference
Runtime API
The verbaly core is ~3 KB gzipped with zero dependencies, and everything builds on native Intl.
createVerbaly(options)
import { createVerbaly } from 'verbaly';
const v = createVerbaly({
locale: 'es',
fallback: ['en'],
messages: { es: { home: { title: 'Inicio' } } },
});| Option | Type | Description |
|---|---|---|
| locale | string | Initial locale; defaults to navigator.language in the browser |
| fallback | string | string[] | Fallback chain after BCP-47 narrowing (es-MX → es) |
| messages | object | Locale → tree; nested keys flatten to dots (home.title). An empty string counts as untranslated and keeps falling back, the same rule as check |
| loaders | object | Lazy catalogs: locale → () => import('./es.json'), loaded on demand |
| formatters | object | Custom {v:name} formatters |
| onMissing | function | (key, locale): return a string to substitute; silences the default warn-once |
| onResolve | function | (info) on every t(): key, locale, value and status (hit / fallback / miss). Powers devtools |
Instance
| Member | Description |
|---|---|
| t(key, params?) | Translate; keys and params are type-checked from your messages |
| t`...` | Tagged template: interpolates source text; the compiler upgrades it |
| t.id(key)`...` | Readable-key opt-in: formats the inline source; the compiler rewrites it to a keyed call |
| setLocale(locale) | Switch locale, notify subscribers, auto-loads a pending lazy catalog |
| loadLocale(locale) | Load a lazy catalog (BCP-47 narrowing, deduped): await it before setLocale for a flash-free switch |
| addMessages(locale, tree) | Merge messages at runtime: the lazy-loading primitive |
| subscribe(fn) | Change listener; returns unsubscribe |
| has(key) | Key exists in the current chain |
| inspect(key) | Origin locale (from) + source text for a key (devtools/tooling). The field was renamed from locale in 0.17.0 (breaking) to match ResolveInfo.from |
| locale | Current locale (readonly) |
| locales | Loaded + loadable locales (readonly): feed a language switcher from one source of truth |
| version | Change counter that powers framework adapters |
Locale helpers
import { negotiateLocale, persistLocale, resolveLocale, resolveRequestLocale, switchLocale } from 'verbaly';
resolveLocale({ supported: ['en', 'es', 'pt'] }); // storage → navigator → fallback
persistLocale('es'); // localStorage + <html lang>
// server-side (0.16.0): match an Accept-Language header
negotiateLocale('es-PE,en;q=0.8', ['en', 'es']); // → 'es'
// per-request (0.17.0): cookie value → header → fallback
resolveRequestLocale({ supported: ['en', 'es'], cookie, header });
// client switch for SSR setups (0.18.0): catalog → locale → cookie + <html lang>
await switchLocale(instance, 'es');resolveLocale picks the initial locale (stored choice, then navigator.languages with BCP-47 narrowing, then fallback); persistLocale remembers a switch. Both are SSR-safe and accept a custom storageKey (false disables storage). See Plain HTML → Locale bootstrap for the full pattern.
negotiateLocale(header, supported, fallback?) (0.16.0) is the server-side counterpart: give it an Accept-Language header and your locales and it returns the best match (quality values respected, es-PE matches es, case-insensitive). It works with any server; the SvelteKit, Nuxt and Next.js integrations use it under the hood.
resolveRequestLocale(options) (0.17.0) makes the whole per-request decision in one call: a stored cookie value first, then the Accept-Language header, then your fallback. LOCALE_STORAGE_KEY exports the shared name (verbaly-locale) used by both the browser storage and the SSR cookie.
switchLocale(instance, locale, options?) (0.18.0) is the client-side switch the SSR integrations share: it loads the catalog first, then switches the locale, writes the verbaly-locale cookie and updates the lang attribute. Safe to call on the server (it does nothing there); @verbaly/sveltekit re-exports it.
Type-level safety
v.t('home.title'); ✓
v.t('home.title', { x: 1 }); ✗ no params declared
v.t('nope'); ✗ unknown keyTypeScript parses your messages: FlatKeys flattens nested trees, ParamNames reads {param} occurrences, and TArgs makes params required exactly when the message declares them.
Low-level exports
Building tooling on top of Verbaly? The package also exports parse(message) (the message AST), flatten(tree) (nested tree → dot-keys), safeHref(href) (the unsafe-scheme guard behind named links) and, since 0.17.0, normalizeLink(link), the one link normalizer every adapter shares, plus the RichLink type. These are the same pieces the compiler uses.