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 🙏

© 2025 – Pkg Stats / Ryan Hefner

intlit

v0.1.0

Published

Type-safe i18n formatter with hooks and gettext-style templates.

Readme

Intlit

Elevate your internationalization (i18n) workflow with Intlit: gettext-compatible formatting, simplified plural handling, and first-class TypeScript support.

Features

  • Simple and intuitive API
  • Full TypeScript support for type safety
  • Unified key for singular and plural forms (unlike gettext which often requires separate msgid and msgid_plural)
  • Includes pluralization support (plugins can be optionally added for more features)
  • Easy to integrate with existing projects
  • Inline /* ... */ comments let you differentiate identical source strings without affecting the fallback text
  • Bundled Korean tagged template adds automatic particle selection when locale is set to ko

Installation

npm install intlit
import { Formatter } from "intlit";

Usage

Basic Usage

const formatter = new Formatter({
  locale: "en",
});

const text = formatter.format("Hello World");

console.log(text); // Output: Hello World

You can pass values in the second argument and interpolate them inside the template. When no translation is registered, the original text is used as the fallback.

const formatter = new Formatter({ locale: "en" });

formatter.format("Hello, {name}!", { name: "Kelly" });
// → "Hello, Kelly!"

Supplying Localized Messages

Provide a message catalog to translate strings. The Formatter generics keep the message keys type-safe.

const messages = {
  "Hello, {name}!": "안녕하세요, {name}!",
};

const formatter = new Formatter<typeof messages>({
  locale: "ko",
  messages,
});

formatter.format("Hello, {name}!", { name: "Kelly" });
// → "안녕하세요, Kelly!"

Handling Plurals

Intlit provides support for handling plural forms in different languages, making it easy to create grammatically correct translations.

First, let's see how to set up Formatter instances for English, Korean, and Arabic, including specific message translations for Korean and Arabic. The pluralization capability is available by default.

const formatter = new Formatter({
  locale: "en",
});

// With count = 1 (singular)
console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 1 }),
); // Output: You have 1 file.

// With count = 2 (plural)
console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 2 }),
); // Output: You have 2 files.
const messages = {
  "You have {count} file{count.other:}s{/}.": "{count}개의 파일이 있습니다.",
};

const formatter = new Formatter({
  locale: "ko",
  messages,
});

// With count = 1
console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 1 }),
); // Output: 1개의 파일이 있습니다.
const messages = {
  "You have {count} file{count.other:}s{/}.":
    "{count.zero:}لا يوجد لديك ملفات.{.one:}لديك ملف واحد.{.two:}لديك ملفان.{.few:}لديك {_} ملفات قليلة.{.many:}لديك {_} ملفات كثيرة.{.other:}لديك {_} ملفات.{/}",
};

const formatter = new Formatter({
  locale: "ar",
  messages: messages,
});

// Arabic has multiple plural forms (zero, one, two, few, many, other). 🫢

console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 0 }),
); // Output: لا يوجد لديك ملفات.

console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 1 }),
); // Output: لديك ملف واحد.

console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 2 }),
); // Output: لديك ملفان.

console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 3 }),
); // Output: لديك 3 ملفات قليلة.

console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 11 }),
); // Output: لديك 11 ملفات كثيرة.

console.log(
  formatter.format("You have {count} file{count.other:}s{/}.", { count: 100 }),
); // Output: لديك 100 ملفات.

Gender and Conditional Segments

Hooks in Intlit behave like chained methods. The default hooks include helpers for gendered output and else fallbacks.

const formatter = new Formatter({ locale: "en" });

formatter.format(
  "{user} added {photoCount.one:}a new photo{.other:}{_} new photos{/} to {gender.male:}his{.female:}her{.other:}their{/} stream.",
  { user: "Alex", photoCount: 2, gender: "female" },
);
// → "Alex added 2 new photos to her stream."

Inside nested segments the current value is available through the special _ parameter, so you can reuse the original number (as seen above when printing the plural count).

Adding Context with Inline Comments

Inline block comments are stripped from the rendered output but kept in the key. They are perfect for differentiating identical phrases that need distinct translations.

const messages = {
  "/* 환영 */안녕하세요.": "Welcome!",
  "/* 일반 */안녕하세요.": "Hello!",
};

const formatter = new Formatter({
  locale: "en",
  messages,
});

formatter.format("/* 환영 */안녕하세요.");
// → "Welcome!"

formatter.format("/* 일반 */안녕하세요.");
// → "Hello!"

// Because the comments are ignored at runtime the fallback remains clean.
new Formatter({ locale: "ko" }).format("/* 환영 */안녕하세요.");
// → "안녕하세요."

Integrating a Tagged Template Handler

Need smarter whitespace management or language-specific post-processing? Intlit ships with a particle-aware template handler that is applied automatically when locale is set to ko.

const formatter = new Formatter({ locale: "ko" });

formatter.format(
  `새 소식이 있습니다:\n{count.one:}새 메시지가 하나 생겼어요{.other:}{_}개의 새 메시지가 있어요{/}
`,
  { count: 3 },
);
// → "새 소식이 있습니다:\n3개의 새 메시지가 있어요"

For other locales you can still provide your own taggedTemplate function to massage the final string (e.g. trim indentation, collapse whitespace, or run additional transforms).

Custom Hooks

Extend Intlit by adding application-specific hooks. A hook receives a HookContext object describing the current placeholder and returns the next value that should flow through the chain.

import { Formatter, type Hook } from "intlit";

const upper: Hook = (ctx) => String(ctx.current ?? "").toUpperCase();

const fallback: Hook = (ctx) =>
  ctx.current == null || ctx.current === "" ? "(none)" : ctx.current;

const formatter = new Formatter({
  locale: "en",
  hooks: { upper, fallback },
});

formatter.format("Hello, {name.upper.fallback}!", { name: "alex" });
// → "Hello, ALEX!"

Hooks receive:

  • ctx.original: The value that came directly from Formatter#format.
  • ctx.current: The value produced so far in the chain. Whatever the hook returns becomes the new ctx.current.
  • ctx.locale: Locale that was supplied when constructing the formatter.
  • args: Arguments passed from the template method such as the currency part in {price.number:currency}. Arguments can include nested templates.

To share state across hook invocations, keep data in a WeakMap<HookContext, …>. The built-in gender and plural hooks follow this pattern to remember which branch has already matched.

Combine custom hooks with the built-in plural helpers to cover complex cases without branching in your code.

Formatter Options

new Formatter(options) accepts the following configuration:

  • locale: Locale passed to plural rules. Defaults to en.
  • messages: A record of source text → translated text pairs.
  • hooks: Extra hook implementations that augment or replace the defaults.
  • taggedTemplate: Custom renderer for the template literal output.

Instantiate different Formatter objects per locale, or swap the messages object dynamically when building multi-locale applications.