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

@dabalabs/lang

v1.0.0

Published

Embeddable i18n/translation widget for dabalabs projects — auto-detects your site's content, translates it into the languages you configured, and lets you correct a translation in place while developing.

Readme

@dabalabs/lang

Embeddable i18n/translation widget for dabalabs projects. Drop it into any site and it detects the page's own text, translates it into whichever languages you configured from the dashboard, and shows a simple language-switcher pill row for visitors.

While you're developing locally, right-click any translated sentence to correct it in place — the correction is saved immediately and becomes the live translation for every visitor from then on. This editing UI only ever appears on a dev-looking hostname (localhost, 127.0.0.1, *.local, *.test) — there's no way to trigger it once your site is deployed.

Works natively in Plain HTML, React, Next.js, Vue, Nuxt, Svelte, SvelteKit, Astro, and any modern JavaScript stack.

Install

npm install @dabalabs/lang

Quick start

Script tag (no build step)

<div data-dabalang data-project-id="YOUR_PROJECT_ID" data-api-key="YOUR_API_KEY"></div>
<script src="https://unpkg.com/@dabalabs/lang/dist/dabalang.min.js"></script>

The <div> is where the language-switcher pills render; the rest of the page's text is what gets crawled and translated.

npm / bundler

import { DabaLang } from "@dabalabs/lang";

new DabaLang({
  container: "#language-switcher",
  projectId: "YOUR_PROJECT_ID",
  apiKey: "YOUR_API_KEY",
});

Get your Project ID and API key

  1. Go to your dabalabs dashboardTranslate widgets.
  2. Create a translate project: pick a source language and the target languages you want to offer.
  3. Create an API key for your domain, with product set to dabalang.
  4. Copy the generated embed snippet.

Options

| Option | Type | Required | Description | | --- | --- | --- | --- | | container | string \| HTMLElement | Yes | CSS selector or element where the language pills render. | | projectId | string | Yes | Your translate project's ID. | | apiKey | string | Yes | A publishable key with product: "dabalang". Safe to ship in frontend source — the same trust model as Stripe.js: scoped server-side to your allowed origins. | | gatewayUrl | string | No | Defaults to https://api.dabalabs.com. | | view | "modal" \| "dropdown" | No | How the switcher presents languages: the flag-grid modal (default) or a compact dropdown anchored to the trigger. Script-tag installs can pass data-view="dropdown". | | onLanguageChange | (langCode: string) => void | No | Fires after a visitor switches language (the source language's own code when they pick "Original"). | | onError | (error: DabaLangError) => void | No | Fires on a failed metadata fetch or translate call. Defaults to console.error. |

What gets translated

Every visible text node in <body> is crawled by default — that's what "detect the content inside the website" means in practice; an opt-in scheme would make the widget translate nothing out of the box. To exclude a section (a code sample, a price ticker you don't want touched, anything with its own i18n already), mark it:

<div data-dabalang-ignore>
  This text is never crawled or translated.
</div>

Password fields, hidden inputs, and credit-card-autocomplete fields are always excluded, with no attribute needed.

Translation is lazy: a language is only translated the first time a visitor selects it, not pre-fetched for every configured language on every page load. The very first visitor to translate a given page into a given language pays the (small) live-translation latency; every subsequent visitor gets an instant cache hit.

Correcting a translation (development only)

On localhost (or 127.0.0.1, a .local/.test hostname), while a non-original language is selected, right-click any translated sentence to open the editor next to it. Save, and:

  • The correction applies immediately on your screen.
  • It's stored against that exact source sentence for that language.
  • Every visitor — in development or production — gets your corrected version from then on, since the point of this workflow is fixing a bad machine translation once, not maintaining a personal dev-only override.

It is a small popover anchored under the phrase you clicked, holding that one string and nothing else. Inline markup splits a sentence into several strings that are translated separately — Everything in <strong>DabaOne</strong>, plus the transcript is three — so when there are others, the popover says + N more in this sentence and expands in place if you want them. Only what you changed is saved. Each field renders in its own text direction, so an Arabic or Hebrew translation reads correctly while the popover's own chrome stays left-to-right.

saves (Shift+↵ for a newline), Esc closes, and a click anywhere outside dismisses it. Right-clicking inside the popover — or on the language switcher — leaves the browser's own menu alone.

It was a double-click before v0.0.9. Double-clicking text is how you select a word, so every correction started by taking the selection away, and translated text that was also a link or a button ran its own handler first.

There is no way to open this editor on a real (non-dev-looking) domain — it's a workflow safety rail for your own QA pass, not a security boundary. Manage or revert past corrections from the project's Corrections tab in the dashboard.

Warming the rest of the site

By default a page is translated when somebody opens it. prewarm: true also covers the page a visitor is on in the languages they are not reading. prewarmSite: true goes one step further and covers the pages nobody has opened yet:

new DabaLang({ container: "#lang", projectId, apiKey, prewarm: true, prewarmSite: true });

It reads /sitemap.xml (or a list you pass as prewarmSite: { urls: [...] }), fetches each page, pulls the text out of its HTML, and translates that — painting nothing and changing nothing on screen. What it fills is the two caches: the gateway's, which is keyed by content hash and shared by every visitor, and this browser's own. So one visitor's idle time makes the whole site instant in every language for everyone after them, instead of each page costing its first visitor in each language a wait.

Off by default, and it should stay off unless you want it: it multiplies one visit's translation cost by pages × languages, and that is your bill. What it does to keep that honest:

  • Runs only when the browser is idle, one request at a time, after the visitor's own page is correct.
  • At most once per tab per project — a route change that remounts the widget does no work at all.
  • Never on a connection reporting saveData.
  • Capped at maxPages (default 20).
  • Same-origin by construction: only the path of each sitemap entry is used, so the canonical host baked into a generated sitemap doesn't stop it working in development or on a preview deployment.

It reads server-rendered HTML. A page whose text only appears after its own JavaScript runs yields nothing here — no cost, and that page is simply translated on arrival as before.

Pre-translated languages

By default the platform translates for you. If the translation already exists, configure the language as pre-translated in the dashboard — it is then never machine-translated. Two shapes:

  • Your site already serves that language (a CMS with localized pages, a localized build): give the language a path prefix and enable path routing. Pages under that prefix (/ar/...) are treated as already translated — the widget doesn't touch their content or spend a single translation call; it only sets lang/dir, marks the switcher, and navigates between prefixes.
  • You have the strings but one set of pages: upload a map of source text → translated text. Uploaded strings are swapped in directly — no machine translation, no per-string cost — and any string your upload doesn't cover falls back to the normal pipeline, so a partial catalogue never breaks a page.

No widget change is needed either way.

URL path routing

Optionally, languages can live under URL path prefixes — example.com/ar/pricing for Arabic, example.com/pricing for the original — configured per language from the dashboard (Languages tab → path prefix, plus the project-level toggle).

With it enabled, the widget:

  • Detects on load: visiting a path under a configured prefix (/ar/...) applies that language immediately — no pill click needed. Un-prefixed paths serve the language marked default (or the original if none is).
  • Navigates on switch: picking a language that has a prefix navigates to the same page under that prefix (query string and hash preserved); picking the original strips it. Languages without a prefix keep translating in place.
  • Never redirects on its own: the widget only reacts to the URL it finds itself on. If you want / to redirect to /ar for Arabic-locale visitors, do that on your server/CDN — the widget will pick the language up from the path it lands on.

Your server must serve the same page under the prefixed paths (a catch-all/rewrite works fine — the widget re-translates from the path). SPAs and static hosts can map /ar/* to the same bundle.

Limitations (v1)

  • Client-side only. There's an unavoidable flash of the original-language content before JavaScript crawls and swaps in a translation — the same tradeoff every client-side-only i18n widget (Weglot, GTranslate's free tier, etc.) makes. A server-side-rendered/SEO-safe tier is not built.
  • No browser-language guessing. The widget never infers a visitor's language from browser settings. Without path routing it always starts on "Original"; with path routing the URL decides.
  • Server-rendered text only, for prewarmSite. Warming other pages reads their HTML without running their JavaScript, so client-rendered content is not covered.

License

MIT

Environments

By default the widget talks to Dabalabs production. Point it at staging with env:

<div data-dabalang
  data-project-id="YOUR_PROJECT_ID"
  data-api-key="YOUR_API_KEY"
  data-dabalang-env="staging"
></div>
new DabaLang({
  // ...
  env: "staging",
});

| env | Gateway | |---|---| | omitted | https://api.dabalabs.com | | "production" | https://api.dabalabs.com | | "staging" | https://api-staging.dabalabs.com |

Production is the default in every direction — an embed that says nothing about its environment is a live site, so staging has to be asked for by name. An unrecognised value warns in the console and falls back to production rather than throwing, so a typo cannot take a customer's page down.

gatewayUrl still overrides env when both are given: it is the only option that can name a host neither tier covers (a self-hosted deployment, a local gateway).