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

@pulsetechnologies/i18n

v0.8.1

Published

Shared language list, detection, formatting and language picker for every Pulse product.

Readme

@pulsetechnologies/i18n

The shared language layer for every Pulse product (PulseWork epic ENG-1, this is ENG-2).

  • 18 languages: en es zh-Hans zh-Hant hi ar fr pt-BR ru bn ja de id ur vi ko fil ht (Arabic and Urdu are right-to-left).
  • Choosing the language: pickLocale({ user, cookie, workspace, browser }) goes in that order, then falls back to English. The pulse_lang cookie covers all of .pulsetechnologies.ai, so a choice made in one product carries over to the others.
  • Matching: matchLocale maps real-world tags to the languages we offer: zh-TW → zh-Hant, pt-PT → pt-BR, es-MX → es, tl → fil.
  • Formatting: createFormatters(locale, { timeZone }) covers numbers, percent, money(minor, currency) for Pulse's minor-unit amounts, dates, times, relative times and lists. Haitian Creole borrows French formats because CLDR has no data for it.
  • i18next: createI18n({ resources, locale }) sets up English fallback, no catalog bleed between zh-Hans and zh-Hant, React-safe interpolation, and an optional en-XA pseudo-locale for catching text that was never extracted.
  • Catalog check: missingKeys(en, target, locale) is plural-aware per language. Arabic needs six plural forms, Japanese one, and Spanish needs _many. Without it, Spanish users see English for counts in the millions. The pulse-i18n check command below runs it in CI.
  • React: <LanguagePicker persist={saveToProfile} /> is a button (flag + short code, so it never crowds a header) that opens a list showing every language with its flag, its English name and its name in its own script. It is keyboard- and screen-reader-accessible, lives in a portal and stays on screen (it flips up near the bottom and lines up with whichever edge fits), and keeps <html lang dir> in step, writes the cookie and calls persist (the ENG-3 profile save). Props: display="code" | "native" | "flag" (what the closed button shows), align, className/style (the button), menuClassName/menuStyle. Theme it with CSS variables on the button or an ancestor: --pulse-lang-fg, --pulse-lang-border, --pulse-lang-trigger-bg, --pulse-lang-menu-bg, --pulse-lang-menu-fg, --pulse-lang-menu-border, --pulse-lang-menu-active, --pulse-lang-menu-muted. Flags are bundled inline (about 12 KB, from the MIT-licensed country-flag-icons), because flag emoji do not render on Windows. <LanguageSelect> (a plain native <select>, no flags), <LanguageMenu> and useLanguage() are the lower-level pieces.
import { createI18n, pickLocale } from "@pulsetechnologies/i18n";
import { LanguagePicker } from "@pulsetechnologies/i18n/react";
import { I18nextProvider, initReactI18next } from "react-i18next";

const { locale } = pickLocale({ user: me?.language, cookie: document.cookie, workspace: org.language, browser: navigator.languages });
const i18n = await createI18n({ resources, locale, use: [initReactI18next], pseudo: import.meta.env.DEV });
// <I18nextProvider i18n={i18n}> … <LanguagePicker persist={(c) => api.patch("/me", { language: c })} />

The core entry has no DOM or React dependency, so it runs on servers (for emails and invoices) and in React Native. The React entry is for the web; React Native gets its own picker in ENG-19.

Develop

npm ci
npm test          # vitest (the picker tests use jsdom)
npm run demo      # http://localhost:5480, a sample invoice in 5 languages + pseudo

Translating a product: the pulse-i18n CLI (ENG-4)

Each product keeps its English catalogs at <dir>/en/<namespace>.json and commits the other languages next to them. Add a pulse-i18n.config.json at the product's root:

{
  "dir": "src/locales",
  "locales": ["es", "fr", "zh-Hans"],
  "glossary": ["PulseVoice", "PiP", "Pulse"],
  "review": ["legal.", "billing:", "e911."],
  "context": "PulseVoice is a business phone system for companies and their staff."
}
  • locales is the set of languages the product ships. Leave it out to ship all 18.
  • glossary lists terms that are never translated, and checks that they come back unchanged.
  • review lists key prefixes ("ns:key" or "key") whose translations a person must sign off: legal, invoice and 911 text.

Commands:

| Command | What it does | |---|---| | npx pulse-i18n check | The CI gate. Exits 1 when, for any language, a string is missing, a translation was made from English that has since changed, a key no longer exists in English, or a {{placeholder}}, $t() or <0> tag, or a glossary term, was dropped or invented. Plural forms are checked per language: Arabic needs 6, Japanese 1, and Spanish, French and Portuguese need _many. Pending reviews show as warnings. | | npx pulse-i18n translate [--locale es,fr] [--batch 60] [--parallel 4] | Fills exactly what check flags, translating up to --parallel languages at once (default 4: about a quarter of the one-at-a-time time), using Claude Opus 5.5 (structured JSON output, fallbacks: "default", a cached prompt prefix). It validates every answer, retries once naming the problem, and reports anything still wrong without writing it. Needs ANTHROPIC_API_KEY (or ant auth login) and npm i -D @anthropic-ai/sdk. | | npx pulse-i18n extract <files…> [--write] [--project tsconfig.json] | Codemod: moves a React/TSX app's English into the catalog. JSX text (joined with text-typed {values} into one message), UI attributes, setErr/notify/confirm-style calls, and sentences with simple inline markup (as <Trans>) become t() calls, with the exact rendered English as the catalog value, so English output can't change. Uses the type checker so a React element is never folded into a string. Computed values with no English in them (list.join(", "), dates) and "Hi " + name chains join the message; <code> content stays a value. Add your own message helpers with "calls": { "run": [1] } (callee → argument positions). Add your components' own text props with "attributes": ["hint", "cta"]. Text in JSX passed as a prop (foot={<Link>Back</Link>}) converts too. With "objectProps": true (or a list of extra names) tables of labels convert as well: label: "All work" becomes t() inside a function, and a getter at module level so it is translated when read; only for apps whose label tables are UI, not data. Re-runs reuse the name t was imported under. Reports what it leaves for a person: module-level constants, data-table labels, values that hold English (plurals), nested markup. Needs "extract": { "import": "src/i18n.ts" } in the config and typescript installed. Dry run unless --write. | | npx pulse-i18n approve <locale> [prefix] | Marks reviewed translations as approved. |

translate keeps <dir>/.pulse-i18n-lock.json (commit it). It records the English each translation was made from, so:

  • when English changes, that string is re-translated;
  • a translation someone edited by hand is never overwritten;
  • translations that existed before the tool are adopted as they are.

A hand edit that breaks a placeholder is redone, because it would fail at runtime.

Product CI:

- run: npx pulse-i18n check

Release

The package is published publicly to npm as @pulsetechnologies/i18n. To release:

  1. Bump version in package.json and merge.
  2. Publish a GitHub release tagged v<version>.

The Publish workflow checks that the tag matches package.json, runs typecheck, tests and build (prepublishOnly), and publishes through npm trusted publishing (OIDC), so no npm token is stored anywhere. npm is retiring token publishing (2FA-bypass tokens stop publishing directly around January 2027).

The very first version (0.1.0) is published by hand, because npm only lets a trusted publisher be configured on a package that already exists. After that, set up npmjs.com → the package → Settings → Trusted publishing → GitHub Actions: pulsetechnologies-ai / pulse-i18n / publish.yml. Then set Publishing access to "Require two-factor authentication and disallow tokens".

Messages: emails, SMS and hosted pages (server side)

For text a server sends to a person (0.8.0). No DOM, no i18next: the same catalogs the apps use.

import { loadMessages } from "@pulsetechnologies/i18n/server";
import { htmlLangAttrs, smsInfo } from "@pulsetechnologies/i18n";

const messages = loadMessages(new URL("./locales", import.meta.url).pathname); // <dir>/<language>/common.json, once at start-up

// Whose language? override > the recipient's own > their organization's > the sending partner's > English.
const { locale, dir, intl, source } = messages.forRecipient({ recipient: user.language, group: org.locale });
const tr = messages.translator(locale);

const subject = tr("invite.subject", { name });           // {{vars}}, plural by `count`, falls back to English then the key
const html = `<html ${htmlLangAttrs(locale)}>…</html>`;  // lang + dir (Arabic and Urdu are right-to-left)

const sms = tr("overdue.sms", { amount });
if (smsInfo(sms).segments > 2) { /* UCS-2 scripts cost several times the segments of English */ }

A language without a catalog in the build is skipped (never English text tagged as that language). Strings a customer can edit in the database, text registered with carriers (SMS opt-in, STOP/HELP) and legal text are not translated by code: keep them English until they are reviewed.

The core (@pulsetechnologies/i18n) and @pulsetechnologies/i18n/server also load with require() (0.8.1: a CommonJS build in dist-cjs), for CommonJS servers such as NestJS. /react and /tools are ESM only.