- Type-safe keys & params
- Inferred from your text
- Runtime size (gzip)
- 3.31 KB, zero deps (6.13 with DOM + urls)
- Key maintenance
- None, auto-extracted
- Dynamic / CMS content
- Real runtime path
- Plain HTML (no framework)
- DOM interpreter
- Missing translations
- Fails CI, every build lists it
- Broken translations
- Fails CI, every build lists it
- Catalog format
- Plain JSON
- Coding agents
- MCP server + skill
Write natural text. Ship type-safe translations.
The compiler extracts your messages, generates the types and splits bundles per locale. Every build lists a missing translation and your CI stops it, so it never reaches your users.
Why Verbaly
Four things the compiler does for you, each one running right here.
Zero config, actually
One Vite plugin. No giant init(), no Babel macros, no proprietary formats. Catalogs are plain JSON you can take anywhere.
The keys are yours
By default you never write one. Name them when you want to read them, or declare them in a module when they are a contract with something outside your app, and Verbaly checks every one against your catalog. The text never changes.
Types from your messages
The text itself declares its params, so if you forget one TypeScript stops you.
Plain HTML, first-class
No framework? The DOM interpreter hydrates data-verbaly nodes, re-renders on locale switch and watches new elements.
Same job, less machinery
An honest look against the tools you'd otherwise reach for. It shows where Verbaly wins, and where the others are fine too.
| Feature | i18next | Lingui | Paraglide | |
|---|---|---|---|---|
| Type-safe keys & params | Inferred from your text | Plugin / manual | Partial | Yes |
| Runtime size (gzip) | 3.31 KB, zero deps (6.13 with DOM + urls) | 13.6 KB (23 with React) | 2 KB (4 with React) | compiled, no shared runtime |
| Key maintenance | None, auto-extracted | By hand | Extract → compile step | By hand |
| Dynamic / CMS content | Real runtime path | Yes | Yes | Weak |
| Plain HTML (no framework) | DOM interpreter | — | — | — |
| Missing translations | Fails CI, every build lists it | Runtime warning | CI step | Build fails |
| Broken translations | Fails CI, every build lists it | — | — | — |
| Catalog format | Plain JSON | JSON | PO / JSON | inlang format |
| Coding agents | MCP server + skill | — | Skill | — |
- Type-safe keys & params
- Plugin / manual
- Runtime size (gzip)
- 13.6 KB (23 with React)
- Key maintenance
- By hand
- Dynamic / CMS content
- Yes
- Plain HTML (no framework)
- —
- Missing translations
- Runtime warning
- Broken translations
- —
- Catalog format
- JSON
- Coding agents
- —
- Type-safe keys & params
- Partial
- Runtime size (gzip)
- 2 KB (4 with React)
- Key maintenance
- Extract → compile step
- Dynamic / CMS content
- Yes
- Plain HTML (no framework)
- —
- Missing translations
- CI step
- Broken translations
- —
- Catalog format
- PO / JSON
- Coding agents
- Skill
- Type-safe keys & params
- Yes
- Runtime size (gzip)
- compiled, no shared runtime
- Key maintenance
- By hand
- Dynamic / CMS content
- Weak
- Plain HTML (no framework)
- —
- Missing translations
- Build fails
- Broken translations
- —
- Catalog format
- inlang format
- Coding agents
- —
From words to shipped locales
Three steps and a guardrail, or let verbaly init scaffold it in one command. No key files written by hand: the compiler does the boring parts while you keep writing product copy.
Install
One command, three packages: the runtime, the plugin and the command.
pnpm add verbaly @verbaly/vite @verbaly/compilerAdd the plugin
Point it at the locale you write in.
vite.config.tsimport verbaly from '@verbaly/vite'; export default { plugins: [verbaly({ sourceLocale: 'es' })] };Write text, not keys
Keys, types and catalogs: generated.
src/app.tsimport { t, setLocale } from 'virtual:verbaly'; t`Hola ${name}`; // keys, types & catalogs: generated await setLocale('pt'); // lazy-loads its chunkLet CI guard it
Exits 1 while any locale is incomplete, so untranslated text never ships.
.github/workflows/ci.yml- run: npx verbaly check # exits 1 on a missing or broken translation
Your first translated page, in five minutes.
Install, add the plugin and write text. Verbaly extracts the messages, writes the types and loads each language when someone asks for it.
Already have catalogs? Open them in Studio