easygettext
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -8,2 +8,10 @@ 'use strict'; | ||
var DEFAULT_FILTERS = exports.DEFAULT_FILTERS = ['i18n', 'translate']; | ||
var DEFAULT_START_DELIMITER = exports.DEFAULT_START_DELIMITER = '{{'; | ||
var DEFAULT_END_DELIMITER = exports.DEFAULT_END_DELIMITER = '}}'; | ||
// Could for example be '::', used by AngularJS to indicate one-time bindings. | ||
var DEFAULT_FILTER_PREFIX = exports.DEFAULT_FILTER_PREFIX = null; | ||
var DEFAULT_DELIMITERS = exports.DEFAULT_DELIMITERS = { | ||
@@ -10,0 +18,0 @@ start: '{{', |
@@ -41,19 +41,27 @@ #!/usr/bin/env node | ||
var extraAttribute = argv.attribute || false; | ||
var extraFilter = argv.filter || false; | ||
var filterPrefix = argv.filterPrefix || constants.DEFAULT_FILTER_PREFIX; | ||
if (!quietMode && (!files || files.length === 0)) { | ||
console.log('Usage:\n\tgettext-extract [--attribute EXTRA-ATTRIBUTE] [--output OUTFILE] <FILES>'); | ||
console.log('Usage:\n\tgettext-extract [--attribute EXTRA-ATTRIBUTE] [--filterPrefix FILTER-PREFIX] [--output OUTFILE] <FILES>'); | ||
process.exit(1); | ||
} | ||
var attributes = constants.DEFAULT_ATTRIBUTES.slice(); | ||
if (extraAttribute) { | ||
if (typeof extraAttribute === 'string') { | ||
// Only one extra attribute was passed. | ||
attributes.push(extraAttribute); | ||
} else { | ||
// Multiple extra attributes were passed. | ||
attributes = attributes.concat(extraAttribute); | ||
function _getExtraNames(extraEntities, defaultEntities) { | ||
var attributes = defaultEntities.slice(); | ||
if (extraEntities) { | ||
if (typeof extraEntities === 'string') { | ||
// Only one extra attribute was passed. | ||
attributes.push(extraEntities); | ||
} else { | ||
// Multiple extra attributes were passed. | ||
attributes = attributes.concat(extraEntities); | ||
} | ||
} | ||
return attributes; | ||
} | ||
var attributes = _getExtraNames(extraAttribute, constants.DEFAULT_ATTRIBUTES); | ||
var filters = _getExtraNames(extraFilter, constants.DEFAULT_FILTERS); | ||
// Extract strings | ||
@@ -63,2 +71,4 @@ var extractor = new _extract.Extractor({ | ||
attributes: attributes, | ||
filters: filters, | ||
filterPrefix: filterPrefix, | ||
startDelimiter: startDelimiter, | ||
@@ -65,0 +75,0 @@ endDelimiter: endDelimiter |
@@ -16,2 +16,4 @@ 'use strict'; | ||
var _utils = require('cheerio/lib/utils'); | ||
var _pofile = require('pofile'); | ||
@@ -90,4 +92,12 @@ | ||
this.reference = reference; | ||
this.context = getExtraAttribute(node, attributes, constants.ATTRIBUTE_CONTEXT) || constants.MARKER_NO_CONTEXT; | ||
this.comment = getExtraAttribute(node, attributes, constants.ATTRIBUTE_COMMENT); | ||
var el = node[0]; | ||
/* NOTE: It might make sense to let _all_ TEXT child nodes inherit the | ||
* `context` and `comment` from the parent, not only single children. | ||
* However, the following conditions generate output equal to | ||
* `angular-gettext-tools`. */ | ||
var doInheritContext = el.type === 'text' && el.prev === null && el.next === null; | ||
this.context = getExtraAttribute(doInheritContext ? node.parent() : node, attributes, constants.ATTRIBUTE_CONTEXT) || constants.MARKER_NO_CONTEXT; | ||
this.comment = getExtraAttribute(doInheritContext ? node.parent() : node, attributes, constants.ATTRIBUTE_COMMENT); | ||
this.plural = getExtraAttribute(node, attributes, constants.ATTRIBUTE_PLURAL); | ||
@@ -117,10 +127,10 @@ } | ||
function Extractor(options) { | ||
var _this = this; | ||
_classCallCheck(this, Extractor); | ||
this.options = _extends({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}', | ||
startDelimiter: constants.DEFAULT_START_DELIMITER, | ||
endDelimiter: constants.DEFAULT_END_DELIMITER, | ||
attributes: constants.DEFAULT_ATTRIBUTES, | ||
filters: constants.DEFAULT_FILTERS, | ||
filterPrefix: constants.DEFAULT_FILTER_PREFIX, | ||
lineNumbers: false | ||
@@ -139,12 +149,34 @@ }, options); | ||
this.items = {}; | ||
this.filterRegexps = this.options.attributes.map(function (attribute) { | ||
var startOrEndQuotes = '(?:\\"|[\\\'"])'; // matches simple / double / HTML quotes | ||
var spacesOrPipeChar = '\\s*\\|\\s*'; // matches the pipe string of the filter | ||
var start = _this.options.startDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
var end = _this.options.endDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
return new RegExp('^' + start + '[^\'"]*' + startOrEndQuotes + '(.*)' + startOrEndQuotes + spacesOrPipeChar + attribute + '\\s*' + end + '$'); | ||
}); | ||
this.attrFilterRegexps = this.createRegexps('attr'); | ||
this.textFilterRegexps = this.createRegexps('text'); | ||
this.splitStringRe = /(?:['"])\s*\+\s*(?:['"])/g; | ||
} | ||
_createClass(Extractor, [{ | ||
key: 'createRegexps', | ||
value: function createRegexps(type) { | ||
var startOrEndQuotes = '(\\"|[\\\'"])'; // matches simple / double / HTML quotes | ||
var spacesOrPipeChar = '\\s*\\|\\s*'; // matches the pipe string of the filter | ||
var startDelimiter = this.options.startDelimiter; | ||
var endDelimiter = this.options.endDelimiter; | ||
if (type === 'text') { | ||
if (startDelimiter === '') { | ||
startDelimiter = constants.DEFAULT_START_DELIMITER; | ||
} | ||
if (endDelimiter === '') { | ||
endDelimiter = constants.DEFAULT_END_DELIMITER; | ||
} | ||
} | ||
var start = startDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
var end = endDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
var prefix = this.options.filterPrefix === null ? '' : '\\s*(?:' + this.options.filterPrefix + ')?\\s*'; | ||
var body = endDelimiter === '' ? '((.|\\n)*)' : '((.|\\n)*?(?!' + end + '))'; | ||
return this.options.filters.map(function (filter) { | ||
return new RegExp('' + start + prefix + '[^\'"]*' + startOrEndQuotes + body + '\\1' + spacesOrPipeChar + filter + '\\s*' + end, 'g'); | ||
}); | ||
} | ||
}, { | ||
key: 'parse', | ||
@@ -246,46 +278,89 @@ value: function parse(filename, content) { | ||
}, { | ||
key: '_extractTranslationData', | ||
value: function _extractTranslationData(filename, content) { | ||
var $ = _cheerio2.default.load(content, { | ||
decodeEntities: false, | ||
withStartIndices: true | ||
}); | ||
key: 'getAttrsAndDatas', | ||
value: function getAttrsAndDatas(node) { | ||
if (node[0].type === 'text' || node[0].type === 'comment') { | ||
return [{ text: node[0].data.trim(), type: 'text' }]; | ||
} | ||
return $('*').map(function (_i, el) { | ||
var _this2 = this; | ||
var data = node.data(); | ||
var attr = node.attr(); | ||
return [].concat(_toConsumableArray(Object.keys(data).map(function (key) { | ||
return { text: data[key], type: 'data' }; | ||
})), _toConsumableArray(Object.keys(attr).map(function (key) { | ||
return { text: attr[key], type: 'attr' }; | ||
}))); | ||
} | ||
}, { | ||
key: '_parseElement', | ||
value: function _parseElement($, el, filename, content) { | ||
var _this = this; | ||
var node = $(el); | ||
var reference = new TranslationReference(filename, content, el.startIndex); | ||
if (this._hasTranslationToken(node)) { | ||
var text = node.html().trim(); | ||
if (text.length !== 0) { | ||
return [new NodeTranslationInfo(node, text, reference, this.options.attributes)]; | ||
} | ||
if (el.type === 'comment' && (0, _utils.isHtml)(el.data)) { | ||
// Recursive parse call if el.data is recognized as HTML. | ||
return this._extractTranslationDataFromNodes(Array.from($(el.data)), $, filename, content); | ||
} | ||
var reference = new TranslationReference(filename, content, el.startIndex); | ||
var node = $(el); | ||
if (this._hasTranslationToken(node)) { | ||
var text = node.html().trim(); | ||
if (text.length !== 0) { | ||
return [new NodeTranslationInfo(node, text, reference, this.options.attributes)]; | ||
} | ||
} | ||
// In-depth search for filters | ||
var attrs = Object.keys(node.attr()).map(function (key) { | ||
return node.attr()[key]; | ||
}); | ||
var datas = Object.keys(node.data()).map(function (key) { | ||
return node.data()[key]; | ||
}); | ||
var tokensFromFilters = []; | ||
[node.html()].concat(_toConsumableArray(attrs), _toConsumableArray(datas)).forEach(function (item) { | ||
var matches = _this2.filterRegexps.map(function (re) { | ||
return re.exec(item); | ||
}).filter(function (x) { | ||
return x !== null; | ||
}); | ||
if (matches.length !== 0) { | ||
var _text = matches[0][1].trim(); | ||
if (_text.length !== 0) { | ||
tokensFromFilters.push(new NodeTranslationInfo(node, _text, reference, _this2.options.attributes)); | ||
// In-depth search for filters | ||
return this.getAttrsAndDatas(node).reduce(function (tokensFromFilters, item) { | ||
function _getAllMatches(matches, re) { | ||
while (true) { | ||
var match = re.exec(item.text); | ||
if (match === null) { | ||
break; | ||
} | ||
matches.push(match); | ||
} | ||
return matches; | ||
} | ||
var regexps = item.type === 'text' ? _this.textFilterRegexps : _this.attrFilterRegexps; | ||
regexps.reduce(_getAllMatches, []).filter(function (match) { | ||
return match.length; | ||
}).map(function (match) { | ||
return match[2].trim(); | ||
}).filter(function (text) { | ||
return text.length !== 0; | ||
}).map(function (text) { | ||
return text.split(_this.splitStringRe).join(''); | ||
}).forEach(function (text) { | ||
tokensFromFilters.push(new NodeTranslationInfo(node, text, reference, _this.options.attributes)); | ||
}); | ||
if (tokensFromFilters.length > 0) { | ||
return tokensFromFilters; | ||
return tokensFromFilters; | ||
}, []); | ||
} | ||
}, { | ||
key: '_traverseTree', | ||
value: function _traverseTree(nodes, sequence) { | ||
var _this2 = this; | ||
nodes.forEach(function (el) { | ||
sequence.push(el); | ||
if (typeof el.children !== 'undefined') { | ||
_this2._traverseTree(el.children, sequence); | ||
} | ||
}.bind(this)).get().reduce(function (acc, cur) { | ||
}); | ||
return sequence; | ||
} | ||
}, { | ||
key: '_extractTranslationDataFromNodes', | ||
value: function _extractTranslationDataFromNodes(rootChildren, $, filename, content) { | ||
var _this3 = this; | ||
return this._traverseTree(rootChildren, []).filter(function (el) { | ||
return el.type === 'tag' || el.type === 'text' || el.type === 'comment'; | ||
}).map(function (el) { | ||
return _this3._parseElement($, el, filename, content); | ||
}).reduce(function (acc, cur) { | ||
return acc.concat(cur); | ||
@@ -297,2 +372,12 @@ }, []).filter(function (x) { | ||
}, { | ||
key: '_extractTranslationData', | ||
value: function _extractTranslationData(filename, content) { | ||
var $ = _cheerio2.default.load(content, { | ||
decodeEntities: false, | ||
withStartIndices: true | ||
}); | ||
return this._extractTranslationDataFromNodes($.root()[0].children, $, filename, content); | ||
} | ||
}, { | ||
key: '_hasTranslationToken', | ||
@@ -299,0 +384,0 @@ value: function _hasTranslationToken(node) { |
@@ -85,3 +85,5 @@ 'use strict'; | ||
describe('Raw translation data', function () { | ||
var extractor = new _extract.Extractor(); | ||
var extractor = new _extract.Extractor({ | ||
filterPrefix: '::' | ||
}); | ||
@@ -147,4 +149,5 @@ it('should extract data and metadata correctly', function () { | ||
var extractorWithBindOnce = new _extract.Extractor({ | ||
startDelimiter: '::', | ||
endDelimiter: '' | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::' | ||
}); | ||
@@ -156,23 +159,221 @@ var data7 = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML3_FILTER7); | ||
it('should handle complex nesting constructs with multiple interpolated filters', function () { | ||
var extractorWithBindOnce = new _extract.Extractor({ | ||
filterPrefix: '::' | ||
}); | ||
var data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_COMPLEX_NESTING); | ||
(0, _chai.expect)(data.length).to.equal(13); | ||
(0, _chai.expect)(data[0].text).to.equal('<div translate="" translate-comment="Inner comment \u2026" translate-context="Inner Context">\n Before {{:: \'I18n before\' |translate }}\n <a href="#" aria-label="{{ \'Test link 1\' |translate }}">\n {{ \'Link part 1\' |translate }}\n {{:: \'Link part 2\' |translate }}\n {{ \'Link part 3\' |translate }}</a>\n Between {{:: \'I18n between\' |translate }}\n <a href="#" aria-label="{{ \'Test link 2\' |translate }}">\n {{ \'Reference part 1\' |translate }}\n {{:: \'Reference part 2\' |translate }}\n {{ \'Reference part 3\' |translate }}</a>\n After {{:: \'I18n after\' |translate }}\n </div>'); | ||
(0, _chai.expect)(data[0].comment).to.equal('Outer comment …'); | ||
(0, _chai.expect)(data[0].context).to.equal('Outer Context'); | ||
(0, _chai.expect)(data[1].text).to.equal('Before {{:: \'I18n before\' |translate }}\n <a href="#" aria-label="{{ \'Test link 1\' |translate }}">\n {{ \'Link part 1\' |translate }}\n {{:: \'Link part 2\' |translate }}\n {{ \'Link part 3\' |translate }}</a>\n Between {{:: \'I18n between\' |translate }}\n <a href="#" aria-label="{{ \'Test link 2\' |translate }}">\n {{ \'Reference part 1\' |translate }}\n {{:: \'Reference part 2\' |translate }}\n {{ \'Reference part 3\' |translate }}</a>\n After {{:: \'I18n after\' |translate }}'); | ||
(0, _chai.expect)(data[1].comment).to.equal('Inner comment …'); | ||
(0, _chai.expect)(data[1].context).to.equal('Inner Context'); | ||
(0, _chai.expect)(data[2].text).to.equal('I18n before'); | ||
(0, _chai.expect)(data[2].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[3].text).to.equal('Test link 1'); | ||
(0, _chai.expect)(data[3].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[4].text).to.equal('Link part 1'); | ||
(0, _chai.expect)(data[4].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[5].text).to.equal('Link part 2'); | ||
(0, _chai.expect)(data[5].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[6].text).to.equal('Link part 3'); | ||
(0, _chai.expect)(data[6].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[7].text).to.equal('I18n between'); | ||
(0, _chai.expect)(data[7].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[8].text).to.equal('Test link 2'); | ||
(0, _chai.expect)(data[8].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[9].text).to.equal('Reference part 1'); | ||
(0, _chai.expect)(data[9].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[10].text).to.equal('Reference part 2'); | ||
(0, _chai.expect)(data[10].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[11].text).to.equal('Reference part 3'); | ||
(0, _chai.expect)(data[11].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[12].text).to.equal('I18n after'); | ||
(0, _chai.expect)(data[12].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
}); | ||
it('should extract filters from nested constructs', function () { | ||
var extractorWithBindOnce = new _extract.Extractor({ | ||
startDelimiter: '::', | ||
endDelimiter: '' | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::' | ||
}); | ||
var data8 = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_NESTED_FILTER); | ||
(0, _chai.expect)(data8.length).to.equal(1); | ||
(0, _chai.expect)(data8[0].text).to.equal('Votes <i class=\'fa fa-star\'></i>'); | ||
var data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_NESTED_FILTER); | ||
(0, _chai.expect)(data.length).to.equal(4); | ||
(0, _chai.expect)(data[0].text).to.equal('Like'); | ||
(0, _chai.expect)(data[1].text).to.equal('Gets extracted now'); | ||
(0, _chai.expect)(data[2].text).to.equal('Number of votes'); | ||
(0, _chai.expect)(data[3].text).to.equal('Votes <i class=\'fa fa-star\'></i>'); | ||
}); | ||
var extractorWithInterpolateBindOnce = new _extract.Extractor({ | ||
startDelimiter: '{{::', | ||
it('should extract filters that are broken across multiple lines', function () { | ||
var extractorInterpolate = new _extract.Extractor({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}' | ||
}); | ||
var data9 = extractorWithInterpolateBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_NESTED_FILTER); | ||
(0, _chai.expect)(data9.length).to.equal(3); | ||
(0, _chai.expect)(data9[0].text).to.equal('Like'); | ||
(0, _chai.expect)(data9[1].text).to.equal('Gets extracted now'); | ||
(0, _chai.expect)(data9[2].text).to.equal('Number of votes'); | ||
var data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_LINEBREAK_FILTER); | ||
(0, _chai.expect)(data.length).to.equal(5); | ||
(0, _chai.expect)(data[0].text).to.equal('Multi-line 0'); | ||
(0, _chai.expect)(data[1].text).to.equal('Multi-line 1'); | ||
(0, _chai.expect)(data[2].text).to.equal('Multi-line 2'); | ||
(0, _chai.expect)(data[3].text).to.equal('Multi-line 3'); | ||
(0, _chai.expect)(data[4].text).to.equal('Multi-line 4'); | ||
}); | ||
it('should extract filters from text before and after elements', function () { | ||
var extractorInterpolate = new _extract.Extractor({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}' | ||
}); | ||
var data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_FILTER); | ||
(0, _chai.expect)(data.length).to.equal(5); | ||
(0, _chai.expect)(data[0].text).to.equal('Outside 0'); | ||
(0, _chai.expect)(data[1].text).to.equal('Text 0'); | ||
(0, _chai.expect)(data[2].text).to.equal('Between 0'); | ||
(0, _chai.expect)(data[3].text).to.equal('Text 3'); | ||
(0, _chai.expect)(data[4].text).to.equal('Outside 1'); | ||
}); | ||
it('should extract multiple filters from text blocks', function () { | ||
var extractorInterpolate = new _extract.Extractor({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}' | ||
}); | ||
var data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_MULTIPLE_FILTER); | ||
(0, _chai.expect)(data.length).to.equal(3); | ||
(0, _chai.expect)(data[0].text).to.equal('Text 0'); | ||
(0, _chai.expect)(data[1].text).to.equal('Text 1'); | ||
(0, _chai.expect)(data[2].text).to.equal('Text 2'); | ||
}); | ||
it('should extract filters from text blocks with empty delimiters', function () { | ||
var extractor = new _extract.Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '' | ||
}); | ||
var data = extractor._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_CHALLENGE); | ||
(0, _chai.expect)(data.length).to.equal(3); | ||
(0, _chai.expect)(data[0].text).to.equal('Thanks for joining …. However, … does not start until'); | ||
(0, _chai.expect)(data[1].text).to.equal(', but will open'); | ||
(0, _chai.expect)(data[2].text).to.equal('minutes before that.'); | ||
}); | ||
it('should ignore comments and directives when extracting filters', function () { | ||
var extractorInterpolate = new _extract.Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::' | ||
}); | ||
var data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_FILTER_COMMENT); | ||
(0, _chai.expect)(data.length).to.equal(1); | ||
(0, _chai.expect)(data[0].text).to.equal('Text 1'); | ||
}); | ||
it('should join split strings', function () { | ||
var extractorInterpolate = new _extract.Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::' | ||
}); | ||
var data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_FILTER_SPLIT_STRING); | ||
(0, _chai.expect)(data.length).to.equal(1); | ||
(0, _chai.expect)(data[0].text).to.equal('Three parts, one whole.'); | ||
}); | ||
it('should join strings split over multiple lines', function () { | ||
var extractorInterpolate = new _extract.Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::' | ||
}); | ||
var data0 = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_FILTER_SPLIT_MULTILINE_STRING_ATTR); | ||
(0, _chai.expect)(data0.length).to.equal(1); | ||
(0, _chai.expect)(data0[0].text).to.equal('Four parts, maybe, one whole.'); | ||
var data1 = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_FILTER_SPLIT_MULTILINE_STRING_INTERPOLATED); | ||
(0, _chai.expect)(data1.length).to.equal(1); | ||
(0, _chai.expect)(data1[0].text).to.equal('Four parts, probably, one whole.'); | ||
}); | ||
it('should extract translatable strings from commented nested filters', function () { | ||
var extractorWithBindOnce = new _extract.Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::' | ||
}); | ||
var data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_COMMENTED_NESTED_FILTER); | ||
(0, _chai.expect)(data.length).to.equal(4); | ||
(0, _chai.expect)(data[0].text).to.equal('Like'); | ||
(0, _chai.expect)(data[1].text).to.equal('Gets extracted now'); | ||
(0, _chai.expect)(data[2].text).to.equal('Number of votes'); | ||
(0, _chai.expect)(data[3].text).to.equal('Votes <i class=\'fa fa-star\'></i>'); | ||
}); | ||
it('should extract strings from commented', function () { | ||
var extractorWithBindOnce = new _extract.Extractor({ | ||
filterPrefix: '::' | ||
}); | ||
var data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_COMMENTED_COMPLEX_NESTING); | ||
(0, _chai.expect)(data.length).to.equal(13); | ||
(0, _chai.expect)(data[0].text).to.equal('<div translate="" translate-comment="Inner comment \u2026" translate-context="Inner Context">\n Before {{:: \'I18n before\' |translate }}\n <a href="#" aria-label="{{ \'Test link 1\' |translate }}">\n {{ \'Link part 1\' |translate }}\n {{:: \'Link part 2\' |translate }}\n {{ \'Link part 3\' |translate }}</a>\n Between {{:: \'I18n between\' |translate }}\n <a href="#" aria-label="{{ \'Test link 2\' |translate }}">\n {{ \'Reference part 1\' |translate }}\n {{:: \'Reference part 2\' |translate }}\n {{ \'Reference part 3\' |translate }}</a>\n After {{:: \'I18n after\' |translate }}\n </div>'); | ||
(0, _chai.expect)(data[0].comment).to.equal('Outer comment …'); | ||
(0, _chai.expect)(data[0].context).to.equal('Outer Context'); | ||
(0, _chai.expect)(data[1].text).to.equal('Before {{:: \'I18n before\' |translate }}\n <a href="#" aria-label="{{ \'Test link 1\' |translate }}">\n {{ \'Link part 1\' |translate }}\n {{:: \'Link part 2\' |translate }}\n {{ \'Link part 3\' |translate }}</a>\n Between {{:: \'I18n between\' |translate }}\n <a href="#" aria-label="{{ \'Test link 2\' |translate }}">\n {{ \'Reference part 1\' |translate }}\n {{:: \'Reference part 2\' |translate }}\n {{ \'Reference part 3\' |translate }}</a>\n After {{:: \'I18n after\' |translate }}'); | ||
(0, _chai.expect)(data[1].comment).to.equal('Inner comment …'); | ||
(0, _chai.expect)(data[1].context).to.equal('Inner Context'); | ||
(0, _chai.expect)(data[2].text).to.equal('I18n before'); | ||
(0, _chai.expect)(data[2].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[3].text).to.equal('Test link 1'); | ||
(0, _chai.expect)(data[3].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[4].text).to.equal('Link part 1'); | ||
(0, _chai.expect)(data[4].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[5].text).to.equal('Link part 2'); | ||
(0, _chai.expect)(data[5].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[6].text).to.equal('Link part 3'); | ||
(0, _chai.expect)(data[6].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[7].text).to.equal('I18n between'); | ||
(0, _chai.expect)(data[7].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[8].text).to.equal('Test link 2'); | ||
(0, _chai.expect)(data[8].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[9].text).to.equal('Reference part 1'); | ||
(0, _chai.expect)(data[9].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[10].text).to.equal('Reference part 2'); | ||
(0, _chai.expect)(data[10].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[11].text).to.equal('Reference part 3'); | ||
(0, _chai.expect)(data[11].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
(0, _chai.expect)(data[12].text).to.equal('I18n after'); | ||
(0, _chai.expect)(data[12].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
}); | ||
}); | ||
//# sourceMappingURL=extract.spec.js.map |
@@ -20,12 +20,34 @@ 'use strict'; | ||
var HTML3_FILTER0 = exports.HTML3_FILTER0 = '<h2 translate-comment="Fugazy">{{ "Hola, hombre" | translate }}</h2>'; | ||
var HTML3_FILTER1 = exports.HTML3_FILTER1 = "<h2 tooltip=\"{{'Hola, mujer'|i18n}}\">StufStuff</h2>"; | ||
var HTML3_FILTER2 = exports.HTML3_FILTER2 = "<h2 tooltip=\"{{ a || 'Hola, hola'|i18n }}\">StufStuff</h2>"; | ||
var HTML3_FILTER3 = exports.HTML3_FILTER3 = "<h2 attr=\"{{ "So long, my dear' |i18n }}\">Martha</h2>"; | ||
var HTML3_FILTER4 = exports.HTML3_FILTER4 = "<h2 attr=\""So long, my dear' |i18n\">Martha</h2>"; | ||
var HTML3_FILTER5 = exports.HTML3_FILTER5 = "<h2 attr=\"{{ 'Guns\'n roses, my dear' |i18n }}\">Martha</h2>"; | ||
var HTML3_FILTER6 = exports.HTML3_FILTER6 = "<h2 attr=\"'Guns\'n roses, my dear' |i18n \">Martha</h2>"; | ||
var HTML3_FILTER7 = exports.HTML3_FILTER7 = "<h2 attr=\"::'Guns\'n roses, my dear' |i18n \">Martha</h2>"; | ||
var HTML3_FILTER1 = exports.HTML3_FILTER1 = '<h2 tooltip="{{\'Hola, mujer\'|i18n}}">StufStuff</h2>'; | ||
var HTML3_FILTER2 = exports.HTML3_FILTER2 = '<h2 tooltip="{{ a || \'Hola, hola\'|i18n }}">StufStuff</h2>'; | ||
var HTML3_FILTER3 = exports.HTML3_FILTER3 = '<h2 attr="{{ "So long, my dear" |i18n }}">Martha</h2>'; | ||
var HTML3_FILTER4 = exports.HTML3_FILTER4 = '<h2 attr=""So long, my dear" |i18n">Martha</h2>'; | ||
var HTML3_FILTER5 = exports.HTML3_FILTER5 = '<h2 attr="{{ \'Guns\'n roses, my dear\' |i18n }}">Son</h2>'; | ||
var HTML3_FILTER6 = exports.HTML3_FILTER6 = '<h2 attr="\'Guns\'n roses, my dear\' |i18n ">Daughter</h2>'; | ||
var HTML3_FILTER7 = exports.HTML3_FILTER7 = '<h2 attr="::\'Guns\'n roses, my dear\' |i18n ">Wife</h2>'; | ||
var HTML_FILTER_SPLIT_STRING = exports.HTML_FILTER_SPLIT_STRING = '\n<h2 ng-bind="::\'Three\' +\' parts, \' + \'one whole.\' |i18n ">Will be replaced</h2>\n'; | ||
var HTML_FILTER_SPLIT_MULTILINE_STRING_ATTR = exports.HTML_FILTER_SPLIT_MULTILINE_STRING_ATTR = '\n<h2 ng-bind="::\'Four\' +\n \' parts, \' +\n \'maybe, \'\n + \'one whole.\' |i18n ">Will be replaced</h2>\n'; | ||
var HTML_FILTER_SPLIT_MULTILINE_STRING_INTERPOLATED = exports.HTML_FILTER_SPLIT_MULTILINE_STRING_INTERPOLATED = '\n<h2>{{::\'Four\' +\n \' parts, \' +\n \'probably, \'\n + \'one whole.\' |i18n}}</h2>\n'; | ||
var HTML_COMPLEX_NESTING = exports.HTML_COMPLEX_NESTING = '<div translate translate-comment="Outer comment \u2026"\n translate-context="Outer Context">\n <div translate translate-comment="Inner comment \u2026"\n translate-context="Inner Context">\n Before {{:: \'I18n before\' |translate }}\n <a href="#" aria-label="{{ \'Test link 1\' |translate }}">\n {{ \'Link part 1\' |translate }}\n {{:: \'Link part 2\' |translate }}\n {{ \'Link part 3\' |translate }}</a>\n Between {{:: \'I18n between\' |translate }}\n <a href="#" aria-label="{{ \'Test link 2\' |translate }}">\n {{ \'Reference part 1\' |translate }}\n {{:: \'Reference part 2\' |translate }}\n {{ \'Reference part 3\' |translate }}</a>\n After {{:: \'I18n after\' |translate }}\n </div>\n</div>'; | ||
var HTML_LINEBREAK_FILTER = exports.HTML_LINEBREAK_FILTER = '\n<div class="buttons">\n<a href="#"\n ng-click="vm.doSomething()"\n class="button">{{ \'Multi-line 0\' |translate }}</a>\n \n<a href="#"\n ng-click="vm.doSomething()"\n class="button">{{ \n \'Multi-line 1\' |translate }}</a>\n\n<a href="#"\n ng-click="vm.doSomething()"\n class="button">{{ \'Multi-line 2\'\n |translate }}</a>\n\n<a href="#"\n ng-click="vm.doSomething()"\n class="button">{{\'Multi-line 3\' |\n translate }}</a>\n\n<a href="#"\n ng-click="vm.doSomething()"\n class="button">{{ \'Multi-line 4\' | translate \n}}</a>\n'; | ||
var HTML_TEXT_CHALLENGE = exports.HTML_TEXT_CHALLENGE = '\n<p>{{ \'Thanks for joining \u2026. However, \u2026 does not start until\'\n |translate }}\n <span>{{ vm.startDatetime |amCalendar}}</span>{{ \', but will open\' |\n translate}}\n {{ vm.roll_call_duration_minutes }}\n {{ \'minutes before that.\' |translate }}\n</p>\n'; | ||
var HTML_TEXT_FILTER = exports.HTML_TEXT_FILTER = '\n{{ \'Outside 0\' |translate }}\n<div class="buttons">\n<a href="#">{{ \'Text 0\' |translate }}</a>\n{{ \'Between 0\' |translate }}\n<a href="#">{{ \'Text 3\' |translate }}</a>\n</div>\n{{ \'Outside 1\' |translate }}\n'; | ||
var HTML_TEXT_MULTIPLE_FILTER = exports.HTML_TEXT_MULTIPLE_FILTER = '\n<a href="#">\n{{ \'Text 0\' |translate }} between\n {{ \'Text 1\' |translate }} between again\n {{ \'Text 2\' |translate }}\n</a>\n'; | ||
var HTML_TEXT_FILTER_COMMENT = exports.HTML_TEXT_FILTER_COMMENT = '\n<!doctype html>\n<a href="#">\n <!-- First comment -->\n {{:: \'Text 1\' |translate }} between again\n <!-- Second comment -->\n</a>\n'; | ||
var HTML_NESTED_FILTER = exports.HTML_NESTED_FILTER = '\n <li class="action thumbs-up"\n title="{{::\'Like\' |translate}}" alt="{{::\'Gets extracted now\' |translate}}">\n <span ng-bind="::vm.voteCount |translate" alt="{{:: \'Number of votes\' |translate}}"></span>\n <span ng-bind-html="::\'Votes <i class=\'fa fa-star\'></i>\' |translate"></span>\n </li>\n'; | ||
var HTML_COMMENTED_NESTED_FILTER = exports.HTML_COMMENTED_NESTED_FILTER = '\n<!--\n' + HTML_NESTED_FILTER + '\n-->\n'; | ||
var HTML_COMMENTED_COMPLEX_NESTING = exports.HTML_COMMENTED_COMPLEX_NESTING = '\n<!--\n' + HTML_COMPLEX_NESTING + '\n-->\n'; | ||
var HTML4_TAG0 = exports.HTML4_TAG0 = '<translate>Duck</translate>'; | ||
@@ -32,0 +54,0 @@ var HTML4_TAG1 = exports.HTML4_TAG1 = '<i18n>Dice</i18n>'; |
{ | ||
"name": "easygettext", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "Simple tools to extract gettext strings", | ||
@@ -27,6 +27,6 @@ "main": "dist/index", | ||
"babel-preset-es2015": "^6.1.18", | ||
"chai": "^3.4.1", | ||
"eslint": "^1.10.1", | ||
"chai": "^4.1.2", | ||
"eslint": "^4.13.0", | ||
"isparta": "^4.0.0", | ||
"mocha": "^2.3.4" | ||
"mocha": "^4.0.1" | ||
}, | ||
@@ -38,3 +38,3 @@ "bin": { | ||
"dependencies": { | ||
"cheerio": "^0.19.0", | ||
"cheerio": "^0.22.0", | ||
"fs": "0.0.2", | ||
@@ -41,0 +41,0 @@ "minimist": "^1.2.0", |
@@ -20,3 +20,3 @@ # easygettext | ||
### Why this library? | ||
### Why This Library? | ||
@@ -30,5 +30,14 @@ Our frontend toolchain, [systematic](https://github.com/Polyconseil/systematic) doesn't rely on Grunt/Gulp/Broccoli/... | ||
Nevertheless, the way [angular-gettext] does it (with tokens, directly in HTML) is elegant, is used by many other | ||
Nevertheless, the way [angular-gettext](https://angular-gettext.rocketeer.be/) does it (with tokens, directly in HTML) is elegant, is used by many other | ||
libraries and will also be the way to do it in Angular2. | ||
### Installation | ||
You can install the [easygettext](https://www.npmjs.com/package/easygettext) package by running | ||
```bash | ||
npm install --dev easygettext | ||
``` | ||
or | ||
```bash | ||
yarn add --save-dev easygettext | ||
``` | ||
@@ -59,3 +68,11 @@ | ||
<translate>Hello World</translate> | ||
<!-- The default delimiters '{{' and '}}' must be changed to empty strings to handle this example --> | ||
<!-- The following becomes 'Broken strings are joined' --> | ||
<span ng-bind="{{ 'Broken ' | ||
+ 'strings ' + | ||
'are ' + | ||
'joined' |translate}}"></span> | ||
<!-- By supplying the --filterPrefix '::' parameter --> | ||
<span>{{:: 'Something …' |translate}}</span> | ||
<!-- The default delimiters '{{' and '}}' must be changed to empty strings to handle these examples --> | ||
<span ng-bind=":: 'Something …' |translate"></span> | ||
<div placeholder="'Hello World' | translate"></div> | ||
@@ -69,3 +86,3 @@ ``` | ||
``` | ||
```bash | ||
gettext-extract --attribute v-translate --output dictionary.pot foo.html bar.jade | ||
@@ -82,6 +99,66 @@ | ||
``` | ||
```bash | ||
gettext-compile --output translations.json fr.po en.po de.po | ||
``` | ||
### AngularJS | ||
If you use `easygettext` to extract files from an AngularJS code base, you might find the following tips helpful. | ||
To cover the cases (1) | ||
```html | ||
<input placeholder="{{:: 'Insert name …' |translate }}"> | ||
<input placeholder="{{ 'Insert name …' |translate }}"> | ||
``` | ||
and (2) | ||
```html | ||
<a href="#" ng-bind=":: 'Link text' |translate"></a> | ||
<a href="#" ng-bind="'Link text' |translate"></a> | ||
<a href="#">{{::'Link text' |translate}}</a> | ||
<a href="#">{{'Link text' |translate}}</a> | ||
``` | ||
you should run the extraction tool twice. Once with the command-line arguments | ||
```bash | ||
--startDelimiter '{{' --endDelimiter '}}' --filterPrefix '::' | ||
``` | ||
and once with the command-line arguments | ||
```bash | ||
--output ${html_b} --startDelimiter '' --endDelimiter '' --filterPrefix '::' | ||
``` | ||
The following Bash script shows how `msgcat` might help | ||
```bash | ||
#!/usr/bin/env bash | ||
input_files="$(find ./src/ -iname \*\.html)" | ||
workdir=$(mktemp -d "${TMPDIR:-/tmp/}$(basename $0).XXXXXXXXXXXX") || exit 1 | ||
html_a=${workdir}/messages-html-interpolate.pot | ||
html_b=${workdir}/messages-html.pot | ||
./dist/extract-cli.js --output ${html_a} --startDelimiter '{{' --endDelimiter '}}' --filterPrefix '::' ${input_files} | ||
./dist/extract-cli.js --output ${html_b} --startDelimiter '' --endDelimiter '' --filterPrefix '::' ${input_files} | ||
# Extract gettext “messages” from JavaScript files here, into ${es_a} … | ||
es_a=${workdir}/ecmascript.pot | ||
# [...] > ${es_a} | ||
# Merge the different catalog templates with `msgcat`: | ||
merged_pot=${workdir}/merged.pot | ||
msgcat ${html_a} ${html_b} ${es_a} > ${merged_pot} | ||
# Cleanup, in case `msgcat` gave merge-conflicts in catalog header. | ||
header=${workdir}/header.pot | ||
sed -e '/^$/q' < ${html_a} > ${header} | ||
body=${workdir}/body.pot | ||
sed '1,/^$/d' < ${merged_pot} > ${body} | ||
cat ${header} ${body} > ${output_file} | ||
# Remove temporary directory with working files. | ||
rm -r ${workdir} | ||
``` | ||
Please note that the script needs to be modified to match your needs and environment. | ||
### Testing | ||
@@ -91,3 +168,3 @@ | ||
```javascript | ||
```bash | ||
npm test | ||
@@ -98,3 +175,3 @@ ``` | ||
```javascript | ||
```bash | ||
npm run cover | ||
@@ -107,3 +184,3 @@ ``` | ||
```javascript | ||
```bash | ||
npm run prepublish | ||
@@ -114,3 +191,3 @@ ``` | ||
``` | ||
```bash | ||
./dist/extract-cli.js --attribute v-translate --attribute v-i18n ~/output.html | ||
@@ -117,0 +194,0 @@ ``` |
@@ -7,2 +7,13 @@ export const DEFAULT_ATTRIBUTES = [ | ||
export const DEFAULT_FILTERS = [ | ||
'i18n', | ||
'translate', | ||
]; | ||
export const DEFAULT_START_DELIMITER = '{{'; | ||
export const DEFAULT_END_DELIMITER = '}}'; | ||
// Could for example be '::', used by AngularJS to indicate one-time bindings. | ||
export const DEFAULT_FILTER_PREFIX = null; | ||
export const DEFAULT_DELIMITERS = { | ||
@@ -9,0 +20,0 @@ start: '{{', |
@@ -24,17 +24,25 @@ #!/usr/bin/env node | ||
const extraAttribute = argv.attribute || false; | ||
const extraFilter = argv.filter || false; | ||
const filterPrefix = argv.filterPrefix || constants.DEFAULT_FILTER_PREFIX; | ||
if (!quietMode && (!files || files.length === 0)) { | ||
console.log('Usage:\n\tgettext-extract [--attribute EXTRA-ATTRIBUTE] [--output OUTFILE] <FILES>'); | ||
console.log('Usage:\n\tgettext-extract [--attribute EXTRA-ATTRIBUTE] [--filterPrefix FILTER-PREFIX] [--output OUTFILE] <FILES>'); | ||
process.exit(1); | ||
} | ||
let attributes = constants.DEFAULT_ATTRIBUTES.slice(); | ||
if (extraAttribute) { | ||
if (typeof extraAttribute === 'string') { // Only one extra attribute was passed. | ||
attributes.push(extraAttribute); | ||
} else { // Multiple extra attributes were passed. | ||
attributes = attributes.concat(extraAttribute); | ||
function _getExtraNames(extraEntities, defaultEntities) { | ||
let attributes = defaultEntities.slice(); | ||
if (extraEntities) { | ||
if (typeof extraEntities === 'string') { // Only one extra attribute was passed. | ||
attributes.push(extraEntities); | ||
} else { // Multiple extra attributes were passed. | ||
attributes = attributes.concat(extraEntities); | ||
} | ||
} | ||
return attributes; | ||
} | ||
const attributes = _getExtraNames(extraAttribute, constants.DEFAULT_ATTRIBUTES); | ||
const filters = _getExtraNames(extraFilter, constants.DEFAULT_FILTERS); | ||
// Extract strings | ||
@@ -44,2 +52,4 @@ const extractor = new Extractor({ | ||
attributes, | ||
filters, | ||
filterPrefix, | ||
startDelimiter, | ||
@@ -49,4 +59,4 @@ endDelimiter, | ||
files.forEach(function(filename) { | ||
let file = filename; | ||
files.forEach(function (filename) { | ||
let file = filename; | ||
const ext = file.split('.').pop(); | ||
@@ -63,3 +73,3 @@ if (ALLOWED_EXTENSIONS.indexOf(ext) === -1) { | ||
// Add empty require function to the context to avoid errors with webpack require inside pug | ||
data = pug.render(data, { filename: file, pretty: true, require: function() {}}); | ||
data = pug.render(data, {filename: file, pretty: true, require: function () {}}); | ||
} | ||
@@ -66,0 +76,0 @@ extractor.parse(file, data); |
import cheerio from 'cheerio'; | ||
import {isHtml} from 'cheerio/lib/utils'; | ||
import Pofile from 'pofile'; | ||
@@ -50,7 +51,19 @@ | ||
export class NodeTranslationInfo { | ||
constructor(node, text, reference, attributes) { | ||
constructor(node, text, reference, attributes) { | ||
this.text = text; | ||
this.reference = reference; | ||
this.context = getExtraAttribute(node, attributes, constants.ATTRIBUTE_CONTEXT) || constants.MARKER_NO_CONTEXT; | ||
this.comment = getExtraAttribute(node, attributes, constants.ATTRIBUTE_COMMENT); | ||
const el = node[0]; | ||
/* NOTE: It might make sense to let _all_ TEXT child nodes inherit the | ||
* `context` and `comment` from the parent, not only single children. | ||
* However, the following conditions generate output equal to | ||
* `angular-gettext-tools`. */ | ||
const doInheritContext | ||
= el.type === 'text' && el.prev === null && el.next === null; | ||
this.context = getExtraAttribute(doInheritContext | ||
? node.parent() : node, attributes, constants.ATTRIBUTE_CONTEXT) | ||
|| constants.MARKER_NO_CONTEXT; | ||
this.comment = getExtraAttribute(doInheritContext | ||
? node.parent() : node, attributes, constants.ATTRIBUTE_COMMENT); | ||
this.plural = getExtraAttribute(node, attributes, constants.ATTRIBUTE_PLURAL); | ||
@@ -76,5 +89,7 @@ } | ||
this.options = Object.assign({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}', | ||
startDelimiter: constants.DEFAULT_START_DELIMITER, | ||
endDelimiter: constants.DEFAULT_END_DELIMITER, | ||
attributes: constants.DEFAULT_ATTRIBUTES, | ||
filters: constants.DEFAULT_FILTERS, | ||
filterPrefix: constants.DEFAULT_FILTER_PREFIX, | ||
lineNumbers: false, | ||
@@ -93,8 +108,29 @@ }, options); | ||
this.items = {}; | ||
this.filterRegexps = this.options.attributes.map((attribute) => { | ||
const startOrEndQuotes = `(?:\\"|[\\'"])`; // matches simple / double / HTML quotes | ||
const spacesOrPipeChar = `\\s*\\|\\s*`; // matches the pipe string of the filter | ||
const start = this.options.startDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
const end = this.options.endDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
return new RegExp(`^${start}[^'"]*${startOrEndQuotes}(.*)${startOrEndQuotes}${spacesOrPipeChar}${attribute}\\s*${end}$`); | ||
this.attrFilterRegexps = this.createRegexps('attr'); | ||
this.textFilterRegexps = this.createRegexps('text'); | ||
this.splitStringRe = /(?:['"])\s*\+\s*(?:['"])/g; | ||
} | ||
createRegexps(type) { | ||
const startOrEndQuotes = `(\\"|[\\'"])`; // matches simple / double / HTML quotes | ||
const spacesOrPipeChar = `\\s*\\|\\s*`; // matches the pipe string of the filter | ||
let startDelimiter = this.options.startDelimiter; | ||
let endDelimiter = this.options.endDelimiter; | ||
if (type === 'text') { | ||
if (startDelimiter === '') { | ||
startDelimiter = constants.DEFAULT_START_DELIMITER; | ||
} | ||
if (endDelimiter === '') { | ||
endDelimiter = constants.DEFAULT_END_DELIMITER; | ||
} | ||
} | ||
const start = startDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
const end = endDelimiter.replace(ESCAPE_REGEX, '\\$&'); | ||
const prefix = this.options.filterPrefix === null ? '' : `\\s*(?:${this.options.filterPrefix})?\\s*`; | ||
const body = endDelimiter === '' ? '((.|\\n)*)' : `((.|\\n)*?(?!${end}))`; | ||
return this.options.filters.map((filter) => { | ||
return new RegExp(`${start}${prefix}[^'"]*${startOrEndQuotes}${body}\\1${spacesOrPipeChar}${filter}\\s*${end}`, 'g'); | ||
}); | ||
@@ -154,35 +190,81 @@ } | ||
_extractTranslationData(filename, content) { | ||
const $ = cheerio.load(content, { | ||
decodeEntities: false, | ||
withStartIndices: true, | ||
}); | ||
getAttrsAndDatas(node) { | ||
if (node[0].type === 'text' || node[0].type === 'comment') { | ||
return [{text: node[0].data.trim(), type: 'text'}]; | ||
} | ||
return $('*').map(function(_i, el) { | ||
const node = $(el); | ||
const reference = new TranslationReference(filename, content, el.startIndex); | ||
if (this._hasTranslationToken(node)) { | ||
const text = node.html().trim(); | ||
if (text.length !== 0) { | ||
return [new NodeTranslationInfo(node, text, reference, this.options.attributes)]; | ||
} | ||
const data = node.data(); | ||
const attr = node.attr(); | ||
return [ | ||
...Object.keys(data).map(key => { | ||
return {text: data[key], type: 'data'}; | ||
}), | ||
...Object.keys(attr).map(key => { | ||
return {text: attr[key], type: 'attr'}; | ||
}), | ||
]; | ||
} | ||
_parseElement($, el, filename, content) { | ||
if (el.type === 'comment' && isHtml(el.data)) { | ||
// Recursive parse call if el.data is recognized as HTML. | ||
return this._extractTranslationDataFromNodes(Array.from($(el.data)), $, filename, content); | ||
} | ||
const reference = new TranslationReference(filename, content, el.startIndex); | ||
const node = $(el); | ||
if (this._hasTranslationToken(node)) { | ||
const text = node.html().trim(); | ||
if (text.length !== 0) { | ||
return [new NodeTranslationInfo(node, text, reference, this.options.attributes)]; | ||
} | ||
} | ||
// In-depth search for filters | ||
const attrs = Object.keys(node.attr()).map(key => node.attr()[key]); | ||
const datas = Object.keys(node.data()).map(key => node.data()[key]); | ||
let tokensFromFilters = []; | ||
[node.html(), ...attrs, ...datas].forEach((item) => { | ||
const matches = this.filterRegexps.map((re) => re.exec(item)).filter((x) => x !== null); | ||
if (matches.length !== 0) { | ||
const text = matches[0][1].trim(); | ||
if (text.length !== 0) { | ||
tokensFromFilters.push(new NodeTranslationInfo(node, text, reference, this.options.attributes)); | ||
// In-depth search for filters | ||
return this.getAttrsAndDatas(node) | ||
.reduce((tokensFromFilters, item) => { | ||
function _getAllMatches(matches, re) { | ||
while (true) { | ||
const match = re.exec(item.text); | ||
if (match === null) { | ||
break; | ||
} | ||
matches.push(match); | ||
} | ||
return matches; | ||
} | ||
}); | ||
if (tokensFromFilters.length > 0) { | ||
const regexps = item.type === 'text' ? this.textFilterRegexps : this.attrFilterRegexps; | ||
regexps | ||
.reduce(_getAllMatches, []) | ||
.filter((match) => match.length) | ||
.map((match) => match[2].trim()) | ||
.filter((text) => text.length !== 0) | ||
.map((text) => text.split(this.splitStringRe).join('')) | ||
.forEach((text) => { | ||
tokensFromFilters.push( | ||
new NodeTranslationInfo(node, text, reference, | ||
this.options.attributes)); | ||
}); | ||
return tokensFromFilters; | ||
}, []); | ||
} | ||
_traverseTree(nodes, sequence) { | ||
nodes.forEach((el) => { | ||
sequence.push(el); | ||
if (typeof el.children !== 'undefined') { | ||
this._traverseTree(el.children, sequence); | ||
} | ||
}.bind(this)).get() | ||
}); | ||
return sequence; | ||
} | ||
_extractTranslationDataFromNodes(rootChildren, $, filename, content) { | ||
return this._traverseTree(rootChildren, []) | ||
.filter((el) => el.type === 'tag' || el.type === 'text' || el.type === 'comment') | ||
.map((el) => this._parseElement($, el, filename, content)) | ||
.reduce((acc, cur) => acc.concat(cur), []) | ||
@@ -192,2 +274,11 @@ .filter((x) => x !== undefined); | ||
_extractTranslationData(filename, content) { | ||
const $ = cheerio.load(content, { | ||
decodeEntities: false, | ||
withStartIndices: true, | ||
}); | ||
return this._extractTranslationDataFromNodes($.root()[0].children, $, filename, content); | ||
} | ||
_hasTranslationToken(node) { | ||
@@ -194,0 +285,0 @@ return this.options.attributes.some((keyword) => node.is(keyword) || node.attr(keyword) !== undefined); |
@@ -77,3 +77,5 @@ import {Extractor} from './extract.js'; | ||
describe('Raw translation data', () => { | ||
const extractor = new Extractor(); | ||
const extractor = new Extractor({ | ||
filterPrefix: '::' | ||
}); | ||
@@ -139,4 +141,5 @@ it('should extract data and metadata correctly', () => { | ||
const extractorWithBindOnce = new Extractor({ | ||
startDelimiter: '::', | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::', | ||
}); | ||
@@ -148,22 +151,268 @@ const data7 = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML3_FILTER7); | ||
it('should handle complex nesting constructs with multiple interpolated filters', () => { | ||
const extractorWithBindOnce = new Extractor({ | ||
filterPrefix: '::', | ||
}); | ||
const data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_COMPLEX_NESTING); | ||
expect(data.length).to.equal(13); | ||
expect(data[0].text).to.equal( | ||
`<div translate="" translate-comment="Inner comment …" translate-context="Inner Context"> | ||
Before {{:: 'I18n before' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 1' |translate }}"> | ||
{{ 'Link part 1' |translate }} | ||
{{:: 'Link part 2' |translate }} | ||
{{ 'Link part 3' |translate }}</a> | ||
Between {{:: 'I18n between' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 2' |translate }}"> | ||
{{ 'Reference part 1' |translate }} | ||
{{:: 'Reference part 2' |translate }} | ||
{{ 'Reference part 3' |translate }}</a> | ||
After {{:: 'I18n after' |translate }} | ||
</div>`); | ||
expect(data[0].comment).to.equal('Outer comment …'); | ||
expect(data[0].context).to.equal('Outer Context'); | ||
expect(data[1].text).to.equal( | ||
`Before {{:: 'I18n before' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 1' |translate }}"> | ||
{{ 'Link part 1' |translate }} | ||
{{:: 'Link part 2' |translate }} | ||
{{ 'Link part 3' |translate }}</a> | ||
Between {{:: 'I18n between' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 2' |translate }}"> | ||
{{ 'Reference part 1' |translate }} | ||
{{:: 'Reference part 2' |translate }} | ||
{{ 'Reference part 3' |translate }}</a> | ||
After {{:: 'I18n after' |translate }}`); | ||
expect(data[1].comment).to.equal('Inner comment …'); | ||
expect(data[1].context).to.equal('Inner Context'); | ||
expect(data[2].text).to.equal(`I18n before`); | ||
expect(data[2].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[3].text).to.equal(`Test link 1`); | ||
expect(data[3].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[4].text).to.equal(`Link part 1`); | ||
expect(data[4].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[5].text).to.equal(`Link part 2`); | ||
expect(data[5].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[6].text).to.equal(`Link part 3`); | ||
expect(data[6].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[7].text).to.equal(`I18n between`); | ||
expect(data[7].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[8].text).to.equal(`Test link 2`); | ||
expect(data[8].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[9].text).to.equal(`Reference part 1`); | ||
expect(data[9].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[10].text).to.equal(`Reference part 2`); | ||
expect(data[10].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[11].text).to.equal(`Reference part 3`); | ||
expect(data[11].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[12].text).to.equal(`I18n after`); | ||
expect(data[12].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
}); | ||
it('should extract filters from nested constructs', () => { | ||
const extractorWithBindOnce = new Extractor({ | ||
startDelimiter: '::', | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::', | ||
}); | ||
const data8 = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_NESTED_FILTER); | ||
expect(data8.length).to.equal(1); | ||
expect(data8[0].text).to.equal('Votes <i class=\'fa fa-star\'></i>'); | ||
const data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_NESTED_FILTER); | ||
expect(data.length).to.equal(4); | ||
expect(data[0].text).to.equal('Like'); | ||
expect(data[1].text).to.equal('Gets extracted now'); | ||
expect(data[2].text).to.equal('Number of votes'); | ||
expect(data[3].text).to.equal('Votes <i class=\'fa fa-star\'></i>'); | ||
}); | ||
const extractorWithInterpolateBindOnce = new Extractor({ | ||
startDelimiter: '{{::', | ||
it('should extract filters that are broken across multiple lines', () => { | ||
const extractorInterpolate = new Extractor({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}', | ||
}); | ||
const data9 = extractorWithInterpolateBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_NESTED_FILTER); | ||
expect(data9.length).to.equal(3); | ||
expect(data9[0].text).to.equal('Like'); | ||
expect(data9[1].text).to.equal('Gets extracted now'); | ||
expect(data9[2].text).to.equal('Number of votes'); | ||
const data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_LINEBREAK_FILTER); | ||
expect(data.length).to.equal(5); | ||
expect(data[0].text).to.equal('Multi-line 0'); | ||
expect(data[1].text).to.equal('Multi-line 1'); | ||
expect(data[2].text).to.equal('Multi-line 2'); | ||
expect(data[3].text).to.equal('Multi-line 3'); | ||
expect(data[4].text).to.equal('Multi-line 4'); | ||
}); | ||
it('should extract filters from text before and after elements', () => { | ||
const extractorInterpolate = new Extractor({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}', | ||
}); | ||
const data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_FILTER); | ||
expect(data.length).to.equal(5); | ||
expect(data[0].text).to.equal('Outside 0'); | ||
expect(data[1].text).to.equal('Text 0'); | ||
expect(data[2].text).to.equal('Between 0'); | ||
expect(data[3].text).to.equal('Text 3'); | ||
expect(data[4].text).to.equal('Outside 1'); | ||
}); | ||
it('should extract multiple filters from text blocks', () => { | ||
const extractorInterpolate = new Extractor({ | ||
startDelimiter: '{{', | ||
endDelimiter: '}}', | ||
}); | ||
const data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_MULTIPLE_FILTER); | ||
expect(data.length).to.equal(3); | ||
expect(data[0].text).to.equal('Text 0'); | ||
expect(data[1].text).to.equal('Text 1'); | ||
expect(data[2].text).to.equal('Text 2'); | ||
}); | ||
it('should extract filters from text blocks with empty delimiters', () => { | ||
const extractor = new Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
}); | ||
const data = extractor._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_CHALLENGE); | ||
expect(data.length).to.equal(3); | ||
expect(data[0].text).to.equal('Thanks for joining …. However, … does not start until'); | ||
expect(data[1].text).to.equal(', but will open'); | ||
expect(data[2].text).to.equal('minutes before that.'); | ||
}); | ||
it('should ignore comments and directives when extracting filters', () => { | ||
const extractorInterpolate = new Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::', | ||
}); | ||
const data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_TEXT_FILTER_COMMENT); | ||
expect(data.length).to.equal(1); | ||
expect(data[0].text).to.equal('Text 1'); | ||
}); | ||
it('should join split strings', () => { | ||
const extractorInterpolate = new Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::', | ||
}); | ||
const data = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_FILTER_SPLIT_STRING); | ||
expect(data.length).to.equal(1); | ||
expect(data[0].text).to.equal('Three parts, one whole.'); | ||
}); | ||
it('should join strings split over multiple lines', () => { | ||
const extractorInterpolate = new Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::', | ||
}); | ||
const data0 = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_FILTER_SPLIT_MULTILINE_STRING_ATTR); | ||
expect(data0.length).to.equal(1); | ||
expect(data0[0].text).to.equal('Four parts, maybe, one whole.'); | ||
const data1 = extractorInterpolate._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_FILTER_SPLIT_MULTILINE_STRING_INTERPOLATED); | ||
expect(data1.length).to.equal(1); | ||
expect(data1[0].text).to.equal('Four parts, probably, one whole.'); | ||
}); | ||
it('should extract translatable strings from commented nested filters', () => { | ||
const extractorWithBindOnce = new Extractor({ | ||
startDelimiter: '', | ||
endDelimiter: '', | ||
filterPrefix: '::', | ||
}); | ||
const data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_COMMENTED_NESTED_FILTER); | ||
expect(data.length).to.equal(4); | ||
expect(data[0].text).to.equal('Like'); | ||
expect(data[1].text).to.equal('Gets extracted now'); | ||
expect(data[2].text).to.equal('Number of votes'); | ||
expect(data[3].text).to.equal('Votes <i class=\'fa fa-star\'></i>'); | ||
}); | ||
it('should extract strings from commented', () => { | ||
const extractorWithBindOnce = new Extractor({ | ||
filterPrefix: '::', | ||
}); | ||
const data = extractorWithBindOnce._extractTranslationData(fixtures.FILENAME_0, fixtures.HTML_COMMENTED_COMPLEX_NESTING); | ||
expect(data.length).to.equal(13); | ||
expect(data[0].text).to.equal( | ||
`<div translate="" translate-comment="Inner comment …" translate-context="Inner Context"> | ||
Before {{:: 'I18n before' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 1' |translate }}"> | ||
{{ 'Link part 1' |translate }} | ||
{{:: 'Link part 2' |translate }} | ||
{{ 'Link part 3' |translate }}</a> | ||
Between {{:: 'I18n between' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 2' |translate }}"> | ||
{{ 'Reference part 1' |translate }} | ||
{{:: 'Reference part 2' |translate }} | ||
{{ 'Reference part 3' |translate }}</a> | ||
After {{:: 'I18n after' |translate }} | ||
</div>`); | ||
expect(data[0].comment).to.equal('Outer comment …'); | ||
expect(data[0].context).to.equal('Outer Context'); | ||
expect(data[1].text).to.equal( | ||
`Before {{:: 'I18n before' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 1' |translate }}"> | ||
{{ 'Link part 1' |translate }} | ||
{{:: 'Link part 2' |translate }} | ||
{{ 'Link part 3' |translate }}</a> | ||
Between {{:: 'I18n between' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 2' |translate }}"> | ||
{{ 'Reference part 1' |translate }} | ||
{{:: 'Reference part 2' |translate }} | ||
{{ 'Reference part 3' |translate }}</a> | ||
After {{:: 'I18n after' |translate }}`); | ||
expect(data[1].comment).to.equal('Inner comment …'); | ||
expect(data[1].context).to.equal('Inner Context'); | ||
expect(data[2].text).to.equal(`I18n before`); | ||
expect(data[2].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[3].text).to.equal(`Test link 1`); | ||
expect(data[3].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[4].text).to.equal(`Link part 1`); | ||
expect(data[4].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[5].text).to.equal(`Link part 2`); | ||
expect(data[5].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[6].text).to.equal(`Link part 3`); | ||
expect(data[6].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[7].text).to.equal(`I18n between`); | ||
expect(data[7].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[8].text).to.equal(`Test link 2`); | ||
expect(data[8].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[9].text).to.equal(`Reference part 1`); | ||
expect(data[9].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[10].text).to.equal(`Reference part 2`); | ||
expect(data[10].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[11].text).to.equal(`Reference part 3`); | ||
expect(data[11].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
expect(data[12].text).to.equal(`I18n after`); | ||
expect(data[12].context).to.equal(constants.MARKER_NO_CONTEXT); | ||
}); | ||
}); |
@@ -6,22 +6,123 @@ export const FILENAME_0 = 'foo.htm'; | ||
export const HTML0_CTX0 = ` | ||
export const HTML0_CTX0 = ` | ||
<div><h4 translate="translate" translate-context="For charlie">Hello world</h4></div>`; | ||
export const HTML0_CTX1 = ` | ||
export const HTML0_CTX1 = ` | ||
<div><h4 translate="translate" translate-context="For jacques">Hello world</h4></div>`; | ||
export const HTML1_PLURAL0 = '<h2 translate="" i18n-plural="We work">I work</h2>'; | ||
export const HTML1_PLURAL1 = '<h2 translate="" translate-plural="Us works">I work</h2>'; | ||
export const HTML1_PLURAL0 = '<h2 translate="" i18n-plural="We work">I work</h2>'; | ||
export const HTML1_PLURAL1 = '<h2 translate="" translate-plural="Us works">I work</h2>'; | ||
export const HTML2_COMMENT0 = '<h2 translate i18n-comment="My first comment">Hello</h2>'; | ||
export const HTML2_COMMENT1 = '<h2 translate="" translate-comment="Another comment">Hello</h2>'; | ||
export const HTML2_COMMENT0 = '<h2 translate i18n-comment="My first comment">Hello</h2>'; | ||
export const HTML2_COMMENT1 = '<h2 translate="" translate-comment="Another comment">Hello</h2>'; | ||
export const HTML3_FILTER0 = '<h2 translate-comment="Fugazy">{{ "Hola, hombre" | translate }}</h2>'; | ||
export const HTML3_FILTER1 = "<h2 tooltip=\"{{'Hola, mujer'|i18n}}\">StufStuff</h2>"; | ||
export const HTML3_FILTER2 = "<h2 tooltip=\"{{ a || 'Hola, hola'|i18n }}\">StufStuff</h2>"; | ||
export const HTML3_FILTER3 = "<h2 attr=\"{{ "So long, my dear' |i18n }}\">Martha</h2>"; | ||
export const HTML3_FILTER4 = "<h2 attr=\""So long, my dear' |i18n\">Martha</h2>"; | ||
export const HTML3_FILTER5 = "<h2 attr=\"{{ 'Guns\'n roses, my dear' |i18n }}\">Martha</h2>"; | ||
export const HTML3_FILTER6 = "<h2 attr=\"'Guns\'n roses, my dear' |i18n \">Martha</h2>"; | ||
export const HTML3_FILTER7 = "<h2 attr=\"::'Guns\'n roses, my dear' |i18n \">Martha</h2>"; | ||
export const HTML3_FILTER0 = `<h2 translate-comment="Fugazy">{{ "Hola, hombre" | translate }}</h2>`; | ||
export const HTML3_FILTER1 = `<h2 tooltip="{{'Hola, mujer'|i18n}}">StufStuff</h2>`; | ||
export const HTML3_FILTER2 = `<h2 tooltip="{{ a || 'Hola, hola'|i18n }}">StufStuff</h2>`; | ||
export const HTML3_FILTER3 = `<h2 attr="{{ "So long, my dear" |i18n }}">Martha</h2>`; | ||
export const HTML3_FILTER4 = `<h2 attr=""So long, my dear" |i18n">Martha</h2>`; | ||
export const HTML3_FILTER5 = `<h2 attr="{{ 'Guns'n roses, my dear' |i18n }}">Son</h2>`; | ||
export const HTML3_FILTER6 = `<h2 attr="'Guns'n roses, my dear' |i18n ">Daughter</h2>`; | ||
export const HTML3_FILTER7 = `<h2 attr="::'Guns'n roses, my dear' |i18n ">Wife</h2>`; | ||
export const HTML_FILTER_SPLIT_STRING = ` | ||
<h2 ng-bind="::'Three' +' parts, ' + 'one whole.' |i18n ">Will be replaced</h2> | ||
`; | ||
export const HTML_FILTER_SPLIT_MULTILINE_STRING_ATTR = ` | ||
<h2 ng-bind="::'Four' + | ||
' parts, ' + | ||
'maybe, ' | ||
+ 'one whole.' |i18n ">Will be replaced</h2> | ||
`; | ||
export const HTML_FILTER_SPLIT_MULTILINE_STRING_INTERPOLATED = ` | ||
<h2>{{::'Four' + | ||
' parts, ' + | ||
'probably, ' | ||
+ 'one whole.' |i18n}}</h2> | ||
`; | ||
export const HTML_COMPLEX_NESTING = `<div translate translate-comment="Outer comment …" | ||
translate-context="Outer Context"> | ||
<div translate translate-comment="Inner comment …" | ||
translate-context="Inner Context"> | ||
Before {{:: 'I18n before' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 1' |translate }}"> | ||
{{ 'Link part 1' |translate }} | ||
{{:: 'Link part 2' |translate }} | ||
{{ 'Link part 3' |translate }}</a> | ||
Between {{:: 'I18n between' |translate }} | ||
<a href="#" aria-label="{{ 'Test link 2' |translate }}"> | ||
{{ 'Reference part 1' |translate }} | ||
{{:: 'Reference part 2' |translate }} | ||
{{ 'Reference part 3' |translate }}</a> | ||
After {{:: 'I18n after' |translate }} | ||
</div> | ||
</div>`; | ||
export const HTML_LINEBREAK_FILTER = ` | ||
<div class="buttons"> | ||
<a href="#" | ||
ng-click="vm.doSomething()" | ||
class="button">{{ 'Multi-line 0' |translate }}</a> | ||
<a href="#" | ||
ng-click="vm.doSomething()" | ||
class="button">{{ | ||
'Multi-line 1' |translate }}</a> | ||
<a href="#" | ||
ng-click="vm.doSomething()" | ||
class="button">{{ 'Multi-line 2' | ||
|translate }}</a> | ||
<a href="#" | ||
ng-click="vm.doSomething()" | ||
class="button">{{'Multi-line 3' | | ||
translate }}</a> | ||
<a href="#" | ||
ng-click="vm.doSomething()" | ||
class="button">{{ 'Multi-line 4' | translate | ||
}}</a> | ||
`; | ||
export const HTML_TEXT_CHALLENGE = ` | ||
<p>{{ 'Thanks for joining …. However, … does not start until' | ||
|translate }} | ||
<span>{{ vm.startDatetime |amCalendar}}</span>{{ ', but will open' | | ||
translate}} | ||
{{ vm.roll_call_duration_minutes }} | ||
{{ 'minutes before that.' |translate }} | ||
</p> | ||
`; | ||
export const HTML_TEXT_FILTER = ` | ||
{{ 'Outside 0' |translate }} | ||
<div class="buttons"> | ||
<a href="#">{{ 'Text 0' |translate }}</a> | ||
{{ 'Between 0' |translate }} | ||
<a href="#">{{ 'Text 3' |translate }}</a> | ||
</div> | ||
{{ 'Outside 1' |translate }} | ||
`; | ||
export const HTML_TEXT_MULTIPLE_FILTER = ` | ||
<a href="#"> | ||
{{ 'Text 0' |translate }} between | ||
{{ 'Text 1' |translate }} between again | ||
{{ 'Text 2' |translate }} | ||
</a> | ||
`; | ||
export const HTML_TEXT_FILTER_COMMENT = ` | ||
<!doctype html> | ||
<a href="#"> | ||
<!-- First comment --> | ||
{{:: 'Text 1' |translate }} between again | ||
<!-- Second comment --> | ||
</a> | ||
`; | ||
export const HTML_NESTED_FILTER = ` | ||
@@ -35,8 +136,20 @@ <li class="action thumbs-up" | ||
export const HTML4_TAG0 = '<translate>Duck</translate>'; | ||
export const HTML4_TAG1 = '<i18n>Dice</i18n>'; | ||
export const HTML4_TAG2 = '<get-text>Rabbit</get-text>'; | ||
export const HTML4_TAG3 = '<i18n translate>overtranslate</i18n>'; | ||
export const HTML_COMMENTED_NESTED_FILTER = ` | ||
<!-- | ||
${HTML_NESTED_FILTER} | ||
--> | ||
`; | ||
export const HTML_LONG = ` | ||
export const HTML_COMMENTED_COMPLEX_NESTING = ` | ||
<!-- | ||
${HTML_COMPLEX_NESTING} | ||
--> | ||
`; | ||
export const HTML4_TAG0 = '<translate>Duck</translate>'; | ||
export const HTML4_TAG1 = '<i18n>Dice</i18n>'; | ||
export const HTML4_TAG2 = '<get-text>Rabbit</get-text>'; | ||
export const HTML4_TAG3 = '<i18n translate>overtranslate</i18n>'; | ||
export const HTML_LONG = ` | ||
<div class="col-xs-4"> | ||
@@ -57,3 +170,3 @@ <h4 translate="translate" translate-context="Pour maman">Hello world</h4> | ||
export const HTML_SORTING = ` | ||
export const HTML_SORTING = ` | ||
<i18n>f</i18n> | ||
@@ -188,3 +301,3 @@ <i18n>0</i18n> | ||
export const POT_OUTPUT_SORTED= `msgid "" | ||
export const POT_OUTPUT_SORTED = `msgid "" | ||
msgstr "" | ||
@@ -191,0 +304,0 @@ "Content-Type: text/plain; charset=utf-8\\n" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
190241
2177
197
+ Addedcheerio@0.22.0(transitive)
+ Addedcss-select@1.2.0(transitive)
+ Addedcss-what@2.1.3(transitive)
+ Addeddomhandler@2.4.2(transitive)
+ Addedgopd@1.0.1(transitive)
+ Addedhtmlparser2@3.10.1(transitive)
+ Addedis-regex@1.1.4(transitive)
+ Addedlodash.assignin@4.2.0(transitive)
+ Addedlodash.bind@4.2.1(transitive)
+ Addedlodash.defaults@4.2.0(transitive)
+ Addedlodash.filter@4.6.0(transitive)
+ Addedlodash.flatten@4.4.0(transitive)
+ Addedlodash.foreach@4.5.0(transitive)
+ Addedlodash.map@4.6.0(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedlodash.pick@4.4.0(transitive)
+ Addedlodash.reduce@4.6.0(transitive)
+ Addedlodash.reject@4.6.0(transitive)
+ Addedlodash.some@4.6.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
- Removedcheerio@0.19.0(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removedcss-select@1.0.0(transitive)
- Removedcss-what@1.0.0(transitive)
- Removeddomhandler@2.3.0(transitive)
- Removeddomutils@1.4.3(transitive)
- Removedentities@1.0.0(transitive)
- Removedgopd@1.1.0(transitive)
- Removedhtmlparser2@3.8.3(transitive)
- Removedis-regex@1.2.0(transitive)
- Removedisarray@0.0.1(transitive)
- Removedlodash@3.10.1(transitive)
- Removedreadable-stream@1.1.14(transitive)
- Removedstring_decoder@0.10.31(transitive)
Updatedcheerio@^0.22.0