1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | 'use strict';
|
12 |
|
13 | var cheerio = require('cheerio');
|
14 | var espree = require('espree');
|
15 | var _ = require('lodash');
|
16 |
|
17 | var escapeRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;
|
18 |
|
19 | function mkAttrRegex(startDelim, endDelim) {
|
20 | var start = startDelim.replace(escapeRegex, '\\$&');
|
21 | var end = endDelim.replace(escapeRegex, '\\$&');
|
22 |
|
23 | if (start === '' && end === '') {
|
24 | start = '^';
|
25 | } else {
|
26 |
|
27 | start += '(?:\\s*\\:\\:\\s*)?';
|
28 | }
|
29 |
|
30 | return new RegExp(start +
|
31 | '\\s*(\'|"|"|')(.*?)\\1\\s*\\|\\s*translate\\s*(' +
|
32 | end + '|\\|)', 'g');
|
33 | }
|
34 |
|
35 | var noDelimRegex = mkAttrRegex('', '');
|
36 |
|
37 | function walkJs(node, fn, parentComment) {
|
38 | fn(node, parentComment);
|
39 | for (var key in node) {
|
40 | if (node.hasOwnProperty(key)) {
|
41 | var obj = node[key];
|
42 | if (node && node.leadingComments) {
|
43 | parentComment = node;
|
44 | }
|
45 | if (typeof obj === 'object') {
|
46 | walkJs(obj, fn, parentComment);
|
47 | }
|
48 | }
|
49 | }
|
50 | }
|
51 |
|
52 | function getJSExpression(node) {
|
53 | var res = '';
|
54 | if (node.type === 'Literal') {
|
55 | res = node.value;
|
56 | }
|
57 | return res;
|
58 | }
|
59 |
|
60 | function getJSExpressionString(node) {
|
61 | var res = '';
|
62 | if (node.type === 'Literal') {
|
63 | res = node.raw;
|
64 | }
|
65 | return res;
|
66 | }
|
67 |
|
68 | var Impeller = (function () {
|
69 | function Impeller(options) {
|
70 | this.options = _.extend({
|
71 | startDelim: '{{',
|
72 | endDelim: '}}',
|
73 | markerName: 'gettext',
|
74 | markerNames: [],
|
75 | attribute: 'translate',
|
76 | attributes: [],
|
77 | lineNumbers: true,
|
78 | extensions: {
|
79 | htm: 'html',
|
80 | html: 'html',
|
81 | php: 'html',
|
82 | phtml: 'html',
|
83 | tml: 'html',
|
84 | ejs: 'html',
|
85 | erb: 'html',
|
86 | js: 'js'
|
87 | },
|
88 | postProcess: function () {}
|
89 | }, options);
|
90 | this.options.markerNames.unshift(this.options.markerName);
|
91 | this.options.attributes.unshift(this.options.attribute);
|
92 |
|
93 | this.strings = {};
|
94 | this.attrRegex = mkAttrRegex(this.options.startDelim,
|
95 | this.options.endDelim);
|
96 | }
|
97 |
|
98 | Impeller.isValidStrategy = function (strategy) {
|
99 | return strategy === 'html' || strategy === 'js';
|
100 | };
|
101 |
|
102 | Impeller.mkAttrRegex = mkAttrRegex;
|
103 |
|
104 | |
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | Impeller.prototype.translateReplace = function (origString, origNode,
|
117 | jsonData, context, jsFuncName) {
|
118 | origString = origString.trim();
|
119 |
|
120 | if (typeof jsonData[origString] !== 'undefined') {
|
121 | var newString = '';
|
122 | if (context && typeof jsonData[origString] === 'object' &&
|
123 | typeof jsonData[origString][context] !== 'undefined') {
|
124 | newString = jsonData[origString][context];
|
125 | } else {
|
126 | newString = jsonData[origString];
|
127 | }
|
128 | var newNode = origNode.replace(origString, newString);
|
129 | var origRegex;
|
130 | if (jsFuncName) {
|
131 | var escapedNode = origNode.replace(/([\^\$\.\|\?\*\+\(\)\[\{])/g, '\\$1');
|
132 | origRegex = new RegExp(jsFuncName + ' *\\( *' + escapedNode + ' *\\)');
|
133 | this.newSrc = this.newSrc.replace(origRegex, function () {
|
134 | return jsFuncName + '(' + newNode + ')';
|
135 | });
|
136 | } else {
|
137 | this.newSrc = this.newSrc.replace(origNode, newNode);
|
138 | }
|
139 | this.replaceCount++;
|
140 | }
|
141 | };
|
142 |
|
143 | Impeller.prototype.translateTextNode = function (node, jsonData, context) {
|
144 | var trimmedOriginal = node.data.trim();
|
145 | var translation = jsonData[trimmedOriginal];
|
146 | if (typeof translation !== 'undefined') {
|
147 | var translatedString = '';
|
148 | if (context && _typeof(translation) === 'object' &&
|
149 | typeof translation[context] !== 'undefined') {
|
150 | translatedString = translation[context];
|
151 | } else {
|
152 | translatedString = translation;
|
153 | }
|
154 | node.data = node.data.replace(trimmedOriginal, translatedString);
|
155 | this.replaceCount++;
|
156 | }
|
157 | };
|
158 |
|
159 | Impeller.prototype.translateInnerHTML = function (node, jsonData, context) {
|
160 | var _this = this;
|
161 | var trimmedHTML = node.html().trim();
|
162 | var translation = jsonData[trimmedHTML];
|
163 | if (typeof translation !== 'undefined') {
|
164 | var translatedString = '';
|
165 | if (context && _typeof(translation) === 'object' &&
|
166 | typeof translation[context] !== 'undefined') {
|
167 | translatedString = translation[context];
|
168 | } else {
|
169 | translatedString = translation;
|
170 | }
|
171 | node.html(node.html().replace(trimmedHTML, translatedString));
|
172 | this.replaceCount++;
|
173 | }
|
174 | };
|
175 |
|
176 | Impeller.prototype.replaceHtml = function (filename, src, jsonData) {
|
177 | var replaceHtml = function (src) {
|
178 | var $ = cheerio.load(src, {
|
179 | decodeEntities: false,
|
180 | withStartIndices: true
|
181 | });
|
182 | var _this = this;
|
183 | var matches;
|
184 | var newlines = function (index) {
|
185 | return src.substr(0, index).match(/\n/g) || [];
|
186 | };
|
187 |
|
188 | $('*').each(function (index, n) {
|
189 | var node = $(n);
|
190 | var getAttr = function (attr) {
|
191 | return node.attr(attr) || node.data(attr);
|
192 | };
|
193 | var str = node.html();
|
194 | var extracted = {};
|
195 | var possibleAttributes = _this.options.attributes;
|
196 |
|
197 | possibleAttributes.forEach(function (attr) {
|
198 | extracted[attr] = {
|
199 | plural: getAttr(attr + '-plural'),
|
200 | extractedComment: getAttr(attr + '-comment'),
|
201 | context: getAttr(attr + '-context')
|
202 | };
|
203 | });
|
204 |
|
205 | if (n.name === 'script') {
|
206 | if (n.attribs.type === 'text/ng-template') {
|
207 | replaceHtml(node.text(), newlines(n.startIndex).length);
|
208 | return;
|
209 | }
|
210 |
|
211 |
|
212 |
|
213 | if (!n.attribs.type || n.attribs.type === 'text/javascript') {
|
214 | _this.replaceJs(filename, node.text(), jsonData,
|
215 | newlines(n.startIndex).length);
|
216 | return;
|
217 | }
|
218 | }
|
219 |
|
220 | if (node.is('translate')) {
|
221 | _this.translateInnerHTML(node, jsonData);
|
222 | return;
|
223 | }
|
224 |
|
225 |
|
226 | var attrs = node.attr();
|
227 | for (var attr in attrs) {
|
228 | if (attrs.hasOwnProperty(attr)) {
|
229 | attr = attr.replace(/^data-/, '');
|
230 | if (possibleAttributes.indexOf(attr) > -1) {
|
231 | var attrValue = extracted[attr];
|
232 |
|
233 | _this.translateInnerHTML(node, jsonData,
|
234 | attrValue.context);
|
235 | } else if (matches = noDelimRegex.exec(node.attr(attr))) {
|
236 | str = matches[2].replace(/\\\'/g, '\'');
|
237 | noDelimRegex.lastIndex = 0;
|
238 | }
|
239 | }
|
240 | }
|
241 | });
|
242 |
|
243 | _this.newSrc = $.html();
|
244 |
|
245 | while (matches = this.attrRegex.exec(src)) {
|
246 | var str = matches[2].replace(/\\\'/g, '\'');
|
247 | _this.translateReplace(str, matches[0], jsonData);
|
248 | }
|
249 | }.bind(this);
|
250 |
|
251 | replaceHtml(src, 0);
|
252 | };
|
253 |
|
254 | Impeller.prototype.replaceJs =
|
255 | function (filename, src, jsonData, lineNumber) {
|
256 |
|
257 | lineNumber = lineNumber || 0;
|
258 | var _this = this;
|
259 | var syntax;
|
260 | try {
|
261 | syntax = espree.parse(src, {
|
262 | tolerant: true,
|
263 | range: true,
|
264 | attachComment: true,
|
265 | loc: true,
|
266 | token: true,
|
267 | ecmaFeatures: {
|
268 | arrowFunctions: true,
|
269 | blockBindings: true,
|
270 | destructuring: true,
|
271 | regexYFlag: true,
|
272 | regexUFlag: true,
|
273 | templateStrings: true,
|
274 | binaryLiterals: true,
|
275 | octalLiterals: true,
|
276 | unicodeCodePointEscapes: true,
|
277 | defaultParams: true,
|
278 | restParams: true,
|
279 | forOf: true,
|
280 | objectLiteralComputedProperties: true,
|
281 | objectLiteralShorthandMethods: true,
|
282 | objectLiteralShorthandProperties: true,
|
283 | objectLiteralDuplicateProperties: true,
|
284 | generators: true,
|
285 | spread: true,
|
286 | classes: true,
|
287 | modules: true,
|
288 | jsx: true,
|
289 | globalReturn: true
|
290 | }
|
291 | });
|
292 | } catch (err) {
|
293 | console.log("Error of parsing js file: ", filename);
|
294 | return;
|
295 | }
|
296 |
|
297 | _this.newSrc = src;
|
298 |
|
299 | function isGettext(node) {
|
300 | return node !== null &&
|
301 | node.type === 'CallExpression' &&
|
302 | node.callee !== null &&
|
303 | (_this.options.markerNames.indexOf(node.callee.name) > -1 || (
|
304 | node.callee.property &&
|
305 | _this.options.markerNames.indexOf(node.callee.property.name) > -1
|
306 | )) &&
|
307 | node.arguments !== null &&
|
308 | node.arguments.length;
|
309 | }
|
310 |
|
311 | function isGetString(node) {
|
312 | return node !== null &&
|
313 | node.type === 'CallExpression' &&
|
314 | node.callee !== null &&
|
315 | node.callee.type === 'MemberExpression' &&
|
316 | node.callee.object !== null && (
|
317 | node.callee.object.name === 'gettextCatalog' || (
|
318 |
|
319 |
|
320 | node.callee.object.property &&
|
321 | node.callee.object.property.name === 'gettextCatalog')) &&
|
322 | node.callee.property !== null &&
|
323 | node.callee.property.name === 'getString' &&
|
324 | node.arguments !== null &&
|
325 | node.arguments.length;
|
326 | }
|
327 |
|
328 | walkJs(syntax, function (node) {
|
329 | var phrase, nodeStr, context;
|
330 | if (isGettext(node) || isGetString(node)) {
|
331 | phrase = getJSExpression(node.arguments[0]);
|
332 | nodeStr = getJSExpressionString(node.arguments[0]);
|
333 |
|
334 | if (node.arguments[2]) {
|
335 | context = getJSExpression(node.arguments[2]);
|
336 | }
|
337 | _this.translateReplace(phrase, nodeStr, jsonData, context);
|
338 | }
|
339 | });
|
340 | };
|
341 |
|
342 | Impeller.prototype.isSupportedByStrategy = function (strategy, extension) {
|
343 | return (extension in this.options.extensions) &&
|
344 | (this.options.extensions[extension] === strategy);
|
345 | };
|
346 |
|
347 | Impeller.prototype.processFile = function (filename, content, jsonData) {
|
348 | var extension = filename.split('.').pop();
|
349 | delete this.newSrc;
|
350 | this.replaceCount = 0;
|
351 | if (this.isSupportedByStrategy('html', extension)) {
|
352 | this.replaceHtml(filename, content, jsonData);
|
353 | }
|
354 | if (this.isSupportedByStrategy('js', extension)) {
|
355 | this.replaceJs(filename, content, jsonData);
|
356 | }
|
357 | return this.replaceCount;
|
358 | };
|
359 |
|
360 | Impeller.prototype.toString = function () {
|
361 | return this.newSrc
|
362 | .replace(/ translate\=""/g, ' translate');
|
363 | };
|
364 |
|
365 | return Impeller;
|
366 | })();
|
367 |
|
368 | module.exports = Impeller;
|