Skip to content

Repository files navigation

⧟ MessageMap

code style: airbnbcode style: prettier

Dynamic string generation with a strongly-typed interface.

💁🏼‍♂️ Introduction

MessageMap is an extra spicy version of String.prototype.replace that applies TypeScript static typing to the concept of C-style string interpolation. Some potential use-cases include:

  • Centrally manage error messages for your JavaScript/TypeScript package.
  • Build a library of reactive translations for your front-end web application.
  • Protect yourself from formatting bugs inside large/unwieldy template strings by maintaining type safety.
  • Probably a lot more I haven't considered yet!

NOTE: This package uses modern TypeScript features to achieve type safety, typescript@>=3.1 is recommended!

🔗 Installation

Install via yarn (recommended):

yarn add message-map

Install via npm:

npm install message-map

🛠️ Usage

Basic example

MessageMap has a chaining API where each method returns a new instance of MessageMap, similar to RxJS. This ensures the type signatures remain accurate and improves readability!

import{MessageMap}from'message-map';constmyStringBuilder=newMessageMap('The date is %month %day, %year. The current epoch is %epoch.').required('month').required('day').required('year').optional('epoch',()=>String(newDate().getTime()));// We can choose to specify a default value for the optional key.myStringBuilder.toString({month: 'January',day: '1',year: '2000',epoch: ... // Optional -- as indicated above. TypeScript will not complain if this prop is missing.});// => "The date is January 1, 2000. The current epoch is 946684800."

Using validator functions

You can optionally provide Validator functions to MessageMap.required or MessageMap.optional. The callback returns a boolean to indicate whether the interpolation should proceed at all OR a string fallback in case the user-provided substitution is missing or undefined (this enables you to specify defaults for MessageMap.optional replacement keys—as shown in the above example for epoch).

import{MessageMap}from'message-map';constmyStringBuilder=newMessageMap('My phone number is %phoneNumber').required('phoneNumber',str=>/^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*$/.test(str));myStringBuilder.toString({phoneNumber: '555-asdf-1234'// This will raise an error because the phone number won't pass validation!});

Using MessageCollection

The MessageCollection class is intended for large-scale use-cases (such as building a language library) and is configurable via JSON-compatible objects. With TypeScript's resolveJsonModule option enabled, or by providing a static object literal, your MessageCollection can be strongly-typed, too!

import*aslanguagelibraryfrom'../path/to/languageLibrary.json';// Requires `resolveJsonModule` in tsconfig.jsonimport{MessageCollection}from'message-map';constmyLanguageLibrary=newMessageCollection(languageLibrary);

languageLibrary.json has the following type signature:

interfaceJsonLanguageLibrary{[key: string]: string|{template: string;optional?: {[substitution: string]: string|null|{default?: string;regex?: string;};};required?: {[substitution: string]: string|null|{default?: string;regex?: string;};};};}

Here's some example JSON:

{
"HELLO_X": {
"template": "Good %partOfDay, %yourName!",
"optional": {
"yourName": {
"default": "Jeeves"
}
},
"required": {
"partOfDay": {
"regex": "//(morning|afternoon|evening)//"
}
}
},
"MORNING": "morning"
}

You can then use myLanguageLibrary as a collection of static MessageMap instances:

myLanguageLibrary.get('HELLO_X').toString({yourName: 'Bojack',partOfDay: myLanguageLibrary.get('MORNING').toString(),});// => "Good morning, Bojack!"

For convenience, the MessageCollection class includes an enum of valid key names:

myLanguageLibrary.keys.HELLO_X;// => "HELLO_X"myLanguageLibrary.keys.MORNING;// => "MORNING"keyoftypeofmyLanguageLibrary.keys;// => "HELLO_X" | "MORNING"

The underlying MessageMap instances are exposed on MessageCollection.messages, but note they are lazily populated by MessageCollection.get(...). The MessageCollection.messages property is exposed primarily for TypeScript typing needs:

myLanguageLibrary.messageMaps// => {// "HELLO_X"?: MessageMap,// "MORNING"?: MessageMap,// }

Then you can trivially extract the substitution parameters like so:

typeMySubstitutionKeys=keyoftypeofmyLanguageLibrary['keys'];typeMySubstitutionConfig<TKeyextendsMySubstitutionKeys>=Parameters<typeofmyLanguageLibrary['messages'][TKey]['toString']>;

Using the UMD build

A UMD build of MessageMap is available via UNPKG. Drop it into your page via a <script> tag:

<scripttype="text/javascript" src="https://unpkg.com/message-map/dist/index.umd.js"></script>

The API for the UMD build has one small difference: MessageMap is the default object with MessageCollection available on MessageMap.Collection. So, you would use...

newMessageMap.Collection(langaugeLibraryJson);

Voila!

About

Dynamic string generation with a strongly-typed interface.

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages