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

@blax-software/i18n

v0.2.0

Published

Per-page, all-locale translation sources merged into vue-i18n messages, plus a CLI that builds one bundle per locale and fails CI on missing keys.

Readme

Blax Software OSS

@blax-software/i18n

npm Node

Per-page, all-locale translation sources for vue-i18n / Nuxt i18n, plus a CLI that builds one message bundle per locale and fails CI when a key is missing.

The source layout it works on

i18n/
  locales/en.ts         export default { common: { save: 'Save' } }
  locales/de.ts         export default { common: { save: 'Speichern' } }
  locales/pl.ts
  pages/cart.ts         export default { en: {...}, de: {...}, pl: {...} } satisfies TranslationFile
  pages/checkout.ts
  .messages/            generated by `blax-i18n build` (gitignore it)

Base files hold one locale each. Page files hold every locale side by side, so a translator sees en/de/pl of the same screen in one place. The merged result is locales seeded first, then every page deep-merged in path order (last wins).

Install

npm install @blax-software/i18n

Runtime API (@blax-software/i18n)

Framework-agnostic, no dependencies, safe in the browser.

import {
  TranslationMissing, type TranslationFile,
  mergeMessages, fallbackChain, createLazyLoaders,
} from '@blax-software/i18n'

| Export | What it does | | --- | --- | | TranslationMissing | The string '[missing]'. Put it where a translation is still open; blax-i18n check reports every occurrence. | | TranslationFile | { [locale: string]: Record<string, any> } — the page-module shape. Use satisfies TranslationFile. | | mergeMessages(locales, pages, { packages? }) | { [locale]: messages } — seed from locales, deep-merge each page's [locale] slice in order. Inputs are not mutated. Only locales present in locales are emitted. packages (page-shaped objects) merge first, so the app overrides them. | | fallbackChain(locales, defaultLocale = 'en') | vue-i18n fallbackLocale map: { de: ['en'], pl: ['en'], default: ['en'] }. | | createLazyLoaders(loaders) | { loadLocaleMessages(locale), hasLocaleBundle(locale) } over static import() calls of the built bundles. | | checkTranslations(sources, { locales, reference? }) | The check logic on already-loaded data (what the CLI runs). formatReport(report) renders it. |

Eager merge (Vite / Nuxt)

// i18n/messageloader.ts
import { mergeMessages } from '@blax-software/i18n'

const localeMods = import.meta.glob('./locales/*.ts', { eager: true, import: 'default' })
const pageMods = import.meta.glob('./pages/**/*.ts', { eager: true, import: 'default' })

const byPath = (mods: Record<string, any>) => Object.keys(mods).sort().map((p) => mods[p])
const locales = Object.fromEntries(
  Object.keys(localeMods).sort().map((p) => [p.replace(/^.*\/([^/.]+)\.ts$/, '$1'), localeMods[p]]),
)

export default mergeMessages(locales, byPath(pageMods))
// i18n.config.ts
import { fallbackChain } from '@blax-software/i18n'
import messages from './i18n/messageloader'

export default {
  fallbackLocale: fallbackChain(['en', 'de', 'pl']),
  messages,
}

Lazy per-locale bundles

Run blax-i18n build (below), then load only the active locale:

// i18n/lazy-loader.ts
import { createLazyLoaders } from '@blax-software/i18n'

export const { loadLocaleMessages, hasLocaleBundle } = createLazyLoaders({
  de: () => import('./.messages/de.js'),
  pl: () => import('./.messages/pl.js'),
})

Leave the default locale out of the map and import it eagerly, so there is always a complete fallback.

Marking open translations

import { TranslationMissing, type TranslationFile } from '@blax-software/i18n'

export default {
  en: { cart: { title: 'Your cart' } },
  de: { cart: { title: 'Dein Warenkorb' } },
  pl: { cart: { title: TranslationMissing } },
} satisfies TranslationFile

CLI (blax-i18n)

blax-i18n build [--src i18n] [--out <src>/.messages] [--locales en,de,pl] [--default en] [--packages auto|a,b]
blax-i18n check [--src i18n] [--locales en,de,pl] [--default en] [--packages auto|a,b] [--json]
  • build writes <out>/<locale>.js, one export default {...} ESM module per locale. Sources are transpiled with esbuild, so .ts files need no bundler. --default is always emitted, on top of --locales or of every base file when --locales is omitted.
  • --packages merges translations that npm packages register (see below). auto takes every dependency in the app's package.json that registers some; a comma list names them.
  • check walks every key across the listed locales and reports, per locale, keys that are missing and values that still hold [missing]. Exit code 1 when anything is missing; --json prints the report object. Without --default the reference key set is the union of all locales; with it, that locale's keys (surplus keys elsewhere are listed as extra but do not fail the run).
{
  "scripts": {
    "i18n:build": "blax-i18n build --src i18n --out i18n/.messages --locales en,de,pl",
    "i18n:check": "blax-i18n check --src i18n --locales en,de,pl"
  }
}
$ blax-i18n check --src i18n --locales en,de,pl
en: ok (312 keys)
de: ok (312 keys)
pl: 1 missing, 2 [missing] (311 keys)
  pages/account.ts  account.logout  missing
  locales/*  common.cancel  [missing]
  pages/cart.ts  cart.empty  [missing]
i18n check: 3 problems

Translation files are loaded as pure data: relative imports between them resolve, import { TranslationMissing } from '@blax-software/i18n' resolves, any other import yields {}.

Translations shipped by packages

A library that shows text of its own (a connection toast, a validation error) can ship those strings instead of making every app redefine them. It points a blax-i18n field in its package.json at a page-shaped module:

{
  "name": "@blax-software/networking",
  "blax-i18n": "./dist/i18n.js",
  "exports": { "./i18n": { "import": "./dist/i18n.js", "require": "./dist/i18n.cjs" } }
}
// src/i18n.ts in the library
export default {
  en: { websocket: { connectionlost: 'Connection lost. Reconnecting…' } },
  de: { websocket: { connectionlost: 'Verbindung unterbrochen. Verbinde neu…' } },
}

The app opts in with --packages auto (or packages: 'auto' in the Node API). Package strings merge first, so any base file or page can override one. check then also verifies every package key in every checked locale, and accepts a locale the package lacks when the app supplies it. Apps that merge at runtime import the module themselves:

import networking from '@blax-software/networking/i18n'
export default mergeMessages(locales, pages, { packages: [networking] })

Keep keys under one namespace per package (websocket.*) so they cannot collide with app keys.

Node API (@blax-software/i18n/node)

readSourceTree, buildMessages, writeMessages, checkSourceTree, readPackageSources and loadModule are what the CLI calls; use them from a build script (for example a Nuxt hook that regenerates bundles on change). Every one of them takes packages and root.

Development

npm install
npm run build      # tsup → dist/ (ESM + CJS + d.ts, bin/cli.js)
npm run typecheck
npm test           # vitest

License

MIT