i18n enable runtime node files

This commit is contained in:
Nick O'Leary 2015-04-25 23:29:53 +01:00
parent 7d41781fb4
commit 6d4c64fcd5
9 changed files with 196 additions and 32 deletions

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,12 +32,12 @@ module.exports = function(RED) {
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000; this.repeat = this.repeat * 1000;
if (RED.settings.verbose) { this.log("repeat = "+this.repeat); } if (RED.settings.verbose) { this.log(RED._("inject.repeat",this)); }
this.interval_id = setInterval( function() { this.interval_id = setInterval( function() {
node.emit("input",{}); node.emit("input",{});
}, this.repeat ); }, this.repeat );
} else if (this.crontab) { } else if (this.crontab) {
if (RED.settings.verbose) { this.log("crontab = "+this.crontab); } if (RED.settings.verbose) { this.log(RED._("inject.crontab",this)); }
this.cronjob = new cron.CronJob(this.crontab, this.cronjob = new cron.CronJob(this.crontab,
function() { function() {
node.emit("input",{}); node.emit("input",{});
@ -68,10 +68,10 @@ module.exports = function(RED) {
InjectNode.prototype.close = function() { InjectNode.prototype.close = function() {
if (this.interval_id != null) { if (this.interval_id != null) {
clearInterval(this.interval_id); clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log("inject: repeat stopped"); } if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
} else if (this.cronjob != null) { } else if (this.cronjob != null) {
this.cronjob.stop(); this.cronjob.stop();
if (RED.settings.verbose) { this.log("inject: cronjob stopped"); } if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
delete this.cronjob; delete this.cronjob;
} }
} }
@ -84,7 +84,7 @@ module.exports = function(RED) {
res.send(200); res.send(200);
} catch(err) { } catch(err) {
res.send(500); res.send(500);
node.error("Inject failed:"+err); node.error(RED._("inject.failed",{error:err}));
} }
} else { } else {
res.send(404); res.send(404);

View File

@ -0,0 +1,8 @@
{
"inject": {
"repeat": "repeat = __repeat__",
"crontab": "crontab = __crontab__",
"stopped": "stopped",
"failed": "Inject failed: __error__"
}
}

View File

@ -19,15 +19,21 @@ var when = require("when");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");
var defaultLang = "en-US";
var resourceMap = { var resourceMap = {
"messages": path.resolve(__dirname+"/../locales") "messages": {
basedir: path.resolve(__dirname+"/../locales"),
file:"messages.json"
}
} }
var resourceCache = {}
function registerMessageCatalog(namespace,dir,file) {
function registerMessageCatalog(namespace,dir) {
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
resourceMap[namespace] = dir; resourceMap[namespace] = { basedir:dir, file:file};
i18n.loadNamespace(namespace,function() { i18n.loadNamespace(namespace,function() {
//console.log(namespace,dir);
resolve(); resolve();
}); });
}); });
@ -36,13 +42,16 @@ function registerMessageCatalog(namespace,dir) {
var MessageFileLoader = { var MessageFileLoader = {
fetchOne: function(lng, ns, callback) { fetchOne: function(lng, ns, callback) {
if (resourceMap[ns]) { if (resourceMap[ns]) {
var file = path.join(resourceMap[ns],lng,"messages.json"); var file = path.join(resourceMap[ns].basedir,lng,resourceMap[ns].file);
fs.readFile(file,"utf8",function(err,content) { fs.readFile(file,"utf8",function(err,content) {
if (err) { if (err) {
callback(err); callback(err);
} else { } else {
try { try {
callback(null, JSON.parse(content.replace(/^\uFEFF/, ''))); //console.log(">>",ns,file);
resourceCache[ns] = resourceCache[ns]||{};
resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, ''));
callback(null, resourceCache[ns][lng]);
} catch(e) { } catch(e) {
callback(e); callback(e);
} }
@ -70,10 +79,26 @@ function init() {
}); });
} }
function getCatalog(namespace,lang) {
var result = null;
if (resourceCache.hasOwnProperty(namespace)) {
result = resourceCache[namespace][lang];
if (!result) {
var langParts = lang.split("-");
if (langParts.length == 2) {
result = getCatalog(namespace,langParts[0]);
}
}
}
return result;
}
var obj = module.exports = { var obj = module.exports = {
init: init, init: init,
registerMessageCatalog: registerMessageCatalog registerMessageCatalog: registerMessageCatalog,
catalog: getCatalog,
i: i18n
} }
obj['_'] = function() { obj['_'] = function() {
@ -81,5 +106,6 @@ obj['_'] = function() {
//if (def) { //if (def) {
// opts.defaultValue = def; // opts.defaultValue = def;
//} //}
//console.log(arguments);
return i18n.t.apply(null,arguments); return i18n.t.apply(null,arguments);
} }

View File

@ -26,8 +26,8 @@ var settings;
function init(_settings) { function init(_settings) {
settings = _settings; settings = _settings;
registry.init(settings);
loader.init(settings); loader.init(settings);
registry.init(settings,loader);
} }
//TODO: defaultNodesDir/disableNodePathScan are to make testing easier. //TODO: defaultNodesDir/disableNodePathScan are to make testing easier.
// When the tests are componentized to match the new registry structure, // When the tests are componentized to match the new registry structure,

View File

@ -18,16 +18,26 @@ var when = require("when");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var events = require("../../events");
var localfilesystem = require("./localfilesystem"); var localfilesystem = require("./localfilesystem");
var registry = require("./registry"); var registry = require("./registry");
var RED;
var settings; var settings;
var i18n = require("../../i18n");
events.on("node-locales-dir", function(info) {
i18n.registerMessageCatalog(info.namespace,info.dir,info.file);
});
function init(_settings) { function init(_settings) {
settings = _settings; settings = _settings;
localfilesystem.init(settings); localfilesystem.init(settings);
RED = require('../../red');
} }
function load(defaultNodesDir,disableNodePathScan) { function load(defaultNodesDir,disableNodePathScan) {
@ -133,6 +143,7 @@ function loadNodeConfig(fileInfo) {
node.types = []; node.types = [];
node.err = err.toString(); node.err = err.toString();
} }
resolve(node);
} else { } else {
var types = []; var types = [];
@ -143,8 +154,32 @@ function loadNodeConfig(fileInfo) {
types.push(match[2]); types.push(match[2]);
} }
node.types = types; node.types = types;
node.config = content;
var langRegExp = /^<script[^>]* data-lang=['"](.+?)['"]/i;
regExp = /(<script[^>]* data-help-name=[\s\S]*?<\/script>)/gi;
match = null;
var mainContent = "";
var helpContent = {};
var index = 0;
while((match = regExp.exec(content)) !== null) {
mainContent += content.substring(index,regExp.lastIndex-match[1].length);
index = regExp.lastIndex;
var help = content.substring(regExp.lastIndex-match[1].length,regExp.lastIndex);
var lang = "en-US";
if ((match = langRegExp.exec(help)) !== null) {
lang = match[1];
}
if (!helpContent.hasOwnProperty(lang)) {
helpContent[lang] = "";
}
helpContent[lang] += help;
}
mainContent += content.substring(index);
node.config = mainContent;
node.help = helpContent;
// TODO: parse out the javascript portion of the template // TODO: parse out the javascript portion of the template
//node.script = ""; //node.script = "";
for (var i=0;i<node.types.length;i++) { for (var i=0;i<node.types.length;i++) {
@ -153,13 +188,38 @@ function loadNodeConfig(fileInfo) {
break; break;
} }
} }
fs.stat(path.join(path.dirname(file),"locales"),function(err,stat) {
if (!err) {
node.namespace = node.id;
i18n.registerMessageCatalog(node.id,
path.join(path.dirname(file),"locales"),
path.basename(file,".js")+".json")
.then(function() {
resolve(node);
});
} else {
node.namespace = node.module;
resolve(node);
}
});
} }
resolve(node);
}); });
}); });
} }
//function getAPIForNode(node) {
// var red = {
// nodes: RED.nodes,
// library: RED.library,
// credentials: RED.credentials,
// events: RED.events,
// log: RED.log,
//
// }
//
//}
/** /**
* Loads the specified node into the runtime * Loads the specified node into the runtime
@ -180,7 +240,20 @@ function loadNodeSet(node) {
var loadPromise = null; var loadPromise = null;
var r = require(node.file); var r = require(node.file);
if (typeof r === "function") { if (typeof r === "function") {
var promise = r(require('../../red'));
var red = {};
for (var i in RED) {
if (RED.hasOwnProperty(i) && !/^(init|start|stop)$/.test(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(RED,i);
Object.defineProperty(red,i,propDescriptor);
}
}
red["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = node.namespace+":"+args[0];
return red.i18n._.apply(null,args);
}
var promise = r(red);
if (promise != null && typeof promise.then === "function") { if (promise != null && typeof promise.then === "function") {
loadPromise = promise.then(function() { loadPromise = promise.then(function() {
node.enabled = true; node.enabled = true;
@ -269,10 +342,43 @@ function addFile(file) {
} }
} }
function loadNodeHelp(node,lang) {
var dir = path.dirname(node.template);
var base = path.basename(node.template);
var localePath = path.join(dir,"locales",lang,base);
try {
// TODO: make this async
var content = fs.readFileSync(localePath, "utf8")
return content;
} catch(err) {
return null;
}
}
function getNodeHelp(node,lang) {
if (!node.help[lang]) {
var help = loadNodeHelp(node,lang);
if (help == null) {
var langParts = lang.split("-");
if (langParts.length == 2) {
help = loadNodeHelp(node,langParts[0]);
}
}
if (help) {
node.help[lang] = help;
} else {
node.help[lang] = node.help["en-US"];
}
}
return node.help[lang];
}
module.exports = { module.exports = {
init: init, init: init,
load: load, load: load,
addModule: addModule, addModule: addModule,
addFile: addFile, addFile: addFile,
loadNodeSet: loadNodeSet loadNodeSet: loadNodeSet,
getNodeHelp: getNodeHelp
} }

View File

@ -87,7 +87,7 @@ function getLocalNodeFiles(dir) {
} }
} else if (stats.isDirectory()) { } else if (stats.isDirectory()) {
// Ignore /.dirs/, /lib/ /node_modules/ // Ignore /.dirs/, /lib/ /node_modules/
if (!/^(\..*|lib|icons|node_modules|test)$/.test(fn)) { if (!/^(\..*|lib|icons|node_modules|test|locales)$/.test(fn)) {
result = result.concat(getLocalNodeFiles(path.join(dir,fn))); result = result.concat(getLocalNodeFiles(path.join(dir,fn)));
} else if (fn === "icons") { } else if (fn === "icons") {
events.emit("node-icon-dir",path.join(dir,fn)); events.emit("node-icon-dir",path.join(dir,fn));
@ -196,6 +196,13 @@ function getNodeFiles(_defaultNodesDir,disableNodePathScan) {
var nodeFiles = getLocalNodeFiles(path.resolve(defaultNodesDir)); var nodeFiles = getLocalNodeFiles(path.resolve(defaultNodesDir));
//console.log(nodeFiles); //console.log(nodeFiles);
var defaultLocalesPath = path.resolve(path.join(defaultNodesDir,"core","locales"));
events.emit("node-locales-dir", {
namespace:"node-red",
dir: defaultLocalesPath,
file: "messages.json"
});
if (settings.userDir) { if (settings.userDir) {
dir = path.join(settings.userDir,"nodes"); dir = path.join(settings.userDir,"nodes");
nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir)); nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir));

View File

@ -23,6 +23,8 @@ var settings;
var Node; var Node;
var loader;
var nodeConfigCache = null; var nodeConfigCache = null;
var moduleConfigs = {}; var moduleConfigs = {};
var nodeList = []; var nodeList = [];
@ -30,8 +32,9 @@ var nodeConstructors = {};
var nodeTypeToId = {}; var nodeTypeToId = {};
var moduleNodes = {}; var moduleNodes = {};
function init(_settings) { function init(_settings,_loader) {
settings = _settings; settings = _settings;
loader = _loader;
if (settings.available()) { if (settings.available()) {
moduleConfigs = loadNodeConfigs(); moduleConfigs = loadNodeConfigs();
} else { } else {
@ -323,7 +326,7 @@ function registerNodeConstructor(type,constructor) {
events.emit("type-registered",type); events.emit("type-registered",type);
} }
function getAllNodeConfigs() { function getAllNodeConfigs(lang) {
if (!nodeConfigCache) { if (!nodeConfigCache) {
var result = ""; var result = "";
var script = ""; var script = "";
@ -332,6 +335,7 @@ function getAllNodeConfigs() {
var config = moduleConfigs[getModule(id)].nodes[getNode(id)]; var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
if (config.enabled && !config.err) { if (config.enabled && !config.err) {
result += config.config; result += config.config;
result += loader.getNodeHelp(config,lang||"en-US")
//script += config.script; //script += config.script;
} }
} }
@ -345,7 +349,7 @@ function getAllNodeConfigs() {
return nodeConfigCache; return nodeConfigCache;
} }
function getNodeConfig(id) { function getNodeConfig(id,lang) {
var config = moduleConfigs[getModule(id)]; var config = moduleConfigs[getModule(id)];
if (!config) { if (!config) {
return null; return null;
@ -353,6 +357,8 @@ function getNodeConfig(id) {
config = config.nodes[getNode(id)]; config = config.nodes[getNode(id)];
if (config) { if (config) {
var result = config.config; var result = config.config;
result += loader.getNodeHelp(config,lang||"en-US")
//if (config.script) { //if (config.script) {
// result += '<script type="text/javascript">'+config.script+'</script>'; // result += '<script type="text/javascript">'+config.script+'</script>';
//} //}

View File

@ -20,6 +20,7 @@ var library = require("./api/library");
var comms = require("./comms"); var comms = require("./comms");
var log = require("./log"); var log = require("./log");
var util = require("./util"); var util = require("./util");
var i18n = require("./i18n");
var fs = require("fs"); var fs = require("fs");
var settings = require("./settings"); var settings = require("./settings");
var credentials = require("./nodes/credentials"); var credentials = require("./nodes/credentials");
@ -56,6 +57,7 @@ var RED = {
credentials: credentials, credentials: credentials,
events: events, events: events,
log: log, log: log,
i18n: i18n,
comms: comms, comms: comms,
settings:settings, settings:settings,
util: util, util: util,
@ -75,4 +77,7 @@ var RED = {
get httpNode() { return server.nodeApp }, get httpNode() { return server.nodeApp },
get server() { return server.server } get server() { return server.server }
}; };
//RED["_"] = i18n._;
module.exports = RED; module.exports = RED;

View File

@ -160,14 +160,16 @@ describe('red/nodes/registry/index', function() {
list[0].should.have.property("enabled",true); list[0].should.have.property("enabled",true);
list[0].should.not.have.property("err"); list[0].should.not.have.property("err");
eventEmitSpy.callCount.should.equal(2); eventEmitSpy.callCount.should.equal(3);
eventEmitSpy.firstCall.args[0].should.be.equal("node-icon-dir"); eventEmitSpy.firstCall.args[0].should.be.equal("node-icon-dir");
eventEmitSpy.firstCall.args[1].should.be.equal( eventEmitSpy.firstCall.args[1].should.be.equal(
resourcesDir + "NestedDirectoryNode" + path.sep + "NestedNode" + path.sep + "icons"); resourcesDir + "NestedDirectoryNode" + path.sep + "NestedNode" + path.sep + "icons");
eventEmitSpy.secondCall.args[0].should.be.equal("type-registered"); eventEmitSpy.secondCall.args[0].should.be.equal("node-locales-dir");
eventEmitSpy.secondCall.args[1].should.be.equal("nested-node-1");
eventEmitSpy.thirdCall.args[0].should.be.equal("type-registered");
eventEmitSpy.thirdCall.args[1].should.be.equal("nested-node-1");
done(); done();
}).catch(function(e) { }).catch(function(e) {
@ -284,11 +286,11 @@ describe('red/nodes/registry/index', function() {
var nodeConfigs = typeRegistry.getNodeConfigs(); var nodeConfigs = typeRegistry.getNodeConfigs();
// TODO: this is brittle... // TODO: this is brittle...
nodeConfigs.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n<script type=\"text/x-red\" data-template-name=\"test-node-2\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-2\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-2',{});</script>\n<style></style>\n"); nodeConfigs.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script><script type=\"text/x-red\" data-template-name=\"test-node-2\"></script>\n\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-2',{});</script>\n<style></style>\n<script type=\"text/x-red\" data-help-name=\"test-node-2\"></script>");
var nodeId = list[0].id; var nodeId = list[0].id;
var nodeConfig = typeRegistry.getNodeConfig(nodeId); var nodeConfig = typeRegistry.getNodeConfig(nodeId);
nodeConfig.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n"); nodeConfig.should.equal("<script type=\"text/x-red\" data-template-name=\"test-node-1\"></script>\n\n<script type=\"text/javascript\">RED.nodes.registerType('test-node-1',{});</script>\n<style></style>\n<p>this should be filtered out</p>\n<script type=\"text/x-red\" data-help-name=\"test-node-1\"></script>");
done(); done();
}).catch(function(e) { }).catch(function(e) {
done(e); done(e);
@ -548,14 +550,18 @@ describe('red/nodes/registry/index', function() {
list[1].should.have.property("err"); list[1].should.have.property("err");
eventEmitSpy.callCount.should.equal(2); eventEmitSpy.callCount.should.equal(3);
eventEmitSpy.firstCall.args[0].should.be.equal("node-locales-dir");
eventEmitSpy.firstCall.args[0].should.be.equal("node-icon-dir");
eventEmitSpy.firstCall.args[1].should.be.equal( eventEmitSpy.secondCall.args[0].should.be.equal("node-icon-dir");
eventEmitSpy.secondCall.args[1].should.be.equal(
resourcesDir + "TestNodeModule" + path.sep+ "node_modules" + path.sep + "TestNodeModule" + path.sep + "icons"); resourcesDir + "TestNodeModule" + path.sep+ "node_modules" + path.sep + "TestNodeModule" + path.sep + "icons");
eventEmitSpy.secondCall.args[0].should.be.equal("type-registered");
eventEmitSpy.secondCall.args[1].should.be.equal("test-node-mod-1"); eventEmitSpy.thirdCall.args[0].should.be.equal("type-registered");
eventEmitSpy.thirdCall.args[1].should.be.equal("test-node-mod-1");
done(); done();
}).catch(function(e) { }).catch(function(e) {