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

@worldware/msg

v0.7.1

Published

Message localization tooling

Readme

msg

A TypeScript library for managing internationalization (i18n) messages with support for message formatting, translation management, and localization workflows.

Overview

msg provides a structured approach to managing translatable messages in your application. It integrates with MessageFormat 2 (MF2) for advanced message formatting and supports:

  • Message Management: Organize messages into resources with keys and values
  • Translation Loading: Load translations from external sources via customizable loaders
  • Pseudo Localization: Request a pseudolocalized resource for UI testing via getTranslation(pseudoLocale)
  • Message Formatting: Format messages with parameters using MessageFormat 2 (MF2) syntax
  • Attributes & Notes: Attach metadata (language, direction, do-not-translate flags) and notes to messages
  • Project Configuration: Configure projects with locale settings and translation loaders

Installation

npm install @worldware/msg

Core Concepts

MsgProject

A project configuration that defines:

  • Project name and version
  • Source and target locales (with language fallback chains)
  • Pseudo locale (for pseudolocalized output via getTranslation)
  • A translation loader function

MsgResource

A collection of messages (extends Map<string, MsgMessage>) representing a resource bundle. Each resource has:

  • A title/name
  • Attributes (language, text direction, do-not-translate flag)
  • Notes (descriptions, context, etc.)
  • Messages indexed by key

MsgMessage

An individual message with:

  • A key (identifier)
  • A value (the message text, supports MessageFormat 2 (MF2) syntax)
  • Attributes (lang, dir, dnt)
  • Notes
  • Formatting methods using MessageFormat 2

Usage

Basic Setup

The following example matches the ES module output of the msg-cli create project command—a typical project file that loads translations from JSON under a translations directory:

import { MsgProject } from '@worldware/msg';

const TRANSLATION_IMPORT_PATH = '../l10n/translations';
const loader = async (project, title, language) => {
  const path = `${TRANSLATION_IMPORT_PATH}/${project}/${language}/${title}.json`;
  try {
    const module = await import(path, { with: { type: 'json' } });
    return module.default;
  } catch (error) {
    console.warn(`Translations for locale ${language} could not be loaded.`, error);
    return {
      title,
      attributes: { lang: language, dir: '' },
      notes: [],
      messages: []
    };
  }
};

export default MsgProject.create({
  project: { name: 'my-app', version: 1 },
  locales: {
    sourceLocale: 'en',
    pseudoLocale: 'en-XA',
    targetLocales: {
      'en': ['en'],
      'es': ['es'],
      'fr': ['fr'],
      'fr-CA': ['fr', 'fr-CA']
    }
  },
  loader
});

When using this in your app, import the default export as your project and pass it to MsgResource.create (see below).

Creating a Resource

// Create a resource with messages
const resource = MsgResource.create({
  title: 'CommonMessages',
  attributes: {
    lang: 'en',
    dir: 'ltr'
  },
  messages: [
    {
      key: 'greeting',
      value: 'Hello, {$name}!'
    },
    {
      key: 'welcome',
      value: 'Welcome to our application'
    }
  ]
}, project);

// Or add messages programmatically
resource.add('goodbye', 'Goodbye, {$name}!', {
  lang: 'en',
  dir: 'ltr'
});

Formatting Messages

// Get a message and format it
const greetingMsg = resource.get('greeting');
const formatted = greetingMsg?.format({ name: 'Alice' });
// Result: "Hello, Alice!"

Loading Translations

// Load a translation for a specific language
const spanishResource = await resource.getTranslation('es');

// The translated resource will have Spanish messages where available,
// falling back to the source messages for missing translations

Language fallbacks and translation layering

The project's targetLocales maps each requested locale to a fallback chain: an array of locale codes ordered from least specific to most specific (e.g. base language first, then region-specific). For example, 'zh-HK': ['zh', 'zh-Hant', 'zh-HK'] means that when you request zh-HK, the chain is first zh, then zh-Hant, then zh-HK. You can get the chain for any locale with project.getTargetLocale(locale).

When you call resource.getTranslation(locale):

  1. The source resource (the resource you called it on) is the base.
  2. For each locale in that locale's chain, the project loader is called to load that locale's translation data.
  3. Each loaded dataset is layered onto the current result: messages in the new data add or override by key; keys missing in the new layer keep the value from the previous layer.
  4. The final resource is the result after all layers have been applied.

So for getTranslation('zh-HK') with chain ['zh', 'zh-Hant', 'zh-HK'], you get: source → then zh overlay → then zh-Hant overlay → then zh-HK overlay. Later entries in the chain override earlier ones for the same key; missing keys fall back to the previous layer (and ultimately to the source).

Pseudo Localization

When getTranslation is called with the project's pseudoLocale (e.g. en-XA), it returns a new resource with pseudolocalized message values—useful for testing UI layout and finding hardcoded strings without loading translation files:

// Request pseudolocalized messages (project locales.pseudoLocale is 'en-XA')
const pseudoResource = await resource.getTranslation('en-XA');

// Message values are transformed: "Hello, {$name}!" → "Ħḗḗŀŀǿǿ, {$name}!"
// Variables and MF2 syntax are preserved; only literal text is pseudolocalized
const greeting = pseudoResource.get('greeting')?.format({ name: 'Alice' });
// Result: "Ħḗḗŀŀǿǿ, Alice!"

Working with Attributes and Notes

// Add notes to messages
resource.add('complex-message', 'You have {$count} items', {
  lang: 'en',
  dir: 'ltr',
  dnt: false // do-not-translate flag
}, [
  {
    type: 'DESCRIPTION',
    content: 'This message appears on the welcome screen'
  },
  {
    type: 'CONTEXT',
    content: 'Used when user first logs in'
  }
]);

// Access attributes
const message = resource.get('complex-message');
console.log(message?.attributes.lang); // 'en'
console.log(message?.attributes.dir);  // 'ltr'
console.log(message?.attributes.dnt);  // false

Serialization

// Convert resource to JSON
const json = resource.toJSON();
// or without notes
const jsonWithoutNotes = resource.toJSON(true);

// Get data object
const data = resource.getData();

// Message objects in the output only include `attributes` when they differ from
// the resource's attributes, keeping the serialized data compact

API Reference

MsgProject

Static Methods:

  • create(data: MsgProjectData): MsgProject - Create a new project instance

Properties:

  • project: MsgProjectSettings - Project name and version
  • locales: MsgLocalesSettings - Locale configuration
  • loader: MsgTranslationLoader - Translation loader function

Methods:

  • getTargetLocale(locale: string): string[] | undefined - Returns the language fallback chain (array of locale codes) for the specified locale, or undefined if the locale is not configured in targetLocales

MsgResource

Static Methods:

  • create(data: MsgResourceData, project: MsgProject): MsgResource - Create a new resource

Methods:

  • add(key: string, value: string, attributes?: MsgAttributes, notes?: MsgNote[]): MsgResource - Add a message
  • translate(data: MsgResourceData): MsgResource - Create a translated version
  • getTranslation(lang: string): Promise<MsgResource> - Load and apply translations. When lang matches the project's pseudoLocale, returns a resource with pseudolocalized message values instead of loading from the loader.
  • getProject(): MsgProject - Returns the project instance associated with the resource
  • getData(stripNotes?: boolean): MsgResourceData - Get resource data. Message objects in the output omit attributes when they match the resource's attributes (to avoid redundancy)
  • toJSON(stripNotes?: boolean): string - Serialize to JSON

Properties:

  • title: string - Resource title
  • attributes: MsgAttributes - Resource attributes
  • notes: MsgNote[] - Resource notes

MsgMessage

Static Methods:

  • create(data: MsgMessageData): MsgMessage - Create a new message

Methods:

  • format(data: Record<string, any>, options?: MessageFormatOptions): string - Format the message
  • formatToParts(data: Record<string, any>, options?: MessageFormatOptions): MessagePart[] - Format to parts
  • addNote(note: MsgNote): void - Add a note
  • getData(stripNotes?: boolean): MsgMessageData - Get message data
  • toJSON(stripNotes?: boolean): string - Serialize to JSON

Properties:

  • key: string - Message key
  • value: string - Message value
  • attributes: MsgAttributes - Message attributes (lang, dir, dnt)
  • notes: MsgNote[] - Message notes

Development

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run coverage

# Build the project
npm run build

License

See LICENSE file for details.