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

say-dictionary

v1.3.0

Published

URL-based i18n with configurable languages and CLI for extracting translation keys

Readme

say-dictionary

URL-based i18n with configurable languages and a CLI for extracting translation keys.

Why?

The dictionary format is designed to be LLM-friendly. Just hand your dictionary.json to an AI and ask it to translate — done. No complex tooling, no translation service integrations, no manual key mapping.

Installation

npm install say-dictionary
# or
pnpm add say-dictionary

Usage

1. Initialize (once in your entry point)

// root.tsx or app entry
import { init } from 'say-dictionary';
import dictionary from './dictionary.json';

init(dictionary);

2. Use anywhere

import { say, getLanguage, setLanguage } from 'say-dictionary';

// Get translated text
say("Order Now"); // Returns "Order Now" or "Panta núna" based on URL

// ICU formatting (only when vars are provided)
say("You have {count, plural, one {# pizza} other {# pizzas}}", {
  count: 2,
});

// Get current language from URL (null if no language prefix)
getLanguage(); // "is" or null

// Navigate to different language
setLanguage('is'); // Redirects to /is/current-path

Dictionary Format

{
  "Order Now": { "en": "Order Now", "is": "Panta núna" },
  "Welcome": { "en": "Welcome", "is": "Velkomin" },
  "You have {count, plural, one {# pizza} other {# pizzas}}": {
    "is": "Þú átt {count, plural, one {# pizzur} other {# pizzur}}"
  }
}

Languages are automatically detected from the dictionary keys. ICU message formatting is opt-in and only runs when you pass variables to say(key, vars). If a translation is missing for the current language, the key itself is treated as the ICU message.

Example:

say(
  "Today is the {day, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} of March",
  { day: 3 }
);
// "Today is the 3rd of March"

say("Event on {date, date, ::MMMM d}", { date: new Date("2026-03-02") });
// "Event on March 2"

URL Structure

The language is detected from the first path segment:

  • /is/about → Icelandic (say() returns translation)
  • /about → No language prefix (say() returns the key itself)

SSR (Next.js, Remix, etc.)

For SSR frameworks, use ssrLang() to prevent hydration mismatch:

// app/[lang]/page.tsx
export default async function Page({ params }) {
  const { lang } = await params;
  return <Home lang={lang} />;
}

// app/page.tsx
"use client";
import { ssrLang, say } from 'say-dictionary';

export default function Home({ lang }: { lang?: string }) {
  ssrLang(lang ?? null);
  return <h1>{say("Hello")}</h1>;
}

This is optional — only needed for SSR. Client-side apps (Vite, CRA) work without it.

CLI

Extract translation keys from your source files:

npx say-dictionary extract -l en,is -i ./app -o ./dictionary.json

Options:

  • -l, --lang - Comma-separated list of languages
  • -i, --in - Source directory to scan
  • -o, --out - Output dictionary file

Output dictionary.json:

{
  "Order Now": { "en": "Order Now", "is": "" },
  "Welcome": { "en": "Welcome", "is": "" }
}

The first language (en) gets the key as its value. Hand this to an LLM to fill in the translations.

License

MIT