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

@weccoframework/i18n

v0.4.0

Published

A simple, minimalistic i18n framework for use in browser

Readme

i18n

CI Status Releases Vulerabilities Dependencies

A simple localization framework for web applications.

Installation

i18n is available via npm as @weccoframework/i18n.

Usage

i18n uses messages identified by message keys. For every piece of text you wish to localize, you add a call to m("message.key") which will be replaced by the actual message in the localized version.

i18n also allows you to use placeholders in a message to be replaced with actual content (think: mini templates) as well as plural support. These placeholders may be formatted using a named Formatter (i.e. to format a Date according to the user's locale).

See the example app for a full example.

Defining Messages

Messages are defined in a datastructure which maps message keys to message content. In its simplest form, a messages structure looks like the following:

const messages = new Map()
    .put("greeting", "Hello, world")
    .put("bye", "See you soon")

This defines a single message identified by the key greeting and the content Hello, world as well as a message with key bye and the content See you soon. Note that in this example all keys and messages are strings. While this is true for all message keys, messages may also be a nested Map when used with plurals (see below).

Loading Messages

Messages must be loaded before you can use them. As being a framework targeting web applications (which must render fast), loading is done before messages can be used.

Messages are loaded as a Bundle. Think of a Bundle as a set of messages grouped together (bundles also contain Formatters which are described below).

i18n defines an interface BundleLoader as well as a couple of implementations which load bundles. You can use one (or multiple) of the bundled loaders or implement your own loaders.

Loaders work asynchronously, i.e. they return a Promise which resolves to the loaded bundle. You can use multiple loaders in your application. i18n provides a CascadingBundleLoader which merges bundles together similar to CSS.

Resolving Messages

The process of replacing a message key with the actual content is called resolving and is performed by a MessageResolver. MessageResolver is a class which accepts loaded bundle and provides synchronous methods for resolving a message given it's key as well as replacing paramaters.

Resolving is done by invoking the m method on the message resolver passing the message's key as the first argument. The method returns the message content.

messageResolver.m("message.key")

Reporting Errors

If the message key is neither found in the local messages nor in the default messages, the resolver returns an error message. This behaviour can be changed by setting the message resolver' property errorReporting to "exception". Once set, the message resolver throws an error reporting the missing key.

Formatting Arguments

A message's content may contain placeholders to be replaced with actual values passed to m. Placeholders are referred to by index and use the typical mustache notation, i.e. {{0}} to refer to the first argument.

If you have a messages definition like

const messages = new Map()
    .put("greeting", "Welcome, {{0}}!")

and you perform a call like

console.log(messageResolver.m("greeting", "John Doe"))

you'll be welcomed with Welcome, John Doe!.

Plurals

Sometimes you want to issue different messages depending on a kind of amount being used. A common example is an inbox which might display No new message, if the inbox is empty, or One new message, if a single new message is available or 145 new messages in case you haven't looked at your inbox for a long time. While it is possible to define different message keys and use arguments to replace the more than one key, i18n provides an easier to use mechanic called plurals.

You can define a message's content to be an object with keys describing the amount case to match. Here is the message definition from the example above:

const messages = new Map()
    .put("inbox.summary", new Map()
        .put(0, "No new message")
        .put(1, "One new message")
        .put(n, "{{n}} new messages")
    )
}

To resolve such a message key, you have to use the mpl method (which is short for plural message) and provide at least two arguments: the message's key and the amount:

console.log(messageResolver.mpl("inbox.summary", numberOfNewMessages))

You can use placeholders in plural messages and provide additional arguments, such as:

const messages = new Map()
    .put("inbox.folder.summary", new Map()
        .put(0, "Your {{0}} folder contains no messages.")
        .put(1, "Your {{0}} folder contains one message.")
        .put(n, "Your {{0}} folder contains {{n}} messages.")
    )
// ...

console.log(messageResolver.mpl("inbox.folder.summary", numberOfMessages, "trash"))

Formatters

Placeholders in messages may be formatted using a dedicated Formatter. In its most simple form a Formatter is just a Javascript function receiving a single argument (the value to be formatted) returning a string. Formatters are great for formatting Javascript values such as numbers, Dates and so on according to the selected locale.

To apply a formatter you specify the formatter's name after the placeholder's index (or the n) separated by a single colon :.

const messages = new Map()
    .put("created", "Created {{0:relativetime}}")

// ...

console.log(messageResolver.m("created", (created.getTime() - new Date().getTime())))

You can define your own formatters or use the standard formatters or do both. To define a formatter you have to register it with the Bundle. A Bundle has an attribute formatters which is a Map mapping the formatter's name to the formatting function.


const bundle = {
    messages: new Map()
        .put("sample", "Abbreviated: {{0:ellipsis}}",        
    formatters: new Map()
        .put("ellipsis", val => `${val}...`)
}

Directly invoking a Formatter

In some cases you want directly invoke a formatter on a value. You can of course define a message key (named like the formatter) which contains a single placeholder with formatter instruction. But this is approach involves a lot of boilerplate code to write.

i18n provides a shortcut syntax. You can invoke the m method passing in a message key starting with a dollar sign $ followed by the formatters name. Additionally, you have to pass the single argument to format. The MessageResolver will not resolve this message key using the normal resolving strategy but look up the formatter and invoke it directly:

messageResolver.m("$date", new Date())

Standard Formatters

i18n provides a set of standard formatters that format dates and times (and a combination of both), relative times and numbers. Being part of the weccoframework, i18n relies on the standard Intl object to do the formatting. The provided formatters are just thin wrappers around a Intl formatter.

To register all of these formatters, invoke the exported funtion i18n.registerStandardFormatters passing the MessageResolver to use as the single argument. See the example app.

The following table lists the standard formatters.

Formatter | Formats/Accepts | Description -- | -- | -- date | Date, number | Formats the given value as date without time in a compact, short notation. Uses Intl.DateTimeFormat to do the formatting. time | Date, number | Formats the given value as time without date in a compact, short notation. Uses Intl.DateTimeFormat to do the formatting. datetime | Date, number | Formats the given value as a date and time in a compact, short notation. Uses Intl.DateTimeFormat to do the formatting. relativetime | number | Formats the given number of milliseconds as a relative time. Determines the unit (seconds, minutes, ...) to be the most relevant unit with days being the larges used unit. Uses Intl.RelativeDateFormat to do the formatting. integer | number | Formats the given number as an integer omitting all fraction digits. Uses Intl.NumberFormat to do the formatting.

Loaders

The following section contains a description of the provided BundleLoader implementions. Make sure you also check out the source code and doc comments for the classes.

ObjectBundleLoader

A BundleLoader that "loads" messages from a given object. This message loader is typically be used in an environment where messages are imported from static files during Javascript assembly.

The object loader's input uses plain Javascript objects like the following:

{
    "message.key": "message content",
    "plural.message.key": {
        "0": "empty message",
        "n": "{{n}} message",
    },
}

See the following example for how to use the ObjectBundleLoader:

import { MessageResolver, ObjectBundleLoader } from "@weccoframework/i18n"
import { en, de, fr } from "./messages"

const messageResolver = await MessageResolver.create(new ObjectBundleLoader(en, {
    en: en,
    de: de,
    fr: fr,
}))

This pattern is very easy to get running and works very for few translations with a small number of messages.

JsonBundleLoader

The JsonBundleLoader supports loading messages from JsonSource. A JsonSource is a function which accepts a language as a single argument and retuns a Promise resolving to a JSON-formatted string which defines the messages. The format of the JSON messages is almost identical to the one used by the ObjectBundleLoader:

{
    "message.key": "message content",
    "plural.message.key": {
        "0": "empty message",
        "n": "{{n}} message",
    },
}

To construct a JsonMessagesLoader you need to pass at least one JsonSource. You may pass two. In this case the first one is used to load the default messages while the second one is used to load the localized messages.

const jsonSource = (locale) => {
    // ...
    // return a Promise resolving to a string. 
}

const messageResolver = await MessageResolver.create(new JsonBundleLoader(jsonSource))

fetchJson

i18n provides a function that creates a JsonSource which uses fetch to load a JSON string from an static URL ignoring the passed Locale.

fetchJsonByLocale

i18n comes with with a ready to use implementation of JsonSource which uses fetch to load messages identified by locale. The implementation uses the following conventions to load messages:

  • all messages are located under a given base URL
  • localized messages for <locale> can be loaded from <baseURL>/<locale>.json

You can pass additional request init options, such as CORS mode, change the request method (which defaults to GET), set headers, ...

CascadingBundleLoader

The CascadingBundleLoader loads messages by using a set of underlying BundleLoaders and merging the messages returned by each loader together in a way similiar to how CSS rules are merged (thus the name). Use this loader to get an aggregated set of messages from multiple sources.

The following (rather academic) example demonstrates the merging.


const loader1 = new ObjectBundleLoader({
    foo: "foo",
    bar: "bar",
})

const loader2 = new ObjectBundleLoader({
    bar: "Bar",
    spam: "Eggs",
})

const messageResolver = await MessageResolver.create(new CascadingBundleLoader(loader1, loader2))

console.log(messageResolver.m("bar")) // Logs: Bar

Author

i18n is written by Alexander Metzner [email protected].

License

Copyright (c) 2021-2024 Alexander Metzner.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.