1 | var reserved = require('reserved-words');
|
2 | var parse = require('messageformat-parser').parse;
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | function Compiler(mf) {
|
14 | this.mf = mf;
|
15 | this.lc = null;
|
16 | this.locales = {};
|
17 | this.runtime = {};
|
18 | this.formatters = {};
|
19 | }
|
20 |
|
21 | module.exports = Compiler;
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | Compiler.propname = function(key, obj) {
|
30 | if (/^[A-Z_$][0-9A-Z_$]*$/i.test(key) &&
|
31 | ['break', 'continue', 'delete', 'else', 'for', 'function', 'if', 'in', 'new',
|
32 | 'return', 'this', 'typeof', 'var', 'void', 'while', 'with', 'case', 'catch',
|
33 | 'default', 'do', 'finally', 'instanceof', 'switch', 'throw', 'try'].indexOf(key) < 0) {
|
34 | return obj ? obj + '.' + key : key;
|
35 | } else {
|
36 | var jkey = JSON.stringify(key);
|
37 | return obj ? obj + '[' + jkey + ']' : jkey;
|
38 | }
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 |
|
44 | Compiler.funcname = function(key) {
|
45 | var fn = key.trim().replace(/\W+/g, '_');
|
46 | return reserved.check(fn, 'es2015', true) || /^\d/.test(fn) ? '_' + fn : fn;
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | Compiler.bidiMarkText = function(text, locale) {
|
60 | function isLocaleRTL(locale) {
|
61 | var rtlLanguages = ['ar', 'ckb', 'fa', 'he', 'ks($|[^bfh])', 'lrc', 'mzn',
|
62 | 'pa-Arab', 'ps', 'ug', 'ur', 'uz-Arab', 'yi'];
|
63 | return new RegExp('^' + rtlLanguages.join('|^')).test(locale);
|
64 | }
|
65 | var mark = JSON.stringify(isLocaleRTL(locale) ? '\u200F' : '\u200E');
|
66 | return mark + ' + ' + text + ' + ' + mark;
|
67 | }
|
68 |
|
69 |
|
70 |
|
71 | Compiler.prototype.cases = function(token, plural) {
|
72 | var needOther = true;
|
73 | var r = token.cases.map(function(c) {
|
74 | if (c.key === 'other') needOther = false;
|
75 | var s = c.tokens.map(function(tok) { return this.token(tok, plural); }, this);
|
76 | return Compiler.propname(c.key) + ': ' + (s.join(' + ') || '""');
|
77 | }, this);
|
78 | if (needOther) throw new Error("No 'other' form found in " + JSON.stringify(token));
|
79 | return '{ ' + r.join(', ') + ' }';
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 | Compiler.prototype.token = function(token, plural) {
|
85 | if (typeof token == 'string') return JSON.stringify(token);
|
86 |
|
87 | var fn, args = [ Compiler.propname(token.arg, 'd') ];
|
88 | switch (token.type) {
|
89 | case 'argument':
|
90 | return this.mf.bidiSupport ? Compiler.bidiMarkText(args[0], this.lc) : args[0];
|
91 |
|
92 | case 'select':
|
93 | fn = 'select';
|
94 | args.push(this.cases(token, this.mf.strictNumberSign ? null : plural));
|
95 | this.runtime.select = true;
|
96 | break;
|
97 |
|
98 | case 'selectordinal':
|
99 | fn = 'plural';
|
100 | args.push(0, Compiler.funcname(this.lc), this.cases(token, token), 1);
|
101 | this.locales[this.lc] = true;
|
102 | this.runtime.plural = true;
|
103 | break;
|
104 |
|
105 | case 'plural':
|
106 | fn = 'plural';
|
107 | args.push(token.offset || 0, Compiler.funcname(this.lc), this.cases(token, token));
|
108 | this.locales[this.lc] = true;
|
109 | this.runtime.plural = true;
|
110 | break;
|
111 |
|
112 | case 'function':
|
113 | if (this.mf.intlSupport && !(token.key in this.mf.fmt) && (token.key in this.mf.constructor.formatters)) {
|
114 | var fmt = this.mf.constructor.formatters[token.key];
|
115 | this.mf.fmt[token.key] = (typeof fmt(this.mf) == 'function') ? fmt(this.mf) : fmt;
|
116 | }
|
117 | if (!this.mf.fmt[token.key]) throw new Error('Formatting function ' + JSON.stringify(token.key) + ' not found!');
|
118 | args.push(JSON.stringify(this.lc));
|
119 | if (token.params) switch (token.params.length) {
|
120 | case 0: break;
|
121 | case 1: args.push(JSON.stringify(token.params[0])); break;
|
122 | default: args.push(JSON.stringify(token.params)); break;
|
123 | }
|
124 | fn = Compiler.propname(token.key, 'fmt');
|
125 | this.formatters[token.key] = true;
|
126 | break;
|
127 |
|
128 | case 'octothorpe':
|
129 | if (!plural) return '"#"';
|
130 | fn = 'number';
|
131 | args = [ Compiler.propname(plural.arg, 'd'), JSON.stringify(plural.arg) ];
|
132 | if (plural.offset) args.push(plural.offset);
|
133 | this.runtime.number = true;
|
134 | break;
|
135 | }
|
136 |
|
137 | if (!fn) throw new Error('Parser error for token ' + JSON.stringify(token));
|
138 | return fn + '(' + args.join(', ') + ')';
|
139 | };
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | Compiler.prototype.compile = function(src, lc, plurals) {
|
154 | if (typeof src != 'object') {
|
155 | this.lc = lc;
|
156 | var pc = plurals[lc] || { cardinal: [], ordinal: [] };
|
157 | var r = parse(src, pc).map(function(token) { return this.token(token); }, this);
|
158 | return 'function(d) { return ' + (r.join(' + ') || '""') + '; }';
|
159 | } else {
|
160 | var result = {};
|
161 | for (var key in src) {
|
162 | var lcKey = plurals.hasOwnProperty(key) ? key : lc;
|
163 | result[key] = this.compile(src[key], lcKey, plurals);
|
164 | }
|
165 | return result;
|
166 | }
|
167 | }
|