npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

verbaly

v0.38.0

Published

Effortless i18n: write natural text, ship type-safe, tree-shakeable translations.

Readme


Most i18n tools make you maintain key files by hand: keys drift from the code, no type-safety, heavy setup. Verbaly inverts the flow: write the source text in your code, a build plugin extracts stable keys, types and per-locale modules. Compiler-grade safety, a runtime under ~3KB, zero dependencies.

// You write this:
t`Hello ${name}, you have ${count} messages`;

// The compiler generates: a stable key + inferred types + per-locale entries.
t('EMo3ph4u', { name, count }); //  ← fully typed, tree-shakeable

// Prefer readable keys? Opt in per message:
t.id('inbox.title')`Hello ${name}`; // → t('inbox.title', { name })

Missing a translation? The build fails, so raw keys never reach production. And a translation that dropped your {name} or flattened a plural block fails too: being filled in is not the same as working.

🚀 Try it in 30 seconds

pnpm add verbaly @verbaly/vite
// vite.config.ts
import verbaly from '@verbaly/vite';

export default { plugins: [verbaly({ locales: ['en', 'es', 'pt'] })] };
// anywhere in your app, extracted + typed on save
import { t, setLocale } from 'virtual:verbaly';

t`Hello ${name}`;
await setLocale('es'); // per-locale bundle loaded on demand

🎛️ Standalone runtime (no compiler)

verbaly also works on its own for dynamic/CMS content:

import { createVerbaly } from 'verbaly';

const v = createVerbaly({
  locale: 'en',
  messages: { en: { greeting: 'Hello {name}' } },
  loaders: { es: () => import('./locales/es.json') }, // lazy catalogs
});

v.t('greeting', { name: 'Aron' }); // "Hello Aron"
v.setLocale('es'); // auto-loads the catalog; or: await v.loadLocale('es') first

🌐 Plain HTML, no framework needed

<h1 data-verbaly="home.title"></h1>
<p data-verbaly="home.intro" data-verbaly-rich></p>
<!-- rich: 'The build <em>gate</em>' renders a real <em> (whitelist, XSS-safe) -->
<p
  data-verbaly="home.cta"
  data-verbaly-rich
  data-verbaly-links='{"repo":"https://github.com/x"}'
></p>
<!-- links: 'See the <repo>repo</repo>' renders <a href>; hrefs from you, never from messages -->
import { bindDom, createVerbaly, persistLocale, resolveLocale } from 'verbaly';

const v = createVerbaly({
  locale: resolveLocale({ supported: ['en', 'es', 'pt'] }), // storage → navigator → fallback
  /* … */
});
bindDom(v, {
  richLinks: { docs: { href: '/docs', target: '_blank', rel: 'noopener' } }, // named links
}); // renders + re-renders on locale change

v.setLocale('es');
persistLocale('es'); // localStorage + <html lang> + <html dir>
// locale switchers without hardcoded names or direction tables
import { localeDirection, localeName } from 'verbaly';

localeName('es'); // 'español' (endonym, via Intl.DisplayNames)
localeName('de', 'en'); // 'German'
localeDirection('ar'); // 'rtl': switchLocale/persistLocale already apply it to <html dir>
// server-side (SSR): one instance per request, locale from the request itself
import { createVerbaly, negotiateLocale } from 'verbaly';

const locale = negotiateLocale(request.headers.get('accept-language'), ['en', 'es', 'pt']);
const v = createVerbaly({ locale, fallback: 'en', messages });

✨ What you get

  • Hybrid compiler + runtime: static text compiled (types + tree-shaking), dynamic content via a real runtime path.
  • Type-safe params: {name}, plurals, currency and dates inferred from the message; wrong/missing params fail to compile.
  • Intl-powered format: number/currency/date/time/relative/list/unit + CLDR plurals and select/gender, tiny surface. 'Updated {when:relative}' · '{langs:list}' · '{d:unit/kilometer}'.
  • Plain, portable JSON catalogs: no proprietary format, no lock-in.
  • DOM interpreter for framework-less HTML, with opt-in rich text (whitelist-based, XSS-safe) and named links (richLinks / data-verbaly-links; hrefs come from the caller, javascript: blocked).
  • Lazy catalogs: loaders + loadLocale load per-locale JSON on demand, in the runtime itself.
  • RTL and locale names built in: localeDirection keeps <html dir> right when you add Arabic or Hebrew (applied by switchLocale/persistLocale and verbaly render), and localeName gives your locale switcher real language names via Intl.DisplayNames.
  • Runtime devtools: opt-in verbaly/devtools answers "what key is this text?" in the browser: hover to see any element's key/locale/source, plus a live missing-keys panel. Tree-shaken out of production (its own chunk). Or wire the onResolve hook yourself.
  • Fast, with receipts: fully memoized hot path, benchmarked every release: 5-35× faster than i18next on lookup, interpolation and plurals.

🧩 Ecosystem

| Package | Description | | -------------------------------------------------------- | ------------------------------------------------------- | | verbaly | Core runtime (this package) | | @verbaly/vite | Zero-config Vite plugin | | @verbaly/unplugin | webpack · Rollup · esbuild · Rspack | | @verbaly/compiler | Extraction + codegen + the verbaly CLI | | @verbaly/react · @verbaly/vue · @verbaly/svelte | Framework adapters (React also covers Preact) | | @verbaly/astro | Astro integration + per-locale static build | | @verbaly/next · @verbaly/nuxt · @verbaly/sveltekit | SSR: locale per request, hydration with no flash | | @verbaly/mcp | MCP server: the translation cycle as tools for an agent |

All twelve share one version number, so you never match compatible ranges.

📖 Docs & live playground: https://verbaly-web.vercel.app

🔁 Coming from i18next? Keep your keys and catalogs: the migration guide maps everything one-to-one.

⚠️ Early development (0.x): API not stable yet.

License

MIT © Aron Soto