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

@ccssmnn/intl

v0.1.1

Published

Type-safe Unicode MessageFormat 2.0 utilities with runtime formatting and React bindings.

Readme

@ccssmnn/intl

A internationalization utility that does not require extraction/codegen, is super typesafe and works great with coding agents. Basically just type and react utilities built on top of the official Unicode MessageFormat 2.0 formatter.

Features

  • ✅ No code generation / message extraction: author messages and use them immediately.
  • ✅ Unicode MessageFormat 2 syntax via messageformat (the only dependency).
  • ✅ React utilities for providers, hooks, and components with markup rendering.
  • ✅ Pairs seamlessly with coding agents—messages due to familiar syntax and friendly type errors.
  • ✅ Works equally well on the server e.g. notifications and emails.

Supported MessageFormat syntax

Runtime support: The library delegates parsing and formatting to messageformat@4 for full MessageFormat 2.0 support.

Type-level support: The TypeScript types currently validate a focused subset of the specification:

✅ Parameters:

  • {$param} - String parameters
  • {$param :number} - Number parameters
  • {$param :datetime} - Date/DateTime parameters
  • {$param :date} - Date parameters
  • {$param :time} - Time parameters

✅ Markup:

  • {#tag}content{/tag} - Simple markup spans (no attributes)
  • Nested markup tags
  • Type-safe component mapping in React

✅ Selection/Pluralization:

  • .input {$var} .match $var patterns
  • one/* plural rules
  • Custom selection branches

❌ Not type-validated (but work at runtime):

  • Parameter options like style=currency
  • Markup tag attributes
  • Complex function calls
  • Other MessageFormat 2.0 features

The type system focuses on the most common i18n patterns while letting you use advanced features when needed.

Installation

pnpm add @ccssmnn/intl

Peer Dependencies:

  • React 17+ (only needed if using React integration)

Requirements:

  • Node.js 16+ or modern browser with ES2020 support

Quick Start

import { messages, createIntl } from "@ccssmnn/intl"

// 1. Define your messages
const copy = messages({
	greeting: "Hello {$name}!",
	count: "You have {$num :number} items",
})

// 2. Create translator
const t = createIntl(copy, "en")

// 3. Use it
t("greeting", { name: "World" }) // "Hello World!"
t("count", { num: 42 }) // "You have 42 items"

Usage

MessageFormat 2.0 Examples

Common patterns with type validation:

const messages = messages({
	// Basic interpolation
	greeting: "Hello {$name}!",

	// Typed parameters (type-validated)
	price: "Price: {$amount :number}",
	date: "Today is {$today :datetime}",
	time: "Meeting at {$when :time}",

	// Pluralization
	items:
		".input {$count :number} .match $count one {{one item}} * {{{$count} items}}",

	// Selection
	status:
		".input {$role} .match $role admin {{Welcome admin}} user {{Hello user}} * {{Hi there}}",

	// Markup tags
	welcome:
		"Welcome {#bold}{$name}{/bold}! Click {#link}here{/link} to continue",

	// Complex nested structure
	cart: `.input {$count :number} {$hasDiscount}
         .match $count $hasDiscount
         0 true {{Your cart is empty, but you have a discount!}}
         0 * {{Your cart is empty}}
         one true {{You have one item with discount applied}}
         one * {{You have one item}}
         * true {{You have {$count} items with discount applied}}
         * * {{You have {$count} items}}`,
})

Core API

import { messages, createIntl, translate } from "@ccssmnn/intl"

// annotates `base` with the messages types
let base = messages({
	greeting: "Hello {$name}!",
	count: "You have {$num :number} items",
})

// guarantees on a type level that `german` has the same message signature as `base`
let german = translate(base, {
	greeting: "Hallo {$name}!",
	count: "Du hast {$num :number} Elemente",
})

// create typesafe message consumption utility!
let t = createIntl(german, "de")

t("greeting", { name: "Carl" })
// → "Hallo Carl!"

All helpers are strongly typed. For example, t refuses calls without the name parameter, and translate enforces that translations keep the same params/markup as the base copy.

Error Handling

The library provides graceful error handling for malformed messages:

const problematic = messages({
	valid: "Hello {$name}!",
	broken: "Invalid syntax {{{", // Malformed message
})

const t = createIntl(problematic, "en")

t("valid", { name: "Carl" }) // "Hello Carl!"
t("broken") // "❌: broken" (fallback with key name)
// Console shows table with error details

When MessageFormat compilation fails, the library:

  1. Logs detailed error information to console
  2. Creates fallback messages showing the problematic key
  3. Continues execution without throwing

React integration

import { createIntlForReact, messages } from "@ccssmnn/intl"

const copy = messages({
	signIn: "Hey {$name}! {#link}Sign in here{/link}",
})

const { IntlProvider, useIntl, T } = createIntlForReact(copy, "en")

function Toolbar() {
	const t = useIntl()
	return <p>{t("signIn", { name: "Carl" })}</p>
}

function Entry() {
	return (
		<IntlProvider>
			<Toolbar />
			<T
				k="signIn"
				params={{ name: "Carl" }}
				components={{ link: ({ children }) => <a href="/login">{children}</a> }}
			/>
		</IntlProvider>
	)
}

The hook exposes the same typed t function as the core API, so TypeScript will flag missing params/markup right in React components. The <T> component uses formatToParts to let you supply React components for markup tags.

API Reference

Core Functions

  • messages(obj) - Create typed message catalog from object literal
  • createIntl(messages, locale) - Create translation function for rendering strings
  • createIntlToParts(messages, locale) - Create formatter for manual rendering (useful for building UI framework adapters)
  • translate(base, translation) - Create type-safe translation that preserves structure
  • merge(...catalogs) - Combine multiple message catalogs (prevents key conflicts)
  • check(base, ...parts) - Verify translation coverage and merge parts

React Integration

  • createIntlForReact(messages, locale) - Returns { IntlProvider, useIntl, T, useLocale }
  • IntlProvider - Context provider for messages and locale
  • useIntl() - Hook returning translation function t(key, params?, components?)
  • T - Component for rendering messages with markup: <T k="key" params={{}} components={{}} />
  • useLocale() - Hook returning current locale string

Usage Guide

Organization workflow

  • Organize message catalogues per feature (e.g. ~/intl/messages.todos.ts) to keep related messages colocated and make them easier to work with.
  • Assemble the global catalogue in ~/intl/messages.ts by merging feature slices with merge(...) and validating each locale against the base via check(...).
  • Instantiate React bindings once in shared/intl/setup.ts using createIntlForReact(messagesEn, "en"); export the resulting IntlProvider, useIntl, T, and useLocale.
  • Reuse the framework-agnostic helpers (createIntl, createIntlToParts) for server tasks such as localized push notifications.

Benefits of this approach:

  • Smaller, focused message modules are easier to navigate and modify
  • Related messages stay close to the features that use them
  • Works better with coding agents that can understand and modify specific feature contexts
  • Prevents merge conflicts when multiple features add messages simultaneously

Typical layout

shared/
  intl/
    setup.ts            # configured React exports
    messages.ts         # merges base + locale slices
    messages.todos.ts   # per-feature catalogue (base + translate)
    messages.*.ts       # additional slices
server/
  notifications/
    send-push.ts        # imports createIntl() for localized payloads

Feature message slice

// ~/intl/messages.todos.ts
import { messages, translate } from "@ccssmnn/intl"

export { baseTodoMessages, deTodoMessages }

let baseTodoMessages = messages({
	"todos.header": "Tasks for {$name}",
	"todos.remaining": "You have {#strong}{$count :number}{/strong} remaining",
})

let deTodoMessages = translate(baseTodoMessages, {
	"todos.header": "Aufgaben für {$name}",
	"todos.remaining": "Du hast noch {#strong}{$count :number}{/strong} übrig",
})

Assembling the global catalog

// ~/intl/messages.ts
import { merge, check } from "@ccssmnn/intl"
import { baseTodoMessages, deTodoMessages } from "./messages.todos"
// import other feature slices...

export { messagesEn, messagesDe }

let messagesEn = merge(
	baseTodoMessages
	/* other base slices */
)

let messagesDe = check(
	messagesEn,
	deTodoMessages
	/* other locale slices */
)

React consumption

// ~/intl/setup.ts
import { createIntlForReact } from "@ccssmnn/intl/react"
import { messagesEn } from "./messages"

// create the provider with the default messages and locale
export const { IntlProvider, useIntl, T, useLocale } = createIntlForReact(
	messagesEn,
	"en"
)

// ui/components/todo-header.tsx
import { useIntl, T } from "~/intl/setup"

export function TodoHeader({
	username,
	remaining,
}: {
	username: string
	remaining: number
}) {
	const t = useIntl()

	return (
		<header>
			<h1>{t("todos.header", { name: username })}</h1>
			<T
				k="todos.remaining"
				params={{ count: remaining }}
				components={{
					strong({ children }) {
						return <strong>{children}</strong>
					},
				}}
			/>
		</header>
	)
}

Locale switching

// app/root.tsx
import { IntlProvider } from "~/intl/setup"
import { messagesEn, messagesDe } from "~/intl/messages"

const catalogs = {
	en: messagesEn,
	de: messagesDe,
}

export function App({ locale }: { locale: keyof typeof catalogs }) {
	return (
		<IntlProvider messages={catalogs[locale]} locale={locale}>
			{/* ... */}
		</IntlProvider>
	)
}

Server Side

// server/notifications/send-push.ts
import { createIntl } from "@ccssmnn/intl"
import { messagesEn, messagesDe } from "~/intl/messages"

const catalogs = {
	en: messagesEn,
	de: messagesDe,
}

export function buildTodoNotification(
	locale: keyof typeof catalogs,
	taskTitle: string,
	dueAt: Date
) {
	const t = createIntl(catalogs[locale], locale)
	return {
		title: t("notifications.todo.title", { title: taskTitle }),
		body: t("notifications.todo.body", { dueAt }),
	}
}

Workflow tips

  • Add new message keys to the base slice first; compilation fails until translations match, keeping locales synchronised.
  • Use <T> only when markup tags are needed; useIntl() suffices for plain strings.
  • Run pnpm test (with type-checking enabled) after modifying catalogues to catch ICU or markup mismatches immediately.
  • When extracting into a shared library, export the same primitives (messages, merge, translate, check, createIntl, createIntlForReact) so both client and server consumers retain the ergonomics.

Development

  • pnpm install
  • pnpm test – run the Vitest suites (core and React).
  • pnpm build – emit ESM + d.ts output into dist/.

License

MIT