jsxgettext
Advanced tools
Comparing version 0.3.4 to 0.3.5
130
lib/cli.js
#!/usr/bin/env node | ||
"use strict"; | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var parsers = require('./parsers'); | ||
var jsxgettext = require('./jsxgettext'); | ||
var opts = require("nomnom") | ||
.script('jsxgettext') | ||
.option('output', { | ||
abbr: 'o', | ||
metavar: 'FILE', | ||
default: 'messages.po', | ||
help: 'write output to specified file' | ||
}) | ||
.option('output-dir', { | ||
abbr: 'p', | ||
metavar: 'DIR', | ||
help: 'output files will be placed in directory DIR' | ||
}) | ||
.option('version', { | ||
abbr: 'v', | ||
flag: true, | ||
help: 'print version and exit', | ||
callback: function() { | ||
return require('../package.json').version; | ||
} | ||
}) | ||
.option('input', { | ||
position: 0, | ||
required: true, | ||
list: true, | ||
help: 'input files' | ||
}) | ||
.option('keyword', { | ||
abbr: 'k', | ||
metavar: 'WORD', | ||
help: 'additional keyword to be looked for' | ||
}) | ||
.option('join-existing', { | ||
abbr: 'j', | ||
flag: true, | ||
help: 'join messages with existing file' | ||
}) | ||
.option('language', { | ||
abbr: 'L', | ||
metavar: 'NAME', | ||
default: 'JavaScript', | ||
help: 'recognise the specified language (JavaScript, EJS, Jinja, Jade, Handlebars)' | ||
}) | ||
.parse(); | ||
.script('jsxgettext') | ||
.option('output', { | ||
abbr: 'o', | ||
metavar: 'FILE', | ||
default: 'messages.po', | ||
help: 'write output to specified file' | ||
}) | ||
.option('output-dir', { | ||
abbr: 'p', | ||
metavar: 'DIR', | ||
help: 'output files will be placed in directory DIR' | ||
}) | ||
.option('version', { | ||
abbr: 'v', | ||
flag: true, | ||
help: 'print version and exit', | ||
callback: function () { | ||
return require('../package.json').version; | ||
} | ||
}) | ||
.option('input', { | ||
position: 0, | ||
required: true, | ||
list: true, | ||
help: 'input files' | ||
}) | ||
.option('keyword', { | ||
abbr: 'k', | ||
metavar: 'WORD', | ||
help: 'additional keyword to be looked for' | ||
}) | ||
.option('join-existing', { | ||
abbr: 'j', | ||
flag: true, | ||
help: 'join messages with existing file' | ||
}) | ||
.option('language', { | ||
abbr: 'L', | ||
metavar: 'NAME', | ||
default: 'javascript', | ||
help: 'use the specified language (' + ['javascript'].concat(Object.keys(parsers)).join(', ') + ')' | ||
}) | ||
.parse(); | ||
function main () { | ||
function gen(sources) { | ||
var result; | ||
var lang = opts.language.toLowerCase(); | ||
if (lang === 'javascript') { | ||
result = jsxgettext.generate(sources, opts); | ||
} else if (lang in parsers) { | ||
result = jsxgettext.generate.apply(jsxgettext, parsers[lang](sources, opts)); | ||
} else { | ||
throw new Error("Unsupported language: " + opts.language); | ||
} | ||
if (opts.output === '-') { | ||
console.log(result); | ||
} else { | ||
fs.writeFileSync(path.resolve(path.join(opts['output-dir'] || '', opts.output)), result, "utf8"); | ||
} | ||
} | ||
function main() { | ||
var files = opts.input; | ||
@@ -73,22 +95,2 @@ var sources = {}; | ||
function gen (sources) { | ||
var result; | ||
if (opts.language.toUpperCase() === 'EJS') { | ||
result = jsxgettext.generateFromEJS(sources, opts); | ||
} else if (opts.language.toUpperCase() === 'JINJA') { | ||
result = jsxgettext.generateFromJinja(sources, opts); | ||
} else if (opts.language.toUpperCase() === 'JADE') { | ||
result = jsxgettext.generateFromJade(sources, opts); | ||
} else if (opts.language.toUpperCase() === 'HANDLEBARS') { | ||
result = jsxgettext.generateFromHandlebars(sources, opts); | ||
} else { | ||
result = jsxgettext.generate(sources, opts); | ||
} | ||
if (opts.output === '-') { | ||
console.log(result); | ||
} else { | ||
fs.writeFileSync(path.resolve(path.join(opts['output-dir'] || '', opts.output)), result, "utf8"); | ||
} | ||
} | ||
main(); |
@@ -1,3 +0,1 @@ | ||
/* jshint node: true, undef: true */ | ||
/* global module, require */ | ||
"use strict"; | ||
@@ -14,3 +12,2 @@ | ||
var estraverse = require('estraverse'); | ||
var jade = require('jade'); | ||
var gettextParser = require('gettext-parser'); | ||
@@ -34,15 +31,27 @@ | ||
function checkExpr(node, keyword) { | ||
var firstArg = node.arguments && node.arguments[0]; | ||
return (node.type === "CallExpression" && // must be a call expression | ||
( | ||
node.callee.name && // Should not be an anonymous function call | ||
(node.callee.name === 'gettext' || // with a gettext call expr | ||
(node.callee.name === keyword)) || // or keyword call expr | ||
(node.callee.type === 'MemberExpression' && // or a member expr | ||
(node.callee.property.name === 'gettext' || | ||
node.callee.property.name === keyword)) | ||
) && | ||
firstArg && (isStrConcatExpr(firstArg) || isStringLiteral(firstArg)) | ||
); | ||
function getTranslatable(node, keyword) { | ||
// must be a call expression with arguments | ||
if (node.type !== "CallExpression" || !node.arguments) | ||
return false; | ||
var callee = node.callee; | ||
var funcName = callee.name; | ||
var arg = node.arguments[0]; | ||
if (!funcName) { | ||
if (callee.type !== 'MemberExpression') | ||
return false; | ||
// Special case for gettext.call calls (or keyword.call) | ||
if (callee.property.name === 'call') { | ||
funcName = callee.object.name; | ||
arg = node.arguments[1]; // skip context object | ||
} else { | ||
funcName = callee.property.name; | ||
} | ||
} | ||
if ((funcName === 'gettext' || funcName === keyword) && | ||
arg && (isStrConcatExpr(arg) || isStringLiteral(arg))) | ||
return arg; | ||
} | ||
@@ -58,4 +67,12 @@ | ||
function loadStrings(poFile) { | ||
try { | ||
return gettextParser.po.parse(fs.readFileSync(path.resolve(poFile)), "utf-8"); | ||
} catch (e) { | ||
return null; | ||
} | ||
} | ||
// generate extracted strings file | ||
function gen (sources, options) { | ||
function gen(sources, options) { | ||
var poJSON; | ||
@@ -89,4 +106,4 @@ if (options['join-existing']) | ||
var ast = parser.parse(source, { | ||
onComment: function (block, text, start, end, line, column) { | ||
text = text.replace(/^\s*L10n:/, '') | ||
onComment: function (block, text, start, end, line/*, column*/) { | ||
text = text.replace(/^\s*L10n:/, ''); | ||
@@ -105,6 +122,6 @@ if (!text) | ||
// finds comments that end on the previous line | ||
function findComments (comments, line) { | ||
function findComments(comments, line) { | ||
return comments.map(function (node) { | ||
var commentLine = node.line; | ||
if (commentLine == line || commentLine + 1 == line) { | ||
if (commentLine === line || commentLine + 1 === line) { | ||
return node.value; | ||
@@ -118,6 +135,7 @@ } | ||
enter: function (node) { | ||
if (!checkExpr(node, options.keyword)) | ||
var arg = getTranslatable(node, options.keyword); | ||
if (!arg) | ||
return; | ||
var str = extractStr(node.arguments[0]); | ||
var str = extractStr(arg); | ||
var line = node.loc.start.line; | ||
@@ -165,242 +183,2 @@ var comments = findComments(astComments, line); | ||
// generate extracted strings file from EJS | ||
function genEJS (ejsSources, options) { | ||
Object.keys(ejsSources).forEach(function (filename) { | ||
ejsSources[filename] = parseEJS(ejsSources[filename]); | ||
}); | ||
return gen(ejsSources, options); | ||
} | ||
// generate extracted strings file from Jade templates | ||
function genJade (jadeSources, options) { | ||
Object.keys(jadeSources).forEach(function (filename) { | ||
jadeSources[filename] = parseJade(jadeSources[filename], options); | ||
}); | ||
return gen(jadeSources, options); | ||
} | ||
// generate extracted strings file from Handlebars/Mustache templates | ||
function genHandlebars (hbSources, options) { | ||
Object.keys(hbSources).forEach(function (filename) { | ||
hbSources[filename] = parseHandlebars(hbSources[filename]); | ||
}); | ||
return gen(hbSources, options); | ||
} | ||
// generate extracted strings file from Jinja2 templates | ||
function genJinja (jinjaSources, options) { | ||
Object.keys(jinjaSources).forEach(function (filename) { | ||
jinjaSources[filename] = parseEJS(jinjaSources[filename], {open: "{{", close: "}}"}); | ||
}); | ||
return gen(jinjaSources, options); | ||
} | ||
function loadStrings (poFile) { | ||
try { | ||
return gettextParser.po.parse(fs.readFileSync(path.resolve(poFile)), "utf-8"); | ||
} catch (e) { | ||
return null; | ||
} | ||
} | ||
// strips everything but the javascript bits | ||
function parseEJS (str, options){ | ||
options = options || {}; | ||
var open = options.open || '<%', | ||
close = options.close || '%>'; | ||
var buf = []; | ||
var lineno = 1; | ||
for (var i = 0, len = str.length; i < len; ++i) { | ||
if (str.slice(i, open.length + i) == open) { | ||
i += open.length; | ||
switch (str.substr(i, 1)) { | ||
case '=': | ||
case '-': | ||
++i; | ||
break; | ||
} | ||
var end = str.indexOf(close, i), js = str.substring(i, end), start = i, n = 0; | ||
if ('-' == js[js.length-1]){ | ||
js = js.substring(0, js.length - 2); | ||
} | ||
/* jshint -W030 */ | ||
while (~(n = js.indexOf("\n", n))) n++,buf.push("\n"); | ||
/* jshint +W030 */ | ||
// skip EJS include statements which are not valid javascript | ||
if (/^\s*include\s*[^\s]+\s*$/.test(js)) js = ""; | ||
buf.push(js, ';'); | ||
i += end - start + close.length - 1; | ||
} else if (str.substr(i, 1) == "\n") { | ||
buf.push("\n"); | ||
} | ||
} | ||
return buf.join(''); | ||
} | ||
// From MDN: | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters | ||
function escapeRegExp(string){ | ||
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); | ||
} | ||
function parseJade(str, options) { | ||
options = options || {}; | ||
var lineno = 1; | ||
var parser = new jade.Parser(str); | ||
var lexer = parser.lexer; | ||
var token; | ||
var gettextRegexPrefix = '(?:gettext'; | ||
if (options.keyword) { | ||
gettextRegexPrefix += '|' + escapeRegExp(options.keyword); | ||
} | ||
gettextRegexPrefix += ')'; | ||
var gettexRegex = new RegExp(gettextRegexPrefix + '(?:\\(\"[^"]+\"|\\(\'[^\']+\')', 'gi'); | ||
function extractGettext(str) { | ||
if (typeof(str) !== 'string') return ''; | ||
var tmp = str.match(gettexRegex) || []; | ||
return tmp.map(function(t) { | ||
return t + ')'; | ||
}).join(';'); | ||
} | ||
function extractFromObj(key) { | ||
/* jshint -W040 */ | ||
return extractGettext(this[key]); | ||
} | ||
function isEmpty(obj) { | ||
return obj.length; | ||
} | ||
var buf = [], lineN, tmp; | ||
do { | ||
token = lexer.next(); | ||
lineN = token.line - 1; | ||
switch(token.type) { | ||
case 'attrs': | ||
tmp = Object.keys(token.attrs).map(extractFromObj, token.attrs).filter(isEmpty); | ||
if(tmp.length) buf[lineN] = tmp.join('') + ';'; | ||
break; | ||
case 'text': | ||
case 'code': | ||
tmp = extractGettext(token.val); | ||
if (tmp.length) buf[lineN] = tmp + ';'; | ||
break; | ||
} | ||
} while(token.type != 'eos'); | ||
return buf.join('\n'); | ||
} | ||
// Turn handlebars helper calls into javascript-syntax functions. | ||
// Also comment blocks are turned into javascript comments. | ||
function parseHandlebars(str, options) { | ||
// Using regexes for parsing, ooooh yeeeahhh! | ||
// Short comments: {{! this is a comment }} | ||
var shortCommentRE = /\{\{\!(.*?)\}\}/ | ||
// Long comments: {{!-- this comment has {{markup}} in it --}} | ||
var longCommentRE = /\{\{\!--(.*?)--\}\}/ | ||
// Block helpers: {{#helper}}template content{{/helper}} | ||
var blockHelperStartRE = /\{\{#(\w+)\}\}/ | ||
var blockHelperEndRE = /\{\{\/(\w+)\}\}/ | ||
// Function helpers: {{ helper value }} or {{ helper "some string" }} | ||
var singleQuotedStringWithEscapes = "'(([^']*?(\\\\')?)+)'"; | ||
var doubleQuotedStringWithEscapes = '"(([^"]*?(\\\\")?)+)"'; | ||
var funcHelperRE = new RegExp("\\{\\{\\s*(\\w+)\\s+((\\w+)|(" + | ||
singleQuotedStringWithEscapes + ")|(" + | ||
doubleQuotedStringWithEscapes + "))\\s*\\}\\}") | ||
var buf = []; | ||
var match = null; | ||
while (str.length) { | ||
// Find the earliest match of any type of tag in the string. | ||
match = str.match(shortCommentRE); | ||
if (match) { | ||
match.type = 'comment'; | ||
} | ||
var nextMatch = str.match(longCommentRE); | ||
if (nextMatch) { | ||
if (!match || nextMatch.index < match.index) { | ||
match = nextMatch; | ||
match.type = 'comment'; | ||
} | ||
} | ||
nextMatch = str.match(blockHelperStartRE); | ||
if (nextMatch) { | ||
if (!match || nextMatch.index < match.index) { | ||
match = nextMatch; | ||
match.type = 'block'; | ||
} | ||
} | ||
nextMatch = str.match(funcHelperRE); | ||
if (nextMatch) { | ||
if (!match || nextMatch.index < match.index) { | ||
match = nextMatch; | ||
match.type = 'func'; | ||
} | ||
} | ||
if (!match) { | ||
break; | ||
} | ||
str = str.substring(match.index + match[0].length); | ||
// Translate the match into an appropriate chunk of javascript. | ||
if (match.type == 'comment') { | ||
// Template comment => javascript comment | ||
match[1].split("\n").forEach(function(comment) { | ||
buf.push("//"); | ||
buf.push(comment); | ||
buf.push("\n"); | ||
}) | ||
} else if (match.type == 'block') { | ||
// Template block helper => javascript function call | ||
var helperName = match[1]; | ||
buf.push(helperName) | ||
buf.push('("') | ||
var endMatch = str.match(blockHelperEndRE); | ||
while (endMatch && endMatch[1] !== helperName) { | ||
var skipTo = endMatch.index + endMatch[0].length; | ||
buf.push(str.substring(0, skipTo).replace('"', '\\"')); | ||
str = str.substring(skipTo); | ||
endMatch = str.match(blockHelperEndRE); | ||
} | ||
if (endMatch) { | ||
buf.push(str.substring(0, endMatch.index).replace('"', '\\"')); | ||
str = str.substring(endMatch.index + endMatch[0].length); | ||
} else { | ||
buf.push(str.replace('"', '\\"')); | ||
str = ''; | ||
} | ||
buf.push('")\n'); | ||
} else if (match.type == 'func') { | ||
// Template function helper => javascript function call | ||
buf.push(match[1]); | ||
buf.push('('); | ||
buf.push(match[2]); | ||
buf.push(')\n'); | ||
} | ||
} | ||
return buf.join(''); | ||
} | ||
exports.generate = gen; | ||
exports.generateFromEJS = genEJS; | ||
exports.generateFromJade = genJade; | ||
exports.generateFromHandlebars = genHandlebars; | ||
exports.generateFromJinja = genJinja; | ||
exports.generate = gen; |
{ | ||
"author": "Zach Carter", | ||
"author": "Zach Carter <zcarter@mozilla.com> (http://twitter.com/zii)", | ||
"contributors": [ | ||
"Burak Yigit Kaya <me@byk.im> (http://byk.im)", | ||
"Kaare A. Larsen <kaare.a.larsen@gmail.com> (http://bottleno.se)", | ||
"Austin King <shout@ozten.com> (https://ozten.com)", | ||
"Lloyd Hilaiel <lloyd@hilaiel.com> (http://lloyd.io)", | ||
"Francois Marier <francois@fmarier.org> (http://fmarier.org)", | ||
"Andris Reinman <andris.reinman@gmail.com> (http://www.andrisreinman.com)", | ||
"Vlad Filippov <github@vf.io> (http://vf.io)", | ||
"Michael Weibel (https://github.com/mweibel)", | ||
"Eduardo Vaz de Mello (https://github.com/wirapuru)", | ||
"Ryan Kelly <ryan@rfk.id.au> (http://www.rfk.id.au)" | ||
], | ||
"name": "jsxgettext", | ||
"description": "Extract gettext calls from JavaScript and EJS files", | ||
"version": "0.3.4", | ||
"version": "0.3.5", | ||
"license": "MPL-2.0", | ||
"description": "Extracts gettext strings from JavaScript, EJS, Jade, Jinja and Handlebars files.", | ||
"keywords": ["i18n", "internationalization", "gettext", "xgettext"], | ||
"homepage": "https://github.com/zaach/jsxgettext", | ||
"bugs": "https://github.com/zaach/jsxgettext/issues", | ||
"repository": { | ||
@@ -21,11 +37,8 @@ "type": "git", | ||
"devDependencies": { | ||
"jshint": "~2.4.1", | ||
"test": "*" | ||
}, | ||
"optionalDependencies": {}, | ||
"engines": { | ||
"node": "*" | ||
}, | ||
"scripts": { | ||
"test": "node tests/all.js" | ||
"test": "node ./node_modules/jshint . && node test" | ||
} | ||
} |
# jsxgettext [](https://travis-ci.org/zaach/jsxgettext) [](http://badge.fury.io/js/jsxgettext) | ||
A node module with a CLI that extracts gettext strings from JavaScript and EJS files. It also extracts comments that begin with "L10n:" when they appear above a `gettext` call. | ||
A node module with a CLI that extracts gettext strings from JavaScript, EJS, Jade, Jinja and Handlebars files. Uses a real parser, [acorn](https://github.com/marijnh/acorn), for JavaScript files and recognizes the following uses: | ||
```javascript | ||
gettext("Hello world!"); | ||
gettext("Hello" + ' world!'); | ||
myModule.gettext("Hello " + 'world!'); | ||
gettext.call(myObj, "Hello " + 'world!'); | ||
``` | ||
It also extracts comments that begin with "L10n:" when they appear above a `gettext` call: | ||
```javascript | ||
// L10n: Salutation to the world | ||
gettext("Hello world!"); | ||
``` | ||
## Install | ||
@@ -31,4 +45,2 @@ | ||
-j, --join-existing join messages with existing file | ||
-L NAME, --language NAME recognise the specified language (JavaScript, EJS, Jinja) | ||
-L NAME, --language NAME use the specified language (javascript, ejs, jinja, handlebars, jade) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
45
1
46
51124
2
774
17
2