Skip to content
v0.45.0latest

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.

Integrations
~3 KBruntime
12packages
MITlicense
i18n that feels like zero config

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.

FeatureVerbalyVerbalyi18nextLinguiParaglide
Type-safe keys & paramsInferred from your textPlugin / manualPartialYes
Runtime size (gzip)3.00 KB, zero deps~14 KB (+9 KB react)~5 KB~0 (compiled)
Key maintenanceNone, auto-extractedBy handExtract → compile stepBy hand
Dynamic / CMS contentReal runtime pathYesYesWeak
Plain HTML (no framework)DOM interpreter
Missing translationsBuild failsRuntime warningCI stepBuild fails
Broken translationsBuild fails
Catalog formatPlain JSONJSONPO / JSONinlang format
Coding agentsMCP server + skillSkill
VerbalyVerbaly
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
i18next
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
Lingui
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
Paraglide
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.

  1. Install

    One command, two packages.

     pnpm add verbaly @verbaly/vite
  2. Add the plugin

    Point it at the locale you write in.

    vite.config.ts
    import verbaly from '@verbaly/vite';
    
    export default { plugins: [verbaly({ sourceLocale: 'es' })] };
  3. Write text, not keys

    Keys, types and catalogs: generated.

    src/app.ts
    import { t, setLocale } from 'virtual:verbaly';
    
    t`Hola ${name}`;      // keys, types & catalogs: generated
    await setLocale('pt'); // lazy-loads its chunk
  4. Let 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
Copied to clipboard