Lightweight internationalization (i18n) library with:
- Variable interpolation (
{{name}}) - CLDR plural rules (
zero | one | two | few | many | other) - Locale and missing key fallbacks
- PluralRules caching for performance
- Zero dependencies (uses native
Intl.PluralRules)
npm install @andersoncustodio/i18nRequires Node >= 14.
const { I18n } = require('@andersoncustodio/i18n');
const locales = {
en: {
greeting: 'Hello, {{name}}!',
items: { zero: 'No items', one: 'One item', other: '{{count}} items' }
},
pt: {
greeting: 'Olá, {{name}}!',
items: { zero: 'Nenhum item', one: 'Um item', other: '{{count}} itens' }
}
};
const i18n = new I18n(locales, () => 'en');
i18n.t('greeting', { name: 'João' }); // Hello, João!
i18n.t('items', { count: 0 }); // No items
i18n.t('items', { count: 1 }); // One item
i18n.t('items', { count: 5 }); // 5 itemsUses Intl.PluralRules to determine the correct plural category based on locale and count.
| Category | Description | Example languages |
|---|---|---|
zero |
Zero quantity | Arabic |
one |
Singular | English, Portuguese, Russian |
two |
Dual | Arabic |
few |
Few items | Russian (2-4), Arabic (3-10) |
many |
Many items | Russian (5-20), Arabic (11-99) |
other |
Default/fallback | All languages |
const ru = {
items: {
one: '{{count}} предмет', // 1, 21, 31...
few: '{{count}} предмета', // 2-4, 22-24...
many: '{{count}} предметов', // 0, 5-20, 25-30...
other: '{{count}} предмета'
}
};
const i18n = new I18n({ ru }, () => 'ru', { defaultLocale: 'ru' });
i18n.t('items', { count: 1 }); // 1 предмет
i18n.t('items', { count: 2 }); // 2 предмета
i18n.t('items', { count: 5 }); // 5 предметов
i18n.t('items', { count: 21 }); // 21 предметconst ar = {
items: {
// 0
zero: 'لا عناصر',
// 1
one: 'عنصر واحد',
// 2
two: 'عنصران',
// 3-10
few: '{{count}} عناصر',
// 11-99
many: '{{count}} عنصراً',
// 100+
other: '{{count}} عنصر'
}
};
const i18n = new I18n({ ar }, () => 'ar', { defaultLocale: 'ar' });
i18n.t('items', { count: 0 }); // 'لا عناصر'
i18n.t('items', { count: 2 }); // 'عنصران'
i18n.t('items', { count: 11 }); // '11 عنصراً'- Missing category falls back to
other - Explicit
zerokey is used whencount === 0, even if the language doesn't have a grammatical zero (like English)
Strings with {{variable}} are replaced with values from options:
i18n.t('greeting', { name: 'World' }); // Hello, World!
i18n.t('greeting'); // Hello, {{name}}!new I18n(locales, getLocale?, options?)locales- Object with locale translationsgetLocale- Function returning current localeoptions.defaultLocale- Fallback locale (default:'en')
t(keyPath, options?)- Translate a keyoptions.count- Number for pluralizationoptions.*- Variables for interpolation
locale- Current active localelocales- Array of available locales
const { AsyncLocalStorage } = require('node:async_hooks');
const { I18n } = require('@andersoncustodio/i18n');
const storage = new AsyncLocalStorage();
const i18n = new I18n(locales, () => storage.getStore()?.locale);
storage.run({ locale: 'pt' }, () => {
console.log(i18n.t('greeting', { name: 'Maria' }));
});MIT