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

@jwrunge/transmut

v1.0.2

Published

A client-side translation helper that watches the DOM, normalizes dynamic content, and populates localized text or attributes as translations become available. It is designed for single-page applications that need to translate asynchronously fetched conte

Readme

transmut Translation Observer

A client-side translation helper that watches the DOM, normalizes dynamic content, and populates localized text or attributes as translations become available. It is designed for single-page applications that need to translate asynchronously fetched content without re-rendering the entire view.

Installation

This package currently ships as source. Add it to your project as a workspace package or copy the src/observer folder into your bundle. Ensure your build pipeline includes the files in src/observer.

# via npm workspaces or a relative dependency
npm install @jwrunge/transmut

Vitest is configured for local testing (npm test).

Using in a TypeScript Project

You can import the sources directly without a publish step. Add a path mapping so the compiler resolves the .ts files shipped with this package:

// tsconfig.json
{
	"compilerOptions": {
		"paths": {
			"@jwrunge/transmut": ["./node_modules/@jwrunge/transmut/src/index.ts"],
			"@jwrunge/transmut/*": ["./node_modules/@jwrunge/transmut/src/*"]
		}
	}
}

Then import the observer or the SQLite helpers straight from your code:

import { TranslationObserver, upsertTranslations } from "@jwrunge/transmut";

const observer = new TranslationObserver(/* ... */);
await upsertTranslations({
	databasePath: "./translations.sqlite",
	locale: "es-MX",
	translations: { Cart: "Carrito" },
});

Bundlers such as Vite/Esbuild can compile the .ts sources during your build. If you run Node directly, build the project first (tsc -p .) before executing the output.

Quick Start

import { TranslationObserver } from "@jwrunge/transmut";

const observer = new TranslationObserver(
	"en-US", // default/source locale
	"es-MX", // initial target locale (optional)
	async ({ langCode, region }, keys, currentUrl) => {
		// Fetch translations from your API
		const response = await fetch(
			`/api/translations?lang=${langCode}&region=${region}`
		);
		return (await response.json()) as Record<string, string>;
	},
	24, // hours before cached entries are considered stale (optional)
	async () => ["session.banner"], // invalidate cache keys (optional)
	{
		requireExplicitOptIn: true,
		skipEditable: true,
	}
);

await observer.changeLocale("es", "MX");

Place data-transmut="include" (or other directives) on elements that provide opt-in. The observer will automatically translate matching text nodes and opted-in attributes.

SQLite Backend (Optional)

The module in src/backend/sqlite-translations.ts persists translations in a local SQLite database using sql.js. It exposes helpers that can run in a Node environment (or be bundled to a standalone binary with tools such as npx pkg, bun build --compile, or deno compile --unstable when the accompanying sql-wasm.wasm file is copied next to the executable).

import {
	createSqliteTranslationProvider,
	upsertTranslations,
} from "./src/backend/sqlite-translations";

// Persist or update translation pairs
await upsertTranslations({
	databasePath: "/var/app/data/translations.sqlite",
	locale: { langCode: "es", region: "MX" },
	translations: {
		"Hello, world!": "¡Hola, mundo!",
		Checkout: "Pagar",
	},
});

// Hook the database up to TranslationObserver on the server
const sqliteProvider = createSqliteTranslationProvider(
	"/var/app/data/translations.sqlite"
);

// Example inside an HTTP handler
app.get("/api/translations", async (req, res) => {
	const { lang, region, keys } = req.query;
	const map = await sqliteProvider(
		{ langCode: String(lang), region: String(region ?? "") },
		Array.isArray(keys) ? keys : String(keys).split(",")
	);
	res.json(map);
});

Additional utilities are available:

  • loadTranslations — fetch a subset of keys directly from disk.
  • listTranslations — inspect all entries for a locale (useful for admin tools or exporting).
  • SqliteTranslationProviderOptions — control locale fallback behaviour.

CLI / Binary Workflow

The src/backend/cli.ts entry provides a small command-line tool for managing translation databases. You can execute it with Node after compiling:

npm run build:pkg:prepare
node dist/backend/cli.js list --db ./translations.sqlite --locale es

To distribute a standalone binary, install pkg globally (npm install -g pkg) or run it via npx, then build:

npm run build:pkg

This produces dist/transmut (Linux x64 by default) together with dist/sql-wasm.wasm. Ship both files and ensure SQLJS_WASM_PATH points to the WASM if you relocate it.

The CLI supports three commands:

  • upsert --db <file> --locale <lang[-REGION]> --input <json or -> — merge JSON translations into the database (STDIN with -).
  • list --db <file> --locale <lang[-REGION]> — dump stored entries for inspection/export.
  • load --db <file> --locale <lang[-REGION]> --keys key1,key2 — fetch a subset, honouring base-locale fallback unless --no-fallback is passed.

Constructor Signature

new TranslationObserver(
	defaultLangCode?: string,
	initialLocale?: string,
	getTranslations: GetTransMapFn,
	expiryHours?: number,
	invalidateFn?: InvalidateFn,
	options?: TranslationObserverOptions
);
  • defaultLangCode: BCP 47 tag representing the source language (defaults to en).
  • initialLocale: Target locale to translate into. If omitted, the browser navigator.language is used.
  • getTranslations: Required async (or sync) function that returns a map of translation strings keyed by normalized source phrases. Returning a JSON string is also supported.
  • expiryHours: Number of hours before cached translations are considered stale. Set to 0 or omit to disable staleness checks.
  • invalidateFn: Optional callback invoked on startup to return translation keys that should be removed from IndexedDB before use.
  • options: Optional configuration (detailed below).

Lifecycle Helpers

  • changeLocale(langCode?: string, region?: string): Promise<void> — updates the target locale, reapplies language metadata, and re-translates tracked nodes/attributes.
  • observeShadowRoot(root: ShadowRoot): void — explicitly opt a shadow root into observation.
  • disconnect(): void — stop observing mutations and close caches. Call this when tearing down your app.

HTML Integration

The observer uses data-transmut-* directives to decide what to translate.

| Attribute | Purpose | Notes | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | data-transmut="include" | Opt-in a node (and descendants) for translation. | Required when requireExplicitOptIn is true. | | data-transmut="skip" | Prevent translation for the node and its subtree. | Equivalent to adding data-transmut-skip. | | data-transmut-skip | When present (any truthy value), skips translation for the subtree. | Empty string counts as true. | | data-transmut-attrs="title,aria-label" | Comma-separated list of attribute names to translate. | Attributes must exist on the element. | | data-transmut-locale | Override locale for a section. Values such as inherit, auto, or empty fall back to observer locale. | When set to another locale the subtree is skipped. | | data-transmut-dir | Force text direction (ltr or rtl). | Applied alongside locale metadata. | | data-transmut-{variable} | Supply values for dynamic placeholders (see below). | The {variable} name is derived from placeholders. |

Dynamic Content Placeholders

Source strings that contain placeholders are normalized before being sent to getTranslations. By default, ${variable} tokens and numbers are replaced with {} in the translation key.

By default, protected token patterns are also normalized to {} so unique values are preserved during reconstruction:

  • Email addresses (e.g. [email protected])
  • UUIDs (e.g. 550e8400-e29b-41d4-a716-446655440000)
  • HTTP(S) URLs (e.g. https://example.com/help)

Example:

<p data-transmut="include" data-transmut-count="5">
	You have ${count} unread messages.
</p>
  • The observer requests a translation for "You have {} unread messages.".
  • After receiving the translation (e.g., "Tienes {} mensajes sin leer."), the observer reconstructs the sentence and replaces the placeholder with the value from data-transmut-count.

You can customise placeholder detection via the variablePattern and variableNameGroup options if your templates use different syntax.

Attribute Translation

Attributes listed in data-transmut-attrs or matched by the default list (title, aria-label, aria-description, placeholder, alt) are translated alongside text. Opt in using directives or selectors when requireExplicitOptIn is enabled.

Svelte Patterns

In Svelte, {value} expressions are resolved before the observer sees the DOM. For predictable translation keys and safe reconstruction, prefer literal placeholders in text plus data-transmut-* value attributes.

<p
	data-transmut="include"
	data-transmut-myfavoritefood={myFavoriteFood}
>
	I love ${myFavoriteFood}!
</p>

This yields the translation key I love {}! and reinserts the runtime value from data-transmut-myfavoritefood.

For sensitive/dynamic values such as emails, UUIDs, or URLs:

  • They are protected by default and normalized to {} in lookup keys.
  • Keep requireExplicitOptIn: true so only marked content is translated.
  • Use data-transmut-skip on subtrees that should never be translated.

Example with mixed translated/static-sensitive content:

<p data-transmut="include">
	My email address is <span data-transmut-skip>{myEmail}</span>.
</p>

Options Reference

TranslationObserverOptions control how the observer targets nodes and handles directionality.

| Option | Type | Default | Description | | ----------------------- | -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | requireExplicitOptIn | boolean | false | When true, only nodes matching textSelector or with data-transmut="include" are processed. | | textSelector | string \| null | requireExplicitOptIn ? "[data-transmut]" : null | Additional CSS selector that opts elements in for text translation. | | attributeSelector | string \| null | "[data-transmut-attrs]" | CSS selector used to detect attribute translation candidates. | | attributeNames | string[] | See defaults above | Attribute names automatically considered for translation. | | skipEditable | boolean | true | When true, editable controls (input, textarea, contentEditable) are ignored. | | setLanguageAttributes | boolean | true | Apply lang, dir, and data-transmut-* metadata to observed roots and the document element. | | direction | 'ltr' \| 'rtl' \| 'auto' | 'auto' | auto infers direction from locale using defaults plus overrides. | | directionOverrides | Record<string, 'ltr' \| 'rtl'> | DEFAULT_DIRECTION_OVERRIDES | Extend or override the built-in map of locale → direction. Keys should be lowercase BCP 47 tags. | | variablePattern | RegExp | /\${\s*([^}]+?)\s*}/g | Pattern used to detect variable placeholders. Must be global (g). | | variableNameGroup | number | 1 | Capture group index that contains the placeholder name. | | protectedPatterns | RegExp[] | email, UUID, and URL defaults | Additional token patterns replaced with {} during lookup and restored after translation. |

Translation Cache

An IndexedDB-backed cache stores translations per locale.

  • Database names follow transmut.<lang>.<region> (region defaults to default).
  • Cached entries include updatedAt timestamps. When expiryHours is provided, stale keys are re-fetched.
  • invalidateFn runs once on startup. Returning an array of keys deletes them across all known locales before translation begins.

If IndexedDB is unavailable (e.g., SSR or non-browser contexts), the cache gracefully no-ops.

Environment Requirements

  • Runs in browsers with MutationObserver and (optionally) indexedDB.
  • For unit testing, the project uses Vitest with a jsdom environment (npm test).
  • Ensure your bundler supports modern ES modules (target is ES2022).

Development Workflow

  • npm install
  • npm test

Tips and Patterns

  • Wrap getTranslations with your own batching logic or memoization to reduce network chatter.
  • Use observer.observeShadowRoot(shadowRoot) for web components.
  • Apply data-transmut-skip to sections that should stay in the source language (e.g., brand names).
  • Consider emitting analytics or logs inside getTranslations to monitor missing keys.

License

MIT