- Type-safe keys & params
- Inferred from your text
- Runtime size (gzip)
- 3.00 KB, zero deps
- Key maintenance
- None, auto-extracted
- Dynamic / CMS content
- Real runtime path
- Plain HTML (no framework)
- DOM interpreter
- Missing translations
- Build fails
- Broken translations
- Build fails
- Catalog format
- Plain JSON
- Coding agents
- MCP server + skill
Writenaturaltext.Shiptype-safetranslations.
The compiler extracts your messages, generates the types and splits bundles per locale. A missing translation fails the build, 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.
Wherever you already are
One runtime under every adapter, so the message you write is the same everywhere. Only the package 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.00 KB, zero deps | ~14 KB (+9 KB react) | ~5 KB | ~0 (compiled) |
| 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 | Build fails | Runtime warning | CI step | Build fails |
| Broken translations | Build fails | — | — | — |
| Catalog format | Plain JSON | JSON | PO / JSON | inlang format |
| Coding agents | MCP server + skill | — | Skill | — |
- Type-safe keys & params
- Plugin / manual
- Runtime size (gzip)
- ~14 KB (+9 KB 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)
- ~5 KB
- 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)
- ~0 (compiled)
- Key maintenance
- By hand
- Dynamic / CMS content
- Weak
- Plain HTML (no framework)
- —
- Missing translations
- Build fails
- Broken translations
- —
- Catalog format
- inlang format
- Coding agents
- —
Sizes are approximate gzip runtime footprints; every project weighs trade-offs differently.
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, two packages.
pnpm add verbaly @verbaly/viteAdd 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 # the build gate, in your pipeline
Soon you can break it yourself.
A playground on the real runtime: edit the catalogs, switch locales, empty a translation and watch the build gate catch it. It is being built right now.
Read the guide meanwhile