Reorganise red/api layout to better componentise

This commit is contained in:
Nick O'Leary
2017-08-22 22:26:29 +01:00
parent 96a0dbea2d
commit 41af5187aa
39 changed files with 1004 additions and 331 deletions

234
red/api/editor/comms.js Normal file
View File

@@ -0,0 +1,234 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var ws = require("ws");
var log;
var server;
var settings;
var wsServer;
var pendingConnections = [];
var activeConnections = [];
var retained = {};
var heartbeatTimer;
var lastSentTime;
function handleStatus(event) {
publish("status/"+event.id,event.status,true);
}
function handleRuntimeEvent(event) {
publish("notification/"+event.id,event.payload||{},event.retain);
}
function init(_server,runtime) {
server = _server;
settings = runtime.settings;
log = runtime.log;
runtime.events.removeListener("node-status",handleStatus);
runtime.events.on("node-status",handleStatus);
runtime.events.removeListener("runtime-event",handleRuntimeEvent);
runtime.events.on("runtime-event",handleRuntimeEvent);
}
function start() {
var Tokens = require("../auth/tokens");
var Users = require("../auth/users");
var Permissions = require("../auth/permissions");
if (!settings.disableEditor) {
Users.default().then(function(anonymousUser) {
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({
server:server,
path:path,
// Disable the deflate option due to this issue
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
perMessageDeflate: false
});
wsServer.on('connection',function(ws) {
log.audit({event: "comms.open"});
var pendingAuth = (settings.adminAuth != null);
if (!pendingAuth) {
activeConnections.push(ws);
} else {
pendingConnections.push(ws);
}
ws.on('close',function() {
log.audit({event: "comms.close",user:ws.user});
removeActiveConnection(ws);
removePendingConnection(ws);
});
ws.on('message', function(data,flags) {
var msg = null;
try {
msg = JSON.parse(data);
} catch(err) {
log.trace("comms received malformed message : "+err.toString());
return;
}
if (!pendingAuth) {
if (msg.subscribe) {
handleRemoteSubscription(ws,msg.subscribe);
}
} else {
var completeConnection = function(userScope,sendAck) {
try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"}));
ws.close();
} else {
pendingAuth = false;
removePendingConnection(ws);
activeConnections.push(ws);
if (sendAck) {
ws.send(JSON.stringify({auth:"ok"}));
}
}
} catch(err) {
// Just in case the socket closes before we attempt
// to send anything.
}
}
if (msg.auth) {
Tokens.get(msg.auth).then(function(client) {
if (client) {
Users.get(client.user).then(function(user) {
if (user) {
ws.user = user;
log.audit({event: "comms.auth",user:ws.user});
completeConnection(client.scope,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
});
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
});
} else {
if (anonymousUser) {
log.audit({event: "comms.auth",user:anonymousUser});
completeConnection(anonymousUser.permissions,false);
//TODO: duplicated code - pull non-auth message handling out
if (msg.subscribe) {
handleRemoteSubscription(ws,msg.subscribe);
}
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
}
}
}
});
ws.on('error', function(err) {
log.warn(log._("comms.error",{message:err.toString()}));
});
});
wsServer.on('error', function(err) {
log.warn(log._("comms.error-server",{message:err.toString()}));
});
lastSentTime = Date.now();
heartbeatTimer = setInterval(function() {
var now = Date.now();
if (now-lastSentTime > webSocketKeepAliveTime) {
publish("hb",lastSentTime);
}
}, webSocketKeepAliveTime);
});
}
}
function stop() {
if (heartbeatTimer) {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
}
if (wsServer) {
wsServer.close();
wsServer = null;
}
}
function publish(topic,data,retain) {
if (server) {
if (retain) {
retained[topic] = data;
} else {
delete retained[topic];
}
lastSentTime = Date.now();
activeConnections.forEach(function(conn) {
publishTo(conn,topic,data);
});
}
}
function publishTo(ws,topic,data) {
var msg = JSON.stringify({topic:topic,data:data});
try {
ws.send(msg);
} catch(err) {
removeActiveConnection(ws);
removePendingConnection(ws);
log.warn(log._("comms.error-send",{message:err.toString()}));
}
}
function handleRemoteSubscription(ws,topic) {
var re = new RegExp("^"+topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
for (var t in retained) {
if (re.test(t)) {
publishTo(ws,t,retained[t]);
}
}
}
function removeActiveConnection(ws) {
for (var i=0;i<activeConnections.length;i++) {
if (activeConnections[i] === ws) {
activeConnections.splice(i,1);
break;
}
}
}
function removePendingConnection(ws) {
for (var i=0;i<pendingConnections.length;i++) {
if (pendingConnections[i] === ws) {
pendingConnections.splice(i,1);
break;
}
}
}
module.exports = {
init:init,
start:start,
stop:stop,
publish:publish
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var log;
var api;
module.exports = {
init: function(runtime) {
log = runtime.log;
api = runtime.nodes;
},
get: function (req, res) {
// TODO: It should verify the given node id is of the type specified -
// but that would add a dependency from this module to the
// registry module that knows about node types.
var nodeType = req.params.type;
var nodeID = req.params.id;
log.audit({event: "credentials.get",type:nodeType,id:nodeID},req);
var credentials = api.getCredentials(nodeID);
if (!credentials) {
res.json({});
return;
}
var definition = api.getCredentialDefinition(nodeType);
var sendCredentials = {};
for (var cred in definition) {
if (definition.hasOwnProperty(cred)) {
if (definition[cred].type == "password") {
var key = 'has_' + cred;
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
continue;
}
sendCredentials[cred] = credentials[cred] || '';
}
}
res.json(sendCredentials);
}
}

108
red/api/editor/index.js Normal file
View File

@@ -0,0 +1,108 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var path = require('path');
var comms = require("./comms");
var library = require("./library");
var auth = require("../auth");
var needsPermission = auth.needsPermission;
var runtime;
var log;
var apiUtil = require("../util");
var ensureRuntimeStarted = function(req,res,next) {
if (!runtime.isStarted()) {
log.error("Node-RED runtime not started");
res.status(503).send("Not started");
} else {
next();
}
}
module.exports = {
init: function(server, _runtime) {
runtime = _runtime;
log = runtime.log;
var settings = runtime.settings;
if (!settings.disableEditor) {
comms.init(server,runtime);
var ui = require("./ui");
ui.init(runtime);
var editorApp = express();
if (settings.requireHttps === true) {
editorApp.enable('trust proxy');
editorApp.use(function (req, res, next) {
if (req.secure) {
next();
} else {
res.redirect('https://' + req.headers.host + req.originalUrl);
}
});
}
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
editorApp.get("/icons/:module/:icon",ui.icon);
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
var theme = require("./theme");
theme.init(runtime);
editorApp.use("/theme",theme.app());
editorApp.use("/",ui.editorResources);
// //Projects
// var projects = require("./projects");
// projects.init(runtime);
// editorApp.get("/projects",projects.app());
// Locales
var locales = require("./locales");
locales.init(runtime);
editorApp.get('/locales/nodes',locales.getAllNodes,apiUtil.errorHandler);
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
// Library
var library = require("./library");
library.init(editorApp,runtime);
editorApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,apiUtil.errorHandler);
editorApp.get("/library/flows",needsPermission("library.read"),library.getAll,apiUtil.errorHandler);
editorApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,apiUtil.errorHandler);
// Credentials
var credentials = require("./credentials");
credentials.init(runtime);
editorApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,apiUtil.errorHandler);
return editorApp;
}
},
start: function() {
var catalogPath = path.resolve(path.join(__dirname,"locales"));
return runtime.i18n.registerMessageCatalogs([
{namespace: "editor", dir: catalogPath, file:"editor.json"},
{namespace: "jsonata", dir: catalogPath, file:"jsonata.json"},
{namespace: "infotips", dir: catalogPath, file:"infotips.json"}
]).then(function(){
comms.start();
});
},
stop: comms.stop,
publish: comms.publish,
registerLibrary: library.register
}

161
red/api/editor/library.js Normal file
View File

@@ -0,0 +1,161 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var fs = require('fs');
var fspath = require('path');
var when = require('when');
var redApp = null;
var storage;
var log;
var redNodes;
var needsPermission = require("../auth").needsPermission;
function createLibrary(type) {
if (redApp) {
redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),needsPermission("library.read"),function(req,res) {
var path = req.params[1]||"";
storage.getLibraryEntry(type,path).then(function(result) {
log.audit({event: "library.get",type:type},req);
if (typeof result === "string") {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write(result);
res.end();
} else {
res.json(result);
}
}).otherwise(function(err) {
if (err) {
log.warn(log._("api.library.error-load-entry",{path:path,message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.get",type:type,error:"forbidden"},req);
res.status(403).end();
return;
}
}
log.audit({event: "library.get",type:type,error:"not_found"},req);
res.status(404).end();
});
});
redApp.post(new RegExp("/library/"+type+"\/(.*)"),needsPermission("library.write"),function(req,res) {
var path = req.params[0];
var meta = req.body;
var text = meta.text;
delete meta.text;
storage.saveLibraryEntry(type,path,meta,text).then(function() {
log.audit({event: "library.set",type:type},req);
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.library.error-save-entry",{path:path,message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.set",type:type,error:"forbidden"},req);
res.status(403).end();
return;
}
log.audit({event: "library.set",type:type,error:"unexpected_error",message:err.toString()},req);
res.status(500).json({error:"unexpected_error", message:err.toString()});
});
});
}
}
module.exports = {
init: function(app,runtime) {
redApp = app;
log = runtime.log;
storage = runtime.storage;
redNodes = runtime.nodes;
},
register: createLibrary,
getAll: function(req,res) {
storage.getAllFlows().then(function(flows) {
log.audit({event: "library.get.all",type:"flow"},req);
var examples = redNodes.getNodeExampleFlows();
if (examples) {
flows.d = flows.d||{};
flows.d._examples_ = redNodes.getNodeExampleFlows();
}
res.json(flows);
});
},
get: function(req,res) {
if (req.params[0].indexOf("_examples_/") === 0) {
var m = /^_examples_\/([^\/]+)\/(.*)$/.exec(req.params[0]);
if (m) {
var module = m[1];
var path = m[2];
var fullPath = redNodes.getNodeExampleFlowPath(module,path);
if (fullPath) {
try {
fs.statSync(fullPath);
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
return res.sendFile(fullPath,{
headers:{
'Content-Type': 'application/json'
}
})
} catch(err) {
console.log(err);
}
}
}
// IF we get here, we didn't find the file
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
return res.status(404).end();
} else {
storage.getFlow(req.params[0]).then(function(data) {
// data is already a JSON string
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
res.set('Content-Type', 'application/json');
res.send(data);
}).otherwise(function(err) {
if (err) {
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
res.status(403).end();
return;
}
}
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
res.status(404).end();
});
}
},
post: function(req,res) {
// if (req.params[0].indexOf("_examples_/") === 0) {
// log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:"forbidden"}));
// log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
// return res.status(403).send({error:"unexpected_error", message:"forbidden"});
// }
var flow = JSON.stringify(req.body);
storage.saveFlow(req.params[0],flow).then(function() {
log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
res.status(204).end();
}).otherwise(function(err) {
log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:err.toString()}));
if (err.code === 'forbidden') {
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
res.status(403).end();
return;
}
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"unexpected_error",message:err.toString()},req);
res.status(500).send({error:"unexpected_error", message:err.toString()});
});
}
}

52
red/api/editor/locales.js Normal file
View File

@@ -0,0 +1,52 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var fs = require('fs');
var path = require('path');
//var apiUtil = require('../util');
var i18n;
var redNodes;
module.exports = {
init: function(runtime) {
i18n = runtime.i18n;
redNodes = runtime.nodes;
},
get: function(req,res) {
var namespace = req.params[0];
var lngs = req.query.lng;
namespace = namespace.replace(/\.json$/,"");
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
var prevLang = i18n.i.lng();
// Trigger a load from disk of the language if it is not the default
i18n.i.setLng(lang, function(){
var catalog = i18n.catalog(namespace,lang);
res.json(catalog||{});
});
i18n.i.setLng(prevLang);
},
getAllNodes: function(req,res) {
var lngs = req.query.lng;
var nodeList = redNodes.getNodeList();
var result = {};
nodeList.forEach(function(n) {
if (n.module !== "node-red") {
result[n.id] = i18n.catalog(n.id,lngs)||{};
}
});
res.json(result);
}
}

View File

@@ -0,0 +1,456 @@
{
"common": {
"label": {
"name": "Name",
"ok": "Ok",
"done":"Done",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close",
"load": "Load",
"save": "Save",
"import": "Import",
"export": "Export"
}
},
"workspace": {
"defaultName": "Flow __number__",
"editFlow": "Edit flow: __name__",
"confirmDelete": "Confirm delete",
"delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here",
"status": "Status",
"enabled": "Enabled",
"disabled":"Disabled",
"info": "Description",
"tip": "Description accepts Markdown and will appear in the Info tab."
},
"menu": {
"label": {
"view": {
"view": "View",
"grid": "Grid",
"showGrid": "Show grid",
"snapGrid": "Snap to grid",
"gridSize": "Grid size",
"textDir": "Text Direction",
"defaultDir": "Default",
"ltr": "Left-to-right",
"rtl": "Right-to-left",
"auto": "Contextual"
},
"sidebar": {
"show": "Show sidebar"
},
"settings": "Settings",
"userSettings": "User Settings",
"nodes": "Nodes",
"displayStatus": "Show node status",
"displayConfig": "Configuration nodes",
"import": "Import",
"export": "Export",
"search": "Search flows",
"searchInput": "search your flows",
"clipboard": "Clipboard",
"library": "Library",
"examples": "Examples",
"subflows": "Subflows",
"createSubflow": "Create Subflow",
"selectionToSubflow": "Selection to Subflow",
"flows": "Flows",
"add": "Add",
"rename": "Rename",
"delete": "Delete",
"keyboardShortcuts": "Keyboard shortcuts",
"login": "Login",
"logout": "Logout",
"editPalette":"Manage palette",
"other": "Other",
"showTips": "Show tips",
"help": "Node-RED website"
}
},
"user": {
"loggedInAs": "Logged in as __name__",
"username": "Username",
"password": "Password",
"login": "Login",
"loginFailed": "Login failed",
"notAuthorized": "Not authorized"
},
"notification": {
"warning": "<strong>Warning</strong>: __message__",
"warnings": {
"undeployedChanges": "node has undeployed changes",
"nodeActionDisabled": "node actions disabled within subflow",
"missing-types": "Flows stopped due to missing node types. Check logs for details.",
"restartRequired": "Node-RED must be restarted to enable upgraded modules"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "Lost connection to server, reconnecting...",
"lostConnectionReconnect": "Lost connection to server, reconnecting in __time__s.",
"lostConnectionTry": "Try now",
"cannotAddSubflowToItself": "Cannot add subflow to itself",
"cannotAddCircularReference": "Cannot add subflow - circular reference detected",
"unsupportedVersion": "Using an unsupported version of Node.js<br/>You should upgrade to the latest Node.js LTS release"
}
},
"clipboard": {
"nodes": "Nodes",
"selectNodes": "Select the text above and copy to the clipboard.",
"pasteNodes": "Paste nodes here",
"importNodes": "Import nodes",
"exportNodes": "Export nodes to clipboard",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"nodesExported": "Nodes exported to clipboard",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"invalidFlow": "Invalid flow: __message__",
"export": {
"selected":"selected nodes",
"current":"current flow",
"all":"all flows",
"compact":"compact",
"formatted":"formatted",
"copy": "Export to clipboard"
},
"import": {
"import": "Import to",
"newFlow": "new flow"
},
"copyMessagePath": "Path copied",
"copyMessageValue": "Value copied",
"copyMessageValue_truncated": "Truncated value copied"
},
"deploy": {
"deploy": "Deploy",
"full": "Full",
"fullDesc": "Deploys everything in the workspace",
"modifiedFlows": "Modified Flows",
"modifiedFlowsDesc": "Only deploys flows that contain changed nodes",
"modifiedNodes": "Modified Nodes",
"modifiedNodesDesc": "Only deploys nodes that have changed",
"successfulDeploy": "Successfully deployed",
"deployFailed": "Deploy failed: __message__",
"unusedConfigNodes":"You have some unused configuration nodes.",
"unusedConfigNodesLink":"Click here to see them",
"errors": {
"noResponse": "no response from server"
},
"confirm": {
"button": {
"ignore": "Ignore",
"confirm": "Confirm deploy",
"review": "Review changes",
"cancel": "Cancel",
"merge": "Merge",
"overwrite": "Ignore & deploy"
},
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
"unknown": "The workspace contains some unknown node types:",
"confirm": "Are you sure you want to deploy?",
"doNotWarn": "do not warn about this again",
"conflict": "The server is running a more recent set of flows.",
"backgroundUpdate": "The flows on the server have been updated.",
"conflictChecking": "Checking to see if the changes can be merged automatically",
"conflictAutoMerge": "The changes include no conflicts and can be merged automatically.",
"conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed."
}
},
"diff": {
"unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts",
"globalNodes": "Global nodes",
"flowProperties": "Flow Properties",
"type": {
"added": "added",
"changed": "changed",
"unchanged": "unchanged",
"deleted": "deleted",
"flowDeleted": "flow deleted",
"flowAdded": "flow added",
"movedTo": "moved to __id__",
"movedFrom": "moved from __id__"
},
"nodeCount": "__count__ node",
"nodeCount_plural": "__count__ nodes",
"local":"Local changes",
"remote":"Remote changes"
},
"subflow": {
"editSubflow": "Edit flow template: __name__",
"edit": "Edit flow template",
"subflowInstances": "There is __count__ instance of this subflow template",
"subflowInstances_plural": "There are __count__ instances of this subflow template",
"editSubflowProperties": "edit properties",
"input": "inputs:",
"output": "outputs:",
"deleteSubflow": "delete subflow",
"info": "Description",
"format":"markdown format",
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
}
},
"editor": {
"configEdit": "Edit",
"configAdd": "Add",
"configUpdate": "Update",
"configDelete": "Delete",
"nodesUse": "__count__ node uses this config",
"nodesUse_plural": "__count__ nodes use this config",
"addNewConfig": "Add new __type__ config node",
"editNode": "Edit __type__ node",
"editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__...",
"nodeProperties": "node properties",
"portLabels": "port labels",
"labelInputs": "Inputs",
"labelOutputs": "Outputs",
"noDefaultLabel": "none",
"defaultLabel": "use default label",
"errors": {
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it"
}
},
"keyboard": {
"title": "Keyboard Shortcuts",
"keyboard": "Keyboard",
"filterActions": "filter actions",
"shortcut": "shortcut",
"scope": "scope",
"unassigned": "Unassigned",
"global": "global",
"workspace": "workspace",
"selectAll": "Select all nodes",
"selectAllConnected": "Select all connected nodes",
"addRemoveNode": "Add/remove node from selection",
"editSelected": "Edit selected node",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export nodes",
"nudgeNode": "Move selected nodes (1px)",
"moveNode": "Move selected nodes (20px)",
"toggleSidebar": "Toggle sidebar",
"copyNode": "Copy selected nodes",
"cutNode": "Cut selected nodes",
"pasteNode": "Paste nodes",
"undoChange": "Undo the last change performed",
"searchBox": "Open search box",
"managePalette": "Manage palette"
},
"library": {
"openLibrary": "Open Library...",
"saveToLibrary": "Save to Library...",
"typeLibrary": "__type__ library",
"unnamedType": "Unnamed __type__",
"exportToLibrary": "Export nodes to library",
"dialogSaveOverwrite": "A __libraryType__ called __libraryName__ already exists. Overwrite?",
"invalidFilename": "Invalid filename",
"savedNodes": "Saved nodes",
"savedType": "Saved __type__",
"saveFailed": "Save failed: __message__",
"filename": "Filename",
"folder": "Folder",
"filenamePlaceholder": "file",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "Library"
},
"palette": {
"noInfo": "no information available",
"filter": "filter nodes",
"search": "search modules",
"label": {
"subflows": "subflows",
"input": "input",
"output": "output",
"function": "function",
"social": "social",
"storage": "storage",
"analysis": "analysis",
"advanced": "advanced"
},
"event": {
"nodeAdded": "Node added to palette:",
"nodeAdded_plural": "Nodes added to palette",
"nodeRemoved": "Node removed from palette:",
"nodeRemoved_plural": "Nodes removed from palette:",
"nodeEnabled": "Node enabled:",
"nodeEnabled_plural": "Nodes enabled:",
"nodeDisabled": "Node disabled:",
"nodeDisabled_plural": "Nodes disabled:",
"nodeUpgraded": "Node module __module__ upgraded to version __version__"
},
"editor": {
"title": "Manage palette",
"palette": "Palette",
"times": {
"seconds": "seconds ago",
"minutes": "minutes ago",
"minutesV": "__count__ minutes ago",
"hoursV": "__count__ hour ago",
"hoursV_plural": "__count__ hours ago",
"daysV": "__count__ day ago",
"daysV_plural": "__count__ days ago",
"weeksV": "__count__ week ago",
"weeksV_plural": "__count__ weeks ago",
"monthsV": "__count__ month ago",
"monthsV_plural": "__count__ months ago",
"yearsV": "__count__ year ago",
"yearsV_plural": "__count__ years ago",
"yearMonthsV": "__y__ year, __count__ month ago",
"yearMonthsV_plural": "__y__ year, __count__ months ago",
"yearsMonthsV": "__y__ years, __count__ month ago",
"yearsMonthsV_plural": "__y__ years, __count__ months ago"
},
"nodeCount": "__label__ node",
"nodeCount_plural": "__label__ nodes",
"moduleCount": "__count__ module available",
"moduleCount_plural": "__count__ modules available",
"inuse": "in use",
"enableall": "enable all",
"disableall": "disable all",
"enable": "enable",
"disable": "disable",
"remove": "remove",
"update": "update to __version__",
"updated": "updated",
"install": "install",
"installed": "installed",
"loading": "Loading catalogues...",
"tab-nodes": "Nodes",
"tab-install": "Install",
"sort": "sort:",
"sortAZ": "a-z",
"sortRecent": "recent",
"more": "+ __count__ more",
"errors": {
"catalogLoadFailed": "Failed to load node catalogue.<br>Check the browser console for more information",
"installFailed": "Failed to install: __module__<br>__message__<br>Check the log for more information",
"removeFailed": "Failed to remove: __module__<br>__message__<br>Check the log for more information",
"updateFailed": "Failed to update: __module__<br>__message__<br>Check the log for more information",
"enableFailed": "Failed to enable: __module__<br>__message__<br>Check the log for more information",
"disableFailed": "Failed to disable: __module__<br>__message__<br>Check the log for more information"
},
"confirm": {
"install": {
"body":"Before installing, please read the node's documentation. Some nodes have dependencies that cannot be automatically resolved and can require a restart of Node-RED. ",
"title": "Install nodes"
},
"remove": {
"body":"Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.",
"title": "Remove nodes"
},
"update": {
"body":"Updating the node will require a restart of Node-RED to complete the update. This must be done manually.",
"title": "Update nodes"
},
"cannotUpdate": {
"body":"An update for this node is available, but it is not installed in a location that the palette manager can update.<br/><br/>Please refer to the documentation for how to update this node."
},
"button": {
"review": "Open node information",
"install": "Install",
"remove": "Remove",
"update": "Update"
}
}
}
},
"sidebar": {
"info": {
"name": "Node information",
"tabName": "Name",
"label": "info",
"node": "Node",
"type": "Type",
"id": "ID",
"status": "Status",
"enabled": "Enabled",
"disabled": "Disabled",
"subflow": "Subflow",
"instances": "Instances",
"properties": "Properties",
"info": "Information",
"blank": "blank",
"null": "null",
"showMore": "show more",
"showLess": "show less",
"flow": "Flow",
"information": "Information",
"arrayItems": "__count__ items",
"showTips":"You can open the tips from the settings panel"
},
"config": {
"name": "Configuration nodes",
"label": "config",
"global": "On all flows",
"none": "none",
"subflows": "subflows",
"flows": "flows",
"filterUnused":"unused",
"filterAll":"all",
"filtered": "__count__ hidden"
},
"palette": {
"name": "Palette management",
"label": "palette"
}
},
"typedInput": {
"type": {
"str": "string",
"num": "number",
"re": "regular expression",
"bool": "boolean",
"json": "JSON",
"bin": "buffer",
"date": "timestamp"
}
},
"editableList": {
"add": "add"
},
"search": {
"empty": "No matches found",
"addNode": "add a node..."
},
"expressionEditor": {
"functions": "Functions",
"insert": "Insert",
"title": "JSONata Expression editor",
"data": "Example message",
"result": "Result",
"format": "format expression",
"compatMode": "Compatibility mode enabled",
"compatModeDesc": "<h3>JSONata compatibility mode</h3><p> The current expression appears to still reference <code>msg</code> so will be evaluated in compatibility mode. Please update the expression to not use <code>msg</code> as this mode will be removed in the future.</p><p> When JSONata support was first added to Node-RED, it required the expression to reference the <code>msg</code> object. For example <code>msg.payload</code> would be used to access the payload.</p><p> That is no longer necessary as the expression will be evaluated against the message directly. To access the payload, the expression should be just <code>payload</code>.</p>",
"noMatch": "No matching result",
"errors": {
"invalid-expr": "Invalid JSONata expression:\n __message__",
"invalid-msg": "Invalid example JSON message:\n __message__",
"context-unsupported": "Cannot test context functions\n $flowContext or $globalContext",
"eval": "Error evaluating expression:\n __message__"
}
},
"jsonEditor": {
"title": "JSON editor",
"format": "format JSON"
},
"bufferEditor": {
"title": "Buffer editor",
"modeString": "Handle as UTF-8 String",
"modeArray": "Handle as JSON array",
"modeDesc":"<h3>Buffer editor</h3><p>The Buffer type is stored as a JSON array of byte values. The editor will attempt to parse the entered value as a JSON array. If it is not valid JSON, it will be treated as a UTF-8 String and converted to an array of the individual character code points.</p><p>For example, a value of <code>Hello World</code> will be converted to the JSON array:<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
}
}

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0" : "You can remove the selected nodes or links with {{core:delete-selection}}",
"tip1" : "Search for nodes using {{core:search}}",
"tip2" : "{{core:toggle-sidebar}} will toggle the view of this sidebar",
"tip3" : "You can manage your palette of nodes with {{core:manage-palette}}",
"tip4" : "Your flow configuration nodes are listed in the sidebar panel. It can been accessed from the menu or with {{core:show-config-tab}}",
"tip5" : "Enable or disable these tips from the option in the settings",
"tip6" : "Move the selected nodes using the [left] [up] [down] and [right] keys. Hold [shift] to nudge them further",
"tip7" : "Dragging a node onto a wire will splice it into the link",
"tip8" : "Export the selected nodes, or the current tab with {{core:show-export-dialog}}",
"tip9" : "Import a flow by dragging its JSON into the editor, or with {{core:show-import-dialog}}",
"tip10" : "[shift] [click] and drag on a node port to move all of the attached wires or just the selected one",
"tip11" : "Show the Info tab with {{core:show-info-tab}} or the Debug tab with {{core:show-debug-tab}}",
"tip12" : "[ctrl] [click] in the workspace to open the quick-add dialog",
"tip13" : "Hold down [ctrl] when you [click] on a node port to enable quick-wiring",
"tip14" : "Hold down [shift] when you [click] on a node to also select all of its connected nodes",
"tip15" : "Hold down [ctrl] when you [click] on a node to add or remove it from the current selection",
"tip16" : "Switch flow tabs with {{core:show-previous-tab}} and {{core:show-next-tab}}",
"tip17" : "You can confirm your changes in the node edit tray with {{core:confirm-edit-tray}} or cancel them with {{core:cancel-edit-tray}}",
"tip18" : "Pressing {{core:edit-selected-node}} will edit the first node in the current selection"
}
}

View File

@@ -0,0 +1,191 @@
{
"$string": {
"args": "arg",
"desc": "Casts the *arg* parameter to a string using the following casting rules:\n\n - Strings are unchanged\n - Functions are converted to an empty string\n - Numeric infinity and NaN throw an error because they cannot be represented as a JSON number\n - All other values are converted to a JSON string using the `JSON.stringify` function"
},
"$length": {
"args": "str",
"desc": "Returns the number of characters in the string `str`. An error is thrown if `str` is not a string."
},
"$substring": {
"args": "str, start[, length]",
"desc": "Returns a string containing the characters in the first parameter `str` starting at position `start` (zero-offset). If `length` is specified, then the substring will contain maximum `length` characters. If `start` is negative then it indicates the number of characters from the end of `str`."
},
"$substringBefore": {
"args": "str, chars",
"desc": "Returns the substring before the first occurrence of the character sequence `chars` in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$substringAfter": {
"args": "str, chars",
"desc": "Returns the substring after the first occurrence of the character sequence `chars` in `str`. If `str` does not contain `chars`, then it returns `str`."
},
"$uppercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to uppercase."
},
"$lowercase": {
"args": "str",
"desc": "Returns a string with all the characters of `str` converted to lowercase."
},
"$trim": {
"args": "str",
"desc": "Normalizes and trims all whitespace characters in `str` by applying the following steps:\n\n - All tabs, carriage returns, and line feeds are replaced with spaces.\n- Contiguous sequences of spaces are reduced to a single space.\n- Trailing and leading spaces are removed.\n\n If `str` is not specified (i.e. this function is invoked with no arguments), then the context value is used as the value of `str`. An error is thrown if `str` is not a string."
},
"$contains": {
"args": "str, pattern",
"desc": "Returns `true` if `str` is matched by `pattern`, otherwise it returns `false`. If `str` is not specified (i.e. this function is invoked with one argument), then the context value is used as the value of `str`. The `pattern` parameter can either be a string or a regular expression."
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "Splits the `str` parameter into an array of substrings. It is an error if `str` is not a string. The optional `separator` parameter specifies the characters within the `str` about which it should be split as either a string or regular expression. If `separator` is not specified, then the empty string is assumed, and `str` will be split into an array of single characters. It is an error if `separator` is not a string. The optional `limit` parameter is a number that specifies the maximum number of substrings to include in the resultant array. Any additional substrings are discarded. If `limit` is not specified, then `str` is fully split with no limit to the size of the resultant array. It is an error if `limit` is not a non-negative number."
},
"$join": {
"args": "array[, separator]",
"desc": "Joins an array of component strings into a single concatenated string with each component string separated by the optional `separator` parameter. It is an error if the input `array` contains an item which isn't a string. If `separator` is not specified, then it is assumed to be the empty string, i.e. no `separator` between the component strings. It is an error if `separator` is not a string."
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "Applies the `str` string to the `pattern` regular expression and returns an array of objects, with each object containing information about each occurrence of a match within `str`."
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "Finds occurrences of `pattern` within `str` and replaces them with `replacement`.\n\nThe optional `limit` parameter is the maximum number of replacements."
},
"$now": {
"args":"",
"desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string."
},
"$base64encode": {
"args":"string",
"desc":"Converts an ASCII string to a base 64 representation. Each character in the string is treated as a byte of binary data. This requires that all characters in the string are in the 0x00 to 0xFF range, which includes all characters in URI encoded strings. Unicode characters outside of that range are not supported."
},
"$base64decode": {
"args":"string",
"desc":"Converts base 64 encoded bytes to a string, using a UTF-8 Unicode codepage."
},
"$number": {
"args": "arg",
"desc": "Casts the `arg` parameter to a number using the following casting rules:\n\n - Numbers are unchanged\n - Strings that contain a sequence of characters that represent a legal JSON number are converted to that number\n - All other values cause an error to be thrown."
},
"$abs": {
"args":"number",
"desc":"Returns the absolute value of the `number` parameter."
},
"$floor": {
"args":"number",
"desc":"Returns the value of `number` rounded down to the nearest integer that is smaller or equal to `number`."
},
"$ceil": {
"args":"number",
"desc":"Returns the value of `number` rounded up to the nearest integer that is greater than or equal to `number`."
},
"$round": {
"args":"number [, precision]",
"desc":"Returns the value of the `number` parameter rounded to the number of decimal places specified by the optional `precision` parameter."
},
"$power": {
"args":"base, exponent",
"desc":"Returns the value of `base` raised to the power of `exponent`."
},
"$sqrt": {
"args":"number",
"desc":"Returns the square root of the value of the `number` parameter."
},
"$random": {
"args":"",
"desc":"Returns a pseudo random number greater than or equal to zero and less than one."
},
"$sum": {
"args": "array",
"desc": "Returns the arithmetic sum of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$max": {
"args": "array",
"desc": "Returns the maximum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$min": {
"args": "array",
"desc": "Returns the minimum number in an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$average": {
"args": "array",
"desc": "Returns the mean value of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$boolean": {
"args": "arg",
"desc": "Casts the argument to a Boolean using the following rules:\n\n - `Boolean` : unchanged\n - `string`: empty : `false`\n - `string`: non-empty : `true`\n - `number`: `0` : `false`\n - `number`: non-zero : `true`\n - `null` : `false`\n - `array`: empty : `false`\n - `array`: contains a member that casts to `true` : `true`\n - `array`: all members cast to `false` : `false`\n - `object`: empty : `false`\n - `object`: non-empty : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "Returns Boolean NOT on the argument. `arg` is first cast to a boolean"
},
"$exists": {
"args": "arg",
"desc": "Returns Boolean `true` if the `arg` expression evaluates to a value, or `false` if the expression does not match anything (e.g. a path to a non-existent field reference)."
},
"$count": {
"args": "array",
"desc": "Returns the number of items in the array"
},
"$append": {
"args": "array, array",
"desc": "Appends two arrays"
},
"$sort": {
"args":"array [, function]",
"desc":"Returns an array containing all the values in the `array` parameter, but sorted into order.\n\nIf a comparator `function` is supplied, then it must be a function that takes two parameters:\n\n`function(left, right)`\n\nThis function gets invoked by the sorting algorithm to compare two values left and right. If the value of left should be placed after the value of right in the desired sort order, then the function must return Boolean `true` to indicate a swap. Otherwise it must return `false`."
},
"$reverse": {
"args":"array",
"desc":"Returns an array containing all the values from the `array` parameter, but in reverse order."
},
"$shuffle": {
"args":"array",
"desc":"Returns an array containing all the values from the `array` parameter, but shuffled into random order."
},
"$zip": {
"args":"array, ...",
"desc":"Returns a convolved (zipped) array containing grouped arrays of values from the `array1` … `arrayN` arguments from index 0, 1, 2...."
},
"$keys": {
"args": "object",
"desc": "Returns an array containing the keys in the object. If the argument is an array of objects, then the array returned contains a de-duplicated list of all the keys in all of the objects."
},
"$lookup": {
"args": "object, key",
"desc": "Returns the value associated with key in object. If the first argument is an array of objects, then all of the objects in the array are searched, and the values associated with all occurrences of key are returned."
},
"$spread": {
"args": "object",
"desc": "Splits an object containing key/value pairs into an array of objects, each of which has a single key/value pair from the input object. If the parameter is an array of objects, then the resultant array contains an object for every key/value pair in every object in the supplied array."
},
"$sift": {
"args":"object, function",
"desc":"Returns an object that contains only the key/value pairs from the `object` parameter that satisfy the predicate `function` passed in as the second parameter.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, key [, object]])`"
},
"$each": {
"args":"object, function",
"desc":"Returns an array containing the values return by the `function` when applied to each key/value pair in the `object`."
},
"$map": {
"args":"array, function",
"desc":"Returns an array containing the results of applying the `function` parameter to each value in the `array` parameter.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args":"array, function",
"desc":"Returns an array containing only the values in the `array` parameter that satisfy the `function` predicate.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args":"array, function [, init]",
"desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`.\n\nThe optional `init` parameter is used as the initial value in the aggregation."
},
"$flowContext": {
"args": "string",
"desc": "Retrieves a flow context property."
},
"$globalContext": {
"args": "string",
"desc": "Retrieves a global context property."
}
}

View File

@@ -0,0 +1,450 @@
{
"common": {
"label": {
"name": "名前",
"ok": "OK",
"done": "完了",
"cancel": "中止",
"delete": "削除",
"close": "閉じる",
"load": "読み込み",
"save": "保存",
"import": "読み込み",
"export": "書き出し"
}
},
"workspace": {
"defaultName": "フロー __number__",
"editFlow": "フローを編集: __name__",
"confirmDelete": "削除の確認",
"delete": "本当に '__label__' を削除しますか?",
"dropFlowHere": "ここにフローをドロップしてください",
"status": "状態",
"enabled": "有効",
"disabled": "無効",
"info": "詳細",
"tip": "マークダウン形式で記述した「詳細」は「情報タブ」に表示されます。"
},
"menu": {
"label": {
"view": {
"view": "表示",
"grid": "グリッド",
"showGrid": "グリッドを表示",
"snapGrid": "ノードの配置を補助",
"gridSize": "グリッドの大きさ",
"textDir": "テキストの方向",
"defaultDir": "標準",
"ltr": "左から右",
"rtl": "右から左",
"auto": "文脈"
},
"sidebar": {
"show": "サイドバーを表示"
},
"settings": "設定",
"userSettings": "ユーザ設定",
"nodes": "ノード",
"displayStatus": "ノードの状態を表示",
"displayConfig": "ノードの設定",
"import": "読み込み",
"export": "書き出し",
"search": "ノードを検索",
"searchInput": "ノードを検索",
"clipboard": "クリップボード",
"library": "ライブラリ",
"examples": "サンプル",
"subflows": "サブフロー",
"createSubflow": "サブフローを作成",
"selectionToSubflow": "選択部分をサブフロー化",
"flows": "フロー",
"add": "フローを新規追加",
"rename": "フロー名を変更",
"delete": "フローを削除",
"keyboardShortcuts": "ショートカットキーの説明",
"login": "ログイン",
"logout": "ログアウト",
"editPalette": "パレットの管理",
"other": "その他",
"showTips": "ヒントを表示",
"help": "Node-REDウェブサイト"
}
},
"user": {
"loggedInAs": "__name__ としてログインしました",
"username": "ユーザ名",
"password": "パスワード",
"login": "ログイン",
"loginFailed": "ログインに失敗しました",
"notAuthorized": "権限がありません"
},
"notification": {
"warning": "<strong>警告</strong>: __message__",
"warnings": {
"undeployedChanges": "ノードの変更をデプロイしていません",
"nodeActionDisabled": "ノードのアクションは、サブフロー内で無効になっています",
"missing-types": "不明なノードが存在するため、フローを停止しました。詳細はログを確認してください。",
"restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります"
},
"error": "<strong>エラー</strong>: __message__",
"errors": {
"lostConnection": "サーバとの接続が切断されました: 再接続しています",
"lostConnectionReconnect": "サーバとの接続が切断されました: __time__ 秒後に再接続します",
"lostConnectionTry": "すぐに接続",
"cannotAddSubflowToItself": "サブフロー自身を追加できません",
"cannotAddCircularReference": "循環参照を検出したため、サブフローを追加できません",
"unsupportedVersion": "サポートされていないバージョンのNode.jsを使用しています。<br/>最新のNode.js LTSに更新してください。"
}
},
"clipboard": {
"nodes": "ノード",
"selectNodes": "上のテキストを選択し、クリップボードへコピーしてください",
"pasteNodes": "JSON形式のフローデータを貼り付けてください",
"importNodes": "フローをクリップボートから読み込み",
"exportNodes": "フローをクリップボードへ書き出し",
"importUnrecognised": "認識できない型が読み込まれました:",
"importUnrecognised_plural": "認識できない型が読み込まれました:",
"nodesExported": "クリップボードへフローを書き出しました",
"nodeCopied": "__count__ 個のノードをコピーしました",
"nodeCopied_plural": "__count__ 個のノードをコピーしました",
"invalidFlow": "不正なフロー: __message__",
"export": {
"selected": "選択したフロー",
"current": "現在のタブ",
"all": "全てのタブ",
"compact": "インデントのないJSONフォーマット",
"formatted": "インデント付きのJSONフォーマット",
"copy": "書き出し"
},
"import": {
"import": "読み込み先",
"newFlow": "新規のタブ"
},
"copyMessagePath": "パスをコピーしました",
"copyMessageValue": "値をコピーしました",
"copyMessageValue_truncated": "切り捨てられた値をコピーしました"
},
"deploy": {
"deploy": "デプロイ",
"full": "全て",
"fullDesc": "ワークスペースを全てデプロイ",
"modifiedFlows": "変更したフロー",
"modifiedFlowsDesc": "変更したノードを含むフローのみデプロイ",
"modifiedNodes": "変更したノード",
"modifiedNodesDesc": "変更したノードのみデプロイ",
"successfulDeploy": "デプロイが成功しました",
"deployFailed": "デプロイが失敗しました: __message__",
"unusedConfigNodes": "使われていない「ノードの設定」があります。",
"unusedConfigNodesLink": "設定を参照する",
"errors": {
"noResponse": "サーバの応答がありません"
},
"confirm": {
"button": {
"ignore": "無視",
"confirm": "デプロイの確認",
"review": "差分を確認",
"cancel": "中止",
"merge": "変更をマージ",
"overwrite": "無視してデプロイ"
},
"undeployedChanges": "デプロイしていない変更があります。このページを抜けると変更が削除されます。",
"improperlyConfigured": "以下のノードは、正しくプロパティが設定されていません:",
"unknown": "ワークスペースに未知の型のノードがあります。",
"confirm": "このままデプロイしても良いですか?",
"doNotWarn": "この警告を再度表示しない",
"conflict": "フローを編集している間に、他のブラウザがフローをデプロイしました。デプロイを継続すると、他のブラウザがデプロイしたフローが削除されます。",
"backgroundUpdate": "サーバ上のフローが更新されました",
"conflictChecking": "変更を自動的にマージしてよいか確認してください。",
"conflictAutoMerge": "変更の衝突がないため、自動的にマージできます。",
"conflictManualMerge": "変更に衝突があるため、デプロイ前に解決する必要があります。"
}
},
"diff": {
"unresolvedCount": "未解決の衝突 __count__",
"unresolvedCount_plural": "未解決の衝突 __count__",
"globalNodes": "グローバルノード",
"flowProperties": "フロープロパティ",
"type": {
"added": "追加",
"changed": "変更",
"unchanged": "変更なし",
"deleted": "削除",
"flowDeleted": "削除されたフロー",
"flowAdded": "追加されたフロー",
"movedTo": "__id__ へ移動",
"movedFrom": "__id__ から移動"
},
"nodeCount": "ノード数 __count__",
"nodeCount_plural": "ノード数 __count__",
"local": "ローカルの変更",
"remote": "リモートの変更"
},
"subflow": {
"editSubflow": "フローのテンプレートを編集: __name__",
"edit": "フローのテンプレートを編集",
"subflowInstances": "このサブフローのテンプレートのインスタンスが __count__ 個存在します",
"subflowInstances_plural": "このサブフローのテンプレートのインスタンスが __count__ 個存在します",
"editSubflowProperties": "プロパティを編集",
"input": "入力:",
"output": "出力:",
"deleteSubflow": "サブフローを削除",
"info": "詳細",
"format": "マークダウン形式",
"errors": {
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
}
},
"editor": {
"configEdit": "編集",
"configAdd": "追加",
"configUpdate": "更新",
"configDelete": "削除",
"nodesUse": "__count__ 個のノードが、この設定を使用しています",
"nodesUse_plural": "__count__ 個のノードが、この設定を使用しています",
"addNewConfig": "新規に __type__ ノードの設定を追加",
"editNode": "__type__ ノードを編集",
"editConfig": "__type__ ノードの設定を編集",
"addNewType": "新規に __type__ を追加...",
"nodeProperties": "プロパティ",
"portLabels": "端子名",
"labelInputs": "入力",
"labelOutputs": "出力",
"noDefaultLabel": "なし",
"defaultLabel": "既定の名前を使用",
"errors": {
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします"
}
},
"keyboard": {
"title": "キーボードショートカット",
"keyboard": "キーボード",
"filterActions": "動作を検索",
"shortcut": "ショートカット",
"scope": "範囲",
"unassigned": "未割当",
"global": "グローバル",
"workspace": "ワークスペース",
"selectAll": "全てのノードを選択",
"selectAllConnected": "接続された全てのノードを選択",
"addRemoveNode": "ノードの選択、選択解除",
"editSelected": "選択したノードを編集",
"deleteSelected": "選択したノードや接続を削除",
"importNode": "フローの読み込み",
"exportNode": "フローの書き出し",
"nudgeNode": "選択したノードを移動(移動量小)",
"moveNode": "選択したノードを移動(移動量大)",
"toggleSidebar": "サイドバーの表示非表示",
"copyNode": "選択したノードをコピー",
"cutNode": "選択したノードを切り取り",
"pasteNode": "ノードを貼り付け",
"undoChange": "変更操作を戻す",
"searchBox": "ノードを検索",
"managePalette": "パレットの管理"
},
"library": {
"openLibrary": "ライブラリを開く",
"saveToLibrary": "ライブラリへ保存",
"typeLibrary": "__type__ ライブラリ",
"unnamedType": "名前なし __type__",
"exportToLibrary": "ライブラリへフローを書き出す",
"dialogSaveOverwrite": "__libraryName__ という __libraryType__ は既に存在しています 上書きしますか?",
"invalidFilename": "不正なファイル名",
"savedNodes": "フローを保存しました",
"savedType": "__type__ を保存しました",
"saveFailed": "保存に失敗しました: __message__",
"filename": "ファイル名",
"folder": "フォルダ",
"filenamePlaceholder": "ファイル",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "ライブラリ"
},
"palette": {
"noInfo": "情報がありません",
"filter": "ノードを検索",
"search": "ノードを検索",
"label": {
"subflows": "サブフロー",
"input": "入力",
"output": "出力",
"function": "機能",
"social": "ソーシャル",
"storage": "ストレージ",
"analysis": "分析",
"advanced": "その他"
},
"event": {
"nodeAdded": "ノードをパレットへ追加しました:",
"nodeAdded_plural": "ノードをパレットへ追加しました",
"nodeRemoved": "ノードをパレットから削除しました:",
"nodeRemoved_plural": "ノードをパレットから削除しました:",
"nodeEnabled": "ノードを有効化しました:",
"nodeEnabled_plural": "ノードを有効化しました:",
"nodeDisabled": "ノードを無効化しました:",
"nodeDisabled_plural": "ノードを無効化しました:",
"nodeUpgraded": "ノードモジュール __module__ をバージョン __version__ へ更新しました"
},
"editor": {
"title": "パレットの管理",
"palette": "パレット",
"times": {
"seconds": "秒前",
"minutes": "分前",
"minutesV": "__count__ 分前",
"hoursV": "__count__ 時間前",
"hoursV_plural": "__count__ 時間前",
"daysV": "__count__ 日前",
"daysV_plural": "__count__ 日前",
"weeksV": "__count__ 週間前",
"weeksV_plural": "__count__ 週間前",
"monthsV": "__count__ ヵ月前",
"monthsV_plural": "__count__ ヵ月前",
"yearsV": "__count__ 年前",
"yearsV_plural": "__count__ 年前",
"yearMonthsV": "__y__ 年 __count__ ヵ月前",
"yearMonthsV_plural": "__y__ 年 __count__ ヵ月前",
"yearsMonthsV": "__y__ 年 __count__ ヵ月前",
"yearsMonthsV_plural": "__y__ 年 __count__ ヵ月前"
},
"nodeCount": "__label__ 個のノード",
"nodeCount_plural": "__label__ 個のノード",
"moduleCount": "__count__ 個のモジュール",
"moduleCount_plural": "__count__ 個のモジュール",
"inuse": "使用中",
"enableall": "全て有効化",
"disableall": "全て無効化",
"enable": "有効化",
"disable": "無効化",
"remove": "削除",
"update": "__version__ へ更新",
"updated": "更新済",
"install": "ノードを追加",
"installed": "追加しました",
"loading": "カタログを読み込み中",
"tab-nodes": "現在のノード",
"tab-install": "ノードを追加",
"sort": "並べ替え:",
"sortAZ": "辞書順",
"sortRecent": "日付順",
"more": "+ さらに __count__ 個",
"errors": {
"catalogLoadFailed": "ノードのカタログの読み込みに失敗しました。<br>詳細はブラウザのコンソールを確認してください。",
"installFailed": "追加処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"removeFailed": "削除処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"updateFailed": "更新処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"enableFailed": "有効化処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。",
"disableFailed": "無効化処理が失敗しました: __module__<br>__message__<br>詳細はログを確認してください。"
},
"confirm": {
"install": {
"body": "ードを追加する前に、ドキュメントを確認してください。ードによっては、モジュールの依存関係を自動的に解決できない場合や、Node-REDの再起動が必要となる場合があります。",
"title": "ノードを追加"
},
"remove": {
"body": "Node-REDからードを削除します。ードはNode-REDが再起動されるまで、リソースを使い続けます。",
"title": "ノードを削除"
},
"update": {
"body": "更新を完了するには手動でNode-REDを再起動する必要があります。",
"title": "ノードの更新"
},
"cannotUpdate": {
"body": "ノードの更新があります。「パレットの管理」の画面では更新されません。ドキュメントを参照し、ノードの更新手順を確認してください。"
},
"button": {
"review": "ノードの情報を参照",
"install": "追加",
"remove": "削除",
"update": "更新"
}
}
}
},
"sidebar": {
"info": {
"name": "ノードの情報を表示",
"tabName": "名前",
"label": "情報",
"node": "ノード",
"type": "型",
"id": "ID",
"status": "状態",
"enabled": "有効",
"disabled": "無効",
"subflow": "サブフロー",
"instances": "インスタンス",
"properties": "プロパティ",
"info": "情報",
"blank": "ブランク",
"null": "ヌル",
"showMore": "さらに表示",
"showLess": "表示を省略",
"flow": "フロー",
"information": "情報",
"arrayItems": "__count__ 要素",
"showTips": "設定からヒントを表示できます"
},
"config": {
"name": "ノードの設定を表示",
"label": "ノードの設定",
"global": "全てのフロー上",
"none": "なし",
"subflows": "サブフロー",
"flows": "フロー",
"filterUnused": "未使用",
"filterAll": "全て",
"filtered": "__count__ 個が無効"
},
"palette": {
"name": "パレットの管理",
"label": "パレット"
}
},
"typedInput": {
"type": {
"str": "文字列",
"num": "数値",
"re": "正規表現",
"bool": "真偽",
"json": "JSON",
"bin": "バッファ",
"date": "日時"
}
},
"editableList": {
"add": "追加"
},
"search": {
"empty": "一致したものが見つかりませんでした",
"addNode": "ノードを追加..."
},
"expressionEditor": {
"functions": "関数",
"insert": "挿入",
"title": "JSONata式エディタ",
"data": "メッセージ例",
"result": "結果",
"format": "形式",
"compatMode": "互換モードが有効になっています",
"compatModeDesc": "<h3>JSONata互換モード</h3><p> 入力された式では <code>msg</code> を参照しているため、互換モードで評価します。このモードは将来廃止予定のため、式で <code>msg</code> を使わないよう修正してください。</p><p> JSONataをNode-REDで最初にサポートした際には、 <code>msg</code> オブジェクトの参照が必要でした。例えば <code>msg.payload</code> がペイロードを参照するために使われていました。</p><p> 直接メッセージに対して式を評価するようになったため、この形式は使えなくなります。ペイロードを参照するには、単に <code>payload</code> にしてください。</p>",
"noMatch": "一致した結果なし",
"errors": {
"invalid-expr": "不正なJSONata式:\n __message__",
"invalid-msg": "不正なJSONメッセージ例:\n __message__",
"context-unsupported": "$flowContext や $globalContextの\nコンテキスト機能をテストできません",
"eval": "表現評価エラー:\n __message__"
}
},
"jsonEditor": {
"title": "JSONエディタ",
"format": "JSONフォーマット"
},
"bufferEditor": {
"title": "バッファエディタ",
"modeString": "UTF-8文字列として処理",
"modeArray": "JSON配列として処理",
"modeDesc": "<h3>バッファエディタ</h3><p>バッファ型は、バイト値から成るJSON配列として格納されます。このエディタは、入力値をJSON配列として構文解析します。もし不正なJSON配列の場合、UTF-8文字列として扱い、各文字コード番号から成る配列へ変換します。</p><p>例えば、 <code>Hello World</code> という値を、以下のJSON配列へ変換します。<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
}
}

View File

@@ -0,0 +1,23 @@
{
"info": {
"tip0": "選択したノードや接続を {{core:delete-selection}} により、削除できます。",
"tip1": "{{core:search}} で、フロー内のノードを検索できます。",
"tip2": "{{core:toggle-sidebar}} で、サイドバーの表示/非表示の切り替えができます。",
"tip3": "{{core:manage-palette}} で「パレットの管理」が表示されます。",
"tip4": "フロー内の「ノードの設定」は、サイドバーに一覧表示できます。メニューから呼び出すか {{core:show-config-tab}} を入力してください。",
"tip5": "設定により、ヒントの表示/非表示を変更できます。",
"tip6": "[left] [up] [down] [right] で選択したノードを移動できます。[shift] を押すと移動量が大きくなります。",
"tip7": "ノードを接続の上へドラッグすると、接続内にノードを挿入できます。",
"tip8": "選択したードやタブ内のフローをJSONデータとして書き出すには {{core:show-export-dialog}} を押してください。",
"tip9": "フローデータが入ったJSONファイルをエディタへドラッグ、または {{core:show-import-dialog}} により、フローを読み込むことができます。",
"tip10": "ノードの端子に複数の接続がある時、[shift] を押しながら [click] しドラッグすることで、複数の接続をまとめて他のノードの端子へ移動できます。",
"tip11": "{{core:show-info-tab}} で「情報」タブを表示します。 {{core:show-debug-tab}} で「デバッグ」タブを表示します。",
"tip12": "[ctrl] を押しながらワークスペースを [click] すると、ノードのダイアログが表示され、素早くノードを追加できます。",
"tip13": "[ctrl] を押しながらノードの端子や後続のノードを [click] すると、複数のノードを素早く接続できます。",
"tip14": "[shift] を押しながらノードを [click] すると、接続された全てのノードを選択できます。",
"tip15": "[ctrl] を押しながらノードを [click] すると、選択/非選択を切り替えできます。",
"tip16": "{{core:show-previous-tab}} や {{core:show-next-tab}} で、タブの切り替えができます。",
"tip17": "ノードのプロバティ設定画面にて {{core:confirm-edit-tray}} を押すと、変更を確定できます。また、 {{core:cancel-edit-tray}} を押すと、変更を取り消せます。",
"tip18": "ノードを選択し、 {{core:edit-selected-node}} を押すとプロパティ設定画面が表示されます。"
}
}

View File

@@ -0,0 +1,190 @@
{
"$string": {
"args": "arg",
"desc": "以下の型変換ルールを用いて、引数 *arg* を文字列へ型変換します。:\n\n - 文字列は変換しません。\n - 関数は空の文字列に変換します。\n - JSONの数値として表現できないため、無限大やNaNはエラーになります。\n - 他の値は `JSON.stringify` 関数を用いて、JSONの文字列へ変換します。"
},
"$length": {
"args": "str",
"desc": "文字列 `str` の文字数を返します。 `str` が文字列でない場合、エラーを返します。"
},
"$substring": {
"args": "str, start[, length]",
"desc": "位置 `start` (ゼロオフセット)から開始する引数 `str` の文字列を返します。 `length` を指定した場合、部分文字列は最大 `length` 文字を持ちます。 `start` が負の値の場合、その値は `str` の末尾からの文字数を指します。"
},
"$substringBefore": {
"args": "str, chars",
"desc": "`str` 内で先頭に存在する文字列 `chars` より前の部分文字列を返します。 `str` が `chars` を持たない場合、 `str` を返します。"
},
"$substringAfter": {
"args": "str, chars",
"desc": "`str` 内で先頭に存在する文字列 `chars` より後ろの部分文字列を返します。 `str` が `chars` を持たない場合、 `str` を返します。"
},
"$uppercase": {
"args": "str",
"desc": "`str` の全ての文字を大文字にした文字列を返します。"
},
"$lowercase": {
"args": "str",
"desc": "`str` の全ての文字を小文字にした文字列を返します。"
},
"$trim": {
"args": "str",
"desc": "以下のステップを適用して `str` 内の全ての空白文字を取り除き、正規化します。\n\n - 全てのタブ、キャリッジリターン、ラインフィードを空白に置き換える。\n- 連続する空白を1つの空白に減らす。\n- 末尾と先頭の空白を削除する。\n\n `str` を指定しない場合(例: 本関数を引数なしで呼び出す)、コンテキスト値を `str` の値として使用します。 `str` が文字列でない場合、エラーになります。"
},
"$contains": {
"args": "str, pattern",
"desc": "`str` が `pattern` とマッチした場合は `true` 、マッチしない場合は `false` を返します。 `str` を指定しない場合(例: 本関数を1つの引数で呼び出す)、コンテキスト値を `str` の値として使用します。引数 `pattern` は文字列や正規表現とすることができます。"
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "引数 `str` を分割し、部分文字列の配列にします。 `str` が文字列でない場合、エラーになります。省略可能な引数 `separator` には `str` を分割する文字を文字列または正規表現で指定します。 `separator` を指定しない場合、空の文字列と見なし、 `str` は1文字ずつから成る配列に分割します。 `separator` が文字列でない場合、エラーになります。省略可能な引数 `limit` には、結果の配列が持つ部分文字列の最大数を指定します。この数を超える部分文字列は破棄されます。 `limit` を指定しない場合、 `str` は結果の配列のサイズに上限なく完全に分割されます。 `limit` が負の値の場合、エラーになります。"
},
"$join": {
"args": "array[, separator]",
"desc": "文字列の配列を、省略可能な引数 `separator` で区切った1つの文字列へ連結します。配列 `array` が文字列でない要素を含む場合、エラーになります。 `separator` を指定しない場合、空の文字列と見なします(例: 文字列間の `separator` なし)。 `separator` が文字列でない場合、エラーになります。"
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "文字列 `str` に対して正規表現 `pattern` を適用し、オブジェクトの配列を返します。配列要素のオブジェクトは `str` のうちマッチした部分の情報を保持します。"
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "文字列 `str` からパターン `pattern` を検索し、置換文字列 `replacement` に置き換えます。\n\n任意の引数 `limit` には、置換回数の上限値を指定します。"
},
"$now": {
"args": "",
"desc": "ISO 8601互換形式の時刻を生成し、文字列として返します。"
},
"$base64encode": {
"args": "string",
"desc": "ASCII形式の文字列をBase 64形式へ変換します。文字列内の各文字は、バイナリデータのバイト値として扱われます。文字列内の文字は、URIエンコードした文字列も含め、0x00から0xFFの範囲である必要があります。範囲外のユニコードの文字はサポートされません。"
},
"$base64decode": {
"args": "string",
"desc": "UTF-8のコードページを用いて、Base 64形式のバイト値を文字列に変換します。"
},
"$number": {
"args": "arg",
"desc": "以下の型変換ルールを用いて、引数 `arg` を数値へ変換します。:\n\n - 数値は、変換しません。\n - 正しいJSONの数値を表す文字列は、数値に変換します。\n - 他の形式の値は、エラーになります。"
},
"$abs": {
"args": "number",
"desc": "引数 `number` の絶対値を返します。"
},
"$floor": {
"args": "number",
"desc": "`number` の値を `number` 以下の最も近い整数値へ切り捨てた値を返します。"
},
"$ceil": {
"args": "number",
"desc": "`number` の値を `number` 以上の最も近い整数値へ切り上げた値を返します。"
},
"$round": {
"args": "number [, precision]",
"desc": "引数 `number` の値を四捨五入した値を返します。任意の引数 `precision` には、四捨五入で用いる小数点以下の桁数を指定します。"
},
"$power": {
"args": "base, exponent",
"desc": "基数 `base` を指数 `exponent` 分、累乗した値を返します。"
},
"$sqrt": {
"args": "number",
"desc": "引数 `number` の平方根を返します。"
},
"$random": {
"args": "",
"desc": "0以上、1未満の疑似乱数を返します。"
},
"$sum": {
"args": "array",
"desc": "数値の配列 `array` の合計値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$max": {
"args": "array",
"desc": "数値の配列 `array` 内の最大値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$min": {
"args": "array",
"desc": "数値の配列 `array` 内の最小値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$average": {
"args": "array",
"desc": "数値の配列 `array` の平均値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
},
"$boolean": {
"args": "arg",
"desc": "以下のルールを用いて、ブーリアン型へ型変換します。:\n\n - `Boolean` : 変換しない\n - `string`: 空 : `false`\n - `string`: 空でない : `true`\n - `number`: `0` : `false`\n - `number`: 0でない : `true`\n - `null` : `false`\n - `array`: 空 : `false`\n - `array`: `true` に型変換された要素を持つ: `true`\n - `array`: 全ての要素が `false` に型変換: `false`\n - `object`: 空 : `false`\n - `object`: 空でない : `true`\n - `function` : `false`"
},
"$not": {
"args": "arg",
"desc": "引数の否定をブーリアン型で返します。 `arg` は最初にブーリアン型に型変換されます。"
},
"$exists": {
"args": "arg",
"desc": "`arg` の式の評価値が存在する場合は `true` 、式の評価結果が未定義の場合(例: 存在しない参照フィールドへのパス)は `false` を返します。"
},
"$count": {
"args": "array",
"desc": "配列の要素数を返します。"
},
"$append": {
"args": "array, array",
"desc": "2つの配列を連結します。"
},
"$sort": {
"args": "array [, function]",
"desc": "配列 `array` 内の値を並び変えた配列を返します。\n\n比較関数 `function` を用いる場合、比較関数は以下のとおり2つの引数を持つ必要があります。\n\n`function(left, right)`\n\n比較関数は、leftとrightの2つの値を比較するために、値を並び替える処理で呼び出されます。もし、求められる並び順にてleftの値をrightの値より後ろに置きたい場合は、比較関数は置き換えを表すブーリアン型の `true` を返す必要があります。一方、置き換えが不要の場合は `false` を返す必要があります。"
},
"$reverse": {
"args": "array",
"desc": "配列 `array` の値を、逆順にした配列を返します。"
},
"$shuffle": {
"args": "array",
"desc": "配列 `array` の値を、ランダムな順番にした配列を返します。"
},
"$zip": {
"args": "array, ...",
"desc": "配列 `array1` … `arrayN` の位置 0, 1, 2.... の値を要素とする配列から成る配列を返します。"
},
"$keys": {
"args": "object",
"desc": "オブジェクト内のキーを含む配列を返します。引数がオブジェクトの配列の場合、返す配列は全オブジェクトの全キーの重複の無いリストとなります。"
},
"$lookup": {
"args": "object, key",
"desc": "オブジェクト内のキーが持つ値を返します。最初の引数がオブジェクトの配列の場合、配列内の全てのオブジェクトを検索し、存在する全てのキーが持つ値を返します。"
},
"$spread": {
"args": "object",
"desc": "key/valueのペアを持つオブジェクトを、各要素が1つのkey/valueのペアを持つオブジェクトの配列に分割します。引数がオブジェクトの配列の場合、結果の配列は各オブジェクトから得た各key/valueのペアのオブジェクトを持ちます。"
},
"$sift": {
"args": "object, function",
"desc": "引数 `object` が持つkey/valueのペアのうち、関数 `function` によってふるい分けたオブジェクトのみを返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "`object` の各key/valueのペアに対して、関数 `function` を適用し、その返却値から成る配列を返します。"
},
"$map": {
"args": "array, function",
"desc": "配列 `array` 内の各値に対して、関数 `function` を適用した結果から成る配列を返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "配列 `array` 内の値のうち、関数 `function` の条件を満たす値のみを持つ配列を返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "配列の各要素値に関数 `function` を連続的に適用して得られる集約値を返します。 `function` の適用の際には、直前の `function` の適用結果と要素値が引数として与えられます。\n\n関数 `function` は引数を2つ取り、配列の各要素の間に配置する中置演算子のように作用しなくてはなりません。\n\n任意の引数 `init` には、集約時の初期値を設定します。"
},
"$flowContext": {
"args": "string",
"desc": "フローコンテキストのプロパティを取得します。"
},
"$globalContext": {
"args": "string",
"desc": "グローバルコンテキストのプロパティを取得します。"
}
}

View File

@@ -0,0 +1,423 @@
{
"common": {
"label": {
"name": "姓名",
"ok": "Ok",
"done": "完成",
"cancel": "取消",
"delete": "删除",
"close": "关闭",
"load": "读取",
"save": "保存",
"import": "导入",
"export": "导出"
}
},
"workspace": {
"defaultName": "流程__number__",
"editFlow": "编辑流程: __name__",
"confirmDelete": "确认删除",
"delete": "你确定想删除 '__label__'?",
"dropFlowHere": "把流程放到这里",
"status": "状态",
"enabled": "有效",
"disabled": "无效",
"info": "详细描述",
"tip": "详细描述支持Markdown轻量级标记语言并将出现在信息标签中。"
},
"menu": {
"label": {
"view": {
"view": "表示",
"showGrid": "表示网格",
"snapGrid": "对齐网格",
"gridSize": "网格尺寸",
"textDir": "文本方向",
"defaultDir": "默认",
"ltr": "从左到右",
"rtl": "从右到左",
"auto": "上下文"
},
"sidebar": {
"show": "显示侧边栏"
},
"userSettings": "设定",
"displayStatus": "表示节点状态",
"displayConfig": "配置节点设定",
"import": "导入",
"export": "导出",
"search": "查询",
"clipboard": "剪贴板",
"library": "库",
"examples": "例子",
"subflows": "子流程",
"createSubflow": "新建子流程",
"selectionToSubflow": "将选择部分更改为子流程",
"flows": "流程",
"add": "增加",
"rename": "重命名",
"delete": "删除",
"keyboardShortcuts": "键盘快捷方式",
"login": "登陆",
"logout": "退出",
"editPalette": "编辑调面板",
"showTips": "显示小提示"
}
},
"user": {
"loggedInAs": "作为__name__登陆",
"login": "登陆",
"loginFailed": "登陆失败",
"notAuthorized": "未授权"
},
"notification": {
"warning": "<strong>警告</strong>: __message__",
"warnings": {
"undeployedChanges": "节点中存在未部署的更改",
"nodeActionDisabled": "节点动作在子流程中被禁用",
"missing-types": "流程由于缺少节点类型而停止。请检查日志的详细信息",
"restartRequired": "Node-RED必须重新启动以启用升级的模块"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "丢失与服务器的连接,重新连接...",
"lostConnectionReconnect": "丢失与服务器的连接__time__秒后重新连接",
"lostConnectionTry": "现在尝试",
"cannotAddSubflowToItself": "无法向其自身添加子流程",
"cannotAddCircularReference": "无法添加子流程 - 循环引用",
"unsupportedVersion": "您正在使用不受支持的Node.js版本<br/>请升级到最新版本的Node.js LTS"
}
},
"clipboard": {
"nodes": "节点",
"selectNodes": "选择上面的文本并复制到剪贴板",
"pasteNodes": "在这里粘贴节点",
"importNodes": "导入节点",
"exportNodes": "导出节点至剪贴板",
"importUnrecognised": "导入了无法识别的类型:",
"importUnrecognised_plural": "导入了无法识别的类型:",
"nodesExported": "节点导出到了剪贴板",
"nodeCopied": "已复制__count__个节点",
"nodeCopied_plural": "已复制__count__个节点",
"invalidFlow": "无效的流程: __message__",
"export": {
"selected": "已选择的节点",
"current": "现在的节点",
"all": "所有流程",
"compact": "紧凑",
"formatted": "已格式化",
"copy": "导出到剪贴板"
},
"import": {
"import": "导入到",
"newFlow": "新流程"
},
"copyMessagePath": "已复制路径",
"copyMessageValue": "已复制数值",
"copyMessageValue_truncated": "已复制舍弃的数值"
},
"deploy": {
"deploy": "部署",
"full": "全面",
"fullDesc": "在工作区中部署所有内容",
"modifiedFlows": "已修改的流程",
"modifiedFlowsDesc": "只部署包含已更改节点的流",
"modifiedNodes": "已更改的节点",
"modifiedNodesDesc": "只部署已经更改的节点",
"successfulDeploy": "部署成功",
"deployFailed": "部署失败: __message__",
"unusedConfigNodes": "您有一些未使用的配置节点",
"unusedConfigNodesLink": "点击此处查看它们",
"errors": {
"noResponse": "服务器没有响应"
},
"confirm": {
"button": {
"ignore": "忽略",
"confirm": "确认部署",
"review": "查看更改",
"cancel": "取消",
"merge": "合并",
"overwrite": "忽略 & 部署"
},
"undeployedChanges": "您有未部署的更改。\n\n离开此页面将丢失这些更改。",
"improperlyConfigured": "工作区包含一些未正确配置的节点:",
"unknown": "工作区包含一些未知的节点类型:",
"confirm": "你确定要部署吗?",
"conflict": "服务器正在运行较新的一组流程。",
"backgroundUpdate": "服务器上的流程已更新。",
"conflictChecking": "检查是否可以自动合并更改",
"conflictAutoMerge": "此更改不包括冲突,可以自动合并",
"conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。"
}
},
"diff": {
"unresolvedCount": "__count__个未解决的冲突",
"unresolvedCount_plural": "__count__个未解决的冲突",
"type": {
"added": "已添加",
"changed": "已更改",
"unchanged": "未更改",
"deleted": "已删除",
"flowDeleted": "已删除流程",
"flowAdded": "已添加流程",
"movedTo": "移动至__id__",
"movedFrom": "从__id__移动"
},
"nodeCount": "__count__个节点",
"nodeCount_plural": "__count__个节点",
"local": "本地",
"remote": "远程"
},
"subflow": {
"editSubflow": "编辑流程模板: __name__",
"edit": "编辑流程模板",
"subflowInstances": "这个子流程模板有__count__个实例",
"subflowInstances_plural": "这个子流程模板有__count__个实例",
"editSubflowProperties": "编辑属性",
"input": "输入:",
"output": "输出:",
"deleteSubflow": "删除子流程",
"info": "详细描述",
"format": "标记格式",
"errors": {
"noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点",
"multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择"
}
},
"editor": {
"configEdit": "编辑",
"configAdd": "添加",
"configUpdate": "更新",
"configDelete": "删除",
"nodesUse": "__count__个节点使用此配置",
"nodesUse_plural": "__count__个节点使用此配置",
"addNewConfig": "添加新的__type__配置节点",
"editNode": "编辑__type__节点",
"editConfig": "编辑__type__配置节点",
"addNewType": "添加新节点__type__...",
"nodeProperties": "节点属性",
"portLabels": "端口标签",
"labelInputs": "输入",
"labelOutputs": "输出",
"noDefaultLabel": "无",
"defaultLabel": "使用默认标签",
"errors": {
"scopeChange": "更改范围将使其他流中的节点无法使用"
}
},
"keyboard": {
"title": "键盘快捷键",
"unassigned": "未分配",
"selectAll": "选择所有节点",
"selectAllConnected": "选择所有连接的节点",
"addRemoveNode": "从选择中添加/删除节点",
"editSelected": "编辑选定节点",
"deleteSelected": "删除选定节点或链接",
"importNode": "导入节点",
"exportNode": "导出节点",
"nudgeNode": "移动所选节点(1px)",
"moveNode": "移动所选节点(20px)",
"toggleSidebar": "切换侧边栏",
"copyNode": "复制所选节点",
"cutNode": "剪切所选节点",
"pasteNode": "粘贴节点",
"undoChange": "撤消上次执行的更改",
"searchBox": "打开搜索框",
"managePalette": "管理面板"
},
"library": {
"openLibrary": "“打开库...",
"saveToLibrary": "保存到库...",
"typeLibrary": "__type__类型库",
"unnamedType": "无名__type__",
"exportToLibrary": "将节点导出到库",
"dialogSaveOverwrite": "一个叫做__libraryName__的__libraryType__已经存在您需要覆盖么",
"invalidFilename": "无效的文件名",
"savedNodes": "保存的节点",
"savedType": "已保存__type__",
"saveFailed": "保存失败: __message__",
"filename": "文件名",
"folder": "文件夹",
"filenamePlaceholder": "文件",
"fullFilenamePlaceholder": "a/b/文件",
"folderPlaceholder": "a/b",
"breadcrumb": "库"
},
"palette": {
"noInfo": "无可用信息",
"filter": "过滤节点",
"search": "搜索模块",
"label": {
"subflows": "子流程",
"input": "输入",
"output": "输出",
"function": "功能",
"social": "社交",
"storage": "存储",
"analysis": "分析",
"advanced": "高级的"
},
"event": {
"nodeAdded": "添加到面板中的节点:",
"nodeAdded_plural": "添加到面板中的多个节点",
"nodeRemoved": "从面板中删除的节点:",
"nodeRemoved_plural": "从面板中删除的多个节点:",
"nodeEnabled": "启用节点:",
"nodeEnabled_plural": "启用多个节点:",
"nodeDisabled": "禁用节点:",
"nodeDisabled_plural": "禁用多个节点:",
"nodeUpgraded": "节点模块__module__升级到__version__版本"
},
"editor": {
"title": "面板管理",
"times": {
"seconds": "秒前",
"minutes": "分前",
"minutesV": "__count__分前",
"hoursV": "__count__小时前",
"hoursV_plural": "__count__小时前",
"daysV": "__count__天前",
"daysV_plural": "__count__天前",
"weeksV": "__count__周前",
"weeksV_plural": "__count__周前",
"monthsV": "__count__月前",
"monthsV_plural": "__count__月前",
"yearsV": "__count__年前",
"yearsV_plural": "__count__年前",
"yearMonthsV": "__y__年, __count__月前",
"yearMonthsV_plural": "__y__年, __count__月前",
"yearsMonthsV": "__y__年, __count__月前",
"yearsMonthsV_plural": "__y__年, __count__月前"
},
"nodeCount": "__label__个节点",
"nodeCount_plural": "__label__个节点",
"moduleCount": "__count__个可用模块",
"moduleCount_plural": "__count__个可用模块",
"inuse": "使用中",
"enableall": "全部启用",
"disableall": "全部禁用",
"enable": "启用",
"disable": "禁用",
"remove": "移除",
"update": "更新至__version__版本",
"updated": "已更新",
"install": "安装",
"installed": "已安装",
"loading": "加载目录...",
"tab-nodes": "节点",
"tab-install": "安装",
"sort": "排序:",
"sortAZ": "a-z顺序",
"sortRecent": "日期顺序",
"more": "增加__count__个",
"errors": {
"catalogLoadFailed": "无法加载节点目录。<br>查看浏览器控制台了解更多信息",
"installFailed": "无法安装: __module__<br>__message__<br>查看日志了解更多信息",
"removeFailed": "无法删除: __module__<br>__message__<br>查看日志了解更多信息",
"updateFailed": "无法更新: __module__<br>__message__<br>查看日志了解更多信息",
"enableFailed": "无法启用: __module__<br>__message__<br>查看日志了解更多信息",
"disableFailed": "无法禁用: __module__<br>__message__<br>查看日志了解更多信息"
},
"confirm": {
"install": {
"body": "在安装之前请阅读节点的文档某些节点的依赖关系不能自动解决可能需要重新启动Node-RED。",
"title": "安装节点"
},
"remove": {
"body": "删除节点将从Node-RED卸载它。节点可能会继续使用资源直到重新启动Node-RED。",
"title": "删除节点"
},
"update": {
"body": "更新节点将需要重新启动Node-RED来完成更新该过程必须由手动完成。",
"title": "更新节点"
},
"cannotUpdate": {
"body": "此节点的更新可用,但不会安装在面板管理器可以更新的位置。<br/><br/>请参阅有关如何更新此节点的文档。"
},
"button": {
"review": "打开节点信息",
"install": "安装",
"remove": "删除",
"update": "更新"
}
}
}
},
"sidebar": {
"info": {
"name": "节点信息",
"label": "信息",
"node": "节点",
"type": "类型",
"id": "ID",
"subflow": "子流程",
"instances": "实例",
"properties": "属性",
"info": "信息",
"blank": "空白",
"null": "空",
"arrayItems": "__count__个项目",
"showTips": "您可以从设置面板打开提示"
},
"config": {
"name": "配置节点",
"label": "配置",
"global": "所有流程",
"none": "无",
"subflows": "子流程",
"flows": "流程",
"filterUnused": "未使用",
"filterAll": "所有",
"filtered": "__count__个隐藏"
},
"palette": {
"name": "面板管理",
"label": "面板"
}
},
"typedInput": {
"type": {
"str": "文字列",
"num": "数字",
"re": "正则表达式",
"bool": "真伪判断",
"json": "JSON",
"date": "时间戳"
}
},
"editableList": {
"add": "添加"
},
"search": {
"empty": "找不到匹配",
"addNode": "添加一个节点..."
},
"expressionEditor": {
"functions": "功能",
"insert": "插入",
"title": "JSONata表达式编辑器",
"data": "示例消息",
"result": "结果",
"format": "格式表达方法",
"compatMode": "兼容模式启用",
"compatModeDesc": "<h3>JSONata的兼容模式</h3><p> 目前的表达式仍然参考<code>msg</code>,所以将以兼容性模式进行评估。请更新表达式,使其不使用<code>msg</code>,因为此模式将在将来删除。</p><p> 当JSONata支持首次添加到Node-RED时它需要表达式引用<code>msg</code>对象。例如<code>msg.payload</code>将用于访问有效负载。</p><p> 这样便不再需要表达式直接针对消息进行评估。要访问有效负载,表达式应该只是<code>payload</code>.</p>",
"noMatch": "无匹配结果",
"errors": {
"invalid-expr": "无效的JSONata表达式:\n __message__",
"invalid-msg": "无效的示例JSON消息:\n __message__",
"context-unsupported": "无法测试上下文函数\n $flowContext 或 $globalContext",
"eval": "评估表达式错误:\n __message__"
}
},
"jsonEditor": {
"title": "JSON编辑器",
"format": "格式化JSON"
},
"bufferEditor": {
"title": "缓冲区编辑器",
"modeString": "作为UTF-8字符串处理",
"modeArray": "作为JSON数组处理",
"modeDesc": "<h3>缓冲区编辑器</h3><p>缓冲区类型被存储为字节值的JSON数组。编辑器将尝试将输入的数值解析为JSON数组。如果它不是有效的JSON它将被视为UTF-8字符串并被转换为单个字符代码点的数组。</p><p>例如,<code>Hello World</code>的值会被转换为JSON数组<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
}
}

196
red/api/editor/theme.js Normal file
View File

@@ -0,0 +1,196 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var util = require("util");
var path = require("path");
var fs = require("fs");
var clone = require("clone");
var defaultContext = {
page: {
title: "Node-RED",
favicon: "favicon.ico",
tabicon: "red/images/node-red-icon-black.svg"
},
header: {
title: "Node-RED",
image: "red/images/node-red.png"
},
asset: {
red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js",
main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js",
}
};
var theme = null;
var themeContext = clone(defaultContext);
var themeSettings = null;
var runtime = null;
var themeApp;
function serveFile(app,baseUrl,file) {
try {
var stats = fs.statSync(file);
var url = baseUrl+path.basename(file);
//console.log(url,"->",file);
app.get(url,function(req, res) {
res.sendFile(file);
});
return "theme"+url;
} catch(err) {
//TODO: log filenotfound
return null;
}
}
function serveFilesFromTheme(themeValue, themeApp, directory) {
var result = [];
if (themeValue) {
var array = themeValue;
if (!util.isArray(array)) {
array = [array];
}
for (var i=0;i<array.length;i++) {
var url = serveFile(themeApp,directory,array[i]);
if (url) {
result.push(url);
}
}
}
return result
}
module.exports = {
init: function(runtime) {
var settings = runtime.settings;
themeContext = clone(defaultContext);
if (runtime.version) {
themeContext.version = runtime.version();
}
themeSettings = null;
theme = settings.editorTheme || {};
},
app: function() {
var i;
var url;
themeSettings = {};
themeApp = express();
if (theme.page) {
themeContext.page.css = serveFilesFromTheme(
theme.page.css,
themeApp,
"/css/")
themeContext.page.scripts = serveFilesFromTheme(
theme.page.scripts,
themeApp,
"/scripts/")
if (theme.page.favicon) {
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
if (url) {
themeContext.page.favicon = url;
}
}
if (theme.page.tabicon) {
url = serveFile(themeApp,"/tabicon/",theme.page.tabicon)
if (url) {
themeContext.page.tabicon = url;
}
}
themeContext.page.title = theme.page.title || themeContext.page.title;
}
if (theme.header) {
themeContext.header.title = theme.header.title || themeContext.header.title;
if (theme.header.hasOwnProperty("url")) {
themeContext.header.url = theme.header.url;
}
if (theme.header.hasOwnProperty("image")) {
if (theme.header.image) {
url = serveFile(themeApp,"/header/",theme.header.image);
if (url) {
themeContext.header.image = url;
}
} else {
themeContext.header.image = null;
}
}
}
if (theme.deployButton) {
if (theme.deployButton.type == "simple") {
themeSettings.deployButton = {
type: "simple"
}
if (theme.deployButton.label) {
themeSettings.deployButton.label = theme.deployButton.label;
}
if (theme.deployButton.icon) {
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
if (url) {
themeSettings.deployButton.icon = url;
}
}
}
}
if (theme.hasOwnProperty("userMenu")) {
themeSettings.userMenu = theme.userMenu;
}
if (theme.login) {
if (theme.login.image) {
url = serveFile(themeApp,"/login/",theme.login.image);
if (url) {
themeContext.login = {
image: url
}
}
}
}
if (theme.hasOwnProperty("menu")) {
themeSettings.menu = theme.menu;
}
if (theme.hasOwnProperty("palette")) {
themeSettings.palette = theme.palette;
}
return themeApp;
},
context: function() {
return themeContext;
},
settings: function() {
return themeSettings;
},
serveFile: function(baseUrl,file) {
return serveFile(themeApp,baseUrl,file);
}
}

56
red/api/editor/ui.js Normal file
View File

@@ -0,0 +1,56 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require('express');
var fs = require("fs");
var path = require("path");
var Mustache = require("mustache");
var theme = require("./theme");
var redNodes;
var templateDir = path.resolve(__dirname+"/../../../editor/templates");
var editorTemplate;
module.exports = {
init: function(runtime) {
redNodes = runtime.nodes;
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
Mustache.parse(editorTemplate);
},
ensureSlash: function(req,res,next) {
var parts = req.originalUrl.split("?");
if (parts[0].slice(-1) != "/") {
parts[0] += "/";
var redirect = parts.join("?");
res.redirect(301,redirect);
} else {
next();
}
},
icon: function(req,res) {
var icon = req.params.icon;
var scope = req.params.scope;
var module = scope ? scope + '/' + req.params.module : req.params.module;
var iconPath = redNodes.getNodeIconPath(module,icon);
res.sendFile(iconPath);
},
editor: function(req,res) {
res.send(Mustache.render(editorTemplate,theme.context()));
},
editorResources: express.static(__dirname + '/../../../public')
};