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. The one for your starting locale is fetched right away, and the UI updates when it lands |
| 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. Bad catalog data never crashes a render and never passes unnoticed either: a missing param, a plural block with no catch-all case, a placeholder missing its argument, a value that is not text at all, each one warns once in the console naming the message or the path it came from |
| 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) |
| 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 { localeDirection, localeFromPath, localeName, localePath, negotiateLocale, persistLocale, resolveLocale, resolveRequestLocale, switchLocale } from 'verbaly';
// which language is this page? a fact of the url, undefined when it carries no prefix
localeFromPath({ supported: ['en', 'es', 'pt'] }) ?? 'en';
// which language does this visitor want? url prefix → storage → navigator → fallback
resolveLocale({ supported: ['en', 'es', 'pt'] });
persistLocale('es'); // localStorage + <html lang> + <html dir>
// server-side: match an Accept-Language header
negotiateLocale('es-PE,en;q=0.8', ['en', 'es']); // → 'es'
// per-request: cookie value → header → fallback
resolveRequestLocale({ supported: ['en', 'es'], cookie, header });
// client switch for SSR setups: catalog → locale → cookie + <html lang> + <html dir>
await switchLocale(instance, 'es');
// language switchers: names and direction from Intl, no tables
localeName('es'); // → 'español'
localeName('de', 'en'); // → 'German'
localeDirection('ar'); // → 'rtl'
// pre-rendered sites: the same page in another language, for a switcher that navigates
localePath('pt', { supported: ['en', 'es', 'pt'], sourceLocale: 'en' }); // /es/docs → /pt/docslocaleFromPath and resolveLocale answer two different questions, and picking the wrong one is the classic bug. localeFromPath(options) asks which language this page is in: it reads the /{locale}/ prefix and returns undefined when the url carries none, so on a site with a tree per language you write localeFromPath(...) ?? sourceLocale and nothing can contradict the url. resolveLocale(options) asks which language this visitor wants: url prefix, then the stored choice, then navigator.languages with BCP-47 narrowing, then fallback (path: false switches the url off). Use it to decide where to send someone, never to decide what a page already says. persistLocale remembers a switch. All are SSR-safe, the first two take a base for a site served from a subfolder, and the storage ones accept a custom storageKey (false disables storage). See Plain HTML → Locale bootstrap for the full pattern.
negotiateLocale(header, supported, fallback?) 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) 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?) 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 and dir attributes. Safe to call on the server (it does nothing there); @verbaly/sveltekit re-exports it.
localeName(locale, displayIn?) and localeDirection(locale) feed a language switcher without hardcoded tables: real language names via Intl.DisplayNames (the name in its own language by default) and the writing direction of any locale. Right-to-left languages need no extra work: switchLocale, persistLocale, the SSR integrations and verbaly render already keep dir right on their own.
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), parseTags(text) with RICH_TAGS (the rich-text tokenizer and its tag whitelist, what a custom renderer needs), flatten(tree) (nested tree → dot-keys), safeHref(href) and safeAttribute(name, value) (the URL and attribute guards), and normalizeLink(link), the one link normalizer every adapter shares, plus the RichLink type. These are the same pieces the compiler uses.