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

i18n-keyless-browser

v3.8.1

Published

Keyless i18n for any web page. No framework required.

Readme

i18n-keyless-browser

Translate any web page without translation keys: the string you write is the key. No framework required: this package is the base layer for Svelte, Alpine, htmx, jQuery, plain HTML and legacy sites. It wraps i18n-keyless-core in a plain store, a DOM helper and one web component, <i18n-t>.

Get an API key at https://i18n-keyless.com/#get-api-key

Quick start: one script tag

<script async type="module" src="https://esm.sh/i18n-keyless-browser/auto"
        data-api-key="YOUR_API_KEY" data-primary="fr" data-supported="fr,en,es"></script>
<h1 data-i18n>Bonjour le monde</h1>
<p><i18n-t context="durée">8 heures</i18n-t></p>
<button onclick="i18nKeyless.setCurrentLanguage('en')">English</button>

That is all. On load the script reads its own data-* attributes, initializes the store, translates every data-i18n element and every <i18n-t>, and exposes the JS API as window.i18nKeyless. Translations are cached in localStorage.

Script tag attributes

| Attribute | Config | Notes | | --- | --- | --- | | data-api-key | API_KEY | required | | data-primary | languages.primary | required: the language the page is written in | | data-supported | languages.supported | comma separated, the primary is always added | | data-lang | languages.initWithDefault | language of the first visit | | data-fallback | languages.fallback | when the requested language is not supported | | data-skip-language-hydration | languages.skipCurrentLanguageHydration | ignore the stored language: the page decides (URL) | | data-api-url | API_URL | self-hosted backend | | data-namespace | defaultNamespace | project-wide namespace | | data-storage | storage | local (default), session or memory | | data-debug | debug | log every step |

window.i18nKeyless holds the whole JS API below, plus ready, a promise that resolves once the store is hydrated.

The async attribute prevents the script from blocking page rendering if the CDN is slow.

The package is plain ESM and imports its dependency by name (i18n-keyless-core), so the URL must come from a CDN that resolves bare specifiers (esm.sh, or unpkg with ?module).

For maximum reliability, pin the exact version in the URL. The bare path (esm.sh/i18n-keyless-browser/auto) re-resolves every 10 minutes and can time out when esm.sh rebuilds the module. A pinned path gets an immutable cache:

<script async type="module" src="https://esm.sh/[email protected]/auto"
        data-api-key="YOUR_API_KEY" data-primary="fr" data-supported="fr,en,es"></script>

To serve the files yourself, add an import map before the script tag:

<script type="importmap">
  { "imports": { "i18n-keyless-core": "/vendor/i18n-keyless-core/dist/index.js" } }
</script>

JS API

npm install i18n-keyless-browser
import { init, getTranslation, setCurrentLanguage, subscribe, translateDom, defineI18nT } from "i18n-keyless-browser";

init({
  API_KEY: "YOUR_API_KEY",
  languages: { primary: "fr", supported: ["fr", "en", "es"] },
  // storage: window.localStorage (default), sessionStorage, or any getItem/setItem/removeItem object
});

defineI18nT();          // registers <i18n-t>
translateDom();         // binds every [data-i18n] element under document.body

const label = getTranslation("Votre email");            // cached translation, or the source
await setCurrentLanguage("en");                          // persists, refetches, notifies
const stop = subscribe((state) => console.log(state.currentLanguage));

| Function | What it does | | --- | --- | | init(config) | Same options as i18n-keyless-react. Hydrates from storage, fetches the current language, sends the usage report once. | | getTranslation(text, options) | The cached translation, or text when it is not there yet (it is then requested). Records usage. Plain function: no subscription. | | resolveTranslation(text, options, state?) | Same lookup, no side effect. For use inside a subscribe listener. | | getTranslationStatus(text, options) | "ready" \| "pending" \| "unavailable", no side effect, never requests. | | resolveTranslationStatus(text, options, state?) | Same status, no side effect. For use inside a subscribe listener. | | watchTranslation(text, options, onText) | Calls onText(translated, lang, status) now and on every change of the string OR the status. Returns the stop function. The building block for Svelte, Alpine, jQuery bindings. | | translateDom(root = document.body) | Binds every [data-i18n] element under root. Returns the stop function. | | defineI18nT(name = "i18n-t") | Registers the web component. | | setCurrentLanguage(lang) / getCurrentLanguage() | Switch and read the language. | | getSupportedLanguages() | The supported list, for a picker. | | subscribe(listener) / getState() | The plain store. listener(state, previous). | | clearI18nKeylessStorageAndStore() | Wipes the cache. The device id stays. |

The precompiled bundle (optional)

init({ bundle: { manifest, load } }) ships the dictionaries exported at build time — the MCP export_bundle tool, or GET /translate/bundle — with the page instead of fetching them: load: (namespace, lang) => import(\./i18n-keyless/${namespace}/${lang}.json`). A (namespace, lang)the manifest covers is read from the file at boot and on a language switch, with the bundle's cursor, instead of requested; the API is only called for a key the bundle does not have and for the delta after it. Storage still wins when it holds something newer in the same language. Programmaticinitonly: the./autoscript-tag entry has no way to pass a loader function through adata-*` attribute.

<i18n-t>

<i18n-t>Bonjour</i18n-t>
<i18n-t context="durée" namespace="shop">8 heures</i18n-t>

The initial text content is the source (trimmed). Light DOM, no shadow root: your CSS applies. The element subscribes on connect and unsubscribes on disconnect.

Attributes: context, namespace, origin-language, unpersisted-namespace, debug. Properties, from JS: replace (an object) and text (the source).

const el = document.querySelector("i18n-t");
el.replace = { "{name}": user.name };   // <i18n-t>Bonjour {name}</i18n-t>

The element reflects its translation status as data-i18n-status="pending|ready|unavailable", so CSS can style it while a translate-on-miss is in flight:

i18n-t[data-i18n-status="pending"] { opacity: 0.5; }

data-i18n

<h1 data-i18n>Bonjour</h1>
<span data-i18n data-i18n-context="durée" data-i18n-namespace="shop">8 heures</span>
<span data-i18n="Bonjour">Hello (already rendered by the server)</span>

The source is the value of data-i18n when it is not empty, else the element's text. The whole text content of the element is replaced by the translation. Other attributes: data-i18n-context, data-i18n-namespace, data-i18n-origin-language, data-i18n-unpersisted-namespace, data-i18n-debug. Content added later needs a new translateDom(newNode) call; calling it twice on the same element is safe.

translateDom() writes data-i18n-status="pending|ready|unavailable" on each element it binds, kept in sync the same way the text is.

Per-translation options

Same as the React package: context, replace, namespace, unpersistedNamespace, forceTemporary, originLanguage, count / ordinal (attributes on <i18n-t>), select (a property, like replace), debug. See the main README for the details.

Languages

AVAILABLE_LANGS, the Lang type, resolveLang and toAppStoreLocale are re-exported from i18n-keyless-core.

import { AVAILABLE_LANGS, resolveLang } from "i18n-keyless-browser";
setCurrentLanguage(resolveLang(navigator.language, { supported: ["fr", "en"], fallback: "en" }));

Example

examples/browser: one HTML file, the auto entry, an offline mock backend.

Docs

https://docs.i18n-keyless.com