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

@sleepyg11/localizer

v0.1.5

Published

Module for localization with plural rules support.

Downloads

72

Readme

Node.js Localizer

Simple localization module with Intl.PluralRules support for pluralization.

⚠ Work in Progress

This module is WIP. Breaking changes may appear recently. Full JSDoc and tests will be provided later.

Install

npm install @sleepyg11/localizer --save

Table of content


Basic usage

// CommonJS
const { default: Localizer } = require('@sleepyg11/localizer')
// ESM
import Localizer from '@sleepyg11/localizer'

const localizer = new Localizer({
    intl: true,
    localization: {
        'en-US': {
            'hello': 'Hello world!',
            'cats': {
                'one': '%s cat',
                'other': '%s cats',
            },
        },
        'ru-RU': {
            'hello': 'Привет мир!',
            'cats': {
                'one': '%s кот',
                'few': '%s кота',
                'other': '%s котов',
            },
        },
    },
})

// Assign localize functions to variables is fine
const l = localizer.localize
const ln = localizer.pluralize

// Get localization data by locale and key.
localize('en-US', 'hello');    // -> Hello world!
localize('ru-RU', 'hello');    // -> Привет мир!

// Using plural rules
pluralize('en-US', 'cats', 1)  // -> 1 cat
pluralize('en-US', 'cats', 2)  // -> 2 cats

pluralize('ru-RU', 'cats', 1)  // -> 1 кот
pluralize('ru-RU', 'cats', 2)  // -> 2 кота
pluralize('ru-RU', 'cats', 5)  // -> 5 котов

// Create scope with pre-defined locale
const scope = localizer.scope('ru-RU')

scope.localize('hello')        // -> Привет мир!
scope.pluralize('cats', 2)     // -> 2 кота

Main structures

Localization table

JSON object with key-value localization data for all locales. For each key, value can be:

  • a string
  • nested data
  • object with specific keys usable for pluralization:
    • plural categories defined in Unicode CLDR
      zero, one, two, few, many, other
    • math intervals as part of ISO 31-11
      • leading ! invert interval
      • have priority over plural categories
      [3]     Match number 3 only
      [3,5]   Match all numbers between 3 and 5 (both inclusive)
      (3,5)   Match all numbers between 3 and 5 (both exclusive)
      [3,5)   Match all numbers between 3 (inclusive) and 5 (exclusive)
      [3,]    Match all numbers bigger or equal 3
      [,5)    Match all numbers less than 5
      ![3]    Match all numbers except 3
      ![3,5]  Match all numbers less than 3 or bigger than 5
  • undefined or null are ignored
  • any other type will be converted to string

Example

{
    // Locale
    'en-US': {
        // String
        hello: 'Hello world!',

        // Nested data
        food: {
            apple: 'Red Apple',
        },

        // Specific keys
        cats: {
            '[21]': 'Twenty one cats',
            '(50,]': 'More than 50 cats',
            'one': '%s cat',
            'other': '%s cats',
        },
    },
}

Fallbacks table

Object with fallback locales for all locales. When localization data for initial locale not found, next that match pattern in table (in order they present) will be used. Repeats until data will be found. * can be used as wildcard in patterns.

Example

{
  // For all English locales, fallback to `en-UK`
  'en-*': 'en-UK',
  // If `en-UK` localization missing, use `en-US` instead
  'en-UK': 'en-US',
}

Plural Rules table

Object with plural rules functions for locales.

Each function take 2 arguments:

  • count (number) - number to pluralize
  • ordinal (boolean) - use ordinal form (1st, 2nd, 3rd, etc.) instead of cardinal (1, 2, 3, etc.)

As result it should return one of plural category:

zero, one, two, few, many, other

Example

{
    'en-US': (count, ordinal = false) => {
        if (ordinal) return count === 2 ? 'two' : count === 1 ? 'one' : 'other'
        return count === 1 ? 'one' : 'other'
    },
    // Using `Intl.PluralRules` for custom locales.
    // Not recommended.
    'russian': (count, ordinal = false) => {
        return new Intl.PluralRules('ru-RU', { 
            type: ordinal ? 'ordinal' : 'cardinal',
        }).select(count)
    }
}

Localizer class

The main class that provide localization methods.

const localizer = new Localizer()

Constructor options (all optional):

  • safe: Should localize methods returns null instead of throw error when invalid arguments passed (default: false).
  • intl: Use Intl.PluralRules to resolve plural categories (default: false).
  • localization: Localization Table.
  • fallbacks: Fallbacks Table.
  • pluralRules: Plural Rules Table. Has priority over Intl.PluralRules.
  • defaultLocale: Locale to use when data for all fallback locales not found (default: null).
  • cacheLocalization: Should cache localization data for all used keys (default: true).
  • cachePluralRules: Should cache plural rules functions for all used locales (default: true).
  • cacheFallbacks: Should cache fallback locales for all used keys (default: true).
  • cachePrintf: Should use cache for patterns insertion (default: true).

All cache options increase localization speed for frequently used locales, keys and data. Each individual can be disabled, which may reduce memory usage by cost of speed.

Using constructor without options equals to:

const localizer = new Localizer({
    safe: false,
    intl: false,
    localization: {},
    fallbacks: {},
    pluralRules: {},
    defaultLocale: null,
    cacheLocalization: true,
    cachePluralRules: true,
    cacheFallbacks: true,
    cachePrintf: true,
})

All options can be updated in any time:

localizer.intl = true
localizer.cacheFallbacks = false
localizer.localization = {
    'en-US': {},
}

LocalizerScope class

Can be taken from Localizer.scope(locale) method.

const scope = localizer.scope('ru-RU')

Scope have similar localize() and pluralize() methods, except they automatically use locale defined in constructor. All other options (like caching, fallbacks, etc.) inherit from Localizer class where it was created.

Methods

localize()

Main method for data localization.

localizer.localize(locale: string, key: string, ...args)
scope.localize(key: string, ...args)
// or
localizer.localize(options, ...args)
scope.localize(options, ...args)

Data search process:

  • Using options.raw or resolve data by locale and key;
  • If data is string, it will be used;
  • If data is object, data.other will be used instead;
  • If data is null or undefined, or key not found, it's ignored;
  • Process repeats for all fallback locales until some data will be found;
  • If nothing found, options.fallback or initial key returned instead.

Has alias: localizer.l().

const localizer = new Localizer({
    localization: {
        'en-US': {
            'items': {
                'apple': 'Red Apple',
            },
            'cats': {
                'one': '%s cat',
                'other': '%s cats',
            },
        },
    },
})

// Can be assigned to variable, will work fine.
const localize = localizer.localize

// Basic usage:
localize('en-US', 'items.apple') // -> 'Red Apple'
localize('en-US', 'cats') // -> '%s cats'

// Options argument:
localize({
    locale: 'en-US',
    key: 'items.apple',

    // Can override constructor options for this call only.
    safe: true,
    cacheLocalization: false,
}) // -> Red Apple

// Raw data:
localize({
    raw: {
        one: 'One Pineapple',
        other: 'A lot of Pineapples',
    },
    fallback: 'Pineapple', // *Required* when raw is object.
}) // -> One Pineapple

pluralize()

Pluralize data by using count argument and plural rules.

localizer.pluralize(locale: string, key: string, count: number, ...args)
scope.pluralize(key: string, count: number, ...args)
// or
localizer.pluralize(options, ...args)
scope.pluralize(options, ...args)

Search process:

  • Using options.raw or resolve data by locale and key;
  • If data is string, it will be used;
  • If data is object, then:
    • First interval match will be used;
    • Or, plural rules will be applied to determine plural category to use:
  • If data has other type, it will be used as string;
  • If data is null or undefined, or key not found, it's ignored;
  • Process repeats for all fallback locales until some data will be found;
  • If nothing found, options.fallback or initial key returned instead.

Has aliases: localizer.p() and localizer.ln().

const localizer = new Localizer({
    localization: {
        'en-US': {
            'cats': {
                '[3,5]': 'From 3 to 5 cats',
                'one': '%s cat',
                'other': '%s cats',
            },
        },
    },
})

// Can be assigned to variable, will work fine.
const pluralize = localizer.pluralize

// Basic usage:
pluralize('en-US', 'cats', 1) // -> '1 cat in my home'
pluralize('en-US', 'cats', 2) // -> '2 cats'
pluralize('en-US', 'cats', 3) // -> 'From 3 to 5 cats'

// Options argument:
pluralize({
    locale: 'en-US',
    key: 'cats',
    count: 2,
    ordinal: true, // Use ordinal form instead of cardinal
}) // -> 2 cats

// Raw data:
pluralize({
    raw: {
        one: '%s Pineapple',
        other: '%s Pineapples',
    },
    fallback: 'Pineapple', // Required when raw is object.
    locale: 'en-US', // Locale to use for plural rules. *Optional*.
    count: 2
}) // -> 2 Pineapples

count always prepended to arguments list:

pluralize({ raw: '%s', count: 10 }) // -> 10

Values insertion

Localization data supports values insertion similar to sprintf-js does, with additional formatters which can be more useful for localization purposes.

Example:

const localizer = new Localizer({
    localization: {
        'en-US': { 
            hello: 'Hello, %s!',
            cats: 'I have %s cats, and one of them called %s.',
        }
    }
})
const l = localizer.l

localize('en-US', 'hello', 'Kitty') // -> Hello, Kitty!
localize({ raw: 'Goodbye, %S!' }, 'Kitty') // -> Goodbye, KITTY!

pluralize('en-US', 'cats', 10, 'Kitty') // -> I have 10 cats, and one of them called Kitty.
pluralize({ raw: 'I have %s %s.', count: 5 }, 'apples') // -> I have 5 apples.

In examples below, printf() function used instead of localize() and pluralize(); insertion works identical for all of them.

import { printf } from '@sleepyg11/localizer'

Utility formatters

%t: insert value as boolean

printf('%t', 1) // -> true
printf('%t', 0) // -> false

%T: insert value type (name of constructor)

printf('%T', undefined)    // -> Undefined
printf('%T', null)         // -> Null
printf('%T', true)         // -> Boolean
printf('%T', 0)            // -> Number
printf('%T', 0n)           // -> BigInt
printf('%T', 'Hello')      // -> String
printf('%T', [1, 2, 3])    // -> Array
printf('%T', { a: 1 })     // -> Object
printf('%T', new Date())   // -> Date

%j: insert value as JSON.

  • JSON.stringify() will be used.
  • BigInt converts to string.
  • If JSON contains circular, [Circular] string will be returned.
printf('%j', { a: 1, b: 1n }) // -> {'a':1,'b':'1'}

let withCircular = {};
withCircular.repeat = withCircular;
printf('%j', withCircular) // -> [Circular]

String formatters

All passed values will be converted to string.

%s: insert value as string

printf('%s', 'New YEAR') // -> New YEAR

%S: insert value as string in upper case

printf('%S', 'New YEAR') // -> NEW YEAR

%c: insert value as string in lower case

printf('%c', 'New YEAR') // -> new year

%C: insert value as string in upper case (similar to %S)

printf('%C', 'New YEAR') // -> NEW YEAR

%n: insert value as string, first word capitalized, others in lower case

printf('%n', 'New YEAR') // -> New year

%N: insert value as string, all words capitalized

printf('%N', 'New YEAR') // -> New Year

Number formatters

All passed values will be converted to number.

  • undefined treated as NaN;
  • null treated as 0 (zero).
  • For some formats, precision can be applied: %.<precision><format>. See examples below.

%b: insert value as binary

printf('%b', 13)          // -> 1101

%o: insert value as octal

printf('%b', 13)          // -> 15

%i: insert value as integer

printf('%i', 13.5)        // -> 13
printf('%i', 2147483648)  // -> 2147483648

%d: insert value as signed decimal

printf('%d', 1)           // -> 15
printf('%d', -1)          // -> -1
printf('%d', 2147483648)  // -> -2147483648

%u: insert value as unsigned decimal

printf('%d', 1)           // -> 1
printf('%d', -1)          // -> 4294967295

%x: insert value as hexadecimal in lower case

printf('%x', 255)         // -> ff

%X: insert value as hexadecimal in upper case

printf('%X', 255)         // -> FF

%e: insert value in exponential form with lower e (precision can be specified)

printf('%e', 12345)       // -> 1.2345e+4
printf('%.1e', 12345)     // -> 1.2e+4

%e: insert value in exponential form with upper e (precision can be specified)

printf('%E', 12345)       // -> 1.2345E+4
printf('%.1E', 12345)     // -> 1.2E+4

%f: insert value as float (precision can be specified)

printf('%f', 13.579)      // -> 13.579
printf('%.2f', 13.579)    // -> 13.57
printf('%.1f', 13)        // -> 13.0

Insertion order

// Insert arguments in order they present
printf('%s, %s and %s', 'One', 'Two', 'Three') // -> 'One, Two and Three'

// Insert arguments in specific order
printf('%2$s, %3$s and %1$s', 'One', 'Two', 'Three') // -> 'Two, Three and One'

// Insert values from object
printf(
    '%(first)s, %(second)s and %(third)s',
    { first: 'fish', second: 'cat', third: 'fox' }
) // -> 'fish, cat and fox'

// Combined
printf(
    '%(first)s %s, %(second)s %s and %(third)s %s',
    'Fishcl', 'Kitty', 'Foxy',
    { first: 'fish', second: 'cat', third: 'fox' }
) // -> 'fish Fishlc, cat Kitty and fox Foxy'

Pad values

All (except %j) processed values can be padded to match minimal width.

//  +{width}: add whitespaces to left
printf('%+4s', 'hi')      // -> '  hi'
printf('%+4s', 'goodbye') // -> 'goodbye'

//  -{width}: add whitespaces to right
printf('%-4s', 'hi')      // -> 'hi  '
printf('%-4s', 'goodbye') // -> 'goodbye'

//  0{width}: add zeroes to left
printf('%04s', 'hi')      // -> '00hi'
printf('%04s', 'goodbye') // -> 'goodbye'
printf('%04s', '23')      // -> '0023'
printf('%04s', '-23')     // -> '0-23', don't do any sign checks!

// ++{width}: add sign for number and whitespaces to left
printf('%++5s', 23)       // -> '  +23'
printf('%++5s', -23)      // -> '  -23'

// +-{width}: add sign for number and whitespaces to right
printf('%+-5s', 23)       // -> '+23  '
printf('%+-5s', -23)      // -> '-23  '

// +0{width}: add sign for number and zeroes to left
printf('%+05s', 23)       // -> '+0023', sign checks applied!
printf('%+05s', -23)      // -> '-0023'

Combined

Some examples of complex but powerful patterns:

printf('%2$+08.2f -> %1$+08.2f', 1.2345, -54.321) // -> '-0054.32 -> +0001.23'
printf('#%+06X', 16753920) // -> '#FFA500', orange color in hex
printf(
    "I live in %(city)N with my %(animal.type)s %(animal.name)n. %(animal.pronounce.0)n %(animal.feeling)S this place.",
    {
        city: "new york",
        animal: {
            pronounce: ['she', 'her'],
            type: "cat",
            name: "kitty",
            feeling: "love",
        },
    },
) // -> "I live in New York with my cat Kitty. She LOVE this place."

No value

If value not provided, pattern will be returned unchanged.

printf('%s and %s', 'fish') // -> fish and %s
printf('%(hello)s and %(goodbye)s', { hello: 'Hola' }) // -> Hola and %(goodbye)s

Calculating value

If value is function, in will be called without arguments and returned value will be used in pattern.

// Most common usage example: Date. Will be displayed current date
printf('%s', () => new Date())
printf('%(date)s', { date: () => new Date() })