@sleepyg11/localizer
v0.1.5
Published
Module for localization with plural rules support.
Downloads
72
Maintainers
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
- leading
- plural categories defined in Unicode CLDR
undefined
ornull
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 pluralizeordinal
(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 bylocale
andkey
; - If data is string, it will be used;
- If data is object,
data.other
will be used instead; - If data is
null
orundefined
, 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 initialkey
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 bylocale
andkey
; - 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:
- Function from Plural Rules Table;
- Or Intl.PluralRules if
localizer.intl = true
; - Or,
data.other
form will be used;
- If data has other type, it will be used as string;
- If data is
null
orundefined
, 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 initialkey
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 asNaN
;null
treated as0
(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() })