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.70.0

Published

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

Downloads

4,879

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 3.31 KB runtime, zero dependencies. That is createVerbaly on its own; a site with no framework, which also pulls the DOM interpreter and the url helpers, ships 6.11 KB. Both are measured on every release by pnpm --filter verbaly size, never estimated.

// 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? Every build says which ones, and verbaly check stops your CI on them, so a raw key never reaches a release. A translation that dropped your {name} or flattened a plural block fails check 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

With the compiler, every t your project holds is typed by its catalog: the generated types fill in Register, so Translate and Locale (both exported here, types only, zero bytes) are your keys and your locales, and an instance's t checks keys and params too.

🎛️ 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
  /* … */
});
// { root: document } so the <title> and the meta description translate with the page
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>
// switchLocale is the whole switch: navigation or in-place swap, then both persistence channels
// server-side (SSR): one instance per request, locale from the request itself
import { createVerbaly, resolveRequestLocale } from 'verbaly';

// the address wins when it carries the language: a url is public, a cookie is one visitor
const locale = resolveRequestLocale({
  supported: ['en', 'es', 'pt'],
  path: url.pathname,
  routing: 'prefix-except-source',
  cookie: cookies.get('verbaly-locale'),
  header: request.headers.get('accept-language'),
  fallback: 'en',
});
const v = createVerbaly({ locale, fallback: 'en', messages });
// the url helpers, all pure and safe on a server
import { alternateLinks, localePath, stripLocalePath } from 'verbaly';

localePath('pt', { supported, sourceLocale: 'en', path: '/es/docs' }); // '/pt/docs'
stripLocalePath({ supported, path: '/es/docs' }); // '/docs', the route behind the url
alternateLinks({ supported, sourceLocale: 'en', path: '/docs', baseUrl: 'https://x.dev' });
// one hreflang entry per locale plus x-default, the same set `verbaly render` writes

✨ 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.
  • A custom formatter knows which message it was called for: the fourth argument carries the param name and the message key, so its own errors can name them.
  • ICU and relative time are weight you only carry when you use them: the compiler sees them in your catalogs and wires what is needed, so an app whose messages use neither never ships those 862 bytes. A message that arrives after the build with ICU syntax shows its own source and says why, rather than rendering something that looks almost right.
  • 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 | | @verbaly/studio | Local review server over your catalogs |

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

📖 Docs: https://verbaly-web.vercel.app/docs/reference/api

🔁 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