Start
Getting started
Verbaly inverts the usual i18n flow: you write natural text in your code and the compiler extracts messages, generates stable keys, types and per-locale bundles. Requirements: Node 22.12+ and Vite 7 or 8.
Install
Add the runtime, the Vite plugin and the compiler, which is where the
verbalycommand lives. Pick your package manager.pnpm add verbaly @verbaly/vite @verbaly/compilerThe runtime is always the same; the second one depends on your setup. On webpack, Rollup, esbuild or Rspack you install
@verbaly/unplugininstead, and Next.js, Nuxt and Astro have their own. See Packages.Scaffold it in one command
npx verbaly initwrites the config, creates thelocales/catalogs and detects your bundler, so it names the plugin you need and where to wire it. It never overwrites anything. Wiring the plugin is yours to do, and it is the next step.Add the plugin
vite.config.tsimport verbaly from '@verbaly/vite'; export default { plugins: [verbaly({ sourceLocale: 'es' })], };sourceLocaleis the language you write in. If you do not listlocales, every JSON catalog inlocales/is a language: drop anen.jsonorpt.jsonthere and Verbaly picks them up. Listlocalesand that list is the whole list, so a file you left behind is ignored.Write text, not keys
src/app.tsimport { t, setLocale } from 'virtual:verbaly'; const saludo = t`Hola ${name}, tienes ${count} mensajes`; await setLocale('pt'); // lazy-loads pt chunkIn dev, the plugin extracts messages live: your source catalog fills itself, other locales get
""placeholders, and the generated types keep keys and params typed.Translate and verify
What the virtual module exports
| Export | Description |
|---|---|
| t | Translate: t('key', params) or tagged t`...` |
| setLocale(locale) | Async: lazy-loads that locale's chunk, then switches |
| getLocale() | Current locale |
| subscribe(fn) | Re-run UI on locale/message changes; returns unsubscribe |
| verbaly | The underlying instance (advanced use) |