make-plural 1.3.0 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2014, Eemeli Aro <eemeli@gmail.com>
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
package/README.md CHANGED
@@ -3,27 +3,32 @@ make-plural
3
3
 
4
4
  A JavaScript module that translates [Unicode CLDR](http://cldr.unicode.org/)
5
5
  [pluralization rules](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html)
6
- to executable JavaScript functions.
6
+ to JavaScript functions.
7
+
8
+ Can be used as a CommonJS or AMD module, or directly in a browser environment.
7
9
 
8
10
 
9
11
  ## Installation
10
12
 
11
- ```sh
13
+ ```
12
14
  npm install make-plural
13
15
  ```
14
16
  or
15
- ```sh
17
+ ```
16
18
  git clone https://github.com/eemeli/make-plural.js.git
17
19
  ```
18
20
 
19
21
  ## Usage: Node
20
22
 
21
23
  ```js
22
- > Plurals = require('make-plural')
23
- { set_rules: [Function], build: [Function] }
24
+ > MakePlural = require('make-plural')
25
+ { [Function] opt: {}, rules: {}, load: [Function] }
24
26
 
25
- > console.log(Plurals.build('sk')) // Slovak
26
- function(n,ord) {
27
+ > sk = MakePlural('sk')
28
+ { [Function] toString: [Function] }
29
+
30
+ > console.log(sk.toString())
31
+ function(n) {
27
32
  var s = String(n).split('.'), i = s[0], v0 = !s[1];
28
33
  return (n == 1 && v0) ? 'one'
29
34
  : ((i >= 2 && i <= 4) && v0) ? 'few'
@@ -31,9 +36,6 @@ function(n,ord) {
31
36
  : 'other';
32
37
  }
33
38
 
34
- > sk = Plurals.build('sk', { 'return_function':1 })
35
- [Function]
36
-
37
39
  > sk(1)
38
40
  'one'
39
41
 
@@ -46,7 +48,10 @@ function(n,ord) {
46
48
  > sk('0')
47
49
  'other'
48
50
 
49
- > console.log(Plurals.build('en', { 'ordinals':1 })) // English
51
+ > en = MakePlural('en', {ordinals:1})
52
+ { [Function] toString: [Function] }
53
+
54
+ > console.log(en.toString())
50
55
  function(n,ord) {
51
56
  var s = String(n).split('.'), v0 = !s[1], t0 = Number(s[0]) == n,
52
57
  n10 = t0 && s[0].substr(-1), n100 = t0 && s[0].substr(-2);
@@ -57,9 +62,6 @@ function(n,ord) {
57
62
  return (n == 1 && v0) ? 'one' : 'other';
58
63
  }
59
64
 
60
- > en = Plurals.build('en', { 'ordinals':1, 'return_function':1 })
61
- [Function]
62
-
63
65
  > en(2)
64
66
  'other'
65
67
 
@@ -72,30 +74,33 @@ function(n,ord) {
72
74
  ```html
73
75
  <script src="path/to/make-plural.js"></script>
74
76
  <script>
75
- console.log(Plurals.build('sk', {'ordinals':1}));
76
- var sk = Plurals.build('sk', { 'ordinals':1, 'return_function':1 });
77
- console.log('1: ' + sk(1) + ', 3.0: ' + sk(3.0) +
78
- ', "1.0": ' + sk('1.0') + ', "0": ' + sk('0'));
77
+ var ru = MakePlural('ru', {ordinals:1});
78
+ console.log(ru.toString());
79
+ console.log('1: ' + ru(1) + ', 3.0: ' + ru(3.0) +
80
+ ', "1.0": ' + ru('1.0') + ', "0": ' + ru('0'));
79
81
  </script>
80
82
  ```
81
83
  With outputs:
82
84
  ```
83
85
  function(n,ord) {
84
- var s = String(n).split('.'), i = s[0], v0 = !s[1];
86
+ var s = String(n).split('.'), i = s[0], v0 = !s[1], i10 = i.substr(-1),
87
+ i100 = i.substr(-2);
85
88
  if (ord) return 'other';
86
- return (n == 1 && v0) ? 'one'
87
- : ((i >= 2 && i <= 4) && v0) ? 'few'
88
- : (!v0) ? 'many'
89
+ return (v0 && i10 == 1 && i100 != 11) ? 'one'
90
+ : (v0 && (i10 >= 2 && i10 <= 4) && (i100 < 12
91
+ || i100 > 14)) ? 'few'
92
+ : (v0 && i10 == 0 || v0 && (i10 >= 5 && i10 <= 9)
93
+ || v0 && (i100 >= 11 && i100 <= 14)) ? 'many'
89
94
  : 'other';
90
95
  }
91
96
 
92
- 1: one, 3.0: few, "1.0": many, "0": other
97
+ 1: one, 3.0: few, "1.0": other, "0": many
93
98
  ```
94
99
 
95
100
  If `request()` isn't available, the CLDR rules are fetched automatically when
96
101
  required using synchronous `XMLHttpRequest` calls for the JSON files at the
97
102
  default locations. If that doesn't work for you, you should call
98
- `Plurals.set_rules(cldr)` before calling `Plurals.build()`.
103
+ `MakePlural.load(cldr)` before calling `MakePlural()`.
99
104
 
100
105
 
101
106
  ## Usage: CLI
@@ -114,7 +119,7 @@ Locales verified ok:
114
119
  to tr ts tzm ug uk ur uz ve vi vo vun wa wae wo xh xog yi yo zh zu
115
120
 
116
121
  $ ./bin/make-plural fr
117
- function(n,ord) {
122
+ function fr(n,ord) {
118
123
  if (ord) return (n == 1) ? 'one' : 'other';
119
124
  return (n >= 0 && n < 2) ? 'one' : 'other';
120
125
  }
@@ -126,41 +131,65 @@ one
126
131
 
127
132
  ## Methods
128
133
 
129
- ### build(lc, opt)
130
- By default, returns a string representation of a function that takes a single
131
- argument `n` and returns its plural category for the given locale `lc`.
134
+ ### MakePlural(lc, opt)
135
+ Returns a function that takes a single argument `n` and returns its plural
136
+ category for the given locale `lc`.
132
137
 
133
- The optional `opt` object may contain the following members, each of which is
138
+ The returned function has an overloaded `toString(name)` method that may be
139
+ used to generate a clean string representation of the function, with an
140
+ optional name `name`.
141
+
142
+ The optional `opt` parameter may contain the following members, each of which is
134
143
  assumed false by default:
135
- * `minify` — if true, the string output of `build` is minified
136
144
  * `no_cardinals` — if true, rules for cardinal values (1 day, 2 days, etc.)
137
145
  are not included
138
146
  * `no_tests` — if true, the generated function is not verified by testing it
139
147
  with each of the example values included in the CLDR rules
140
148
  * `ordinals` — if true, rules for ordinal values (1st, 2nd, etc.) are included
141
149
  * `quiet` — if true, no output is reported to `console.error` on error
142
- * `return_function` — if true, `build` returns an executable function of `n`
143
- rather than a string
144
150
 
145
- if `opt.ordinals` is true and `opt.no_cardinals` is not true, the returned
151
+ If `opt.ordinals` is true and `opt.no_cardinals` is not true, the returned
146
152
  function takes a second parameter `ord`. Then, if `ord` is true, the function
147
153
  will return the ordinal rather than cardinal category applicable to `n` in
148
154
  locale `lc`.
149
155
 
150
- ### set_rules(cldr)
151
- Sets the used CLDR rules to `cldr`, which may be an object or the path to a JSON
152
- file formatted like [this](http://www.unicode.org/repos/cldr-aux/json/26/supplemental/plurals.json).
156
+ If `opt` is not set, it takes the value of `MakePlural.opt`. If `lc` is not set,
157
+ it takes the value of `opt.lc`.
158
+
159
+ In a context where `module.exports` is not available but `exports` is, this
160
+ function is exported as `MakePlural.get()`.
161
+
162
+ ### MakePlural.load(cldr, ...)
163
+ Loads CLDR rules from one or more `cldr` variables, each of which may be an
164
+ object or the path to a JSON file formatted like
165
+ [this](http://www.unicode.org/repos/cldr-aux/json/26/supplemental/plurals.json).
166
+ The stored rules are kept in `MakePlural.rules.cardinal` and
167
+ `MakePlural.rules.ordinal`, which may also be directly accessed.
153
168
 
154
169
  If called within a context where `request()` isn't available and `cldr` is a
155
170
  string, it's taken as the URL of the JSON file that'll be fetched and parsed
156
171
  using a synchronous `XMLHttpRequest`.
157
172
 
158
- By default, `build()` will call `set_rules(cldr)` when required, using the
159
- rules included in `data/`: `unicode-cldr-plural-rules.json` and
173
+ By default, `MakePlural()` will call `MakePlural.load(cldr)` when required,
174
+ using the rules included in `data/`, `unicode-cldr-plural-rules.json` and
160
175
  `unicode-cldr-ordinal-rules.json`.
161
176
 
177
+
162
178
  ## Dependencies
163
179
 
164
180
  None. CLDR plural rule data is included in JSON format; make-plural supports the
165
181
  [LDML Language Plural Rules](http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules)
166
182
  as used in CLDR release 24 and later.
183
+
184
+ Using `MakePlural.load()`, you may make use of external sources of CLDR data.
185
+ For example, the following works when using together with
186
+ [cldr-data](https://www.npmjs.org/package/cldr-data):
187
+ ```js
188
+ > cldr = require('cldr-data');
189
+ > MakePlural = require('make-plural').load(
190
+ cldr('supplemental/plurals'),
191
+ cldr('supplemental/ordinals')
192
+ );
193
+ > MakePlural('ar')(3.14);
194
+ 'other'
195
+ ```
package/bin/make-plural CHANGED
@@ -8,17 +8,20 @@
8
8
  * ./bin/make-plural [lc] [v] // prints the plural category for `v` in locale `lc`
9
9
  */
10
10
 
11
- var Plurals = require('..');
11
+ var MakePlural = require('..');
12
12
 
13
- if (process.argv[3]) {
14
- console.log(Plurals.build(process.argv[2], {'return_function':1})(process.argv[3]));
15
- } else if (process.argv[2]) {
16
- console.log(Plurals.build(process.argv[2], {'ordinals':1}));
13
+ if (process.argv[2]) {
14
+ MakePlural.opt = { 'lc':process.argv[2], 'ordinals':1 };
15
+ if (process.argv[3]) {
16
+ console.log(MakePlural()(process.argv[3]));
17
+ } else {
18
+ console.log(MakePlural().toString(MakePlural.opt.lc));
19
+ }
17
20
  } else {
18
21
  var ok = [];
19
- Plurals.set_rules('./data/unicode-cldr-plural-rules.json');
20
- for (var lc in Plurals.rules['cardinal']) {
21
- if (Plurals.build(lc, {'ordinals':1})) ok.push(lc);
22
+ MakePlural.load('./data/unicode-cldr-plural-rules.json');
23
+ for (var lc in MakePlural.rules['cardinal']) {
24
+ if (MakePlural(lc, {'ordinals':1})) ok.push(lc);
22
25
  else console.error('Locale "' + lc + '" FAILED\n');
23
26
  }
24
27
  console.log('Locales verified ok:' + ok.join(' ').replace(/(.{1,73})( |$)/g, '\n $1'));
package/make-plural.js CHANGED
@@ -59,14 +59,6 @@ function parse(cond, symbols) {
59
59
  .replace(/ = /g, ' == ');
60
60
  }
61
61
 
62
- function test_values(str) {
63
- return str
64
- .replace(/decimal|integer/g, '')
65
- .replace(/^[ ,]+|[ ,…]+$/g, '')
66
- .replace(/(0\.[0-9])~(1\.[1-9])/g, '$1 1.0 $2')
67
- .split(/[ ,~…]+/);
68
- }
69
-
70
62
  function vars(symbols) {
71
63
  var vars = [];
72
64
  if (symbols['i']) vars.push("i = s[0]");
@@ -77,7 +69,7 @@ function vars(symbols) {
77
69
  if (symbols['t0'] || symbols['n10'] || symbols['n100']) vars.push("t0 = Number(s[0]) == n");
78
70
  for (var k in symbols) if (/^.10+$/.test(k)) {
79
71
  var k0 = (k[0] == 'n') ? 't0 && s[0]' : k[0];
80
- vars.push(k + ' = ' + k0 + '.substr(-' + k.substr(2).length + ')');
72
+ vars.push(k + ' = ' + k0 + '.slice(-' + k.substr(2).length + ')');
81
73
  }
82
74
  if (vars.length) {
83
75
  vars.unshift("s = String(n).split('.')");
@@ -86,6 +78,55 @@ function vars(symbols) {
86
78
  return '';
87
79
  }
88
80
 
81
+ function build(lc, opt, tests) {
82
+ var lines = [], symbols = {},
83
+ _fold = function(l) { return l.replace(/(.{1,72})( \|\| |$) ?/gm, '$1\n $2').replace(/\s+$/gm, ''); },
84
+ _compile = function(type, req) {
85
+ var cases = [];
86
+ if (!MakePlural.rules || !MakePlural.rules[type]) {
87
+ MakePlural.load((type == 'ordinal')
88
+ ? './data/unicode-cldr-ordinal-rules.json'
89
+ : './data/unicode-cldr-plural-rules.json');
90
+ }
91
+ if (MakePlural.rules[type][lc]) {
92
+ for (var r in MakePlural.rules[type][lc]) {
93
+ var key = r.replace('pluralRule-count-', ''),
94
+ parts = MakePlural.rules[type][lc][r].split(/@\w*/),
95
+ cond = parts.shift().trim();
96
+ if (cond) cases.push([parse(cond, symbols), key]);
97
+ tests[type][key] = parts.join(' ')
98
+ .replace(/^[ ,]+|[ ,…]+$/g, '')
99
+ .replace(/(0\.[0-9])~(1\.[1-9])/g, '$1 1.0 $2')
100
+ .split(/[ ,~…]+/);
101
+ }
102
+ } else if (req) {
103
+ if (!opt['quiet']) console.error('Locale "' + lc + '" ' + type + ' rules not found');
104
+ return false;
105
+ }
106
+ if (!cases.length) return "'other'";
107
+ if (cases.length == 1) return "(" + cases[0][0] + ") ? '" + cases[0][1] + "' : 'other'";
108
+ return cases.map(function(c) { return "(" + c[0] + ") ? '" + c[1] + "'"; }).concat("'other'").join('\n : ');
109
+ };
110
+
111
+ if (opt['ordinals']) {
112
+ if (opt['no_cardinals']) {
113
+ var l = _compile('ordinal', true);
114
+ if (!l) return null;
115
+ lines.push(_fold(' return ' + l + ';'));
116
+ } else {
117
+ lines.push(_fold(' if (ord) return ' + _compile('ordinal', false) + ';'));
118
+ }
119
+ }
120
+ if (!opt['no_cardinals']) {
121
+ var l = _compile('cardinal', true);
122
+ if (!l) return null;
123
+ lines.push(_fold(' return ' + l + ';'));
124
+ }
125
+ var fn_vars = vars(symbols).replace(/(.{1,78})(,|$) ?/g, '\n $1$2').trim();
126
+ if (fn_vars) lines.unshift(' ' + fn_vars);
127
+ return lines.join('\n');
128
+ }
129
+
89
130
  function test(lc, fn, tests, opt) {
90
131
  var ok = true,
91
132
  _test = function(k, x, ord) {
@@ -114,91 +155,61 @@ function test(lc, fn, tests, opt) {
114
155
  return ok;
115
156
  }
116
157
 
117
- var Plurals = {};
158
+ function xhr_require(src, url) {
159
+ if (src && (url[0] == '.')) url = src.replace(/[^\/]*$/, url);
160
+ var xhr = new XMLHttpRequest();
161
+ xhr.open('get', url, false);
162
+ xhr.send();
163
+ return (xhr.status == 200) && JSON.parse(xhr.responseText);
164
+ }
165
+
118
166
 
119
- Plurals.set_rules = function(cldr) {
120
- var _require = (typeof require == 'function') ? require : function(url) {
121
- if (Plurals.src_url && (url[0] == '.')) url = Plurals.src_url.replace(/[^\/]*$/, url);
122
- var xhr = new XMLHttpRequest();
123
- xhr.open('get', url, false);
124
- xhr.send();
125
- return (xhr.status == 200) && JSON.parse(xhr.responseText);
126
- };
127
- if (typeof cldr == 'string') cldr = _require(cldr);
128
- if (cldr && cldr['supplemental']) {
129
- if (!Plurals.rules) Plurals.rules = {};
130
- if ('plurals-type-cardinal' in cldr['supplemental']) {
131
- Plurals.rules['cardinal'] = cldr['supplemental']['plurals-type-cardinal'];
132
- }
133
- if ('plurals-type-ordinal' in cldr['supplemental']) {
134
- Plurals.rules['ordinal'] = cldr['supplemental']['plurals-type-ordinal'];
135
- }
167
+ var MakePlural = function(lc, opt) {
168
+ if (typeof lc == 'object') { opt = lc; lc = opt.lc; }
169
+ else {
170
+ if (!opt) opt = MakePlural.opt;
171
+ if (!lc) lc = opt.lc;
136
172
  }
173
+ var tests = { 'ordinal':{}, 'cardinal':{} },
174
+ fn_body = build(lc, opt, tests),
175
+ fn = opt['ordinals'] && !opt['no_cardinals']
176
+ ? new Function('n', 'ord', fn_body)
177
+ : new Function('n', fn_body);
178
+ fn.toString = function(name) {
179
+ var s = Function.prototype.toString.call(fn);
180
+ return s.replace(/^function( \w+)?/, name ? 'function ' + name : 'function');
181
+ };
182
+ return fn_body && (opt['no_tests'] || test(lc, fn, tests, opt)) ? fn : null;
137
183
  };
138
184
 
139
- Plurals.build = function(lc, opt) {
140
- var fn, fn_str, fn_vars, lines = [], symbols = {},
141
- tests = { 'ordinal':{}, 'cardinal':{} },
142
- _compile = function(type, indent, req) {
143
- var cases = [];
144
- if (!Plurals.rules || !Plurals.rules[type]) {
145
- Plurals.set_rules((type == 'ordinal')
146
- ? './data/unicode-cldr-ordinal-rules.json'
147
- : './data/unicode-cldr-plural-rules.json');
148
- }
149
- if (Plurals.rules[type][lc]) {
150
- for (var r in Plurals.rules[type][lc]) {
151
- var key = r.replace('pluralRule-count-', ''),
152
- parts = Plurals.rules[type][lc][r].split(/@\w*/),
153
- cond = parts.shift().trim();
154
- if (cond) cases.push([parse(cond, symbols), key]);
155
- tests[type][key] = test_values(parts.join(' '));
156
- }
157
- } else if (req) {
158
- if (!opt['quiet']) console.error('Locale "' + lc + '" ' + type + ' rules not found');
159
- return false;
160
- }
161
- if (!cases.length) return "'other'";
162
- if (cases.length == 1) return "(" + cases[0][0] + ") ? '" + cases[0][1] + "' : 'other'";
163
- return cases.map(function(c) { return "(" + c[0] + ") ? '" + c[1] + "'"; }).concat("'other'").join('\n : ');
164
- }, _fold = function(l) {
165
- return l.replace(/(.{1,72})( \|\| |$) ?/gm, '$1\n $2').replace(/\s+$/gm, '');
166
- };
167
- if (!opt) opt = {};
168
- if (opt['ordinals']) {
169
- if (opt['no_cardinals']) {
170
- var l = _compile('ordinal', ' ', true);
171
- if (!l) return null;
172
- lines.push(_fold(' return ' + l + ';'));
173
- } else {
174
- lines.push(_fold(' if (ord) return ' + _compile('ordinal', ' ', false) + ';'));
175
- }
176
- }
177
- if (!opt['no_cardinals']) {
178
- var l = _compile('cardinal', ' ', true);
179
- if (!l) return null;
180
- lines.push(_fold(' return ' + l + ';'));
185
+ MakePlural.opt = {};
186
+ MakePlural.rules = {};
187
+
188
+ MakePlural.load = function(/* arguments */) {
189
+ var _require = (typeof require == 'function') ? require : function(url) { return xhr_require(MakePlural.src_url, url); };
190
+ if (!MakePlural.rules) MakePlural.rules = {};
191
+ for (var i = 0; i < arguments.length; ++i) {
192
+ var cldr = (typeof arguments[i] == 'string') ? _require(arguments[i]) : arguments[i];
193
+ if (cldr && cldr['supplemental']) ['cardinal', 'ordinal'].forEach(function(type) {
194
+ var set = cldr['supplemental']['plurals-type-' + type];
195
+ if (set) MakePlural.rules[type] = set;
196
+ });
181
197
  }
182
- fn_vars = vars(symbols).replace(/(.{1,78})(,|$) ?/g, '\n $1$2').trim();
183
- if (fn_vars) lines.unshift(' ' + fn_vars);
184
- fn = new Function('n', 'ord', lines.join('\n').trim());
185
- if (!opt['no_tests'] && !test(lc, fn, tests, opt)) return null;
186
- if (opt['return_function']) return fn;
187
- fn_str = (opt['ordinals'] && !opt['no_cardinals'] ? 'function(n,ord)' : 'function(n)')
188
- + ' {\n' + lines.join('\n').replace(/{\s*(return [^;]+;)\s*}/, '$1') + '\n}';
189
- if (opt['minify']) fn_str = fn_str.replace(/\s+/g, '').replace(/{var/, '{var ');
190
- return fn_str;
198
+ return MakePlural;
191
199
  };
192
200
 
201
+
193
202
  if ((typeof module !== 'undefined') && module.exports) {
194
- module.exports = Plurals;
203
+ module.exports = MakePlural;
195
204
  } else if (typeof exports !== 'undefined') {
196
- // won't expose Plurals.rules
197
- for (var p in Plurals) exports[p] = Plurals[p];
205
+ for (var p in MakePlural) exports[p] = MakePlural[p];
206
+ exports.get = MakePlural;
207
+ } else if (typeof define !== 'undefined' && define.amd) {
208
+ define(function() { return MakePlural; });
198
209
  } else {
199
- try { Plurals.src_url = Array.prototype.slice.call(document.getElementsByTagName('script')).pop().src; }
200
- catch (e) { Plurals.src_url = ''; }
201
- global.Plurals = Plurals;
210
+ try { MakePlural.src_url = Array.prototype.slice.call(document.getElementsByTagName('script')).pop().src; }
211
+ catch (e) { MakePlural.src_url = ''; }
212
+ global.MakePlural = MakePlural;
202
213
  }
203
214
 
204
215
  })(this);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-plural",
3
- "version": "1.3.0",
3
+ "version": "2.1.3",
4
4
  "description": "Translates Unicode CLDR pluralization rules to executable JavaScript",
5
5
  "keywords": ["unicode", "cldr", "i18n", "internationalization", "pluralization"],
6
6
  "author": "Eemeli Aro <eemeli@gmail.com>",