├── .gitignore ├── .jscs.json ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── index.js ├── lib ├── compile.js └── extract.js ├── package-lock.json ├── package.json └── test ├── compile.js ├── extract.js ├── extract_comments.js ├── extract_complete.js ├── extract_custom_filter_standard_attribute.js ├── extract_custom_filters.js ├── extract_extensions.js ├── extract_filters.js ├── extract_javascript.js ├── extract_javascript_custom_module_method.js ├── extract_line_numbers.js ├── extract_multiline.js ├── extract_plurals.js ├── extract_quotes.js ├── extract_regex.js ├── extract_template_literal.js ├── fixtures ├── DecoratedClassWithProperties.ts ├── bind-once.html ├── catalog.js ├── comments.html ├── comments.js ├── comments.ts ├── comments2.html ├── complete │ ├── README.md │ └── issue23.html ├── concat.js ├── context-custom.html ├── context.html ├── context.po ├── convertible_html_entities.po ├── corrupt.html ├── custom-attribute.html ├── custom.extension ├── custom.js_extension ├── custom_marker_name.js ├── custom_marker_name_plural.js ├── custom_marker_names.js ├── custom_marker_names_plural.js ├── custom_method.js ├── custom_module.js ├── data.html ├── deeppath_catalog.js ├── deeppath_catalog_invalid.js ├── delim.html ├── depth │ └── fr.po ├── duplicate-comments.html ├── ejs.ejs ├── empty.html ├── empty.po ├── entities.html ├── erb.erb ├── es6-class.js ├── es6-dynamic-import.js ├── es6-export.js ├── es6-import.js ├── escaped-quotes.html ├── escaped_quotes-custom-standard-attribute.html ├── escaped_quotes-custom.html ├── escaped_quotes.html ├── filter-custom-standard-attribute.html ├── filter-custom.html ├── filter-data-attributes.html ├── filter-in-multiple-expression-attributes-custom-standard-attribute.html ├── filter-in-multiple-expression-attributes-custom.html ├── filter-in-multiple-expression-attributes.html ├── filter.html ├── fr.po ├── fuzzy.po ├── inconvertible_html_entities.po ├── inline-templates.html ├── issue_188.html ├── issue_188.ts ├── js-in-script-tags │ ├── no-type.html │ ├── not-javascript.html │ └── type-javascript.html ├── jsp.jsp ├── line_numbers.html ├── line_numbers.js ├── merge.html ├── module.js ├── multi-line-comments.js ├── multifilter-custom-standard-attribute.html ├── multifilter-custom.html ├── multifilter.html ├── ngif.html ├── nl.po ├── no_delimiter.html ├── obsolete.po ├── php.php ├── plural.html ├── quotes.html ├── second.html ├── single.html ├── sort.html ├── source-property.js ├── source.js ├── strip.html ├── tag.tag ├── tapestry.tml ├── template-literal-component.js ├── template-literal-separated.js ├── translate-element.html ├── ts.ts ├── ts.tsx └── widget.html ├── issue_188.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .idea 3 | *.iml 4 | /node_modules 5 | /tmp 6 | /test/fixtures/*.mo 7 | .project 8 | -------------------------------------------------------------------------------- /.jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"], 3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 4 | "requireParenthesesAroundIIFE": true, 5 | "requireSpacesInFunctionExpression": { 6 | "beforeOpeningCurlyBrace": true 7 | }, 8 | "requireSpacesInAnonymousFunctionExpression": { 9 | "beforeOpeningRoundBrace": true 10 | }, 11 | "disallowSpacesInNamedFunctionExpression": { 12 | "beforeOpeningRoundBrace": true 13 | }, 14 | "disallowSpacesInFunctionDeclaration": { 15 | "beforeOpeningRoundBrace": true 16 | }, 17 | "disallowMixedSpacesAndTabs": true, 18 | "disallowMultipleVarDecl": true, 19 | "requireSpacesInsideObjectBrackets": "all", 20 | "disallowDanglingUnderscores": true, 21 | "disallowSpaceAfterObjectKeys": true, 22 | "requireCommaBeforeLineBreak": true, 23 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 24 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 25 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 26 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], 27 | "requireSpacesInConditionalExpression": true, 28 | "validateQuoteMarks": { "mark": true, "escape": true }, 29 | "validateIndentation": 4, 30 | "disallowTrailingWhitespace": true, 31 | "disallowKeywordsOnNewLine": ["else"], 32 | "requireCapitalizedConstructors": true, 33 | "safeContextKeyword": "self", 34 | "requireDotNotation": true, 35 | "disallowYodaConditions": true 36 | } 37 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true, 13 | "strict": true, 14 | "white": true, 15 | "indent": 4, 16 | "predef": [ 17 | "$", 18 | "angular", 19 | "describe", 20 | "it", 21 | "before", 22 | "beforeEach", 23 | "after", 24 | "afterEach" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | tmp 3 | Gruntfile.coffee 4 | .npmignore 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "node" 5 | 6 | before_script: 7 | - npm install -g grunt-cli 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.loadNpmTasks('grunt-contrib-clean'); 3 | grunt.loadNpmTasks('grunt-contrib-jshint'); 4 | grunt.loadNpmTasks('grunt-contrib-watch'); 5 | grunt.loadNpmTasks('grunt-jscs'); 6 | grunt.loadNpmTasks('grunt-mocha-cli'); 7 | grunt.loadNpmTasks('grunt-bump'); 8 | 9 | grunt.initConfig({ 10 | jshint: { 11 | all: ['{lib,test}/**/*.js', 'index.js', '!test/fixtures/*.js'], 12 | options: { 13 | jshintrc: '.jshintrc' 14 | } 15 | }, 16 | 17 | jscs: { 18 | src: { 19 | options: { 20 | config: '.jscs.json' 21 | }, 22 | files: { 23 | src: ['*.js', '{lib,test}/**/*.js', '!test/fixtures/*.js'] 24 | } 25 | } 26 | }, 27 | 28 | clean: { 29 | tmp: ['tmp'] 30 | }, 31 | 32 | watch: { 33 | test: { 34 | files: ['lib/**.js', 'test/**/*.{js,coffee}'], 35 | tasks: ['test'] 36 | } 37 | }, 38 | 39 | mochacli: { 40 | spec: { 41 | options: { 42 | reporter: 'spec' 43 | } 44 | } 45 | }, 46 | 47 | bump: { 48 | options: { 49 | files: ['package.json'], 50 | commitFiles: ['-a'], 51 | pushTo: 'origin' 52 | } 53 | } 54 | }); 55 | 56 | grunt.registerTask('default', ['test']); 57 | grunt.registerTask('build', ['clean', 'jshint', 'jscs']); 58 | grunt.registerTask('test', ['build', 'mochacli']); 59 | }; 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Ruben Vermeersch 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-gettext-tools 2 | 3 | > Tools for extracting/compiling angular-gettext strings. 4 | 5 | Used to construct build tools for [`angular-gettext`](https://github.com/rubenv/angular-gettext). 6 | 7 | [![Build Status](https://travis-ci.org/rubenv/angular-gettext-tools.png?branch=master)](https://travis-ci.org/rubenv/angular-gettext-tools) 8 | 9 | Implementations: 10 | 11 | * [Grunt plugin](https://github.com/rubenv/grunt-angular-gettext) 12 | * [Gulp plugin](https://github.com/gabegorelick/gulp-angular-gettext) 13 | * [CLI utility](https://github.com/huston007/angular-gettext-cli) 14 | * [Webpack loader (compilation)](https://github.com/princed/angular-gettext-loader) 15 | * [Webpack plugin (compilation and extraction)](https://github.com/augusto-altman/angular-gettext-plugin) 16 | 17 | Check the website for usage instructions: [http://angular-gettext.rocketeer.be/](http://angular-gettext.rocketeer.be/). 18 | 19 | ## Options 20 | 21 | All options and defaults are displayed below: 22 | 23 | ```JSON 24 | { 25 | "startDelim": "{{", 26 | "endDelim": "}}", 27 | "markerName": "gettext", 28 | "markerNames": [], 29 | "markerNamePlural": null, 30 | "markerNamesPlural": [], 31 | "moduleName": "gettextCatalog", 32 | "moduleMethodString": "getString", 33 | "moduleMethodPlural": "getPlural", 34 | "attribute": "translate", 35 | "attributes": [], 36 | "lineNumbers": true, 37 | "filterName": "translate", 38 | "format": "javascript", 39 | "defaultLanguage": false, 40 | "requirejs": false 41 | } 42 | ``` 43 | 44 | ## License 45 | 46 | (The MIT License) 47 | 48 | Copyright (C) 2013-2015 by Ruben Vermeersch 49 | 50 | Permission is hereby granted, free of charge, to any person obtaining a copy 51 | of this software and associated documentation files (the "Software"), to deal 52 | in the Software without restriction, including without limitation the rights 53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 54 | copies of the Software, and to permit persons to whom the Software is 55 | furnished to do so, subject to the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included in 58 | all copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 66 | THE SOFTWARE. 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Compiler: require('./lib/compile'), 3 | Extractor: require('./lib/extract') 4 | }; 5 | -------------------------------------------------------------------------------- /lib/compile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var po = require('pofile'); 4 | var _ = require('lodash'); 5 | 6 | var formats = { 7 | javascript: { 8 | addLocale: function (locale, strings) { 9 | return ' gettextCatalog.setStrings(\'' + locale + '\', ' + JSON.stringify(strings) + ');\n'; 10 | }, 11 | format: function (locales, options) { 12 | var angular = 'angular'; 13 | if (options.browserify) { 14 | angular = 'require(\'angular\')'; 15 | } 16 | var module = angular + '.module(\'' + options.module + '\')' + 17 | '.run([\'gettextCatalog\', function (gettextCatalog) {\n' + 18 | '/* jshint -W100 */\n' + 19 | locales.join('') + 20 | '/* jshint +W100 */\n'; 21 | if (options.defaultLanguage) { 22 | module += 'gettextCatalog.currentLanguage = \'' + options.defaultLanguage + '\';\n'; 23 | } 24 | module += '}]);'; 25 | 26 | if (options.requirejs) { 27 | return 'define([\'angular\', \'' + options.modulePath + '\'], function (angular) {\n' + module + '\n});'; 28 | } 29 | 30 | return module; 31 | } 32 | }, 33 | json: { 34 | addLocale: function (locale, strings) { 35 | return { 36 | name: locale, 37 | strings: strings 38 | }; 39 | }, 40 | format: function (locales, options) { 41 | var result = {}; 42 | locales.forEach(function (locale) { 43 | if (!result[locale.name]) { 44 | result[locale.name] = {}; 45 | } 46 | _.assign(result[locale.name], locale.strings); 47 | }); 48 | return JSON.stringify(result); 49 | } 50 | } 51 | }; 52 | 53 | var noContext = '$$noContext'; 54 | 55 | var Compiler = (function () { 56 | function Compiler(options) { 57 | this.options = _.extend({ 58 | format: 'javascript', 59 | ignoreFuzzyString: true, 60 | module: 'gettext' 61 | }, options); 62 | } 63 | 64 | Compiler.browserConvertedHTMLEntities = { 65 | 'hellip': '…', 66 | 'cent': '¢', 67 | 'pound': '£', 68 | 'euro': '€', 69 | 'laquo': '«', 70 | 'raquo': '»', 71 | 'rsaquo': '›', 72 | 'lsaquo': '‹', 73 | 'copy': '©', 74 | 'reg': '®', 75 | 'trade': '™', 76 | 'sect': '§', 77 | 'deg': '°', 78 | 'plusmn': '±', 79 | 'para': '¶', 80 | 'middot': '·', 81 | 'ndash': '–', 82 | 'mdash': '—', 83 | 'lsquo': '‘', 84 | 'rsquo': '’', 85 | 'sbquo': '‚', 86 | 'ldquo': '“', 87 | 'rdquo': '”', 88 | 'bdquo': '„', 89 | 'dagger': '†', 90 | 'Dagger': '‡', 91 | 'bull': '•', 92 | 'prime': '′', 93 | 'Prime': '″', 94 | 'asymp': '≈', 95 | 'ne': '≠', 96 | 'le': '≤', 97 | 'ge': '≥', 98 | 'sup2': '²', 99 | 'sup3': '³', 100 | 'frac12': '½', 101 | 'frac14': '¼', 102 | 'frac13': '⅓', 103 | 'frac34': '¾' 104 | }; 105 | 106 | Compiler.hasFormat = function (format) { 107 | return formats.hasOwnProperty(format); 108 | }; 109 | 110 | Compiler.prototype.convertPo = function (inputs) { 111 | var format = formats[this.options.format]; 112 | var ignoreFuzzyString = this.options.ignoreFuzzyString; 113 | var locales = []; 114 | 115 | inputs.forEach(function (input) { 116 | var catalog = po.parse(input); 117 | 118 | if (!catalog.headers.Language) { 119 | throw new Error('No Language header found!'); 120 | } 121 | 122 | var strings = {}; 123 | for (var i = 0; i < catalog.items.length; i++) { 124 | var item = catalog.items[i]; 125 | var ctx = item.msgctxt || noContext; 126 | var msgid = item.msgid; 127 | 128 | var convertedEntity; 129 | var unconvertedEntity; 130 | var unconvertedEntityPattern; 131 | 132 | for ( unconvertedEntity in Compiler.browserConvertedHTMLEntities ) { 133 | convertedEntity = Compiler.browserConvertedHTMLEntities[ unconvertedEntity ]; 134 | unconvertedEntityPattern = new RegExp( '&' + unconvertedEntity + ';?', 'g' ); 135 | msgid = msgid.replace( unconvertedEntityPattern, convertedEntity ); 136 | } 137 | 138 | var nonEmptyUpToDateStr = item.msgstr[0].length > 0 && !item.obsolete; 139 | var useNonFuzzyStrOnly = ignoreFuzzyString && !item.flags.fuzzy; 140 | 141 | if (nonEmptyUpToDateStr && (useNonFuzzyStrOnly || !ignoreFuzzyString)) { 142 | if (!strings[msgid]) { 143 | strings[msgid] = {}; 144 | } 145 | 146 | // Add array for plural, single string for signular. 147 | strings[msgid][ctx] = item.msgstr.length === 1 ? item.msgstr[0] : item.msgstr; 148 | } 149 | } 150 | 151 | // Strip context from strings that have no context. 152 | for (var key in strings) { 153 | if (Object.keys(strings[key]).length === 1 && strings[key][noContext]) { 154 | strings[key] = strings[key][noContext]; 155 | } 156 | } 157 | 158 | locales.push(format.addLocale(catalog.headers.Language, strings)); 159 | }); 160 | 161 | return format.format(locales, this.options); 162 | }; 163 | 164 | return Compiler; 165 | })(); 166 | 167 | module.exports = Compiler; 168 | -------------------------------------------------------------------------------- /lib/extract.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cheerio = require('cheerio'); 4 | var Po = require('pofile'); 5 | var babelParser = require('@babel/parser'); 6 | var search = require('binary-search'); 7 | var _ = require('lodash'); 8 | 9 | var escapeRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g; 10 | var noContext = '$$noContext'; 11 | 12 | function mkAttrRegex(startDelim, endDelim, attribute) { 13 | var start = startDelim.replace(escapeRegex, '\\$&'); 14 | var end = endDelim.replace(escapeRegex, '\\$&'); 15 | 16 | if (start === '' && end === '') { 17 | start = '^'; 18 | } else { 19 | // match optional :: (Angular 1.3's bind once syntax) without capturing 20 | start += '(?:\\s*\\:\\:\\s*)?'; 21 | } 22 | 23 | if (!_.isString(attribute) || attribute.length === 0) { 24 | attribute = 'translate'; 25 | } 26 | 27 | return new RegExp(start + '\\s*(\'|"|"|')(.*?)\\1\\s*\\|\\s*' + attribute + '\\s*:?\\s?(?:(\'|"|"|')\\s*(.*?)\\3)?\\s*(?:' + end + '|\\|)', 'g'); 28 | } 29 | 30 | function stringCompare(a, b) { 31 | return a === b ? 0 : a > b ? 1 : -1; 32 | } 33 | 34 | function contextCompare(a, b) { 35 | if (a !== null && b === null) { 36 | return -1; 37 | } else if (a === null && b !== null) { 38 | return 1; 39 | } 40 | return stringCompare(a, b); 41 | } 42 | 43 | function comments2String(comments) { 44 | return comments.join(', '); 45 | } 46 | 47 | function walkJs(node, fn, parentComment) { 48 | fn(node, parentComment); 49 | 50 | // Handle ts comments 51 | if (node && node.comments) { 52 | parentComment = node; 53 | parentComment.comments.reverse(); 54 | } 55 | 56 | for (var key in node) { 57 | var obj = node[key]; 58 | if (node && node.leadingComments) { 59 | parentComment = node; 60 | } 61 | 62 | if (typeof obj === 'object') { 63 | walkJs(obj, fn, parentComment); 64 | } 65 | } 66 | } 67 | 68 | function isStringLiteral(node) { 69 | return node.type === 'StringLiteral' || (node.type === 'Literal' && typeof(node.value) === 'string'); 70 | } 71 | 72 | function getJSExpression(node) { 73 | var res = ''; 74 | if (isStringLiteral(node)) { 75 | res = node.value; 76 | } 77 | 78 | if (node.type === 'TemplateLiteral') { 79 | node.quasis.forEach(function (elem) { 80 | res += elem.value.raw; 81 | }); 82 | } 83 | 84 | if (node.type === 'BinaryExpression' && node.operator === '+') { 85 | res += getJSExpression(node.left); 86 | res += getJSExpression(node.right); 87 | } 88 | return res; 89 | } 90 | 91 | var Extractor = (function () { 92 | function Extractor(options) { 93 | this.options = _.extend({ 94 | startDelim: '{{', 95 | endDelim: '}}', 96 | markerName: 'gettext', 97 | markerNames: [], 98 | markerNamePlural: null, 99 | markerNamesPlural: [], 100 | moduleName: 'gettextCatalog', 101 | moduleMethodString: 'getString', 102 | moduleMethodPlural: 'getPlural', 103 | attribute: 'translate', 104 | attributes: [], 105 | filterName: null, 106 | lineNumbers: true, 107 | extensions: { 108 | htm: 'html', 109 | html: 'html', 110 | php: 'html', 111 | phtml: 'html', 112 | tml: 'html', 113 | ejs: 'html', 114 | erb: 'html', 115 | js: 'js', 116 | tag: 'html', 117 | jsp: 'html', 118 | ts: 'js', 119 | tsx: 'js', 120 | }, 121 | postProcess: function (po) {} 122 | }, options); 123 | this.options.markerNames.unshift(this.options.markerName); 124 | if (this.options.markerNamePlural) { 125 | this.options.markerNamesPlural.unshift(this.options.markerNamePlural); 126 | } 127 | 128 | this.options.attributes.unshift(this.options.attribute); 129 | 130 | if (!this.options.filterName) { 131 | // If the filter name is not specified, assume the specified attribute is also the filter name 132 | this.options.filterName = this.options.attribute; 133 | } 134 | 135 | this.strings = {}; 136 | this.attrRegex = mkAttrRegex(this.options.startDelim, this.options.endDelim, this.options.filterName); 137 | this.noDelimRegex = mkAttrRegex('', '', this.options.filterName); 138 | } 139 | 140 | Extractor.isValidStrategy = function (strategy) { 141 | return strategy === 'html' || strategy === 'js'; 142 | }; 143 | 144 | Extractor.mkAttrRegex = mkAttrRegex; 145 | 146 | Extractor.prototype.addString = function (reference, string, plural, extractedComment, context) { 147 | // maintain backwards compatibility 148 | if (_.isString(reference)) { 149 | reference = { file: reference }; 150 | } 151 | 152 | string = string.trim(); 153 | 154 | if (string.length === 0) { 155 | return; 156 | } 157 | 158 | if (!context) { 159 | context = noContext; 160 | } 161 | 162 | if (!this.strings[string] || typeof this.strings[string] !== 'object') { 163 | this.strings[string] = {}; 164 | } 165 | 166 | if (!this.strings[string][context]) { 167 | this.strings[string][context] = new Po.Item(); 168 | } 169 | 170 | var item = this.strings[string][context]; 171 | item.msgid = string; 172 | 173 | var refString = reference.file; 174 | if (this.options.lineNumbers && reference.location && reference.location.start) { 175 | var line = reference.location.start.line; 176 | if (line || line === 0) { 177 | refString += ':' + reference.location.start.line; 178 | } 179 | } 180 | var refIndex = search(item.references, refString, stringCompare); 181 | if (refIndex < 0) { // don't add duplicate references 182 | // when not found, binary-search returns -(index_where_it_should_be + 1) 183 | item.references.splice(Math.abs(refIndex + 1), 0, refString); 184 | } 185 | 186 | if (context !== noContext) { 187 | item.msgctxt = context; 188 | } 189 | 190 | if (plural && plural !== '') { 191 | if (item.msgid_plural && item.msgid_plural !== plural) { 192 | throw new Error('Incompatible plural definitions for ' + string + ': ' + item.msgid_plural + ' / ' + plural + ' (in: ' + (item.references.join(', ')) + ')'); 193 | } 194 | item.msgid_plural = plural; 195 | item.msgstr = ['', '']; 196 | } 197 | if (extractedComment) { 198 | var commentIndex = search(item.extractedComments, extractedComment, stringCompare); 199 | if (commentIndex < 0) { // don't add duplicate comments 200 | item.extractedComments.splice(Math.abs(commentIndex + 1), 0, extractedComment); 201 | } 202 | } 203 | }; 204 | 205 | Extractor.prototype.extractJs = function (filename, src, lineNumber) { 206 | // used for line number of JS in HTML 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/issue_188.html: -------------------------------------------------------------------------------- 1 | constructor 2 | {{'constructor'|translate}} 3 | constructor 4 | -------------------------------------------------------------------------------- /test/fixtures/issue_188.ts: -------------------------------------------------------------------------------- 1 | angular.module("myApp").controller("helloController", (gettext, gettextCatalog) => { 2 | var simpleString: string = gettext("constructor"); 3 | var catalogString: string = gettextCatalog.getString("constructor"); 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/js-in-script-tags/no-type.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/js-in-script-tags/not-javascript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/js-in-script-tags/type-javascript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/jsp.jsp: -------------------------------------------------------------------------------- 1 | <%--JSP comment--%> 2 | <%@ attribute url="www.test.com" required="true" %> 3 | message 4 | -------------------------------------------------------------------------------- /test/fixtures/line_numbers.html: -------------------------------------------------------------------------------- 1 |
Line 1
2 | -------------------------------------------------------------------------------- /test/fixtures/line_numbers.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp").controller("helloController", function (gettext, gettextCatalog) { 2 | gettext('Line number 2'); 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/merge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Bird

4 |
Bird
5 |

Bird

6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/module.js: -------------------------------------------------------------------------------- 1 | define(['angular'], function (angular) { 2 | return angular.module('myApp', []); 3 | }) -------------------------------------------------------------------------------- /test/fixtures/multi-line-comments.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp").controller("helloController", function (gettext) { 2 | /// B 3 | /// A 4 | gettext('0'); 5 | 6 | /// A 7 | /// B 8 | gettext('0'); 9 | 10 | /// B 11 | /// A 12 | gettext('1'); 13 | }); 14 | -------------------------------------------------------------------------------- /test/fixtures/multifilter-custom-standard-attribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{'World'|trans|lowercase}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/multifilter-custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{'World'|trans|lowercase}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/multifilter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{'Second'|translate|lowercase}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/ngif.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
  • Show {{trackcount}} song...
  • 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/nl.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Language: nl\n" 4 | "MIME-Version: 1.0\n" 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 8 | "Project-Id-Version: \n" 9 | "POT-Creation-Date: \n" 10 | "PO-Revision-Date: \n" 11 | "Last-Translator: \n" 12 | "Language-Team: \n" 13 | "X-Generator: Poedit 1.5.7\n" 14 | 15 | #: test/fixtures/single.html test/fixtures/second.html 16 | msgid "Hello!" 17 | msgstr "Hallo!" 18 | 19 | #: test/fixtures/second.html 20 | msgid "This is a test" 21 | msgstr "Dit is een test" 22 | 23 | #: test/fixtures/plural.html 24 | msgid "Bird" 25 | msgid_plural "Birds" 26 | msgstr[0] "Vogel" 27 | msgstr[1] "Vogels" 28 | 29 | #: test/fixtures/quotes.html 30 | msgid "Hello \"world\"" 31 | msgstr "Hallo \"wereld\"" 32 | -------------------------------------------------------------------------------- /test/fixtures/no_delimiter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Upload {{count}} file 10 | 11 | -------------------------------------------------------------------------------- /test/fixtures/obsolete.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Language: nl\n" 4 | "MIME-Version: 1.0\n" 5 | "Content-Type: text/plain; charset=UTF-8\n" 6 | "Content-Transfer-Encoding: 8bit\n" 7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 8 | "Project-Id-Version: \n" 9 | "POT-Creation-Date: \n" 10 | "PO-Revision-Date: \n" 11 | "Last-Translator: \n" 12 | "Language-Team: \n" 13 | "X-Generator: Poedit 1.5.7\n" 14 | 15 | #: test/fixtures/single.html 16 | msgid "Hello!" 17 | msgstr "Hallo!" 18 | 19 | #~ msgid "Hello \"world\"" 20 | #~ msgstr "Hallo \"wereld\"" 21 | -------------------------------------------------------------------------------- /test/fixtures/php.php: -------------------------------------------------------------------------------- 1 |
  • 2 | Play 3 | 4 |
  • 5 | -------------------------------------------------------------------------------- /test/fixtures/plural.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    Bird
    4 |
    Hobbit
    5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/quotes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello "world"!

    4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/second.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello!

    4 |

    This is a test

    5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello!

    4 |

    Hello!

    5 | polo 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/sort.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    a

    4 |

    c

    5 |

    b

    6 |

    d

    7 |

    d

    8 |

    d

    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/source-property.js: -------------------------------------------------------------------------------- 1 | var HelloController = function (gettext) { 2 | this.gettext = gettext; 3 | 4 | this.someProperties = [ 5 | this.gettext("Hello"), 6 | this.gettext("World"), 7 | ]; 8 | }; 9 | 10 | HelloController.prototype.someFunction = function () { 11 | return this.gettext("Hello world"); 12 | }; 13 | 14 | angular.module("myApp").controller("helloController", HelloController); 15 | -------------------------------------------------------------------------------- /test/fixtures/source.js: -------------------------------------------------------------------------------- 1 | angular.module("myApp").controller("helloController", function (gettext) { 2 | var myString = gettext("Hello"); 3 | gettext(); // Should be ignored. 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/strip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    4 | Hello! 5 |

    6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/tag.tag: -------------------------------------------------------------------------------- 1 | <%--JSP comment--%> 2 | <%@ attribute url="www.test.com" required="true" %> 3 | message 4 | -------------------------------------------------------------------------------- /test/fixtures/tapestry.tml: -------------------------------------------------------------------------------- 1 | 2 |

    Bonjour from HelloWorld component.

    3 | 4 | -------------------------------------------------------------------------------- /test/fixtures/template-literal-component.js: -------------------------------------------------------------------------------- 1 | class MyLiteralController { 2 | /* @ngInject */ 3 | constructor() {} 4 | } 5 | 6 | export const MyLiteralComponent = { 7 | controller: MyLiteralController, 8 | bindings: {}, 9 | template: ` 10 |
    11 |

    Hi

    12 |
    13 | `, 14 | }; 15 | -------------------------------------------------------------------------------- /test/fixtures/template-literal-separated.js: -------------------------------------------------------------------------------- 1 | // template.js 2 | export default ` 3 |
    4 |

    Hi

    5 |
    `; -------------------------------------------------------------------------------- /test/fixtures/translate-element.html: -------------------------------------------------------------------------------- 1 | 1: message 2 | 3 | 2: message with comment and plural 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/ts.ts: -------------------------------------------------------------------------------- 1 | angular.module("myApp").controller("helloController", (gettext) => { 2 | var myString: string = gettext("Hello"); 3 | var longString: string = gettext(`One 4 | Two 5 | Three`); 6 | var castedVar: any = gettext("Casted"); 7 | gettext(); // Should be ignored. 8 | }); 9 | -------------------------------------------------------------------------------- /test/fixtures/ts.tsx: -------------------------------------------------------------------------------- 1 | const Test = () => ( 2 |
    3 |

    {gettext("Hello World")}

    4 |

    5 | {gettext(`One 6 | Two 7 | Three`)} 8 |

    9 |
    10 | ); 11 | -------------------------------------------------------------------------------- /test/fixtures/widget.html: -------------------------------------------------------------------------------- 1 |

    {{options.caption}}

    {{options.remark}}
    Available for {{$count}} {{datatype}} ({{($count / total * 100) | number:2}}%)
    {{sticker.key}}
    {{sticker.value}}
    {{col.caption}}
    {{row[col.id]}}
    {{'No data' | translate}}
    2 | -------------------------------------------------------------------------------- /test/issue_188.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var fs = require('fs'); 5 | var Extractor = require('..').Extractor; 6 | var testExtract = require('./utils').testExtract; 7 | 8 | describe('Extract', function () { 9 | it('Extract "constructor" from HTML', function () { 10 | var files = [ 11 | 'test/fixtures/issue_188.html' 12 | ]; 13 | var catalog = testExtract(files); 14 | 15 | assert.equal(catalog.items.length, 1); 16 | assert.equal(catalog.items[0].msgid, 'constructor'); 17 | assert.equal(catalog.items[0].msgstr, ''); 18 | assert.deepEqual(catalog.items[0].references, ['test/fixtures/issue_188.html:1', 'test/fixtures/issue_188.html:2', 'test/fixtures/issue_188.html:3']); 19 | }); 20 | 21 | it('Extract "constructor" from TypeScript', function () { 22 | var files = [ 23 | 'test/fixtures/issue_188.ts' 24 | ]; 25 | var catalog = testExtract(files); 26 | 27 | assert.equal(catalog.items.length, 1); 28 | assert.equal(catalog.items[0].msgid, 'constructor'); 29 | assert.equal(catalog.items[0].msgstr, ''); 30 | assert.deepEqual(catalog.items[0].references, ['test/fixtures/issue_188.ts:2', 'test/fixtures/issue_188.ts:3']); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PO = require('pofile'); 4 | var fs = require('fs'); 5 | var Extractor = require('..').Extractor; 6 | 7 | function testExtract(filenames, options) { 8 | var extractor = new Extractor(options); 9 | filenames.forEach(function (filename) { 10 | extractor.parse(filename, fs.readFileSync(filename, 'utf8')); 11 | }); 12 | 13 | return PO.parse(extractor.toString()); 14 | } 15 | 16 | module.exports = { 17 | testExtract: testExtract 18 | }; 19 | --------------------------------------------------------------------------------