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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@f-fjs/intl-messageformat

v8.2.3

Published

Formats ICU Message strings with number, date, plural, and select placeholders to create localized messages.

Downloads

8

Readme

Intl MessageFormat

Formats ICU Message strings with number, date, plural, and select placeholders to create localized messages.

npm Version

@f-fjs/intl-messageformat minzipped size @f-fjs/intl-messageformat/core minzipped size

Overview

Goals

This package aims to provide a way for you to manage and format your JavaScript app's string messages into localized strings for people using your app. You can use this package in the browser and on the server via Node.js.

This implementation is based on the Strawman proposal, but there are a few places this implementation diverges.

Note: This IntlMessageFormat API may change to stay in sync with ECMA-402, but this package will follow semver.

How It Works

Messages are provided into the constructor as a String message, or a pre-parsed AST object.

const msg = new IntlMessageFormat(message, locales, [formats], [opts]);

The string message is parsed, then stored internally in a compiled form that is optimized for the format() method to produce the formatted string for displaying to the user.

const output = msg.format(values);

Common Usage Example

A very common example is formatting messages that have numbers with plural labels. With this package you can make sure that the string is properly formatted for a person's locale, e.g.:

const MESSAGES = {
  'en-US': {
    NUM_PHOTOS:
      'You have {numPhotos, plural, ' +
      '=0 {no photos.}' +
      '=1 {one photo.}' +
      'other {# photos.}}',
  },

  'es-MX': {
    NUM_PHOTOS:
      'Usted {numPhotos, plural, ' +
      '=0 {no tiene fotos.}' +
      '=1 {tiene una foto.}' +
      'other {tiene # fotos.}}',
  },
};

let output;

const enNumPhotos = new IntlMessageFormat(
  MESSAGES['en-US'].NUM_PHOTOS,
  'en-US'
);
output = enNumPhotos.format({numPhotos: 1000});
console.log(output); // => "You have 1,000 photos."

const esNumPhotos = new IntlMessageFormat(
  MESSAGES['es-MX'].NUM_PHOTOS,
  'es-MX'
);
output = esNumPhotos.format({numPhotos: 1000});
console.log(output); // => "Usted tiene 1,000 fotos."

Message Syntax

The message syntax that this package uses is not proprietary, in fact it's a common standard message syntax that works across programming languages and one that professional translators are familiar with. This package uses the ICU Message syntax and works for all CLDR languages which have pluralization rules defined.

Features

  • Uses industry standards: ICU Message syntax and CLDR locale data.

  • Supports plural, select, and selectordinal message arguments.

  • Formats numbers and dates/times in messages using Intl.NumberFormat and Intl.DateTimeFormat, respectively.

  • Optimized for repeated calls to an IntlMessageFormat instance's format() method.

  • Supports defining custom format styles/options.

  • Supports escape sequences for message syntax chars, e.g.: "'{foo}'" will output: "{foo}" in the formatted output instead of interpreting it as a foo argument.

Usage

Modern Intl Dependency

This package assumes that the Intl global object exists in the runtime. Intl is present in all modern browsers (IE11+) and Node (with full ICU). The Intl methods we rely on are:

  1. Intl.NumberFormat for number formatting (can be polyfilled using Intl.js)
  2. Intl.DateTimeFormat for date time formatting (can be polyfilled using Intl.js)
  3. Intl.PluralRules for plural/ordinal formatting (can be polyfilled using @f-fjs/intl-pluralrules)

Loading Intl MessageFormat in a browser

<script src="@f-fjs/intl-messageformat/@f-fjs/intl-messageformat.min.js"></script>

Loading Intl MessageFormat in Node.js

Either do:

import IntlMessageFormat from '@f-fjs/intl-messageformat';
const IntlMessageFormat = require('@f-fjs/intl-messageformat').default;

NOTE: Your Node has to include full ICU

Public API

IntlMessageFormat Constructor

To create a message to format, use the IntlMessageFormat constructor. The constructor takes three parameters:

  • message - {String | AST} - String message (or pre-parsed AST) that serves as formatting pattern.

  • locales - {String | String[]} - A string with a BCP 47 language tag, or an array of such strings. If you do not provide a locale, the default locale will be used. When an array of locales is provided, each item and its ancestor locales are checked and the first one with registered locale data is returned. See: Locale Resolution for more details.

  • [formats] - {Object} - Optional object with user defined options for format styles.

  • [opts] - { formatters?: Formatters }: Optional options.

    • formatters: Map containing memoized formatters for performance.
const msg = new IntlMessageFormat('My name is {name}.', 'en-US');

Locale Resolution

IntlMessageFormat uses Intl.NumberFormat.supportedLocalesOf() to determine which locale data to use based on the locales value passed to the constructor. The result of this resolution process can be determined by call the resolvedOptions() prototype method.

resolvedOptions() Method

This method returns an object with the options values that were resolved during instance creation. It currently only contains a locale property; here's an example:

const msg = new IntlMessageFormat('', 'en-us');
console.log(msg.resolvedOptions().locale); // => "en-US"

Notice how the specified locale was the all lower-case value: "en-us", but it was resolved and normalized to: "en-US".

format(values) Method

Once the message is created, formatting the message is done by calling the format() method on the instance and passing a collection of values:

const output = msg.format({name: 'Eric'});
console.log(output); // => "My name is Eric."

Note: A value must be supplied for every argument in the message pattern the instance was constructed with.

Rich Text support

const mf = new IntlMessageFormat('hello <b>world</b>', 'en');
mf.format({b: str => <span>{str}</span>});
// returns ['hello ', React element rendered as <span>world</span>]

We support embedded XML tag in the message, e.g this is a <b>strong</b> tag. This is not meant to be a full-fledged method to embed HTML, but rather to tag specific text chunk so translation can be more contextual. Therefore, the following restrictions apply:

  1. Any attributes on the HTML tag are also ignored.
  2. Self-closing tags are treated as string literal and not supported, please use regular ICU placeholder like {placeholder}.
  3. All tags specified must have corresponding values and will throw error if it's missing, e.g: new IntlMessageFormat("a<b>strong</b>").format({ b: (...chunks) => <strong>chunks</strong> }).
  4. XML/HTML tags are escaped using apostrophe just like other ICU constructs. In order to escape you can do things like:
new IntlMessageFormat("I '<'3 cats").format(); // "I <3 cats"
new IntlMessageFormat("raw '<b>HTML</b>'").format(); // "raw <b>HTML</b>"
new IntlMessageFormat(
  "raw '<b>HTML</b>' with '<a>'{placeholder}'</a>'"
).format({placeholder: 'some word'}); // "raw <b>HTML</b> with <a>some word</a>"
  1. Embedded valid HTML tag is a bit of a grey area right now since we're not supporting the full HTML/XHTML/XML spec.

getAst Method

Return the underlying AST for the compiled message.

User Defined Formats

Define custom format styles is useful you need supply a set of options to the underlying formatter; e.g., outputting a number in USD:

const msg = new IntlMessageFormat(
  'The price is: {price, number, USD}',
  'en-US',
  {
    number: {
      USD: {
        style: 'currency',
        currency: 'USD',
      },
    },
  }
);

const output = msg.format({price: 100});
console.log(output); // => "The price is: $100.00"

In this example, we're defining a USD number format style which is passed to the underlying Intl.NumberFormat instance as its options.

Advanced Usage

Passing in AST

You can pass in pre-parsed AST to IntlMessageFormat like this:

import IntlMessageFormat from '@f-fjs/intl-messageformat';
new IntlMessageFormat('hello').format(); // prints out hello

// is equivalent to

import IntlMessageFormat from '@f-fjs/intl-messageformat/core';
import parser from '@f-fjs/intl-messageformat-parser';
new IntlMessageFormat(parser.parse('hello')).format(); // prints out hello

This helps performance for cases like SSR or preload/precompilation-supported platforms since AST can be cached.

If your messages are all in ASTs, you can alias @f-fjs/intl-messageformat-parser to {default: undefined} to save some bytes during bundling.

Formatters

For complex messages, initializing Intl.* constructors can be expensive. Therefore, we allow user to pass in formatters to provide memoized instances of these Intl objects. This opts combines with passing in AST and @f-fjs/intl-format-cache can speed things up by 30x per the benchmark down below.

For example:

import IntlMessageFormat from '@f-fjs/intl-messageformat';
import memoizeIntlConstructor from '@f-fjs/intl-format-cache';
const formatters = {
  getNumberFormat: memoizeIntlConstructor(Intl.NumberFormat),
  getDateTimeFormat: memoizeIntlConstructor(Intl.DateTimeFormat),
  getPluralRules: memoizeIntlConstructor(Intl.PluralRules),
};
new IntlMessageFormat('hello {number, number}', 'en', undefined, {
  formatters,
}).format({number: 3}); // prints out `hello, 3`

Examples

Plural Label

This example shows how to use the ICU Message syntax to define a message that has a plural label; e.g., "You have 10 photos":

You have {numPhotos, plural,
    =0 {no photos.}
    =1 {one photo.}
    other {# photos.}
}
const MESSAGES = {
    photos: '...', // String from code block above.
    ...
};

const msg = new IntlMessageFormat(MESSAGES.photos, 'en-US');

console.log(msg.format({numPhotos: 0}));    // => "You have no photos."
console.log(msg.format({numPhotos: 1}));    // => "You have one photo."
console.log(msg.format({numPhotos: 1000})); // => "You have 1,000 photos."

Note: how when numPhotos was 1000, the number is formatted with the correct thousands separator.

Benchmark

format_cached_complex_msg x 539,674 ops/sec ±1.87% (87 runs sampled)
format_cached_string_msg x 99,311,640 ops/sec ±2.15% (87 runs sampled)
new_complex_msg_preparsed x 1,490 ops/sec ±8.37% (54 runs sampled)
new_complex_msg x 836 ops/sec ±31.96% (67 runs sampled)
new_string_msg x 27,752 ops/sec ±8.25% (65 runs sampled)
complex msg format x 799 ops/sec ±9.38% (55 runs sampled)
complex msg w/ formatters format x 1,878 ops/sec ±16.63% (64 runs sampled)
complex preparsed msg w/ formatters format x 26,482 ops/sec ±2.55% (84 runs sampled)