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

@ai-translate/message-formats

v0.2.0

Published

Message format adapters for ai-translate: ICU MessageFormat plural and select parity, i18next interpolation and nesting, and CLDR plural key strategies.

Readme

@ai-translate/message-formats

Message format adapters for ai-translate. A message format decides how a single string encodes variables, markup, and plural selection — and therefore what "the translation preserved the source's structure" means for that string.

Without one, every message is compared as plain text with {token}-style placeholder parity. That is right for a marketing headline and wrong for {count, plural, one {# item} other {# items}}.

Install

npm install @ai-translate/message-formats

ICU MessageFormat

For next-intl, react-intl, and anything else that parses messages with ICU.

import { createLocalizedJsonDocument } from "@ai-translate/fs-json";
import { icuMessageFormat } from "@ai-translate/message-formats";

createLocalizedJsonDocument({
  messageFormat: icuMessageFormat,
  rootDir: "messages",
  sourceLocale: "en",
  unitId: "messages",
});

Parity is checked structurally rather than textually:

  • every argument in the source is present in the target, with the same type
  • tags (<b>…</b>) match in name and nesting
  • plural and select branches match the target locale's own CLDR categories, not the source's

That last point is what makes ICU work across locales. English {count, plural, one {…} other {…}} translated into Polish is expected to gain few and many; a Polish translation that keeps only two branches is the error, and a Japanese translation that keeps only other is correct.

i18next

For i18next, react-i18next, and next-i18next.

import { createNamespaceJsonCatalog } from "@ai-translate/fs-json";
import { i18nextMessageFormat, i18nextPluralKeys } from "@ai-translate/message-formats";

createNamespaceJsonCatalog({
  messageFormat: i18nextMessageFormat,
  plurals: i18nextPluralKeys,
  rootDir: "public/locales",
  sourceLocale: "en",
});

Two separate concerns, because i18next splits them:

i18nextMessageFormat validates a single message — {{variable}} interpolation, HTML and indexed tags, and $t(namespace:key) nesting. Keys inside $t() are references, not prose, so a translated key is reported rather than accepted.

i18nextPluralKeys describes how plurals are spelled across sibling keys (items_one, items_other). Pass it as plurals and target files gain the forms their locale requires, seeded from the nearest source form and then translated like any other new entry:

// public/locales/en/inventory.json          // public/locales/pl/inventory.json
{                                            {
  "items_one":   "{{count}} item",             "items_one":   "…",
  "items_other": "{{count}} items"             "items_few":   "…",
}                                              "items_many":  "…",
                                               "items_other": "…"
                                             }

Forms the source declares are always kept, even where the target locale has no grammatical use for them. Japanese needs only other, but deleting items_one would leave the English source with a pointer the Japanese file lacks. Surplus keys are inert at runtime; absent ones are bugs. The same rule preserves i18next's _zero, which is a deliberate extra form outside CLDR.

Writing your own

import type { MessageFormat } from "@ai-translate/core/message-format";

export const myFormat: MessageFormat = {
  id: "my-format",
  tokenize: (value) => [{ raw: value, type: "text" }],
  validateParity: ({ locale, sourceText, targetText }) => [],
};

Pass it to a catalog as messageFormat and it registers itself. Ids must be unique across a config, and they are part of the validation cache key, so changing what an id means invalidates the translations validated under it.

License

MIT