Skip to content

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' } } },
});
OptionTypeDescription
localestringInitial locale; defaults to navigator.language in the browser
fallbackstring | string[]Fallback chain after BCP-47 narrowing (es-MXes)
messagesobjectLocale → tree; nested keys flatten to dots (home.title). An empty string counts as untranslated and keeps falling back, the same rule as check
loadersobjectLazy 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
formattersobjectCustom {v:name} formatters
onMissingfunction(key, locale): return a string to substitute; silences the default warn-once
onResolvefunction(info) on every t(): key, locale, value and status (hit / fallback / miss). Powers devtools

Instance

MemberDescription
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)
localeCurrent locale (readonly)
localesLoaded + loadable locales (readonly): feed a language switcher from one source of truth
versionChange 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/docs

localeFromPath 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

editor
v.t('home.title');            
v.t('home.title', { x: 1 });  ✗ no params declared
v.t('nope');                  ✗ unknown key

TypeScript 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.

Copied to clipboard