-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
202 lines (173 loc) · 5.18 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
'use strict';
var newline = /\r?\n|\r/g;
var escapeRegExp = function (str) {
// source: https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
return str.replace(/([.*+?^${}()|[\]/\\])/g, '\\$1');
};
var trim = function (str) {
return str.replace(/^\s+|\s+$/g, '');
};
var trimQuotes = function (str) {
return str.replace(/^['"]|['"]$/g, '');
};
var isQuote = function (chr) {
return /['"]/.test(chr);
};
var groupParams = function (result, part) {
if (result.length > 0) {
var last = result[result.length - 1];
var firstChar = last[0];
var lastChar = last[last.length - 1];
if (isQuote(firstChar) && (!isQuote(lastChar) || last[last.length - 2] === '\\')) {
// merge with previous
result[result.length - 1] += ',' + part;
} else {
result.push(part);
}
} else {
result.push(part);
}
return result;
};
/**
* Constructor
* @param Object keywordSpec An object with keywords as keys and parameter indexes as values
*/
function Parser (keywordSpec) {
// make new optional
if (!(this instanceof Parser)) {
return new Parser(keywordSpec);
}
keywordSpec = keywordSpec || Parser.keywordSpec;
Object.keys(keywordSpec).forEach(function (keyword) {
var positions = keywordSpec[keyword];
if ('msgid' in positions) {
return;
} else if (Array.isArray(positions) && positions.indexOf('msgid') >= 0) {
// maintain backwards compatibility with `_: ['msgid']` format
keywordSpec[keyword] = positions.reduce(function (result, key, idx) {
result[key] = idx;
return result;
}, {});
} else if (Array.isArray(positions) && positions.length > 0) {
// maintain backwards compatibility with `_: [0]` format
var order = ['msgid', 'msgid_plural'];
keywordSpec[keyword] = positions.slice(0).reduce(function (result, pos, idx) {
result[order[idx]] = pos;
return result;
}, {});
}
});
Object.keys(keywordSpec).forEach(function (keyword) {
if (!('msgid' in keywordSpec[keyword])) {
throw new Error('Every keyword must have a msgid key, but "' + keyword + '" doesn\'t have one');
}
});
this.keywordSpec = keywordSpec;
this.expressionPattern = new RegExp([
'{{ *',
'(' + Object.keys(keywordSpec).map(escapeRegExp).join('|') + ')',
'\\(',
'([\\s\\S]*?)',
'\\)',
' *}}'
].join(''), 'g');
}
// default keywords, copied from GNU xgettext's JavaScript keywords
Parser.keywordSpec = {
_: {
msgid: 0
},
gettext: {
msgid: 0
},
dgettext: {
msgid: 1
},
dcgettext: {
msgid: 1
},
ngettext: {
msgid: 0,
msgid_plural: 1
},
dngettext: {
msgid: 1,
msgid_plural: 2
},
pgettext: {
msgctxt: 0,
msgid: 1
},
npgettext: {
msgctxt: 0,
msgid: 1,
msgid_plural: 2
},
dpgettext: {
msgctxt: 1,
msgid: 2
}
};
// Same as what Jed.js uses
Parser.contextDelimiter = String.fromCharCode(4);
Parser.messageToKey = function (msgid, msgctxt) {
return msgctxt ? msgctxt + Parser.contextDelimiter + msgid : msgid;
};
/**
* Given a Swig template string returns the list of i18n strings.
*
* @param String template The content of a Swig template.
* @return Object The list of translatable strings, the line(s) on which each appears and an optional plural form.
*/
Parser.prototype.parse = function (template) {
var results = {};
var match;
var keyword;
var params;
var msgid;
while ((match = this.expressionPattern.exec(template)) !== null) {
keyword = match[1];
params = match[2].split(',').reduce(groupParams, []).map(trim).map(trimQuotes);
// Parse message.
var msgidIndex = this.keywordSpec[keyword].msgid;
msgid = params[msgidIndex];
// Prepare the result object.
var result = {
msgid: msgid,
line: []
};
// Parse context.
if (this.keywordSpec[keyword].msgctxt !== undefined) {
var contextIndex = this.keywordSpec[keyword].msgctxt;
result.msgctxt = result.msgctxt || params[contextIndex];
}
// Parse plural form.
if (this.keywordSpec[keyword].msgid_plural !== undefined) {
var pluralIndex = this.keywordSpec[keyword].msgid_plural;
var pluralValue = params[pluralIndex];
if (typeof pluralValue !== 'string' && !(pluralValue instanceof String)) {
throw new Error('Plural must be a string literal for msgid ' + result.msgid);
}
var existingPlural = results[result.msgid];
if (existingPlural && existingPlural.plural && (existingPlural.plural !== pluralValue)) {
throw new Error('Incompatible plural definitions for msgid "' + result.msgid +
'" ("' + existingPlural.plural + '" and "' + pluralValue + '")');
}
result.plural = result.msgid_plural = result.plural || pluralValue;
}
// Parse message lines.
result.line.push(template.substr(0, match.index).split(newline).length);
// Define result key.
var resultKey = Parser.messageToKey(result.msgid, result.msgctxt);
// Add result to results.
var foundResult = results[resultKey];
if (foundResult) {
foundResult.line.push(result.line[0]);
} else {
results[resultKey] = result;
}
}
return results;
};
module.exports = Parser;