plural-rules
Version:
Evaluates plural rules, so that localization libraries can choose the right plural form.
413 lines (407 loc) • 12.4 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pluralRules = {}));
})(this, (function (exports) { 'use strict';
function pluralRuleParser(rule, number) {
rule = rule.split("@")[0].replace(/^\s*/, "").replace(/\s*$/, "");
if (!rule.length) {
return true;
}
let pos = 0;
const whitespace = makeRegexParser(/^\s+/);
const value = makeRegexParser(/^\d+/);
const _n_ = makeStringParser("n");
const _i_ = makeStringParser("i");
const _f_ = makeStringParser("f");
const _t_ = makeStringParser("t");
const _v_ = makeStringParser("v");
const _w_ = makeStringParser("w");
const _is_ = makeStringParser("is");
const _isnot_ = makeStringParser("is not");
const _isnot_sign_ = makeStringParser("!=");
const _equal_ = makeStringParser("=");
const _mod_ = makeStringParser("mod");
const _percent_ = makeStringParser("%");
const _not_ = makeStringParser("not");
const _in_ = makeStringParser("in");
const _within_ = makeStringParser("within");
const _range_ = makeStringParser("..");
const _comma_ = makeStringParser(",");
const _or_ = makeStringParser("or");
const _and_ = makeStringParser("and");
function debug() {
}
function choice(parserSyntax) {
return function() {
let i2, result2;
for (i2 = 0; i2 < parserSyntax.length; i2++) {
result2 = parserSyntax[i2]();
if (result2 !== null) {
return result2;
}
}
return null;
};
}
function sequence(parserSyntax) {
let i2;
let parserRes;
const originalPos = pos;
const result2 = [];
for (i2 = 0; i2 < parserSyntax.length; i2++) {
parserRes = parserSyntax[i2]();
if (parserRes === null) {
pos = originalPos;
return null;
}
result2.push(parserRes);
}
return result2;
}
function nOrMore(n2, p) {
return function() {
const originalPos = pos;
const result2 = [];
let parsed = p();
while (parsed !== null) {
result2.push(parsed);
parsed = p();
}
if (result2.length < n2) {
pos = originalPos;
return null;
}
return result2;
};
}
function makeStringParser(s) {
const len = s.length;
return function() {
let result2 = null;
if (rule.substr(pos, len) === s) {
result2 = s;
pos += len;
}
return result2;
};
}
function makeRegexParser(regex) {
return function() {
const matches = rule.substr(pos).match(regex);
if (matches === null) {
return null;
}
pos += matches[0].length;
return matches[0];
};
}
function i() {
let result2 = _i_();
if (result2 === null) {
return result2;
}
result2 = parseInt(number, 10);
return result2;
}
function n() {
let result2 = _n_();
if (result2 === null) {
return result2;
}
result2 = parseFloat(number, 10);
return result2;
}
function f() {
let result2 = _f_();
if (result2 === null) {
return result2;
}
result2 = (number + ".").split(".")[1] || 0;
return result2;
}
function t() {
let result2 = _t_();
if (result2 === null) {
return result2;
}
result2 = (number + ".").split(".")[1].replace(/0$/, "") || 0;
return result2;
}
function v() {
let result2 = _v_();
if (result2 === null) {
return result2;
}
result2 = (number + ".").split(".")[1].length || 0;
return result2;
}
function w() {
let result2 = _w_();
if (result2 === null) {
return result2;
}
result2 = (number + ".").split(".")[1].replace(/0$/, "").length || 0;
return result2;
}
const operand = choice([n, i, f, t, v, w]);
const expression = choice([mod, operand]);
function mod() {
const result2 = sequence([operand, whitespace, choice([_mod_, _percent_]), whitespace, value]);
if (result2 === null) {
return null;
}
debug(" -- passed ", parseInt(result2[0], 10), result2[2], parseInt(result2[4], 10));
return parseFloat(result2[0]) % parseInt(result2[4], 10);
}
function not() {
const result2 = sequence([whitespace, _not_]);
if (result2 === null) {
return null;
}
return result2[1];
}
function is() {
const result2 = sequence([expression, whitespace, choice([_is_]), whitespace, value]);
if (result2 !== null) {
debug(" -- passed is :", result2[0], " == ", parseInt(result2[4], 10));
return result2[0] === parseInt(result2[4], 10);
}
return null;
}
function isnot() {
const result2 = sequence([expression, whitespace, choice([_isnot_, _isnot_sign_]), whitespace, value]);
if (result2 !== null) {
debug(" -- passed isnot: ", result2[0], " != ", parseInt(result2[4], 10));
return result2[0] !== parseInt(result2[4], 10);
}
return null;
}
function not_in() {
let i2;
let range_list;
const result2 = sequence([expression, whitespace, _isnot_sign_, whitespace, rangeList]);
if (result2 !== null) {
debug(" -- passed not_in: ", result2[0], " != ", result2[4]);
range_list = result2[4];
for (i2 = 0; i2 < range_list.length; i2++) {
if (parseInt(range_list[i2], 10) === parseInt(result2[0], 10)) {
return false;
}
}
return true;
}
return null;
}
function rangeList() {
const result2 = sequence([choice([range, value]), nOrMore(0, rangeTail)]);
let resultList = [];
if (result2 !== null) {
resultList = resultList.concat(result2[0]);
if (result2[1][0]) {
resultList = resultList.concat(result2[1][0]);
}
return resultList;
}
return null;
}
function rangeTail() {
const result2 = sequence([_comma_, rangeList]);
if (result2 !== null) {
return result2[1];
}
return null;
}
function range() {
let i2;
let array;
let left;
let right;
const result2 = sequence([value, _range_, value]);
if (result2 !== null) {
array = [];
left = parseInt(result2[0], 10);
right = parseInt(result2[2], 10);
for (i2 = left; i2 <= right; i2++) {
array.push(i2);
}
return array;
}
return null;
}
function _in() {
const result2 = sequence([expression, nOrMore(0, not), whitespace, choice([_in_, _equal_]), whitespace, rangeList]);
if (result2 !== null) {
const rangeList2 = result2[5];
for (let i2 = 0; i2 < rangeList2.length; i2++) {
if (parseInt(rangeList2[i2], 10) === parseFloat(result2[0])) {
return result2[1][0] !== "not";
}
}
return result2[1][0] === "not";
}
return null;
}
function within() {
const result2 = sequence([expression, nOrMore(0, not), whitespace, _within_, whitespace, rangeList]);
if (result2 !== null) {
const range_list = result2[5];
if (result2[0] >= parseInt(range_list[0], 10) && result2[0] < parseInt(range_list[range_list.length - 1], 10)) {
return result2[1][0] !== "not";
}
return result2[1][0] === "not";
}
return null;
}
const relation = choice([is, not_in, isnot, _in, within]);
function and() {
let i2;
const result2 = sequence([relation, nOrMore(0, andTail)]);
if (result2) {
if (!result2[0]) {
return false;
}
for (i2 = 0; i2 < result2[1].length; i2++) {
if (!result2[1][i2]) {
return false;
}
}
return true;
}
return null;
}
function andTail() {
const result2 = sequence([whitespace, _and_, whitespace, relation]);
if (result2 !== null) {
return result2[3];
}
return null;
}
function orTail() {
const result2 = sequence([whitespace, _or_, whitespace, and]);
if (result2 !== null) {
debug(" -- passed orTail: ", result2[3]);
return result2[3];
}
return null;
}
function condition() {
let i2;
const result2 = sequence([and, nOrMore(0, orTail)]);
if (result2) {
for (i2 = 0; i2 < result2[1].length; i2++) {
if (result2[1][i2]) {
return true;
}
}
return result2[0];
}
return false;
}
const result = condition();
if (result === null) {
throw new Error("Parse error at position " + pos.toString() + " for rule: " + rule);
}
if (pos !== rule.length) {
debug("Warning: Rule not parsed completely. Parser stopped at ", rule.substr(0, pos));
}
return result;
}
function parsePluralForms (serializedForms) {
return serializedForms
.split(',')
.reduce((result, serializedForm) => {
const parts = serializedForm.split(':');
result[parts[0]] = +parts[1];
return result
}, {})
}
function indexPluralForms (forms, rules) {
return Object
.keys(forms)
.reduce((result, formName) => {
let rule = forms[formName];
if (typeof rule === 'string') {
rule = rule
.trim()
.replace(/ {2}/g, ' ');
let ruleIndex = rules.indexOf(rule);
if (ruleIndex < 0) {
ruleIndex = rules.length;
rules.push(rule);
}
rule = ruleIndex;
}
if (formName.length > 17) {
formName = formName.substr(17);
}
result[formName] = rule;
return result
}, {})
}
let rules = [];
let cardinals = {};
function normalizeLocale (locale) {
return locale.toLowerCase().replace('_', '-')
}
function getLanguage (locale) {
const separator = locale.indexOf('-');
return separator > 0 && locale.substr(0, separator)
}
function getPluralRulesForCardinals (locale) {
locale = normalizeLocale(locale);
let forms = cardinals[locale];
let language;
if (forms === undefined) {
language = getLanguage(locale);
if (language) {
forms = cardinals[language];
}
}
if (forms === undefined) {
throw new Error(`Unrecognised locale: "${locale}".`)
} else if (typeof forms === 'string') {
forms = parsePluralForms(forms);
cardinals[language || locale] = forms;
}
return forms
}
function getPluralFormForCardinal (locale, count) {
const forms = typeof locale !== 'string'
? locale
: getPluralRulesForCardinals(locale);
for (const form in forms) {
const rule = forms[form];
if (pluralRuleParser(rules[rule], count)) {
return form
}
}
}
function setPluralFormsForCardinals (locale, forms) {
locale = normalizeLocale(locale);
cardinals[locale] = indexPluralForms(forms, rules);
}
function populatePluralData (data) {
rules = data.rules;
cardinals = data.cardinals;
}
let supportedLocales;
function getSupportedLocales() {
if (!supportedLocales) supportedLocales = Object.keys(cardinals);
return supportedLocales
}
function getPluralFormsForLocale(locale) {
const cardinal = cardinals[locale];
if (cardinal === undefined) return
return cardinal
.split(',')
.map(form => form.substring(0, form.indexOf(':')))
}
exports.getPluralFormForCardinal = getPluralFormForCardinal;
exports.getPluralFormsForLocale = getPluralFormsForLocale;
exports.getPluralRulesForCardinals = getPluralRulesForCardinals;
exports.getSupportedLocales = getSupportedLocales;
exports.populatePluralData = populatePluralData;
exports.setPluralFormsForCardinals = setPluralFormsForCardinals;
}));
//# sourceMappingURL=code.umd.js.map