From fcd87affad2d22b3389b33176ed97783adaa2e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 12:50:20 +0200 Subject: [PATCH 01/14] :bug: Fix `defaultLocale` watcher --- packages/editor-ui/src/App.vue | 6 +++++- packages/editor-ui/src/views/NodeView.vue | 4 ---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index c4f6abfa8cad1..172362d94b43e 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -28,6 +28,7 @@ import { showMessage } from './components/mixins/showMessage'; import { IUser } from './Interface'; import { mapGetters } from 'vuex'; import { userHelpers } from './components/mixins/userHelpers'; +import { loadLanguage } from './plugins/i18n'; export default mixins( showMessage, @@ -40,7 +41,7 @@ export default mixins( Modals, }, computed: { - ...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']), + ...mapGetters('settings', ['defaultLocale', 'isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']), ...mapGetters('users', ['currentUser']), }, data() { @@ -160,6 +161,9 @@ export default mixins( this.trackPage(); }, + '$store.getters.defaultLocale'(newLocale) { + loadLanguage(newLocale); + }, }, }); diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index f30472ee79e4a..966bba57a7f78 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -170,7 +170,6 @@ import { import { mapGetters } from 'vuex'; import { - loadLanguage, addNodeTranslation, addHeaders, } from '@/plugins/i18n'; @@ -232,9 +231,6 @@ export default mixins( deep: true, }, - async defaultLocale (newLocale, oldLocale) { - loadLanguage(newLocale); - }, }, async beforeRouteLeave(to, from, next) { const result = this.$store.getters.getStateIsDirty; From 8015f99211809a752017097cff6f613bc2ef4b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 13:17:52 +0200 Subject: [PATCH 02/14] :zap: Improve error handling for headers --- packages/cli/src/Server.ts | 7 +++++-- packages/editor-ui/src/views/NodeView.vue | 8 ++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index e87b36e81f91d..33a8918d60415 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -29,7 +29,7 @@ /* eslint-disable no-await-in-loop */ import * as express from 'express'; -import { readFileSync } from 'fs'; +import { existsSync, readFileSync } from 'fs'; import { readFile } from 'fs/promises'; import { cloneDeep } from 'lodash'; import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path'; @@ -1503,10 +1503,13 @@ class App { async (req: express.Request, res: express.Response): Promise => { const packagesPath = pathJoin(__dirname, '..', '..', '..'); const headersPath = pathJoin(packagesPath, 'nodes-base', 'dist', 'nodes', 'headers'); + + if (!existsSync(`${headersPath}.js`)) return; + try { return require(headersPath); } catch (error) { - res.status(500).send('Failed to find headers file'); + res.status(500).send('Failed to load headers file'); } }, ), diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 966bba57a7f78..5c77bdf1fc063 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -2742,12 +2742,8 @@ export default mixins( await Promise.all(loadPromises); if (this.defaultLocale !== 'en') { - try { - const headers = await this.restApi().getNodeTranslationHeaders(); - addHeaders(headers, this.defaultLocale); - } catch (_) { - // no headers available - } + const headers = await this.restApi().getNodeTranslationHeaders(); + if (headers) addHeaders(headers, this.defaultLocale); } } catch (error) { this.$showError( From b7ba200db0f21271e8a48e447740e215c0c8885f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 13:20:27 +0200 Subject: [PATCH 03/14] :pencil2: Improve naming --- packages/editor-ui/src/views/NodeView.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 5c77bdf1fc063..ebf1238716272 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -267,7 +267,7 @@ export default mixins( defaultLocale (): string { return this.$store.getters.defaultLocale; }, - englishLocale(): boolean { + isEnglishLocale(): boolean { return this.defaultLocale === 'en'; }, ...mapGetters(['nativelyNumberSuffixedDefaults']), @@ -374,7 +374,7 @@ export default mixins( type?: string, }) { const allNodeNamesOnCanvas = this.$store.getters.allNodes.map((n: INodeUi) => n.name); - originalName = this.englishLocale ? originalName : this.translateName(type, originalName); + originalName = this.isEnglishLocale ? originalName : this.translateName(type, originalName); if ( !allNodeNamesOnCanvas.includes(originalName) && @@ -384,7 +384,7 @@ export default mixins( } let natives: string[] = this.nativelyNumberSuffixedDefaults; - natives = this.englishLocale ? natives : natives.map(name => { + natives = this.isEnglishLocale ? natives : natives.map(name => { const type = name.toLowerCase().replace('_', ''); return this.translateName(type, name); }); From da718da48047ad6eaa2a34cac6423bdb865b2c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 13:25:34 +0200 Subject: [PATCH 04/14] :bug: Fix hiring banner check --- packages/editor-ui/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index 172362d94b43e..3883b6b665c93 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -80,7 +80,7 @@ export default mixins( } }, logHiringBanner() { - if (!this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) { + if (this.isHiringBannerEnabled && this.$route.name !== VIEWS.DEMO) { console.log(HIRING_BANNER); // eslint-disable-line no-console } }, From 862482a1225ea6a11879d3436e344ccfb87914f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 17:38:46 +0200 Subject: [PATCH 05/14] :zap: Flatten base text keys --- .../src/plugins/i18n/locales/en.json | 1806 +++++++---------- 1 file changed, 716 insertions(+), 1090 deletions(-) diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 4405081688b0d..7b957a53f5f06 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -1,1093 +1,719 @@ { - "about": { - "aboutN8n": "About n8n", - "close": "Close", - "license": "License", - "n8nVersion": "n8n Version", - "n8nLicense": "Sustainable Use License", - "sourceCode": "Source Code" - }, - "binaryDataDisplay": { - "backToList": "Back to list", - "backToOverviewPage": "Back to overview page", - "noDataFoundToDisplay": "No data found to display", - "yourBrowserDoesNotSupport": "Your browser does not support the video element. Kindly update it to latest version." - }, - "codeEdit": { - "edit": "Edit" - }, - "collectionParameter": { - "choose": "Choose...", - "noProperties": "No properties" - }, - "credentialEdit": { - "credentialConfig": { - "accountConnected": "Account connected", - "clickToCopy": "Click To Copy", - "connectionTestedSuccessfully": "Connection tested successfully", - "couldntConnectWithTheseSettings": "Couldn’t connect with these settings", - "needHelpFillingOutTheseFields": "Need help filling out these fields?", - "oAuthRedirectUrl": "OAuth Redirect URL", - "openDocs": "Open docs", - "pleaseCheckTheErrorsBelow": "Please check the errors below", - "reconnect": "reconnect", - "reconnectOAuth2Credential": "Reconnect OAuth2 Credential", - "redirectUrlCopiedToClipboard": "Redirect URL copied to clipboard", - "retry": "Retry", - "retryCredentialTest": "Retry credential test", - "retrying": "Retrying", - "subtitle": "In {appName}, use the URL above when prompted to enter an OAuth callback or redirect URL", - "theServiceYouReConnectingTo": "the service you're connecting to" - }, - "credentialEdit": { - "confirmMessage": { - "beforeClose1": { - "cancelButtonText": "Keep Editing", - "confirmButtonText": "Close", - "headline": "Close without saving?", - "message": "Are you sure you want to throw away the changes you made to the {credentialDisplayName} credential?" - }, - "beforeClose2": { - "cancelButtonText": "Keep Editing", - "confirmButtonText": "Close", - "headline": "Close without connecting?", - "message": "You need to connect your credential for it to work" - }, - "deleteCredential": { - "cancelButtonText": "", - "confirmButtonText": "Yes, delete", - "headline": "Delete Credential?", - "message": "Are you sure you want to delete \"{savedCredentialName}\"?" - } - }, - "connection": "Connection", - "couldNotFindCredentialOfType": "Could not find credential of type", - "couldNotFindCredentialWithId": "Could not find credential with ID", - "details": "Details", - "delete": "Delete", - "saving": "Saving", - "showError": { - "createCredential": { - "title": "Problem creating credential" - }, - "deleteCredential": { - "title": "Problem deleting credential" - }, - "generateAuthorizationUrl": { - "message": "There was a problem generating the authorization URL", - "title": "OAuth Authorization Error" - }, - "loadCredential": { - "title": "Problem loading credential" - }, - "updateCredential": { - "title": "Problem updating credential" - } - }, - "showMessage": { - "title": "Credential deleted" - }, - "testing": "Testing" - }, - "credentialInfo": { - "allowUseBy": "Allow use by", - "created": "Created", - "id": "ID", - "lastModified": "Last modified" - }, - "oAuthButton": { - "connectMyAccount": "Connect my account", - "signInWithGoogle": "Sign in with Google" - } - }, - "credentialSelectModal": { - "addNewCredential": "Add new credential", - "continue": "Continue", - "searchForApp": "Search for app...", - "selectAnAppOrServiceToConnectTo": "Select an app or service to connect to" - }, - "credentialsList": { - "addNew": "Add New", - "confirmMessage": { - "cancelButtonText": "", - "confirmButtonText": "Yes, delete", - "headline": "Delete Credential?", - "message": "Are you sure you want to delete {credentialName}?" - }, - "createNewCredential": "Create New Credential", - "created": "Created", - "credentials": "Credentials", - "deleteCredential": "Delete Credential", - "editCredential": "Edit Credential", - "name": "@:reusableBaseText.name", - "operations": "Operations", - "showError": { - "deleteCredential": { - "title": "Problem deleting credential" - } - }, - "showMessage": { - "title": "Credential deleted" - }, - "type": "Type", - "updated": "Updated", - "yourSavedCredentials": "Your saved credentials", - "errorLoadingCredentials": "Error loading credentials" - }, - "dataDisplay": { - "needHelp": "Need help?", - "nodeDocumentation": "Node Documentation", - "openDocumentationFor": "Open {nodeTypeDisplayName} documentation" - }, - "displayWithChange": { - "cancelEdit": "Cancel Edit", - "clickToChange": "Click to Change", - "setValue": "Set Value" - }, - "duplicateWorkflowDialog": { - "cancel": "@:reusableBaseText.cancel", - "chooseOrCreateATag": "Choose or create a tag", - "duplicateWorkflow": "Duplicate Workflow", - "enterWorkflowName": "Enter workflow name", - "save": "@:reusableBaseText.save", - "showMessage": { - "message": "Please enter a name.", - "title": "Name missing" - } - }, - "executionDetails": { - "executionFailed": "Execution failed", - "executionId": "Execution ID", - "executionWaiting": "Execution waiting", - "executionWasSuccessful": "Execution was successful", - "openWorkflow": "Open Workflow", - "of": "of", - "workflow": "workflow", - "readOnly": { - "readOnly": "Read only", - "youreViewingTheLogOf": "You're viewing the log of a previous execution. You cannot
\n\t\tmake changes since this execution already occurred. Make changes
\n\t\tto this workflow by clicking on its name on the left." - } - }, - "executionsList": { - "allWorkflows": "All Workflows", - "anyStatus": "Any Status", - "autoRefresh": "Auto refresh", - "confirmMessage": { - "cancelButtonText": "", - "confirmButtonText": "Yes, delete", - "headline": "Delete Executions?", - "message": "Are you sure that you want to delete the {numSelected} selected execution(s)?" - }, - "deleteSelected": "Delete Selected", - "error": "Error", - "filters": "Filters", - "loadMore": "Load More", - "mode": "Mode", - "modes": { - "error": "error", - "integrated": "integrated", - "manual": "manual", - "retry": "retry", - "trigger": "trigger", - "webhook": "webhook" - }, - "name": "@:reusableBaseText.name", - "openPastExecution": "Open Past Execution", - "retryExecution": "Retry execution", - "retryOf": "Retry of", - "retryWithCurrentlySavedWorkflow": "Retry with currently saved workflow", - "retryWithOriginalworkflow": "Retry with original workflow", - "running": "Running", - "runningTime": "Running Time", - "selectStatus": "Select Status", - "selectWorkflow": "Select Workflow", - "selected": "Selected", - "showError": { - "handleDeleteSelected": { - "title": "Problem deleting executions" - }, - "loadMore": { - "title": "Problem loading workflows" - }, - "loadWorkflows": { - "title": "Problem loading workflows" - }, - "refreshData": { - "title": "Problem loading data" - }, - "retryExecution": { - "title": "Problem with retry" - }, - "stopExecution": { - "title": "Problem stopping execution" - } - }, - "showMessage": { - "handleDeleteSelected": { - "title": "Execution deleted" - }, - "retrySuccessfulFalse": { - "title": "Retry unsuccessful" - }, - "retrySuccessfulTrue": { - "title": "Retry successful" - }, - "stopExecution": { - "message": "Execution ID {activeExecutionId}", - "title": "Execution stopped" - } - }, - "startedAtId": "Started At / ID", - "status": "Status", - "statusTooltipText": { - "theWorkflowExecutionFailed": "The workflow execution failed.", - "theWorkflowExecutionFailedButTheRetryWasSuccessful": "The workflow execution failed but the retry {entryRetrySuccessId} was successful.", - "theWorkflowExecutionIsProbablyStillRunning": "The workflow execution is probably still running but it may have crashed and n8n cannot safely tell. ", - "theWorkflowExecutionWasARetryOfAndFailed": "The workflow execution was a retry of {entryRetryOf} and failed.
New retries have to be started from the original execution.", - "theWorkflowExecutionWasARetryOfAndItWasSuccessful": "The workflow execution was a retry of {entryRetryOf} and it was successful.", - "theWorkflowExecutionWasSuccessful": "The worklow execution was successful.", - "theWorkflowIsCurrentlyExecuting": "The worklow is currently executing.", - "theWorkflowIsWaitingIndefinitely": "The workflow is waiting indefinitely for an incoming webhook call.", - "theWorkflowIsWaitingTill": "The worklow is waiting till {waitDateDate} {waitDateTime}." - }, - "stopExecution": "Stop Execution", - "success": "Success", - "successRetry": "Success retry", - "unknown": "Unknown", - "unsavedWorkflow": "[UNSAVED WORKFLOW]", - "waiting": "Waiting", - "workflowExecutions": "Workflow Executions" - }, - "expressionEdit": { - "editExpression": "Edit Expression", - "expression": "Expression", - "result": "Result", - "variableSelector": "Variable Selector" - }, - "fixedCollectionParameter": { - "choose": "Choose...", - "currentlyNoItemsExist": "Currently no items exist", - "deleteItem": "Delete item", - "moveDown": "Move down", - "moveUp": "Move up" - }, - "genericHelpers": { - "loading": "Loading", - "min": "min", - "sec": "sec", - "showMessage": { - "message": "This is a read-only version of the workflow. To make changes, either open the original workflow or save it under a new name.", - "title": "Workflow cannot be changed" - } - }, - "mainSidebar": { - "aboutN8n": "About n8n", - "confirmMessage": { - "workflowDelete": { - "cancelButtonText": "", - "confirmButtonText": "Yes, delete", - "headline": "Delete Workflow?", - "message": "Are you sure that you want to delete '{workflowName}'?" - }, - "workflowNew": { - "cancelButtonText": "Leave without saving", - "confirmButtonText": "Save", - "headline": "Save changes before leaving?", - "message": "If you don't save, you will lose your changes." - } - }, - "credentials": "Credentials", - "delete": "Delete", - "download": "Download", - "duplicate": "Duplicate", - "executions": "Executions", - "help": "Help", - "helpMenuItems": { - "course": "Course", - "documentation": "Documentation", - "forum": "Forum" - }, - "importFromFile": "Import from File", - "importFromUrl": "Import from URL", - "new": "New", - "newTemplate": "New from template", - "open": "Open", - "prompt": { - "cancel": "@:reusableBaseText.cancel", - "import": "Import", - "importWorkflowFromUrl": "Import Workflow from URL", - "invalidUrl": "Invalid URL", - "workflowUrl": "Workflow URL" - }, - "save": "@:reusableBaseText.save", - "settings": "Settings", - "showError": { - "stopExecution": { - "title": "Problem stopping execution" - } - }, - "showMessage": { - "handleFileImport": { - "message": "The file does not contain valid JSON data", - "title": "Could not import file" - }, - "handleSelect1": { - "title": "Workflow deleted" - }, - "handleSelect2": { - "title": "Workflow created" - }, - "handleSelect3": { - "title": "Workflow created" - }, - "stopExecution": { - "title": "Execution stopped" - } - }, - "templates": "Templates", - "workflows": "Workflows" - }, - "multipleParameter": { - "addItem": "Add item", - "currentlyNoItemsExist": "Currently no items exist", - "deleteItem": "Delete item", - "moveDown": "Move down", - "moveUp": "Move up" - }, - "noTagsView": { - "readyToOrganizeYourWorkflows": "Ready to organize your workflows?", - "withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows" - }, - "node": { - "activateDeactivateNode": "Activate/Deactivate Node", - "deleteNode": "Delete Node", - "disabled": "Disabled", - "duplicateNode": "Duplicate Node", - "editNode": "Edit Node", - "executeNode": "Execute Node", - "issues": "Issues", - "nodeIsExecuting": "Node is executing", - "nodeIsWaitingTill": "Node is waiting until {date} {time}", - "theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall": "The node is waiting for an incoming webhook call (indefinitely)", - "waitingForYouToCreateAnEventIn": "Waiting for you to create an event in {nodeType}" - }, - "nodeCreator": { - "categoryNames": { - "analytics": "Analytics", - "communication": "Communication", - "coreNodes": "Core Nodes", - "customNodes": "Custom Nodes", - "dataStorage": "Data & Storage", - "development": "Development", - "financeAccounting": "Finance & Accounting", - "marketingContent": "Marketing & Content", - "miscellaneous": "Miscellaneous", - "productivity": "Productivity", - "sales": "Sales", - "suggestedNodes": "Suggested Nodes ✨", - "utility": "Utility" - }, - "mainPanel": { - "all": "All", - "regular": "Regular", - "trigger": "Trigger" - }, - "noResults": { - "dontWorryYouCanProbablyDoItWithThe": "Don’t worry, you can probably do it with the", - "httpRequest": "HTTP Request", - "node": "node", - "or": "or", - "requestTheNode": "Request the node", - "wantUsToMakeItFaster": "Want us to make it faster?", - "weDidntMakeThatYet": "We didn't make that... yet", - "webhook": "Webhook" - }, - "searchBar": { - "searchNodes": "Search nodes..." - }, - "subcategoryDescriptions": { - "dataTransformation": "Manipulate data fields, run code", - "files": "Work with CSV, XML, text, images etc.", - "flow": "Branches, core triggers, merge data", - "helpers": "HTTP Requests (API calls), date and time, scrape HTML" - }, - "subcategoryNames": { - "dataTransformation": "Data Transformation", - "files": "Files", - "flow": "Flow", - "helpers": "Helpers" - } - }, - "nodeCredentials": { - "createNew": "Create New", - "credentialFor": "Credential for {credentialType}", - "issues": "Issues", - "selectCredential": "Select Credential", - "showMessage": { - "message": "Nodes that used credential \"{oldCredentialName}\" have been updated to use \"{newCredentialName}\"", - "title": "Node credential updated" - }, - "updateCredential": "Update Credential" - }, - "nodeErrorView": { - "cause": "Cause", - "copyToClipboard": "Copy to Clipboard", - "dataBelowMayContain": "Data below may contain sensitive information. Proceed with caution when sharing.", - "details": "Details", - "error": "ERROR", - "httpCode": "HTTP Code", - "showMessage": { - "title": "Copied to clipboard" - }, - "stack": "Stack", - "theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed", - "time": "Time" - }, - "nodeBase": { - "clickToAddNodeOrDragToConnect": "Click to add node
or drag to connect" - }, - "nodeSettings": { - "alwaysOutputData": { - "description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.", - "displayName": "Always Output Data" - }, - "clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io", - "continueOnFail": { - "description": "If active, the workflow continues even if this node's execution fails. When this occurs, the node passes along input data from previous nodes - so your workflow should account for unexpected output data.", - "displayName": "Continue On Fail" - }, - "executeOnce": { - "description": "If active, the node executes only once, with data from the first item it receives", - "displayName": "Execute Once" - }, - "maxTries": { - "description": "Number of times to attempt to execute the node before failing the execution", - "displayName": "Max. Tries" - }, - "noDescriptionFound": "No description found", - "nodeDescription": "Node Description", - "notes": { - "description": "Optional note to save with the node", - "displayName": "Notes" - }, - "notesInFlow": { - "description": "If active, the note above will display in the flow as a subtitle", - "displayName": "Display note in flow?" - }, - "parameters": "Parameters", - "retryOnFail": { - "description": "If active, the node tries to execute again when it fails", - "displayName": "Retry On Fail" - }, - "settings": "Settings", - "theNodeIsNotValidAsItsTypeIsUnknown": "The node is not valid as its type ({nodeType}) is unknown", - "thisNodeDoesNotHaveAnyParameters": "This node does not have any parameters", - "waitBetweenTries": { - "description": "How long to wait between each attempt (in milliseconds)", - "displayName": "Wait Between Tries (ms)" - } - }, - "nodeView": { - "addNode": "Add node", - "confirmMessage": { - "beforeRouteLeave": { - "cancelButtonText": "Leave without saving", - "confirmButtonText": "Save", - "headline": "Save changes before leaving?", - "message": "If you don't save, you will lose your changes." - }, - "initView": { - "cancelButtonText": "Leave without saving", - "confirmButtonText": "Save", - "headline": "Save changes before leaving?", - "message": "If you don't save, you will lose your changes." - }, - "receivedCopyPasteData": { - "cancelButtonText": "", - "confirmButtonText": "Yes, import", - "headline": "Import Workflow?", - "message": "Workflow will be imported from
{plainTextData}" - } - }, - "couldntImportWorkflow": "Could not import workflow", - "deletesTheCurrentExecutionData": "Deletes the current execution data", - "executesTheWorkflowFromTheStartOrWebhookNode": "Executes the workflow from the 'start' or 'webhook' node", - "itLooksLikeYouHaveBeenEditingSomething": "It looks like you made some edits. If you leave before saving, your changes will be lost.", - "loadingTemplate": "Loading template", - "moreInfo": "More info", - "noNodesGivenToAdd": "No nodes to add specified", - "prompt": { - "cancel": "@:reusableBaseText.cancel", - "invalidName": "Invalid Name", - "newName": "New Name", - "rename": "Rename", - "renameNode": "Rename Node" - }, - "redirecting": "Redirecting", - "refresh": "Refresh", - "resetZoom": "Reset Zoom", - "runButtonText": { - "executeWorkflow": "Execute Workflow", - "executingWorkflow": "Executing Workflow", - "waitingForTriggerEvent": "Waiting for Trigger Event" - }, - "showError": { - "getWorkflowDataFromUrl": { - "title": "Problem loading workflow" - }, - "importWorkflowData": { - "title": "Problem importing workflow" - }, - "mounted1": { - "message": "There was a problem loading init data", - "title": "Init Problem" - }, - "mounted2": { - "message": "There was a problem initializing the workflow", - "title": "Init Problem" - }, - "openExecution": { - "title": "Problem loading execution" - }, - "openWorkflow": { - "title": "Problem opening workflow" - }, - "stopExecution": { - "title": "Problem stopping execution" - }, - "stopWaitingForWebhook": { - "title": "Problem deleting test webhook" - } - }, - "showMessage": { - "addNodeButton": { - "message": "'{nodeTypeName}' is an unknown node type", - "title": "Could not create node" - }, - "keyDown": { - "title": "Workflow created" - }, - "showMaxNodeTypeError": { - "message": { - "plural": "Only {maxNodes} '{nodeTypeDataDisplayName}' nodes are allowed in a workflow", - "singular": "Only {maxNodes} '{nodeTypeDataDisplayName}' node is allowed in a workflow" - }, - "title": "Could not create node" - }, - "stopExecutionCatch": { - "message": "It completed before it could be stopped", - "title": "Workflow finished executing" - }, - "stopExecutionTry": { - "title": "Execution stopped" - }, - "stopWaitingForWebhook": { - "title": "Webhook deleted" - } - }, - "stopCurrentExecution": "Stop current execution", - "stopWaitingForWebhookCall": "Stop waiting for webhook call", - "stoppingCurrentExecution": "Stopping current execution", - "thereWasAProblemLoadingTheNodeParametersOfNode": "There was a problem loading the parameters of the node", - "thisExecutionHasntFinishedYet": "This execution hasn't finished yet", - "toSeeTheLatestStatus": "to see the latest status", - "workflowTemplateWithIdCouldNotBeFound": "Workflow template with ID \"{templateId}\" could not be found", - "workflowWithIdCouldNotBeFound": "Workflow with ID \"{workflowId}\" could not be found", - "zoomIn": "Zoom In", - "zoomOut": "Zoom Out", - "zoomToFit": "Zoom to Fit" - }, - "nodeWebhooks": { - "clickToCopyWebhookUrls": "Click to copy webhook URLs", - "clickToDisplayWebhookUrls": "Click to display webhook URLs", - "clickToHideWebhookUrls": "Click to hide webhook URLs", - "invalidExpression": "[INVALID EXPRESSION]", - "productionUrl": "Production URL", - "showMessage": { - "title": "URL copied" - }, - "testUrl": "Test URL", - "webhookUrls": "Webhook URLs" - }, - "parameterInput": { - "addExpression": "Add Expression", - "error": "ERROR", - "issues": "Issues", - "loadingOptions": "Loading options...", - "openEditWindow": "Open Edit Window", - "parameter": "Parameter: \"{shortPath}\"", - "parameterHasExpression": "Parameter: \"{shortPath}\" has an expression", - "parameterHasIssues": "Parameter: \"{shortPath}\" has issues", - "parameterHasIssuesAndExpression": "Parameter: \"{shortPath}\" has issues and an expression", - "parameterOptions": "Parameter Options", - "refreshList": "Refresh List", - "removeExpression": "Remove Expression", - "resetValue": "Reset Value", - "selectDateAndTime": "Select date and time", - "select": "Select" - }, - "parameterInputExpanded": { - "openDocs": "Open docs", - "thisFieldIsRequired": "This field is required" - }, - "parameterInputList": { - "delete": "Delete", - "deleteParameter": "Delete Parameter", - "parameterOptions": "Parameter Options" - }, - "personalizationModal": { - "automationConsulting": "Automation consulting", - "continue": "Continue", - "customizeN8n": "Customize n8n to you", - "eCommerce": "eCommerce", - "errorWhileSubmittingResults": "Error while submitting results", - "executiveTeam": "Executive team", - "financeOrInsurance": "Finance / Insurance", - "getStarted": "Get started", - "government": "Government", - "healthcare": "Healthcare", - "howAreYourCodingSkills": "How are your coding skills?", - "howBigIsYourCompany": "How big is your company?", - "hr": "HR", - "it": "IT", - "iCanCodeSomeUsefulThingsBut": "2. I can code some useful things, but I spend a lot of time stuck", - "iCanDoAlmostAnythingIWant": "5. I can do almost anything I want, easily (pro coder)", - "iCanFigureMostThingsOut": "4. I can figure most things out", - "iGetStuckTooQuicklyToAchieveMuch": "1. I get stuck too quickly to achieve much", - "iKnowEnoughToBeDangerousBut": "3. I know enough to be dangerous, but I'm no expert", - "imNotUsingN8nForWork": "I'm not using n8n for work", - "individualConsumers": "Customers are individual consumers", - "smallBusinesses": "Customers are small businesses (under 20 employees)", - "mediumBusinesses": "Customers are medium businesses (20-499 employees)", - "largeBusinesses": "Customers are large businesses (500+ employees)", - "whatKindOfCustomersDoYouServe": "What kind of customers do you serve?", - "cloudInfrastructure": "Cloud infrastructure", - "itSupport": "IT support", - "networkingOrCommunication": "Networking / Communication", - "itEngineering": "IT / Engineering", - "legal": "Legal", - "lessThan20People": "Less than 20 people", - "lookOutForThingsMarked": "Look out for things marked with a ✨. They are personalized to make n8n more relevant to you.", - "marketing": "Marketing", - "media": "Media", - "manufacturing": "Manufacturing", - "managedServiceProvider": "Managed service provider", - "digitalAgencyOrConsultant": "Digital agency/consultant", - "automationAgencyOrConsultant": "Automation agency/consultant", - "neverCoded": "0. Never coded", - "operations": "Operations", - "otherPleaseSpecify": "Other (please specify)", - "other": "Other", - "people": "people", - "proCoder": "Pro coder", - "physicalRetailOrServices": "Physical retail or services", - "realEstateOrConstruction": "Real estate / Construction", - "saas": "SaaS", - "education": "Education", - "salesBizDev": "Sales / Bizdev", - "salesBusinessDevelopment": "Sales / Business Development", - "security": "Security", - "select": "Select...", - "specifyYourCompanysIndustry": "Specify your company's industry", - "specifyYourWorkArea": "Specify your work area", - "support": "Support", - "systemsIntegration": "Systems Integration", - "systemsIntegrator": "Systems Integrator", - "thanks": "Thanks!", - "telecoms": "Telecoms", - "theseQuestionsHelpUs": "These questions help us tailor n8n to you", - "whichIndustriesIsYourCompanyIn": "Which industries is your company in?", - "whatBestDescribesYourCompany": "What best describes your company?", - "whatDoesYourCompanyFocusOn": "Which services does your company focus on?", - "pleaseSpecifyYourCompanyFocus": "Please specify your company focus", - "whatAreYouLookingToAutomate": "What are you looking to automate?", - "customerIntegrations": "Customer integrations", - "customerSupport": "Customer support", - "financeOrAccounting": "Finance / Accounting", - "product": "Product (e.g. fast prototyping)", - "salesAndMarketing": "Sales and Marketing", - "notSureYet": "Not sure yet", - "specifyYourAutomationGoal": "Please specify your automation goal" - }, - "pushConnection": { - "showMessage": { - "title": "Workflow executed successfully" - } - }, - "pushConnectionTracker": { - "cannotConnectToServer": "You have a connection issue or the server is down.
n8n should reconnect automatically once the issue is resolved.", - "connectionLost": "Connection lost" - }, - "reusableBaseText": { - "cancel": "Cancel", - "name": "Name", - "save": "Save" - }, - "reusableDynamicText": { - "oauth2": { - "clientId": "Client ID", - "clientSecret": "Client Secret" - } - }, - "runData": { - "binary": "Binary", - "copyItemPath": "Copy Item Path", - "copyParameterPath": "Copy Parameter Path", - "copyToClipboard": "Copy to Clipboard", - "copyValue": "Copy Value", - "dataOfExecution": "Data of Execution", - "dataReturnedByThisNodeWillDisplayHere": "Data returned by this node will display here", - "displayDataAnyway": "Display Data Anyway", - "entriesExistButThey": "Entries exist but they do not contain any JSON data", - "executeNode": "Execute Node", - "executesThisNodeAfterExecuting": "Executes this {nodeName} node after executing any previous nodes that have not yet returned data", - "executionTime": "Execution Time", - "fileExtension": "File Extension", - "fileName": "File Name", - "items": "Items", - "json": "JSON", - "mimeType": "Mime Type", - "ms": "ms", - "noBinaryDataFound": "No binary data found", - "noData": "No data", - "noTextDataFound": "No text data found", - "nodeReturnedALargeAmountOfData": "Node returned a large amount of data", - "output": "Output", - "downloadBinaryData": "Download", - "showBinaryData": "View", - "startTime": "Start Time", - "table": "Table", - "theNodeContains": "The node contains {numberOfKb} KB of data.
Displaying it could cause problems.

If you do decide to display it, consider avoiding the JSON view." - }, - "saveButton": { - "save": "@:reusableBaseText.save", - "saved": "Saved", - "saving": "Saving" - }, - "showMessage": { - "cancel": "@:reusableBaseText.cancel", - "ok": "OK", - "showDetails": "Show Details" - }, - "tagsDropdown": { - "createTag": "Create tag \"{filter}\"", - "manageTags": "Manage tags", - "noMatchingTagsExist": "No matching tags exist", - "noTagsExist": "No tags exist", - "showError": { - "message": "A problem occurred when trying to create the '{name}' tag", - "title": "Could not create tag" - }, - "typeToCreateATag": "Type to create a tag" - }, - "tagsManager": { - "couldNotDeleteTag": "Could not delete tag", - "done": "Done", - "manageTags": "Manage tags", - "showError": { - "onCreate": { - "message": "A problem occurred when trying to create the tag '{escapedName}'", - "title": "Could not create tag" - }, - "onDelete": { - "message": "A problem occurred when trying to delete the tag '{escapedName}'", - "title": "Could not delete tag" - }, - "onUpdate": { - "message": "A problem occurred when trying to update the tag '{escapedName}'", - "title": "Could not update tag" - } - }, - "showMessage": { - "onDelete": { - "title": "Tag deleted" - }, - "onUpdate": { - "title": "Tag updated" - } - }, - "tagNameCannotBeEmpty": "Tag name cannot be empty" - }, - "tagsTable": { - "areYouSureYouWantToDeleteThisTag": "Are you sure you want to delete this tag?", - "cancel": "@:reusableBaseText.cancel", - "createTag": "Create tag", - "deleteTag": "Delete tag", - "editTag": "Edit Tag", - "name": "@:reusableBaseText.name", - "noMatchingTagsExist": "No matching tags exist", - "saveChanges": "Save changes?", - "usage": "Usage" - }, - "tagsTableHeader": { - "addNew": "Add new", - "searchTags": "Search Tags" - }, - "tagsView": { - "inUse": { - "plural": "{count} workflows", - "singular": "{count} workflow" - }, - "notBeingUsed": "Not being used" - }, - "template": { - "buttons": { - "goBackButton": "Go back", - "useThisWorkflowButton": "Use this workflow" - }, - "details": { - "appsInTheWorkflow": "Apps in this workflow", - "appsInTheCollection": "This collection features", - "by": "by", - "categories": "Categories", - "created": "Created", - "details": "Details", - "times": "times", - "viewed": "Viewed" - } - }, - "templates": { - "allCategories": "All Categories", - "categoriesHeading": "Categories", - "collection": "Collection", - "collections": "Collections", - "collectionsNotFound": "Collection could not be found", - "endResult": "Share your own useful workflows through your n8n.io account", - "heading": "Workflow templates", - "newButton": "New blank workflow", - "noSearchResults": "Nothing found. Try adjusting your search to see more.", - "searchPlaceholder": "Search workflows", - "workflow": "Workflow", - "workflows": "Workflows", - "workflowsNotFound": "Workflow could not be found", - "connectionWarning": "⚠️ There was a problem fetching workflow templates. Check your internet connection." - }, - "textEdit": { - "edit": "Edit" - }, - "timeAgo": { - "daysAgo": "%s days ago", - "hoursAgo": "%s hours ago", - "inDays": "in %s days", - "inHours": "in %s hours", - "inMinutes": "in %s minutes", - "inMonths": "in %s months", - "inOneDay": "in 1 day", - "inOneHour": "in 1 hour", - "inOneMinute": "in 1 minute", - "inOneMonth": "in 1 month", - "inOneWeek": "in 1 week", - "inOneYear": "in 1 year", - "inWeeks": "in %s weeks", - "inYears": "in %s years", - "justNow": "Just now", - "minutesAgo": "%s minutes ago", - "monthsAgo": "%s months ago", - "oneDayAgo": "1 day ago", - "oneHourAgo": "1 hour ago", - "oneMinuteAgo": "1 minute ago", - "oneMonthAgo": "1 month ago", - "oneWeekAgo": "1 week ago", - "oneYearAgo": "1 year ago", - "rightNow": "Right now", - "weeksAgo": "%s weeks ago", - "yearsAgo": "%s years ago" - }, - "updatesPanel": { - "andIs": "and is", - "behindTheLatest": "behind the latest and greatest n8n", - "howToUpdateYourN8nVersion": "How to update your n8n version", - "version": "{numberOfVersions} version{howManySuffix}", - "weVeBeenBusy": "We’ve been busy ✨", - "youReOnVersion": "You’re on {currentVersionName}, which was released" - }, - "variableSelector": { - "context": "Context", - "currentNode": "Current Node", - "nodes": "Nodes", - "outputData": "Output Data", - "parameters": "Parameters", - "variableFilter": "Variable filter..." - }, - "variableSelectorItem": { - "empty": "--- EMPTY ---", - "selectItem": "Select Item" - }, - "versionCard": { - "breakingChanges": "Breaking changes", - "released": "Released", - "securityUpdate": "Security update", - "thisVersionHasASecurityIssue": "This version has a security issue.
It is listed here for completeness.", - "unknown": "unknown", - "version": "Version" - }, - "workflowActivator": { - "activateWorkflow": "Activate workflow", - "deactivateWorkflow": "Deactivate workflow", - "showError": { - "title": "Workflow could not be {newStateName}" - }, - "showMessage": { - "activeChangedNodesIssuesExistTrue": { - "message": "Please resolve outstanding issues before you activate it", - "title": "Problem activating workflow" - }, - "activeChangedWorkflowIdUndefined": { - "message": "Please save it before activating", - "title": "Problem activating workflow" - }, - "displayActivationError": { - "message": { - "catchBlock": "Sorry there was a problem requesting the error", - "errorDataNotUndefined": "The following error occurred on workflow activation:
{message}", - "errorDataUndefined": "Unknown error" - }, - "title": "Problem activating workflow" - } - }, - "theWorkflowIsSetToBeActiveBut": "The workflow is activated but could not be started.
Click to display error message.", - "thisWorkflowHasNoTriggerNodes": "This workflow has no trigger nodes that require activation" - }, - "workflowDetails": { - "active": "Active", - "addTag": "Add tag", - "showMessage": { - "message": "Please enter a name, or press 'esc' to go back to the old one", - "title": "Name missing" - }, - "chooseOrCreateATag": "Choose or create a tag" - }, - "workflowHelpers": { - "showMessage": { - "title": "Problem saving workflow" - } - }, - "workflowOpen": { - "active": "Active", - "confirmMessage": { - "cancelButtonText": "Leave without saving", - "confirmButtonText": "Save", - "headline": "Save changes before leaving?", - "message": "If you don't save, you will lose your changes." - }, - "created": "Created", - "name": "@:reusableBaseText.name", - "openWorkflow": "Open Workflow", - "filterWorkflows": "Filter by tags", - "searchWorkflows": "Search workflows...", - "showError": { - "title": "Problem loading workflows" - }, - "showMessage": { - "message": "This is the current workflow", - "title": "Workflow already open" - }, - "updated": "Updated", - "couldNotLoadActiveWorkflows": "Could not load active workflows" - }, - "workflowRun": { - "noActiveConnectionToTheServer": "Lost connection to the server", - "showError": { - "title": "Problem running workflow" - }, - "showMessage": { - "message": "Please fix them before executing", - "title": "Workflow has issues" - } - }, - "workflowSettings": { - "defaultTimezone": "Default - {defaultTimezoneValue}", - "defaultTimezoneNotValid": "Default Timezone not valid", - "errorWorkflow": "Error Workflow", - "helpTexts": { - "errorWorkflow": "A second workflow to run if the current one fails.
The second workflow should an 'Error Trigger' node.", - "executionTimeout": "How long the workflow should wait before timing out", - "executionTimeoutToggle": "Whether to cancel workflow execution after a defined time", - "saveDataErrorExecution": "Whether to save data of executions that fail", - "saveDataSuccessExecution": "Whether to save data of executions that finish successfully", - "saveExecutionProgress": "Whether to save data after each node execution. This allows you to resume from where execution stopped if there is an error, but may increase latency.", - "saveManualExecutions": "Whether to save data of executions that are started manually from the editor", - "timezone": "The timezone in which the workflow should run. Used by 'cron' node, for example." - }, - "hours": "hours", - "minutes": "minutes", - "noWorkflow": "- No Workflow -", - "save": "@:reusableBaseText.save", - "saveDataErrorExecution": "Save failed executions", - "saveDataErrorExecutionOptions": { - "defaultSave": "Default - {defaultValue}", - "doNotSave": "Do not save", - "save": "@:reusableBaseText.save" - }, - "saveDataSuccessExecution": "Save successful executions", - "saveDataSuccessExecutionOptions": { - "defaultSave": "Default - {defaultValue}", - "doNotSave": "Do not save", - "save": "@:reusableBaseText.save" - }, - "saveExecutionProgress": "Save execution progress", - "saveExecutionProgressOptions": { - "defaultSave": "Default - {defaultValue}", - "no": "No", - "yes": "Yes" - }, - "saveManualExecutions": "Save manual executions", - "saveManualOptions": { - "defaultSave": "Default - {defaultValue}", - "no": "No", - "yes": "Yes" - }, - "seconds": "seconds", - "selectOption": "Select Option", - "settingsFor": "Settings for {workflowName} (#{workflowId})", - "showError": { - "saveSettings1": { - "errorMessage": "Timeout is activated but set to 0", - "message": "There was a problem saving the settings", - "title": "Problem saving settings" - }, - "saveSettings2": { - "errorMessage": "Maximum Timeout is: {hours} hours, {minutes} minutes, {seconds} seconds", - "message": "The timeout is longer than allowed", - "title": "Problem saving settings" - }, - "saveSettings3": { - "title": "Problem saving settings" - } - }, - "showMessage": { - "saveSettings": { - "title": "Workflow settings saved" - } - }, - "timeoutAfter": "Timeout After", - "timeoutWorkflow": "Timeout Workflow", - "timezone": "Timezone" - }, - "activationModal": { - "workflowActivated": "Workflow activated", - "theseExecutionsWillNotShowUp": "These executions will not show up immediately in the editor,", - "butYouCanSeeThem": "but you can see them in the", - "executionList": "execution list", - "ifYouChooseTo": "if you choose to", - "saveExecutions": "save executions.", - "dontShowAgain": "Don't show again", - "yourTriggersWillNowFire": "Your triggers will now fire production executions automatically.", - "yourTriggerWillNowFire": "Your trigger will now fire production executions automatically.", - "yourWorkflowWillNowRegularlyCheck": "Your workflow will now regularly check {serviceName} for events and trigger executions for them.", - "yourWorkflowWillNowListenForEvents": "Your workflow will now listen for events from {serviceName} and trigger executions.", - "gotIt": "Got it" - }, - "workflowPreview": { - "showError": { - "previewError": { - "message": "Unable to preview workflow", - "title": "Preview error" - }, - "missingWorkflow": "Missing workflow", - "arrayEmpty": "Must have an array of nodes" - } - }, + "reusableBaseText.cancel": "Cancel", + "reusableBaseText.name": "Name", + "reusableBaseText.save": "Save", + "reusableDynamicText.oauth2.clientId": "Client ID", + "reusableDynamicText.oauth2.clientSecret": "Client Secret", + "about.aboutN8n": "About n8n", + "about.close": "Close", + "about.license": "License", + "about.n8nVersion": "n8n Version", + "about.n8nLicense": "Sustainable Use License", + "about.sourceCode": "Source Code", + "binaryDataDisplay.backToList": "Back to list", + "binaryDataDisplay.backToOverviewPage": "Back to overview page", + "binaryDataDisplay.noDataFoundToDisplay": "No data found to display", + "binaryDataDisplay.yourBrowserDoesNotSupport": "Your browser does not support the video element. Kindly update it to latest version.", + "codeEdit.edit": "Edit", + "collectionParameter.choose": "Choose...", + "collectionParameter.noProperties": "No properties", + "credentialEdit.credentialConfig.accountConnected": "Account connected", + "credentialEdit.credentialConfig.clickToCopy": "Click To Copy", + "credentialEdit.credentialConfig.connectionTestedSuccessfully": "Connection tested successfully", + "credentialEdit.credentialConfig.couldntConnectWithTheseSettings": "Couldn’t connect with these settings", + "credentialEdit.credentialConfig.needHelpFillingOutTheseFields": "Need help filling out these fields?", + "credentialEdit.credentialConfig.oAuthRedirectUrl": "OAuth Redirect URL", + "credentialEdit.credentialConfig.openDocs": "Open docs", + "credentialEdit.credentialConfig.pleaseCheckTheErrorsBelow": "Please check the errors below", + "credentialEdit.credentialConfig.reconnect": "reconnect", + "credentialEdit.credentialConfig.reconnectOAuth2Credential": "Reconnect OAuth2 Credential", + "credentialEdit.credentialConfig.redirectUrlCopiedToClipboard": "Redirect URL copied to clipboard", + "credentialEdit.credentialConfig.retry": "Retry", + "credentialEdit.credentialConfig.retryCredentialTest": "Retry credential test", + "credentialEdit.credentialConfig.retrying": "Retrying", + "credentialEdit.credentialConfig.subtitle": "In {appName}, use the URL above when prompted to enter an OAuth callback or redirect URL", + "credentialEdit.credentialConfig.theServiceYouReConnectingTo": "the service you're connecting to", + "credentialEdit.credentialEdit.confirmMessage.beforeClose1.cancelButtonText": "Keep Editing", + "credentialEdit.credentialEdit.confirmMessage.beforeClose1.confirmButtonText": "Close", + "credentialEdit.credentialEdit.confirmMessage.beforeClose1.headline": "Close without saving?", + "credentialEdit.credentialEdit.confirmMessage.beforeClose1.message": "Are you sure you want to throw away the changes you made to the {credentialDisplayName} credential?", + "credentialEdit.credentialEdit.confirmMessage.beforeClose2.cancelButtonText": "Keep Editing", + "credentialEdit.credentialEdit.confirmMessage.beforeClose2.confirmButtonText": "Close", + "credentialEdit.credentialEdit.confirmMessage.beforeClose2.headline": "Close without connecting?", + "credentialEdit.credentialEdit.confirmMessage.beforeClose2.message": "You need to connect your credential for it to work", + "credentialEdit.credentialEdit.confirmMessage.deleteCredential.cancelButtonText": "", + "credentialEdit.credentialEdit.confirmMessage.deleteCredential.confirmButtonText": "Yes, delete", + "credentialEdit.credentialEdit.confirmMessage.deleteCredential.headline": "Delete Credential?", + "credentialEdit.credentialEdit.confirmMessage.deleteCredential.message": "Are you sure you want to delete \"{savedCredentialName}\"?", + "credentialEdit.credentialEdit.connection": "Connection", + "credentialEdit.credentialEdit.couldNotFindCredentialOfType": "Could not find credential of type", + "credentialEdit.credentialEdit.couldNotFindCredentialWithId": "Could not find credential with ID", + "credentialEdit.credentialEdit.details": "Details", + "credentialEdit.credentialEdit.delete": "Delete", + "credentialEdit.credentialEdit.saving": "Saving", + "credentialEdit.credentialEdit.showError.createCredential.title": "Problem creating credential", + "credentialEdit.credentialEdit.showError.deleteCredential.title": "Problem deleting credential", + "credentialEdit.credentialEdit.showError.generateAuthorizationUrl.message": "There was a problem generating the authorization URL", + "credentialEdit.credentialEdit.showError.generateAuthorizationUrl.title": "OAuth Authorization Error", + "credentialEdit.credentialEdit.showError.loadCredential.title": "Problem loading credential", + "credentialEdit.credentialEdit.showError.updateCredential.title": "Problem updating credential", + "credentialEdit.credentialEdit.showMessage.title": "Credential deleted", + "credentialEdit.credentialEdit.testing": "Testing", + "credentialEdit.credentialInfo.allowUseBy": "Allow use by", + "credentialEdit.credentialInfo.created": "Created", + "credentialEdit.credentialInfo.id": "ID", + "credentialEdit.credentialInfo.lastModified": "Last modified", + "credentialEdit.oAuthButton.connectMyAccount": "Connect my account", + "credentialEdit.oAuthButton.signInWithGoogle": "Sign in with Google", + "credentialSelectModal.addNewCredential": "Add new credential", + "credentialSelectModal.continue": "Continue", + "credentialSelectModal.searchForApp": "Search for app...", + "credentialSelectModal.selectAnAppOrServiceToConnectTo": "Select an app or service to connect to", + "credentialsList.addNew": "Add New", + "credentialsList.confirmMessage.cancelButtonText": "", + "credentialsList.confirmMessage.confirmButtonText": "Yes, delete", + "credentialsList.confirmMessage.headline": "Delete Credential?", + "credentialsList.confirmMessage.message": "Are you sure you want to delete {credentialName}?", + "credentialsList.createNewCredential": "Create New Credential", + "credentialsList.created": "Created", + "credentialsList.credentials": "Credentials", + "credentialsList.deleteCredential": "Delete Credential", + "credentialsList.editCredential": "Edit Credential", + "credentialsList.name": "@:reusableBaseText.name", + "credentialsList.operations": "Operations", + "credentialsList.showError.deleteCredential.title": "Problem deleting credential", + "credentialsList.showMessage.title": "Credential deleted", + "credentialsList.type": "Type", + "credentialsList.updated": "Updated", + "credentialsList.yourSavedCredentials": "Your saved credentials", + "credentialsList.errorLoadingCredentials": "Error loading credentials", + "dataDisplay.needHelp": "Need help?", + "dataDisplay.nodeDocumentation": "Node Documentation", + "dataDisplay.openDocumentationFor": "Open {nodeTypeDisplayName} documentation", + "displayWithChange.cancelEdit": "Cancel Edit", + "displayWithChange.clickToChange": "Click to Change", + "displayWithChange.setValue": "Set Value", + "duplicateWorkflowDialog.cancel": "@:reusableBaseText.cancel", + "duplicateWorkflowDialog.chooseOrCreateATag": "Choose or create a tag", + "duplicateWorkflowDialog.duplicateWorkflow": "Duplicate Workflow", + "duplicateWorkflowDialog.enterWorkflowName": "Enter workflow name", + "duplicateWorkflowDialog.save": "@:reusableBaseText.save", + "duplicateWorkflowDialog.showMessage.message": "Please enter a name.", + "duplicateWorkflowDialog.showMessage.title": "Name missing", + "executionDetails.executionFailed": "Execution failed", + "executionDetails.executionId": "Execution ID", + "executionDetails.executionWaiting": "Execution waiting", + "executionDetails.executionWasSuccessful": "Execution was successful", + "executionDetails.openWorkflow": "Open Workflow", + "executionDetails.of": "of", + "executionDetails.workflow": "workflow", + "executionDetails.readOnly.readOnly": "Read only", + "executionDetails.readOnly.youreViewingTheLogOf": "You're viewing the log of a previous execution. You cannot
\n\t\tmake changes since this execution already occurred. Make changes
\n\t\tto this workflow by clicking on its name on the left.", + "executionsList.allWorkflows": "All Workflows", + "executionsList.anyStatus": "Any Status", + "executionsList.autoRefresh": "Auto refresh", + "executionsList.confirmMessage.cancelButtonText": "", + "executionsList.confirmMessage.confirmButtonText": "Yes, delete", + "executionsList.confirmMessage.headline": "Delete Executions?", + "executionsList.confirmMessage.message": "Are you sure that you want to delete the {numSelected} selected execution(s)?", + "executionsList.deleteSelected": "Delete Selected", + "executionsList.error": "Error", + "executionsList.filters": "Filters", + "executionsList.loadMore": "Load More", + "executionsList.mode": "Mode", + "executionsList.modes.error": "error", + "executionsList.modes.integrated": "integrated", + "executionsList.modes.manual": "manual", + "executionsList.modes.retry": "retry", + "executionsList.modes.trigger": "trigger", + "executionsList.modes.webhook": "webhook", + "executionsList.name": "@:reusableBaseText.name", + "executionsList.openPastExecution": "Open Past Execution", + "executionsList.retryExecution": "Retry execution", + "executionsList.retryOf": "Retry of", + "executionsList.retryWithCurrentlySavedWorkflow": "Retry with currently saved workflow", + "executionsList.retryWithOriginalworkflow": "Retry with original workflow", + "executionsList.running": "Running", + "executionsList.runningTime": "Running Time", + "executionsList.selectStatus": "Select Status", + "executionsList.selectWorkflow": "Select Workflow", + "executionsList.selected": "Selected", + "executionsList.showError.handleDeleteSelected.title": "Problem deleting executions", + "executionsList.showError.loadMore.title": "Problem loading executions", + "executionsList.showError.loadWorkflows.title": "Problem loading workflows", + "executionsList.showError.refreshData.title": "Problem loading data", + "executionsList.showError.retryExecution.title": "Problem with retry", + "executionsList.showError.stopExecution.title": "Problem stopping execution", + "executionsList.showMessage.handleDeleteSelected.title": "Execution deleted", + "executionsList.showMessage.retrySuccessfulFalse.title": "Retry unsuccessful", + "executionsList.showMessage.retrySuccessfulTrue.title": "Retry successful", + "executionsList.showMessage.stopExecution.message": "Execution ID {activeExecutionId}", + "executionsList.showMessage.stopExecution.title": "Execution stopped", + "executionsList.startedAtId": "Started At / ID", + "executionsList.status": "Status", + "executionsList.statusTooltipText.theWorkflowExecutionFailed": "The workflow execution failed.", + "executionsList.statusTooltipText.theWorkflowExecutionFailedButTheRetryWasSuccessful": "The workflow execution failed but the retry {entryRetrySuccessId} was successful.", + "executionsList.statusTooltipText.theWorkflowExecutionIsProbablyStillRunning": "The workflow execution is probably still running but it may have crashed and n8n cannot safely tell. ", + "executionsList.statusTooltipText.theWorkflowExecutionWasARetryOfAndFailed": "The workflow execution was a retry of {entryRetryOf} and failed.
New retries have to be started from the original execution.", + "executionsList.statusTooltipText.theWorkflowExecutionWasARetryOfAndItWasSuccessful": "The workflow execution was a retry of {entryRetryOf} and it was successful.", + "executionsList.statusTooltipText.theWorkflowExecutionWasSuccessful": "The worklow execution was successful.", + "executionsList.statusTooltipText.theWorkflowIsCurrentlyExecuting": "The worklow is currently executing.", + "executionsList.statusTooltipText.theWorkflowIsWaitingIndefinitely": "The workflow is waiting indefinitely for an incoming webhook call.", + "executionsList.statusTooltipText.theWorkflowIsWaitingTill": "The worklow is waiting till {waitDateDate} {waitDateTime}.", + "executionsList.stopExecution": "Stop Execution", + "executionsList.success": "Success", + "executionsList.successRetry": "Success retry", + "executionsList.unknown": "Unknown", + "executionsList.unsavedWorkflow": "[UNSAVED WORKFLOW]", + "executionsList.waiting": "Waiting", + "executionsList.workflowExecutions": "Workflow Executions", + "expressionEdit.editExpression": "Edit Expression", + "expressionEdit.expression": "Expression", + "expressionEdit.result": "Result", + "expressionEdit.variableSelector": "Variable Selector", + "fixedCollectionParameter.choose": "Choose...", + "fixedCollectionParameter.currentlyNoItemsExist": "Currently no items exist", + "fixedCollectionParameter.deleteItem": "Delete item", + "fixedCollectionParameter.moveDown": "Move down", + "fixedCollectionParameter.moveUp": "Move up", + "genericHelpers.loading": "Loading", + "genericHelpers.min": "min", + "genericHelpers.sec": "sec", + "genericHelpers.showMessage.message": "This is a read-only version of the workflow. To make changes, either open the original workflow or save it under a new name.", + "genericHelpers.showMessage.title": "Workflow cannot be changed", + "mainSidebar.aboutN8n": "About n8n", + "mainSidebar.confirmMessage.workflowDelete.cancelButtonText": "", + "mainSidebar.confirmMessage.workflowDelete.confirmButtonText": "Yes, delete", + "mainSidebar.confirmMessage.workflowDelete.headline": "Delete Workflow?", + "mainSidebar.confirmMessage.workflowDelete.message": "Are you sure that you want to delete '{workflowName}'?", + "mainSidebar.confirmMessage.workflowNew.cancelButtonText": "Leave without saving", + "mainSidebar.confirmMessage.workflowNew.confirmButtonText": "Save", + "mainSidebar.confirmMessage.workflowNew.headline": "Save changes before leaving?", + "mainSidebar.confirmMessage.workflowNew.message": "If you don't save, you will lose your changes.", + "mainSidebar.credentials": "Credentials", + "mainSidebar.delete": "Delete", + "mainSidebar.download": "Download", + "mainSidebar.duplicate": "Duplicate", + "mainSidebar.executions": "Executions", + "mainSidebar.help": "Help", + "mainSidebar.helpMenuItems.course": "Course", + "mainSidebar.helpMenuItems.documentation": "Documentation", + "mainSidebar.helpMenuItems.forum": "Forum", + "mainSidebar.importFromFile": "Import from File", + "mainSidebar.importFromUrl": "Import from URL", + "mainSidebar.new": "New", + "mainSidebar.newTemplate": "New from template", + "mainSidebar.open": "Open", + "mainSidebar.prompt.cancel": "@:reusableBaseText.cancel", + "mainSidebar.prompt.import": "Import", + "mainSidebar.prompt.importWorkflowFromUrl": "Import Workflow from URL", + "mainSidebar.prompt.invalidUrl": "Invalid URL", + "mainSidebar.prompt.workflowUrl": "Workflow URL", + "mainSidebar.save": "@:reusableBaseText.save", + "mainSidebar.settings": "Settings", + "mainSidebar.showError.stopExecution.title": "Problem stopping execution", + "mainSidebar.showMessage.handleFileImport.message": "The file does not contain valid JSON data", + "mainSidebar.showMessage.handleFileImport.title": "Could not import file", + "mainSidebar.showMessage.handleSelect1.title": "Workflow deleted", + "mainSidebar.showMessage.handleSelect2.title": "Workflow created", + "mainSidebar.showMessage.handleSelect3.title": "Workflow created", + "mainSidebar.showMessage.stopExecution.title": "Execution stopped", + "mainSidebar.templates": "Templates", + "mainSidebar.workflows": "Workflows", + "multipleParameter.addItem": "Add item", + "multipleParameter.currentlyNoItemsExist": "Currently no items exist", + "multipleParameter.deleteItem": "Delete item", + "multipleParameter.moveDown": "Move down", + "multipleParameter.moveUp": "Move up", + "noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?", + "noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows", + "node.activateDeactivateNode": "Activate/Deactivate Node", + "node.deleteNode": "Delete Node", + "node.disabled": "Disabled", + "node.duplicateNode": "Duplicate Node", + "node.editNode": "Edit Node", + "node.executeNode": "Execute Node", + "node.issues": "Issues", + "node.nodeIsExecuting": "Node is executing", + "node.nodeIsWaitingTill": "Node is waiting until {date} {time}", + "node.theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall": "The node is waiting for an incoming webhook call (indefinitely)", + "node.waitingForYouToCreateAnEventIn": "Waiting for you to create an event in {nodeType}", + "nodeCreator.categoryNames.analytics": "Analytics", + "nodeCreator.categoryNames.communication": "Communication", + "nodeCreator.categoryNames.coreNodes": "Core Nodes", + "nodeCreator.categoryNames.customNodes": "Custom Nodes", + "nodeCreator.categoryNames.dataStorage": "Data & Storage", + "nodeCreator.categoryNames.development": "Development", + "nodeCreator.categoryNames.financeAccounting": "Finance & Accounting", + "nodeCreator.categoryNames.marketingContent": "Marketing & Content", + "nodeCreator.categoryNames.miscellaneous": "Miscellaneous", + "nodeCreator.categoryNames.productivity": "Productivity", + "nodeCreator.categoryNames.sales": "Sales", + "nodeCreator.categoryNames.suggestedNodes": "Suggested Nodes ✨", + "nodeCreator.categoryNames.utility": "Utility", + "nodeCreator.mainPanel.all": "All", + "nodeCreator.mainPanel.regular": "Regular", + "nodeCreator.mainPanel.trigger": "Trigger", + "nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe": "Don’t worry, you can probably do it with the", + "nodeCreator.noResults.httpRequest": "HTTP Request", + "nodeCreator.noResults.node": "node", + "nodeCreator.noResults.or": "or", + "nodeCreator.noResults.requestTheNode": "Request the node", + "nodeCreator.noResults.wantUsToMakeItFaster": "Want us to make it faster?", + "nodeCreator.noResults.weDidntMakeThatYet": "We didn't make that... yet", + "nodeCreator.noResults.webhook": "Webhook", + "nodeCreator.searchBar.searchNodes": "Search nodes...", + "nodeCreator.subcategoryDescriptions.dataTransformation": "Manipulate data fields, run code", + "nodeCreator.subcategoryDescriptions.files": "Work with CSV, XML, text, images etc.", + "nodeCreator.subcategoryDescriptions.flow": "Branches, core triggers, merge data", + "nodeCreator.subcategoryDescriptions.helpers": "HTTP Requests (API calls), date and time, scrape HTML", + "nodeCreator.subcategoryNames.dataTransformation": "Data Transformation", + "nodeCreator.subcategoryNames.files": "Files", + "nodeCreator.subcategoryNames.flow": "Flow", + "nodeCreator.subcategoryNames.helpers": "Helpers", + "nodeCredentials.createNew": "Create New", + "nodeCredentials.credentialFor": "Credential for {credentialType}", + "nodeCredentials.issues": "Issues", + "nodeCredentials.selectCredential": "Select Credential", + "nodeCredentials.showMessage.message": "Nodes that used credential \"{oldCredentialName}\" have been updated to use \"{newCredentialName}\"", + "nodeCredentials.showMessage.title": "Node credential updated", + "nodeCredentials.updateCredential": "Update Credential", + "nodeErrorView.cause": "Cause", + "nodeErrorView.copyToClipboard": "Copy to Clipboard", + "nodeErrorView.dataBelowMayContain": "Data below may contain sensitive information. Proceed with caution when sharing.", + "nodeErrorView.details": "Details", + "nodeErrorView.error": "ERROR", + "nodeErrorView.httpCode": "HTTP Code", + "nodeErrorView.showMessage.title": "Copied to clipboard", + "nodeErrorView.stack": "Stack", + "nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed", + "nodeErrorView.time": "Time", + "nodeBase.clickToAddNodeOrDragToConnect": "Click to add node
or drag to connect", + "nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.", + "nodeSettings.alwaysOutputData.displayName": "Always Output Data", + "nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io", + "nodeSettings.continueOnFail.description": "If active, the workflow continues even if this node's execution fails. When this occurs, the node passes along input data from previous nodes - so your workflow should account for unexpected output data.", + "nodeSettings.continueOnFail.displayName": "Continue On Fail", + "nodeSettings.executeOnce.description": "If active, the node executes only once, with data from the first item it receives", + "nodeSettings.executeOnce.displayName": "Execute Once", + "nodeSettings.maxTries.description": "Number of times to attempt to execute the node before failing the execution", + "nodeSettings.maxTries.displayName": "Max. Tries", + "nodeSettings.noDescriptionFound": "No description found", + "nodeSettings.nodeDescription": "Node Description", + "nodeSettings.notes.description": "Optional note to save with the node", + "nodeSettings.notes.displayName": "Notes", + "nodeSettings.notesInFlow.description": "If active, the note above will display in the flow as a subtitle", + "nodeSettings.notesInFlow.displayName": "Display note in flow?", + "nodeSettings.parameters": "Parameters", + "nodeSettings.retryOnFail.description": "If active, the node tries to execute again when it fails", + "nodeSettings.retryOnFail.displayName": "Retry On Fail", + "nodeSettings.settings": "Settings", + "nodeSettings.theNodeIsNotValidAsItsTypeIsUnknown": "The node is not valid as its type ({nodeType}) is unknown", + "nodeSettings.thisNodeDoesNotHaveAnyParameters": "This node does not have any parameters", + "nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)", + "nodeSettings.waitBetweenTries.displayName": "Wait Between Tries (ms)", + "nodeView.addNode": "Add node", + "nodeView.confirmMessage.beforeRouteLeave.cancelButtonText": "Leave without saving", + "nodeView.confirmMessage.beforeRouteLeave.confirmButtonText": "Save", + "nodeView.confirmMessage.beforeRouteLeave.headline": "Save changes before leaving?", + "nodeView.confirmMessage.beforeRouteLeave.message": "If you don't save, you will lose your changes.", + "nodeView.confirmMessage.initView.cancelButtonText": "Leave without saving", + "nodeView.confirmMessage.initView.confirmButtonText": "Save", + "nodeView.confirmMessage.initView.headline": "Save changes before leaving?", + "nodeView.confirmMessage.initView.message": "If you don't save, you will lose your changes.", + "nodeView.confirmMessage.receivedCopyPasteData.cancelButtonText": "", + "nodeView.confirmMessage.receivedCopyPasteData.confirmButtonText": "Yes, import", + "nodeView.confirmMessage.receivedCopyPasteData.headline": "Import Workflow?", + "nodeView.confirmMessage.receivedCopyPasteData.message": "Workflow will be imported from
{plainTextData}", + "nodeView.couldntImportWorkflow": "Could not import workflow", + "nodeView.deletesTheCurrentExecutionData": "Deletes the current execution data", + "nodeView.executesTheWorkflowFromTheStartOrWebhookNode": "Executes the workflow from the 'start' or 'webhook' node", + "nodeView.itLooksLikeYouHaveBeenEditingSomething": "It looks like you made some edits. If you leave before saving, your changes will be lost.", + "nodeView.loadingTemplate": "Loading template", + "nodeView.moreInfo": "More info", + "nodeView.noNodesGivenToAdd": "No nodes to add specified", + "nodeView.prompt.cancel": "@:reusableBaseText.cancel", + "nodeView.prompt.invalidName": "Invalid Name", + "nodeView.prompt.newName": "New Name", + "nodeView.prompt.rename": "Rename", + "nodeView.prompt.renameNode": "Rename Node", + "nodeView.redirecting": "Redirecting", + "nodeView.refresh": "Refresh", + "nodeView.resetZoom": "Reset Zoom", + "nodeView.runButtonText.executeWorkflow": "Execute Workflow", + "nodeView.runButtonText.executingWorkflow": "Executing Workflow", + "nodeView.runButtonText.waitingForTriggerEvent": "Waiting for Trigger Event", + "nodeView.showError.getWorkflowDataFromUrl.title": "Problem loading workflow", + "nodeView.showError.importWorkflowData.title": "Problem importing workflow", + "nodeView.showError.mounted1.message": "There was a problem loading init data", + "nodeView.showError.mounted1.title": "Init Problem", + "nodeView.showError.mounted2.message": "There was a problem initializing the workflow", + "nodeView.showError.mounted2.title": "Init Problem", + "nodeView.showError.openExecution.title": "Problem loading execution", + "nodeView.showError.openWorkflow.title": "Problem opening workflow", + "nodeView.showError.stopExecution.title": "Problem stopping execution", + "nodeView.showError.stopWaitingForWebhook.title": "Problem deleting test webhook", + "nodeView.showMessage.addNodeButton.message": "'{nodeTypeName}' is an unknown node type", + "nodeView.showMessage.addNodeButton.title": "Could not create node", + "nodeView.showMessage.keyDown.title": "Workflow created", + "nodeView.showMessage.showMaxNodeTypeError.message.plural": "Only {maxNodes} '{nodeTypeDataDisplayName}' nodes are allowed in a workflow", + "nodeView.showMessage.showMaxNodeTypeError.message.singular": "Only {maxNodes} '{nodeTypeDataDisplayName}' node is allowed in a workflow", + "nodeView.showMessage.showMaxNodeTypeError.title": "Could not create node", + "nodeView.showMessage.stopExecutionCatch.message": "It completed before it could be stopped", + "nodeView.showMessage.stopExecutionCatch.title": "Workflow finished executing", + "nodeView.showMessage.stopExecutionTry.title": "Execution stopped", + "nodeView.showMessage.stopWaitingForWebhook.title": "Webhook deleted", + "nodeView.stopCurrentExecution": "Stop current execution", + "nodeView.stopWaitingForWebhookCall": "Stop waiting for webhook call", + "nodeView.stoppingCurrentExecution": "Stopping current execution", + "nodeView.thereWasAProblemLoadingTheNodeParametersOfNode": "There was a problem loading the parameters of the node", + "nodeView.thisExecutionHasntFinishedYet": "This execution hasn't finished yet", + "nodeView.toSeeTheLatestStatus": "to see the latest status", + "nodeView.workflowTemplateWithIdCouldNotBeFound": "Workflow template with ID \"{templateId}\" could not be found", + "nodeView.workflowWithIdCouldNotBeFound": "Workflow with ID \"{workflowId}\" could not be found", + "nodeView.zoomIn": "Zoom In", + "nodeView.zoomOut": "Zoom Out", + "nodeView.zoomToFit": "Zoom to Fit", + "nodeWebhooks.clickToCopyWebhookUrls": "Click to copy webhook URLs", + "nodeWebhooks.clickToDisplayWebhookUrls": "Click to display webhook URLs", + "nodeWebhooks.clickToHideWebhookUrls": "Click to hide webhook URLs", + "nodeWebhooks.invalidExpression": "[INVALID EXPRESSION]", + "nodeWebhooks.productionUrl": "Production URL", + "nodeWebhooks.showMessage.title": "URL copied", + "nodeWebhooks.testUrl": "Test URL", + "nodeWebhooks.webhookUrls": "Webhook URLs", + "parameterInput.addExpression": "Add Expression", + "parameterInput.error": "ERROR", + "parameterInput.issues": "Issues", + "parameterInput.loadingOptions": "Loading options...", + "parameterInput.openEditWindow": "Open Edit Window", + "parameterInput.parameter": "Parameter: \"{shortPath}\"", + "parameterInput.parameterHasExpression": "Parameter: \"{shortPath}\" has an expression", + "parameterInput.parameterHasIssues": "Parameter: \"{shortPath}\" has issues", + "parameterInput.parameterHasIssuesAndExpression": "Parameter: \"{shortPath}\" has issues and an expression", + "parameterInput.parameterOptions": "Parameter Options", + "parameterInput.refreshList": "Refresh List", + "parameterInput.removeExpression": "Remove Expression", + "parameterInput.resetValue": "Reset Value", + "parameterInput.selectDateAndTime": "Select date and time", + "parameterInput.select": "Select", + "parameterInputExpanded.openDocs": "Open docs", + "parameterInputExpanded.thisFieldIsRequired": "This field is required", + "parameterInputList.delete": "Delete", + "parameterInputList.deleteParameter": "Delete Parameter", + "parameterInputList.parameterOptions": "Parameter Options", + "personalizationModal.automationConsulting": "Automation consulting", + "personalizationModal.continue": "Continue", + "personalizationModal.customizeN8n": "Customize n8n to you", + "personalizationModal.eCommerce": "eCommerce", + "personalizationModal.errorWhileSubmittingResults": "Error while submitting results", + "personalizationModal.executiveTeam": "Executive team", + "personalizationModal.financeOrInsurance": "Finance / Insurance", + "personalizationModal.getStarted": "Get started", + "personalizationModal.government": "Government", + "personalizationModal.healthcare": "Healthcare", + "personalizationModal.howAreYourCodingSkills": "How are your coding skills?", + "personalizationModal.howBigIsYourCompany": "How big is your company?", + "personalizationModal.hr": "HR", + "personalizationModal.it": "IT", + "personalizationModal.iCanCodeSomeUsefulThingsBut": "2. I can code some useful things, but I spend a lot of time stuck", + "personalizationModal.iCanDoAlmostAnythingIWant": "5. I can do almost anything I want, easily (pro coder)", + "personalizationModal.iCanFigureMostThingsOut": "4. I can figure most things out", + "personalizationModal.iGetStuckTooQuicklyToAchieveMuch": "1. I get stuck too quickly to achieve much", + "personalizationModal.iKnowEnoughToBeDangerousBut": "3. I know enough to be dangerous, but I'm no expert", + "personalizationModal.imNotUsingN8nForWork": "I'm not using n8n for work", + "personalizationModal.individualConsumers": "Customers are individual consumers", + "personalizationModal.smallBusinesses": "Customers are small businesses (under 20 employees)", + "personalizationModal.mediumBusinesses": "Customers are medium businesses (20-499 employees)", + "personalizationModal.largeBusinesses": "Customers are large businesses (500+ employees)", + "personalizationModal.whatKindOfCustomersDoYouServe": "What kind of customers do you serve?", + "personalizationModal.cloudInfrastructure": "Cloud infrastructure", + "personalizationModal.itSupport": "IT support", + "personalizationModal.networkingOrCommunication": "Networking / Communication", + "personalizationModal.itEngineering": "IT / Engineering", + "personalizationModal.legal": "Legal", + "personalizationModal.lessThan20People": "Less than 20 people", + "personalizationModal.lookOutForThingsMarked": "Look out for things marked with a ✨. They are personalized to make n8n more relevant to you.", + "personalizationModal.marketing": "Marketing", + "personalizationModal.media": "Media", + "personalizationModal.manufacturing": "Manufacturing", + "personalizationModal.managedServiceProvider": "Managed service provider", + "personalizationModal.digitalAgencyOrConsultant": "Digital agency/consultant", + "personalizationModal.automationAgencyOrConsultant": "Automation agency/consultant", + "personalizationModal.neverCoded": "0. Never coded", + "personalizationModal.operations": "Operations", + "personalizationModal.otherPleaseSpecify": "Other (please specify)", + "personalizationModal.other": "Other", + "personalizationModal.people": "people", + "personalizationModal.proCoder": "Pro coder", + "personalizationModal.physicalRetailOrServices": "Physical retail or services", + "personalizationModal.realEstateOrConstruction": "Real estate / Construction", + "personalizationModal.saas": "SaaS", + "personalizationModal.education": "Education", + "personalizationModal.salesBizDev": "Sales / Bizdev", + "personalizationModal.salesBusinessDevelopment": "Sales / Business Development", + "personalizationModal.security": "Security", + "personalizationModal.select": "Select...", + "personalizationModal.specifyYourCompanysIndustry": "Specify your company's industry", + "personalizationModal.specifyYourWorkArea": "Specify your work area", + "personalizationModal.support": "Support", + "personalizationModal.systemsIntegration": "Systems Integration", + "personalizationModal.systemsIntegrator": "Systems Integrator", + "personalizationModal.thanks": "Thanks!", + "personalizationModal.telecoms": "Telecoms", + "personalizationModal.theseQuestionsHelpUs": "These questions help us tailor n8n to you", + "personalizationModal.whichIndustriesIsYourCompanyIn": "Which industries is your company in?", + "personalizationModal.whatBestDescribesYourCompany": "What best describes your company?", + "personalizationModal.whatDoesYourCompanyFocusOn": "Which services does your company focus on?", + "personalizationModal.pleaseSpecifyYourCompanyFocus": "Please specify your company focus", + "personalizationModal.whatAreYouLookingToAutomate": "What are you looking to automate?", + "personalizationModal.customerIntegrations": "Customer integrations", + "personalizationModal.customerSupport": "Customer support", + "personalizationModal.financeOrAccounting": "Finance / Accounting", + "personalizationModal.product": "Product (e.g. fast prototyping)", + "personalizationModal.salesAndMarketing": "Sales and Marketing", + "personalizationModal.notSureYet": "Not sure yet", + "personalizationModal.specifyYourAutomationGoal": "Please specify your automation goal", + "pushConnection.showMessage.title": "Workflow executed successfully", + "pushConnectionTracker.cannotConnectToServer": "You have a connection issue or the server is down.
n8n should reconnect automatically once the issue is resolved.", + "pushConnectionTracker.connectionLost": "Connection lost", + "runData.binary": "Binary", + "runData.copyItemPath": "Copy Item Path", + "runData.copyParameterPath": "Copy Parameter Path", + "runData.copyToClipboard": "Copy to Clipboard", + "runData.copyValue": "Copy Value", + "runData.dataOfExecution": "Data of Execution", + "runData.dataReturnedByThisNodeWillDisplayHere": "Data returned by this node will display here", + "runData.displayDataAnyway": "Display Data Anyway", + "runData.entriesExistButThey": "Entries exist but they do not contain any JSON data", + "runData.executeNode": "Execute Node", + "runData.executesThisNodeAfterExecuting": "Executes this {nodeName} node after executing any previous nodes that have not yet returned data", + "runData.executionTime": "Execution Time", + "runData.fileExtension": "File Extension", + "runData.fileName": "File Name", + "runData.items": "Items", + "runData.json": "JSON", + "runData.mimeType": "Mime Type", + "runData.ms": "ms", + "runData.noBinaryDataFound": "No binary data found", + "runData.noData": "No data", + "runData.noTextDataFound": "No text data found", + "runData.nodeReturnedALargeAmountOfData": "Node returned a large amount of data", + "runData.output": "Output", + "runData.downloadBinaryData": "Download", + "runData.showBinaryData": "View", + "runData.startTime": "Start Time", + "runData.table": "Table", + "runData.theNodeContains": "The node contains {numberOfKb} KB of data.
Displaying it could cause problems.

If you do decide to display it, consider avoiding the JSON view.", + "saveButton.save": "@:reusableBaseText.save", + "saveButton.saved": "Saved", + "saveButton.saving": "Saving", + "showMessage.cancel": "@:reusableBaseText.cancel", + "showMessage.ok": "OK", + "showMessage.showDetails": "Show Details", + "tagsDropdown.createTag": "Create tag \"{filter}\"", + "tagsDropdown.manageTags": "Manage tags", + "tagsDropdown.noMatchingTagsExist": "No matching tags exist", + "tagsDropdown.noTagsExist": "No tags exist", + "tagsDropdown.showError.message": "A problem occurred when trying to create the '{name}' tag", + "tagsDropdown.showError.title": "Could not create tag", + "tagsDropdown.typeToCreateATag": "Type to create a tag", + "tagsManager.couldNotDeleteTag": "Could not delete tag", + "tagsManager.done": "Done", + "tagsManager.manageTags": "Manage tags", + "tagsManager.showError.onCreate.message": "A problem occurred when trying to create the tag '{escapedName}'", + "tagsManager.showError.onCreate.title": "Could not create tag", + "tagsManager.showError.onDelete.message": "A problem occurred when trying to delete the tag '{escapedName}'", + "tagsManager.showError.onDelete.title": "Could not delete tag", + "tagsManager.showError.onUpdate.message": "A problem occurred when trying to update the tag '{escapedName}'", + "tagsManager.showError.onUpdate.title": "Could not update tag", + "tagsManager.showMessage.onDelete.title": "Tag deleted", + "tagsManager.showMessage.onUpdate.title": "Tag updated", + "tagsManager.tagNameCannotBeEmpty": "Tag name cannot be empty", + "tagsTable.areYouSureYouWantToDeleteThisTag": "Are you sure you want to delete this tag?", + "tagsTable.cancel": "@:reusableBaseText.cancel", + "tagsTable.createTag": "Create tag", + "tagsTable.deleteTag": "Delete tag", + "tagsTable.editTag": "Edit Tag", + "tagsTable.name": "@:reusableBaseText.name", + "tagsTable.noMatchingTagsExist": "No matching tags exist", + "tagsTable.saveChanges": "Save changes?", + "tagsTable.usage": "Usage", + "tagsTableHeader.addNew": "Add new", + "tagsTableHeader.searchTags": "Search Tags", + "tagsView.inUse.plural": "{count} workflows", + "tagsView.inUse.singular": "{count} workflow", + "tagsView.notBeingUsed": "Not being used", + "template.buttons.goBackButton": "Go back", + "template.buttons.useThisWorkflowButton": "Use this workflow", + "template.details.appsInTheWorkflow": "Apps in this workflow", + "template.details.appsInTheCollection": "This collection features", + "template.details.by": "by", + "template.details.categories": "Categories", + "template.details.created": "Created", + "template.details.details": "Details", + "template.details.times": "times", + "template.details.viewed": "Viewed", + "templates.allCategories": "All Categories", + "templates.categoriesHeading": "Categories", + "templates.collection": "Collection", + "templates.collections": "Collections", + "templates.collectionsNotFound": "Collection could not be found", + "templates.endResult": "Share your own useful workflows through your n8n.io account", + "templates.heading": "Workflow templates", + "templates.newButton": "New blank workflow", + "templates.noSearchResults": "Nothing found. Try adjusting your search to see more.", + "templates.searchPlaceholder": "Search workflows", + "templates.workflow": "Workflow", + "templates.workflows": "Workflows", + "templates.workflowsNotFound": "Workflow could not be found", + "templates.connectionWarning": "⚠️ There was a problem fetching workflow templates. Check your internet connection.", + "textEdit.edit": "Edit", + "timeAgo.daysAgo": "%s days ago", + "timeAgo.hoursAgo": "%s hours ago", + "timeAgo.inDays": "in %s days", + "timeAgo.inHours": "in %s hours", + "timeAgo.inMinutes": "in %s minutes", + "timeAgo.inMonths": "in %s months", + "timeAgo.inOneDay": "in 1 day", + "timeAgo.inOneHour": "in 1 hour", + "timeAgo.inOneMinute": "in 1 minute", + "timeAgo.inOneMonth": "in 1 month", + "timeAgo.inOneWeek": "in 1 week", + "timeAgo.inOneYear": "in 1 year", + "timeAgo.inWeeks": "in %s weeks", + "timeAgo.inYears": "in %s years", + "timeAgo.justNow": "Just now", + "timeAgo.minutesAgo": "%s minutes ago", + "timeAgo.monthsAgo": "%s months ago", + "timeAgo.oneDayAgo": "1 day ago", + "timeAgo.oneHourAgo": "1 hour ago", + "timeAgo.oneMinuteAgo": "1 minute ago", + "timeAgo.oneMonthAgo": "1 month ago", + "timeAgo.oneWeekAgo": "1 week ago", + "timeAgo.oneYearAgo": "1 year ago", + "timeAgo.rightNow": "Right now", + "timeAgo.weeksAgo": "%s weeks ago", + "timeAgo.yearsAgo": "%s years ago", + "updatesPanel.andIs": "and is", + "updatesPanel.behindTheLatest": "behind the latest and greatest n8n", + "updatesPanel.howToUpdateYourN8nVersion": "How to update your n8n version", + "updatesPanel.version": "{numberOfVersions} version{howManySuffix}", + "updatesPanel.weVeBeenBusy": "We’ve been busy ✨", + "updatesPanel.youReOnVersion": "You’re on {currentVersionName}, which was released", + "variableSelector.context": "Context", + "variableSelector.currentNode": "Current Node", + "variableSelector.nodes": "Nodes", + "variableSelector.outputData": "Output Data", + "variableSelector.parameters": "Parameters", + "variableSelector.variableFilter": "Variable filter...", + "variableSelectorItem.empty": "--- EMPTY ---", + "variableSelectorItem.selectItem": "Select Item", + "versionCard.breakingChanges": "Breaking changes", + "versionCard.released": "Released", + "versionCard.securityUpdate": "Security update", + "versionCard.thisVersionHasASecurityIssue": "This version has a security issue.
It is listed here for completeness.", + "versionCard.unknown": "unknown", + "versionCard.version": "Version", + "workflowActivator.activateWorkflow": "Activate workflow", + "workflowActivator.deactivateWorkflow": "Deactivate workflow", + "workflowActivator.showError.title": "Workflow could not be {newStateName}", + "workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.message": "Please resolve outstanding issues before you activate it", + "workflowActivator.showMessage.activeChangedNodesIssuesExistTrue.title": "Problem activating workflow", + "workflowActivator.showMessage.activeChangedWorkflowIdUndefined.message": "Please save it before activating", + "workflowActivator.showMessage.activeChangedWorkflowIdUndefined.title": "Problem activating workflow", + "workflowActivator.showMessage.displayActivationError.message.catchBlock": "Sorry there was a problem requesting the error", + "workflowActivator.showMessage.displayActivationError.message.errorDataNotUndefined": "The following error occurred on workflow activation:
{message}", + "workflowActivator.showMessage.displayActivationError.message.errorDataUndefined": "Unknown error", + "workflowActivator.showMessage.displayActivationError.title": "Problem activating workflow", + "workflowActivator.theWorkflowIsSetToBeActiveBut": "The workflow is activated but could not be started.
Click to display error message.", + "workflowActivator.thisWorkflowHasNoTriggerNodes": "This workflow has no trigger nodes that require activation", + "workflowDetails.active": "Active", + "workflowDetails.addTag": "Add tag", + "workflowDetails.showMessage.message": "Please enter a name, or press 'esc' to go back to the old one", + "workflowDetails.showMessage.title": "Name missing", + "workflowDetails.chooseOrCreateATag": "Choose or create a tag", + "workflowHelpers.showMessage.title": "Problem saving workflow", + "workflowOpen.active": "Active", + "workflowOpen.confirmMessage.cancelButtonText": "Leave without saving", + "workflowOpen.confirmMessage.confirmButtonText": "Save", + "workflowOpen.confirmMessage.headline": "Save changes before leaving?", + "workflowOpen.confirmMessage.message": "If you don't save, you will lose your changes.", + "workflowOpen.created": "Created", + "workflowOpen.name": "@:reusableBaseText.name", + "workflowOpen.openWorkflow": "Open Workflow", + "workflowOpen.filterWorkflows": "Filter by tags", + "workflowOpen.searchWorkflows": "Search workflows...", + "workflowOpen.showError.title": "Problem loading workflows", + "workflowOpen.showMessage.message": "This is the current workflow", + "workflowOpen.showMessage.title": "Workflow already open", + "workflowOpen.updated": "Updated", + "workflowOpen.couldNotLoadActiveWorkflows": "Could not load active workflows", + "workflowRun.noActiveConnectionToTheServer": "Lost connection to the server", + "workflowRun.showError.title": "Problem running workflow", + "workflowRun.showMessage.message": "Please fix them before executing", + "workflowRun.showMessage.title": "Workflow has issues", + "workflowSettings.defaultTimezone": "Default - {defaultTimezoneValue}", + "workflowSettings.defaultTimezoneNotValid": "Default Timezone not valid", + "workflowSettings.errorWorkflow": "Error Workflow", + "workflowSettings.helpTexts.errorWorkflow": "A second workflow to run if the current one fails.
The second workflow should an 'Error Trigger' node.", + "workflowSettings.helpTexts.executionTimeout": "How long the workflow should wait before timing out", + "workflowSettings.helpTexts.executionTimeoutToggle": "Whether to cancel workflow execution after a defined time", + "workflowSettings.helpTexts.saveDataErrorExecution": "Whether to save data of executions that fail", + "workflowSettings.helpTexts.saveDataSuccessExecution": "Whether to save data of executions that finish successfully", + "workflowSettings.helpTexts.saveExecutionProgress": "Whether to save data after each node execution. This allows you to resume from where execution stopped if there is an error, but may increase latency.", + "workflowSettings.helpTexts.saveManualExecutions": "Whether to save data of executions that are started manually from the editor", + "workflowSettings.helpTexts.timezone": "The timezone in which the workflow should run. Used by 'cron' node, for example.", + "workflowSettings.hours": "hours", + "workflowSettings.minutes": "minutes", + "workflowSettings.noWorkflow": "- No Workflow -", + "workflowSettings.save": "@:reusableBaseText.save", + "workflowSettings.saveDataErrorExecution": "Save failed executions", + "workflowSettings.saveDataErrorExecutionOptions.defaultSave": "Default - {defaultValue}", + "workflowSettings.saveDataErrorExecutionOptions.doNotSave": "Do not save", + "workflowSettings.saveDataErrorExecutionOptions.save": "@:reusableBaseText.save", + "workflowSettings.saveDataSuccessExecution": "Save successful executions", + "workflowSettings.saveDataSuccessExecutionOptions.defaultSave": "Default - {defaultValue}", + "workflowSettings.saveDataSuccessExecutionOptions.doNotSave": "Do not save", + "workflowSettings.saveDataSuccessExecutionOptions.save": "@:reusableBaseText.save", + "workflowSettings.saveExecutionProgress": "Save execution progress", + "workflowSettings.saveExecutionProgressOptions.defaultSave": "Default - {defaultValue}", + "workflowSettings.saveExecutionProgressOptions.no": "No", + "workflowSettings.saveExecutionProgressOptions.yes": "Yes", + "workflowSettings.saveManualExecutions": "Save manual executions", + "workflowSettings.saveManualOptions.defaultSave": "Default - {defaultValue}", + "workflowSettings.saveManualOptions.no": "No", + "workflowSettings.saveManualOptions.yes": "Yes", + "workflowSettings.seconds": "seconds", + "workflowSettings.selectOption": "Select Option", + "workflowSettings.settingsFor": "Settings for {workflowName} (#{workflowId})", + "workflowSettings.showError.saveSettings1.errorMessage": "Timeout is activated but set to 0", + "workflowSettings.showError.saveSettings1.message": "There was a problem saving the settings", + "workflowSettings.showError.saveSettings1.title": "Problem saving settings", + "workflowSettings.showError.saveSettings2.errorMessage": "Maximum Timeout is: {hours} hours, {minutes} minutes, {seconds} seconds", + "workflowSettings.showError.saveSettings2.message": "The timeout is longer than allowed", + "workflowSettings.showError.saveSettings2.title": "Problem saving settings", + "workflowSettings.showError.saveSettings3.title": "Problem saving settings", + "workflowSettings.showMessage.saveSettings.title": "Workflow settings saved", + "workflowSettings.timeoutAfter": "Timeout After", + "workflowSettings.timeoutWorkflow": "Timeout Workflow", + "workflowSettings.timezone": "Timezone", + "activationModal.workflowActivated": "Workflow activated", + "activationModal.theseExecutionsWillNotShowUp": "These executions will not show up immediately in the editor,", + "activationModal.butYouCanSeeThem": "but you can see them in the", + "activationModal.executionList": "execution list", + "activationModal.ifYouChooseTo": "if you choose to", + "activationModal.saveExecutions": "save executions.", + "activationModal.dontShowAgain": "Don't show again", + "activationModal.yourTriggersWillNowFire": "Your triggers will now fire production executions automatically.", + "activationModal.yourTriggerWillNowFire": "Your trigger will now fire production executions automatically.", + "activationModal.yourWorkflowWillNowRegularlyCheck": "Your workflow will now regularly check {serviceName} for events and trigger executions for them.", + "activationModal.yourWorkflowWillNowListenForEvents": "Your workflow will now listen for events from {serviceName} and trigger executions.", + "activationModal.gotIt": "Got it", + "workflowPreview.showError.previewError.message": "Unable to preview workflow", + "workflowPreview.showError.previewError.title": "Preview error", + "workflowPreview.showError.missingWorkflow": "Missing workflow", + "workflowPreview.showError.arrayEmpty": "Must have an array of nodes", "auth.changePassword": "Change Password", "auth.changePassword.currentPassword": "Current Password", "auth.changePassword.error": "Problem changing the password", @@ -1132,7 +758,7 @@ "auth.signup.setupYourAccount": "Set up your account", "auth.signup.setupYourAccountError": "Problem setting up your account", "auth.signup.tokenValidationError": "Issue validating invite token", - "error": "Error", + "error.error": "Error", "error.goBack": "Go back", "error.pageNotFound": "Oops, couldn’t find that", "forgotPassword": "Forgot my password", From c6a2752cc6ba051feaace424f960f2358000fd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 17:40:20 +0200 Subject: [PATCH 06/14] :zap: Fix miscorrected key --- packages/editor-ui/src/plugins/i18n/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 7b957a53f5f06..3098f3fad2197 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -758,7 +758,7 @@ "auth.signup.setupYourAccount": "Set up your account", "auth.signup.setupYourAccountError": "Problem setting up your account", "auth.signup.tokenValidationError": "Issue validating invite token", - "error.error": "Error", + "error": "Error", "error.goBack": "Go back", "error.pageNotFound": "Oops, couldn’t find that", "forgotPassword": "Forgot my password", From f187c3c73ac292760758c9b730f00d9796590613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 18:26:42 +0200 Subject: [PATCH 07/14] :zap: Implement pluralization --- .../src/components/TagsManager/TagsView/TagsView.vue | 10 +--------- packages/editor-ui/src/plugins/i18n/index.ts | 6 +++++- packages/editor-ui/src/plugins/i18n/locales/en.json | 6 ++---- packages/editor-ui/src/views/NodeView.vue | 11 +++-------- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/packages/editor-ui/src/components/TagsManager/TagsView/TagsView.vue b/packages/editor-ui/src/components/TagsManager/TagsView/TagsView.vue index ce0121e204650..d4a42fc26bf53 100644 --- a/packages/editor-ui/src/components/TagsManager/TagsView/TagsView.vue +++ b/packages/editor-ui/src/components/TagsManager/TagsView/TagsView.vue @@ -57,15 +57,7 @@ export default Vue.extend({ }, rows(): ITagRow[] { const getUsage = (count: number | undefined) => count && count > 0 - ? this.$locale.baseText( - count > 1 ? - 'tagsView.inUse.plural' : 'tagsView.inUse.singular', - { - interpolate: { - count: count.toString(), - }, - }, - ) + ? this.$locale.baseText('tagsView.inUse', { adjustToNumber: count }) : this.$locale.baseText('tagsView.notBeingUsed'); const disabled = this.isCreateEnabled || this.$data.updateId || this.$data.deleteId; diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index fe1d681bfa246..89e064c8309fd 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -63,8 +63,12 @@ export class I18nClass { */ baseText( key: string, - options?: { interpolate: { [key: string]: string } }, + options?: { adjustToNumber: number; interpolate: { [key: string]: string } }, ): string { + if (options && options.adjustToNumber) { + return this.i18n.tc(key, options.adjustToNumber, options && options.interpolate).toString(); + } + return this.i18n.t(key, options && options.interpolate).toString(); } diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 3098f3fad2197..11b4dd700bff7 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -356,8 +356,7 @@ "nodeView.showMessage.addNodeButton.message": "'{nodeTypeName}' is an unknown node type", "nodeView.showMessage.addNodeButton.title": "Could not create node", "nodeView.showMessage.keyDown.title": "Workflow created", - "nodeView.showMessage.showMaxNodeTypeError.message.plural": "Only {maxNodes} '{nodeTypeDataDisplayName}' nodes are allowed in a workflow", - "nodeView.showMessage.showMaxNodeTypeError.message.singular": "Only {maxNodes} '{nodeTypeDataDisplayName}' node is allowed in a workflow", + "nodeView.showMessage.showMaxNodeTypeError.message": "Only {count} '{nodeTypeDataDisplayName}' node is allowed in a workflow | Only {count} '{nodeTypeDataDisplayName}' nodes are allowed in a workflow", "nodeView.showMessage.showMaxNodeTypeError.title": "Could not create node", "nodeView.showMessage.stopExecutionCatch.message": "It completed before it could be stopped", "nodeView.showMessage.stopExecutionCatch.title": "Workflow finished executing", @@ -541,8 +540,7 @@ "tagsTable.usage": "Usage", "tagsTableHeader.addNew": "Add new", "tagsTableHeader.searchTags": "Search Tags", - "tagsView.inUse.plural": "{count} workflows", - "tagsView.inUse.singular": "{count} workflow", + "tagsView.inUse": "{count} workflow | {count} workflows", "tagsView.notBeingUsed": "Not being used", "template.buttons.goBackButton": "Go back", "template.buttons.useThisWorkflowButton": "Use this workflow", diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index ebf1238716272..4d092cae5c4b9 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1256,15 +1256,10 @@ export default mixins( const maxNodes = nodeTypeData.maxNodes; this.$showMessage({ title: this.$locale.baseText('nodeView.showMessage.showMaxNodeTypeError.title'), - message: this.$locale.baseText( - maxNodes === 1 - ? 'nodeView.showMessage.showMaxNodeTypeError.message.singular' - : 'nodeView.showMessage.showMaxNodeTypeError.message.plural', + message: this.$locale.baseText('nodeView.showMessage.showMaxNodeTypeError.message', { - interpolate: { - maxNodes: maxNodes!.toString(), - nodeTypeDataDisplayName: nodeTypeData.displayName, - }, + adjustToNumber: maxNodes, + interpolate: { nodeTypeDataDisplayName: nodeTypeData.displayName }, }, ), type: 'error', From 5e4fa8f9e5c4491ba226932723a6929c885b40d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 30 Mar 2022 18:28:04 +0200 Subject: [PATCH 08/14] :pencil2: Update docs --- .../src/plugins/i18n/docs/ADDENDUM.md | 66 ++++----- .../editor-ui/src/plugins/i18n/docs/README.md | 127 ++++++------------ 2 files changed, 74 insertions(+), 119 deletions(-) diff --git a/packages/editor-ui/src/plugins/i18n/docs/ADDENDUM.md b/packages/editor-ui/src/plugins/i18n/docs/ADDENDUM.md index 8c19e032d214c..644b26269c529 100644 --- a/packages/editor-ui/src/plugins/i18n/docs/ADDENDUM.md +++ b/packages/editor-ui/src/plugins/i18n/docs/ADDENDUM.md @@ -2,16 +2,24 @@ ## Base text +### Pluralization + +Certain base text strings accept [singular and plural versions](https://kazupon.github.io/vue-i18n/guide/pluralization.html) separated by a `|` character: + +```json +{ + "tagsView.inUse": "{count} workflow | {count} workflows", +} +``` + ### Interpolation -Certain base text strings use [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) to allow for a variable to be passed in, signalled by curly braces: +Certain base text strings use [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) to allow for a variable between curly braces: ```json { - "stopExecution": { - "message": "The execution with the ID {activeExecutionId} got stopped!", - "title": "Execution stopped" - } + "stopExecution.message": "The execution with the ID {activeExecutionId} got stopped!", + "stopExecution.title": "Execution stopped" } ``` @@ -19,10 +27,8 @@ When translating a string containing an interpolated variable, leave the variabl ```json { - "stopExecution": { - "message": "Die Ausführung mit der ID {activeExecutionId} wurde gestoppt", - "title": "Execution stopped" - } + "stopExecution.message": "Die Ausführung mit der ID {activeExecutionId} wurde gestoppt", + "stopExecution.title": "Execution stopped" } ``` @@ -32,18 +38,12 @@ As a convenience, the base text file may contain the special key `reusableBaseTe ```json { - "reusableBaseText": { - "save": "🇩🇪 Save", - }, - "duplicateWorkflowDialog": { - "enterWorkflowName": "🇩🇪 Enter workflow name", - "save": "@:reusableBaseText.save", - }, - "saveButton": { - "save": "@:reusableBaseText.save", - "saving": "🇩🇪 Saving", - "saved": "🇩🇪 Saved", - }, + "reusableBaseText.save": "🇩🇪 Save", + "duplicateWorkflowDialog.enterWorkflowName": "🇩🇪 Enter workflow name", + "duplicateWorkflowDialog.save": "@:reusableBaseText.save", + "saveButton.save": "@:reusableBaseText.save", + "saveButton.saving": "🇩🇪 Saving", + "saveButton.saved": "🇩🇪 Saved", } ``` @@ -92,23 +92,27 @@ Currently only the keys `oauth.clientId` and `oauth.clientSecret` are supported ```json { - "reusableDynamicText": { - "oauth2": { - "clientId": "🇩🇪 Client ID", - "clientSecret": "🇩🇪 Client Secret", - } - } + "reusableDynamicText.oauth2.clientId": "🇩🇪 Client ID", + "reusableDynamicText.oauth2.clientSecret": "🇩🇪 Client Secret", } ``` ### Special cases -`eventTriggerDescription` is a dynamic node property that is not part of node parameters. To translate it, set the `eventTriggerDescription` key at the root level of the `nodeView` property in the node translation file. +`eventTriggerDescription` and `activationMessage` are dynamic node properties that are not part of node parameters. To translate them, set the key at the root level of the `nodeView` property in the node translation file. + +Webhook node: + +```json +{ + "nodeView.eventTriggerDescription": "🇩🇪 Waiting for you to call the Test URL", +} +``` + +Cron node: ```json { - "nodeView": { - "eventTriggerDescription": "🇩🇪 Waiting for you to call the Test URL" - } + "nodeView.activationMessage": "🇩🇪 'Your cron trigger will now trigger executions on the schedule you have defined." } ``` diff --git a/packages/editor-ui/src/plugins/i18n/docs/README.md b/packages/editor-ui/src/plugins/i18n/docs/README.md index cdee3d747d4ba..125d5a73bedee 100644 --- a/packages/editor-ui/src/plugins/i18n/docs/README.md +++ b/packages/editor-ui/src/plugins/i18n/docs/README.md @@ -6,6 +6,7 @@ n8n allows for internalization of the majority of UI text: - base text, e.g. menu display items in the left-hand sidebar menu, - node text, e.g. parameter display names and placeholders in the node view, +- credential text, e.g. parameter display names and placeholders in the credential modal, - header text, e.g. node display names and descriptions at various spots. Currently, n8n does _not_ allow for internalization of: @@ -55,12 +56,10 @@ Base text is rendered with no dependencies, i.e. base text is fixed and does not The base text file for each locale is located at `/packages/editor-ui/src/plugins/i18n/locales/` and is named `{localeIdentifier}.json`. Keys in the base text file can be Vue component dirs, Vue component names, and references to symbols in those Vue components. These keys are added by the team as the UI is modified or expanded. ```json -"nodeCreator": { - "categoryNames": { - "analytics": "🇩🇪 Analytics", - "communication": "🇩🇪 Communication", - "coreNodes": "🇩🇪 Core Nodes" - } +{ + "nodeCreator.categoryNames.analytics": "🇩🇪 Analytics", + "nodeCreator.categoryNames.communication": "🇩🇪 Communication", + "nodeCreator.categoryNames.coreNodes": "🇩🇪 Core Nodes" } ``` @@ -98,9 +97,9 @@ A credential translation file is placed at `/nodes-base/credentials/translations ``` credentials └── translations - └── de - ├── githubApi.json - └── githubOAuth2Api.json + └── de + ├── githubApi.json + └── githubOAuth2Api.json ``` Every credential must have its own credential translation file. @@ -123,9 +122,9 @@ GitHub ├── GitHub.node.ts ├── GitHubTrigger.node.ts └── translations - └── de - ├── github.json - └── githubTrigger.json + └── de + ├── github.json + └── githubTrigger.json ``` Every node must have its own node translation file. @@ -184,16 +183,10 @@ The object for each node credential parameter allows for the keys `displayName`, ```json { - "server": { - "displayName": "🇩🇪 Github Server", - "description": "🇩🇪 The server to connect to. Only has to be set if Github Enterprise is used.", - }, - "user": { - "placeholder": "🇩🇪 Hans", - }, - "accessToken": { - "placeholder": "🇩🇪 123", - }, + "server.displayName": "🇩🇪 Github Server", + "server.description": "🇩🇪 The server to connect to. Only has to be set if Github Enterprise is used.", + "user.placeholder": "🇩🇪 Hans", + "accessToken.placeholder": "🇩🇪 123", } ``` @@ -224,10 +217,8 @@ export class Github implements INodeType { ```json { - "header": { - "displayName": "🇩🇪 GitHub", - "description": "🇩🇪 Consume GitHub API", - }, + "header.displayName": "🇩🇪 GitHub", + "header.description": "🇩🇪 Consume GitHub API", } ``` @@ -264,11 +255,7 @@ export class Github implements INodeType { ```json { - "nodeView": { - "resource": { - "displayName": "🇩🇪 Resource", - }, - }, + "nodeView.resource.displayName": "🇩🇪 Resource", } ``` @@ -291,13 +278,9 @@ Allowed keys: `displayName`, `description`, `placeholder` ```json { - "nodeView": { - "owner": { - "displayName": "🇩🇪 Repository Owner", - "placeholder": "🇩🇪 n8n-io", - "description": "🇩🇪 Owner of the repository", - }, - }, + "nodeView.owner.displayName": "🇩🇪 Repository Owner", + "nodeView.owner.placeholder": "🇩🇪 n8n-io", + "nodeView.owner.description": "🇩🇪 Owner of the repository", } ``` @@ -333,20 +316,10 @@ Allowed subkeys: `options.{optionName}.displayName` and `options.{optionName}.de ```json { - "nodeView": { - "resource": { - "displayName": "🇩🇪 Resource", - "description": "🇩🇪 Resource to operate on", - "options": { - "file": { - "displayName": "🇩🇪 File", - }, - "issue": { - "displayName": "🇩🇪 Issue", - }, - }, - }, - }, + "nodeView.resource.displayName": "🇩🇪 Resource", + "nodeView.resource.description": "🇩🇪 Resource to operate on", + "nodeView.resource.options.file.displayName": "🇩🇪 File", + "nodeView.resource.options.issue.displayName": "🇩🇪 Issue", } ``` @@ -394,19 +367,11 @@ Example of `collection` parameter: ```json { - "nodeView": { - "labels": { - "displayName": "🇩🇪 Labels", - "multipleValueButtonText": "🇩🇪 Add Label", - "options": { - "label": { - "displayName": "🇩🇪 Label", - "description": "🇩🇪 Label to add to issue", - "placeholder": "🇩🇪 Some placeholder" - } - } - } - } + "nodeView.labels.displayName": "🇩🇪 Labels", + "nodeView.labels.multipleValueButtonText": "🇩🇪 Add Label", + "nodeView.labels.options.label.displayName": "🇩🇪 Label", + "nodeView.labels.options.label.description": "🇩🇪 Label to add to issue", + "nodeView.labels.options.label.placeholder": "🇩🇪 Some placeholder" } ``` @@ -461,29 +426,15 @@ Example of `fixedCollection` parameter: ```json { - "nodeView": { - "additionalParameters": { - "displayName": "🇩🇪 Additional Parameters", - "placeholder": "🇩🇪 Add Field", - "options": { - "author": { - "displayName": "🇩🇪 Author", - "values": { - "name": { - "displayName": "🇩🇪 Name", - "description": "🇩🇪 Name of the author of the commit", - "placeholder": "🇩🇪 Jan" - }, - "email": { - "displayName": "🇩🇪 Email", - "description": "🇩🇪 Email of the author of the commit", - "placeholder": "🇩🇪 jan@n8n.io" - } - } - }, - } - } - } + "nodeView.additionalParameters.displayName": "🇩🇪 Additional Parameters", + "nodeView.additionalParameters.placeholder": "🇩🇪 Add Field", + "nodeView.additionalParameters.options.author.displayName": "🇩🇪 Author", + "nodeView.additionalParameters.options.author.values.name.displayName": "🇩🇪 Name", + "nodeView.additionalParameters.options.author.values.name.description": "🇩🇪 Name of the author of the commit", + "nodeView.additionalParameters.options.author.values.name.placeholder": "🇩🇪 Jan", + "nodeView.additionalParameters.options.author.values.email.displayName": "🇩🇪 Email", + "nodeView.additionalParameters.options.author.values.email.description": "🇩🇪 Email of the author of the commit", + "nodeView.additionalParameters.options.author.values.email.placeholder": "🇩🇪 jan@n8n.io", } ``` From 3b20d32dd947a9c9c40b32d875670600563b0145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Fri, 1 Apr 2022 16:51:12 +0200 Subject: [PATCH 09/14] :truck: Move headers fetching to `App.vue` --- packages/editor-ui/src/App.vue | 16 +++++++++++++--- packages/editor-ui/src/views/NodeView.vue | 5 ----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index 3883b6b665c93..e58b5683b6ff0 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -28,11 +28,13 @@ import { showMessage } from './components/mixins/showMessage'; import { IUser } from './Interface'; import { mapGetters } from 'vuex'; import { userHelpers } from './components/mixins/userHelpers'; -import { loadLanguage } from './plugins/i18n'; +import { addHeaders, loadLanguage } from './plugins/i18n'; +import { restApi } from '@/components/mixins/restApi'; export default mixins( showMessage, userHelpers, + restApi, ).extend({ name: 'App', components: { @@ -41,8 +43,11 @@ export default mixins( Modals, }, computed: { - ...mapGetters('settings', ['defaultLocale', 'isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']), + ...mapGetters('settings', ['isHiringBannerEnabled', 'isTemplatesEnabled', 'isTemplatesEndpointReachable', 'isUserManagementEnabled', 'showSetupPage']), ...mapGetters('users', ['currentUser']), + defaultLocale (): string { + return this.$store.getters.defaultLocale; + }, }, data() { return { @@ -153,6 +158,11 @@ export default mixins( this.trackPage(); this.$externalHooks().run('app.mount'); + + if (this.defaultLocale !== 'en') { + const headers = await this.restApi().getNodeTranslationHeaders(); + if (headers) addHeaders(headers, this.defaultLocale); + } }, watch: { $route(route) { @@ -161,7 +171,7 @@ export default mixins( this.trackPage(); }, - '$store.getters.defaultLocale'(newLocale) { + defaultLocale(newLocale) { loadLanguage(newLocale); }, }, diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 4d092cae5c4b9..f6f8123ddd289 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -2735,11 +2735,6 @@ export default mixins( try { await Promise.all(loadPromises); - - if (this.defaultLocale !== 'en') { - const headers = await this.restApi().getNodeTranslationHeaders(); - if (headers) addHeaders(headers, this.defaultLocale); - } } catch (error) { this.$showError( error, From b56baa91245c726bd6854817ad60d119df79a092 Mon Sep 17 00:00:00 2001 From: Mutasem Date: Mon, 11 Apr 2022 10:35:01 +0200 Subject: [PATCH 10/14] fix hiring banner --- packages/editor-ui/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue index e58b5683b6ff0..d4a8a2d2b2cc1 100644 --- a/packages/editor-ui/src/App.vue +++ b/packages/editor-ui/src/App.vue @@ -149,8 +149,8 @@ export default mixins( }, }, async mounted() { - this.logHiringBanner(); await this.initialize(); + this.logHiringBanner(); this.authenticate(); this.redirectIfNecessary(); From d3f1a52fd5b97361e071282112245e387e9d1f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 11 Apr 2022 10:59:08 +0200 Subject: [PATCH 11/14] :zap: Fix missing import --- packages/cli/src/Server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index e104440d9e7e6..22be162689a5a 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -30,7 +30,7 @@ /* eslint-disable no-await-in-loop */ import express from 'express'; -import { readFileSync } from 'fs'; +import { readFileSync, existsSync } from 'fs'; import { readFile } from 'fs/promises'; import _, { cloneDeep } from 'lodash'; import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path'; From de2b55059c30fe94ee5ca8ce4cd38f8223bc3ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 11 Apr 2022 10:59:27 +0200 Subject: [PATCH 12/14] :pencil2: Alphabetize translations --- .../src/plugins/i18n/locales/en.json | 334 +++++++++--------- 1 file changed, 167 insertions(+), 167 deletions(-) diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 11b4dd700bff7..6a715c9c3748b 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -1,15 +1,66 @@ { - "reusableBaseText.cancel": "Cancel", - "reusableBaseText.name": "Name", - "reusableBaseText.save": "Save", - "reusableDynamicText.oauth2.clientId": "Client ID", - "reusableDynamicText.oauth2.clientSecret": "Client Secret", "about.aboutN8n": "About n8n", "about.close": "Close", "about.license": "License", - "about.n8nVersion": "n8n Version", "about.n8nLicense": "Sustainable Use License", + "about.n8nVersion": "n8n Version", "about.sourceCode": "Source Code", + "activationModal.butYouCanSeeThem": "but you can see them in the", + "activationModal.dontShowAgain": "Don't show again", + "activationModal.executionList": "execution list", + "activationModal.gotIt": "Got it", + "activationModal.ifYouChooseTo": "if you choose to", + "activationModal.saveExecutions": "save executions.", + "activationModal.theseExecutionsWillNotShowUp": "These executions will not show up immediately in the editor,", + "activationModal.workflowActivated": "Workflow activated", + "activationModal.yourTriggerWillNowFire": "Your trigger will now fire production executions automatically.", + "activationModal.yourTriggersWillNowFire": "Your triggers will now fire production executions automatically.", + "activationModal.yourWorkflowWillNowListenForEvents": "Your workflow will now listen for events from {serviceName} and trigger executions.", + "activationModal.yourWorkflowWillNowRegularlyCheck": "Your workflow will now regularly check {serviceName} for events and trigger executions for them.", + "auth.changePassword": "Change Password", + "auth.changePassword.currentPassword": "Current Password", + "auth.changePassword.error": "Problem changing the password", + "auth.changePassword.missingTokenError": "Missing token", + "auth.changePassword.missingUserIdError": "Missing user ID", + "auth.changePassword.passwordUpdated": "Password updated", + "auth.changePassword.passwordUpdatedMessage": "You can now sign in with your new password", + "auth.changePassword.passwordsMustMatchError": "Passwords must match", + "auth.changePassword.reenterNewPassword": "Re-enter New Password", + "auth.changePassword.tokenValidationError": "Issue validating invite token", + "auth.defaultPasswordRequirements": "8+ characters, at least 1 number and 1 capital letter", + "auth.email": "Email", + "auth.firstName": "First Name", + "auth.lastName": "Last Name", + "auth.newPassword": "New Password", + "auth.password": "Password", + "auth.role": "Role", + "auth.roles.member": "Member", + "auth.roles.owner": "Owner", + "auth.setup.confirmOwnerSetup": "Set up owner account?", + "auth.setup.confirmOwnerSetupMessage": "To give others access to your {entities}, you’ll need to share these account details with them. Or you can continue as before with no account, by going back and skipping this setup. More info", + "auth.setup.createAccount": "Create account", + "auth.setup.goBack": "Go back", + "auth.setup.next": "Next", + "auth.setup.ownerAccountBenefits": "Setting up an owner account allows you to invite others, and prevents people using n8n without an account", + "auth.setup.settingUpOwnerError": "Problem setting up owner", + "auth.setup.setupConfirmation.concatEntities": "{workflows} and {credentials}", + "auth.setup.setupConfirmation.credentialsCount": "{count} credentials", + "auth.setup.setupConfirmation.oneCredentialCount": "{count} credential", + "auth.setup.setupConfirmation.oneWorkflowCount": "{count} existing workflow", + "auth.setup.setupConfirmation.workflowsCount": "{count} existing workflows", + "auth.setup.setupOwner": "Set up owner account", + "auth.setup.skipOwnerSetupQuestion": "Skip owner account setup?", + "auth.setup.skipSetup": "Skip setup", + "auth.setup.skipSetupTemporarily": "Skip setup for now", + "auth.signin": "Sign in", + "auth.signin.error": "Problem logging in", + "auth.signout": "Sign out", + "auth.signout.error": "Could not sign out", + "auth.signup.finishAccountSetup": "Finish account setup", + "auth.signup.missingTokenError": "Missing token", + "auth.signup.setupYourAccount": "Set up your account", + "auth.signup.setupYourAccountError": "Problem setting up your account", + "auth.signup.tokenValidationError": "Issue validating invite token", "binaryDataDisplay.backToList": "Back to list", "binaryDataDisplay.backToOverviewPage": "Back to overview page", "binaryDataDisplay.noDataFoundToDisplay": "No data found to display", @@ -48,8 +99,8 @@ "credentialEdit.credentialEdit.connection": "Connection", "credentialEdit.credentialEdit.couldNotFindCredentialOfType": "Could not find credential of type", "credentialEdit.credentialEdit.couldNotFindCredentialWithId": "Could not find credential with ID", - "credentialEdit.credentialEdit.details": "Details", "credentialEdit.credentialEdit.delete": "Delete", + "credentialEdit.credentialEdit.details": "Details", "credentialEdit.credentialEdit.saving": "Saving", "credentialEdit.credentialEdit.showError.createCredential.title": "Problem creating credential", "credentialEdit.credentialEdit.showError.deleteCredential.title": "Problem deleting credential", @@ -79,6 +130,7 @@ "credentialsList.credentials": "Credentials", "credentialsList.deleteCredential": "Delete Credential", "credentialsList.editCredential": "Edit Credential", + "credentialsList.errorLoadingCredentials": "Error loading credentials", "credentialsList.name": "@:reusableBaseText.name", "credentialsList.operations": "Operations", "credentialsList.showError.deleteCredential.title": "Problem deleting credential", @@ -86,7 +138,6 @@ "credentialsList.type": "Type", "credentialsList.updated": "Updated", "credentialsList.yourSavedCredentials": "Your saved credentials", - "credentialsList.errorLoadingCredentials": "Error loading credentials", "dataDisplay.needHelp": "Need help?", "dataDisplay.nodeDocumentation": "Node Documentation", "dataDisplay.openDocumentationFor": "Open {nodeTypeDisplayName} documentation", @@ -100,15 +151,18 @@ "duplicateWorkflowDialog.save": "@:reusableBaseText.save", "duplicateWorkflowDialog.showMessage.message": "Please enter a name.", "duplicateWorkflowDialog.showMessage.title": "Name missing", + "error": "Error", + "error.goBack": "Go back", + "error.pageNotFound": "Oops, couldn’t find that", "executionDetails.executionFailed": "Execution failed", "executionDetails.executionId": "Execution ID", "executionDetails.executionWaiting": "Execution waiting", "executionDetails.executionWasSuccessful": "Execution was successful", - "executionDetails.openWorkflow": "Open Workflow", "executionDetails.of": "of", - "executionDetails.workflow": "workflow", + "executionDetails.openWorkflow": "Open Workflow", "executionDetails.readOnly.readOnly": "Read only", "executionDetails.readOnly.youreViewingTheLogOf": "You're viewing the log of a previous execution. You cannot
\n\t\tmake changes since this execution already occurred. Make changes
\n\t\tto this workflow by clicking on its name on the left.", + "executionDetails.workflow": "workflow", "executionsList.allWorkflows": "All Workflows", "executionsList.anyStatus": "Any Status", "executionsList.autoRefresh": "Auto refresh", @@ -176,6 +230,15 @@ "fixedCollectionParameter.deleteItem": "Delete item", "fixedCollectionParameter.moveDown": "Move down", "fixedCollectionParameter.moveUp": "Move up", + "forgotPassword": "Forgot my password", + "forgotPassword.emailSentIfExists": "We’ve emailed {email} (if there’s a matching account)", + "forgotPassword.getRecoveryLink": "Email me a recovery link", + "forgotPassword.noSMTPToSendEmailWarning": "Please contact your admin. n8n isn’t set up to send email right now.", + "forgotPassword.recoverPassword": "Recover password", + "forgotPassword.recoveryEmailSent": "Recovery email sent", + "forgotPassword.returnToSignIn": "Back to sign in", + "forgotPassword.sendingEmailError": "Problem sending email", + "forgotPassword.smtpErrorContactAdministrator": "Please contact your administrator (problem with your SMTP setup)", "genericHelpers.loading": "Loading", "genericHelpers.min": "min", "genericHelpers.sec": "sec", @@ -238,6 +301,7 @@ "node.nodeIsWaitingTill": "Node is waiting until {date} {time}", "node.theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall": "The node is waiting for an incoming webhook call (indefinitely)", "node.waitingForYouToCreateAnEventIn": "Waiting for you to create an event in {nodeType}", + "nodeBase.clickToAddNodeOrDragToConnect": "Click to add node
or drag to connect", "nodeCreator.categoryNames.analytics": "Analytics", "nodeCreator.categoryNames.communication": "Communication", "nodeCreator.categoryNames.coreNodes": "Core Nodes", @@ -288,7 +352,6 @@ "nodeErrorView.stack": "Stack", "nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed", "nodeErrorView.time": "Time", - "nodeBase.clickToAddNodeOrDragToConnect": "Click to add node
or drag to connect", "nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.", "nodeSettings.alwaysOutputData.displayName": "Always Output Data", "nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io", @@ -381,6 +444,8 @@ "nodeWebhooks.showMessage.title": "URL copied", "nodeWebhooks.testUrl": "Test URL", "nodeWebhooks.webhookUrls": "Webhook URLs", + "openWorkflow.workflowImportError": "Could not import workflow", + "openWorkflow.workflowNotFoundError": "Could not find workflow", "parameterInput.addExpression": "Add Expression", "parameterInput.error": "ERROR", "parameterInput.issues": "Issues", @@ -394,19 +459,26 @@ "parameterInput.refreshList": "Refresh List", "parameterInput.removeExpression": "Remove Expression", "parameterInput.resetValue": "Reset Value", - "parameterInput.selectDateAndTime": "Select date and time", "parameterInput.select": "Select", + "parameterInput.selectDateAndTime": "Select date and time", "parameterInputExpanded.openDocs": "Open docs", "parameterInputExpanded.thisFieldIsRequired": "This field is required", "parameterInputList.delete": "Delete", "parameterInputList.deleteParameter": "Delete Parameter", "parameterInputList.parameterOptions": "Parameter Options", + "personalizationModal.automationAgencyOrConsultant": "Automation agency/consultant", "personalizationModal.automationConsulting": "Automation consulting", + "personalizationModal.cloudInfrastructure": "Cloud infrastructure", "personalizationModal.continue": "Continue", + "personalizationModal.customerIntegrations": "Customer integrations", + "personalizationModal.customerSupport": "Customer support", "personalizationModal.customizeN8n": "Customize n8n to you", + "personalizationModal.digitalAgencyOrConsultant": "Digital agency/consultant", "personalizationModal.eCommerce": "eCommerce", + "personalizationModal.education": "Education", "personalizationModal.errorWhileSubmittingResults": "Error while submitting results", "personalizationModal.executiveTeam": "Executive team", + "personalizationModal.financeOrAccounting": "Finance / Accounting", "personalizationModal.financeOrInsurance": "Finance / Insurance", "personalizationModal.getStarted": "Get started", "personalizationModal.government": "Government", @@ -414,7 +486,6 @@ "personalizationModal.howAreYourCodingSkills": "How are your coding skills?", "personalizationModal.howBigIsYourCompany": "How big is your company?", "personalizationModal.hr": "HR", - "personalizationModal.it": "IT", "personalizationModal.iCanCodeSomeUsefulThingsBut": "2. I can code some useful things, but I spend a lot of time stuck", "personalizationModal.iCanDoAlmostAnythingIWant": "5. I can do almost anything I want, easily (pro coder)", "personalizationModal.iCanFigureMostThingsOut": "4. I can figure most things out", @@ -422,60 +493,59 @@ "personalizationModal.iKnowEnoughToBeDangerousBut": "3. I know enough to be dangerous, but I'm no expert", "personalizationModal.imNotUsingN8nForWork": "I'm not using n8n for work", "personalizationModal.individualConsumers": "Customers are individual consumers", - "personalizationModal.smallBusinesses": "Customers are small businesses (under 20 employees)", - "personalizationModal.mediumBusinesses": "Customers are medium businesses (20-499 employees)", - "personalizationModal.largeBusinesses": "Customers are large businesses (500+ employees)", - "personalizationModal.whatKindOfCustomersDoYouServe": "What kind of customers do you serve?", - "personalizationModal.cloudInfrastructure": "Cloud infrastructure", - "personalizationModal.itSupport": "IT support", - "personalizationModal.networkingOrCommunication": "Networking / Communication", + "personalizationModal.it": "IT", "personalizationModal.itEngineering": "IT / Engineering", + "personalizationModal.itSupport": "IT support", + "personalizationModal.largeBusinesses": "Customers are large businesses (500+ employees)", "personalizationModal.legal": "Legal", "personalizationModal.lessThan20People": "Less than 20 people", "personalizationModal.lookOutForThingsMarked": "Look out for things marked with a ✨. They are personalized to make n8n more relevant to you.", + "personalizationModal.managedServiceProvider": "Managed service provider", + "personalizationModal.manufacturing": "Manufacturing", "personalizationModal.marketing": "Marketing", "personalizationModal.media": "Media", - "personalizationModal.manufacturing": "Manufacturing", - "personalizationModal.managedServiceProvider": "Managed service provider", - "personalizationModal.digitalAgencyOrConsultant": "Digital agency/consultant", - "personalizationModal.automationAgencyOrConsultant": "Automation agency/consultant", + "personalizationModal.mediumBusinesses": "Customers are medium businesses (20-499 employees)", + "personalizationModal.networkingOrCommunication": "Networking / Communication", "personalizationModal.neverCoded": "0. Never coded", + "personalizationModal.notSureYet": "Not sure yet", "personalizationModal.operations": "Operations", - "personalizationModal.otherPleaseSpecify": "Other (please specify)", "personalizationModal.other": "Other", + "personalizationModal.otherPleaseSpecify": "Other (please specify)", "personalizationModal.people": "people", - "personalizationModal.proCoder": "Pro coder", "personalizationModal.physicalRetailOrServices": "Physical retail or services", + "personalizationModal.pleaseSpecifyYourCompanyFocus": "Please specify your company focus", + "personalizationModal.proCoder": "Pro coder", + "personalizationModal.product": "Product (e.g. fast prototyping)", "personalizationModal.realEstateOrConstruction": "Real estate / Construction", "personalizationModal.saas": "SaaS", - "personalizationModal.education": "Education", + "personalizationModal.salesAndMarketing": "Sales and Marketing", "personalizationModal.salesBizDev": "Sales / Bizdev", "personalizationModal.salesBusinessDevelopment": "Sales / Business Development", "personalizationModal.security": "Security", "personalizationModal.select": "Select...", + "personalizationModal.smallBusinesses": "Customers are small businesses (under 20 employees)", + "personalizationModal.specifyYourAutomationGoal": "Please specify your automation goal", "personalizationModal.specifyYourCompanysIndustry": "Specify your company's industry", "personalizationModal.specifyYourWorkArea": "Specify your work area", "personalizationModal.support": "Support", "personalizationModal.systemsIntegration": "Systems Integration", "personalizationModal.systemsIntegrator": "Systems Integrator", - "personalizationModal.thanks": "Thanks!", "personalizationModal.telecoms": "Telecoms", + "personalizationModal.thanks": "Thanks!", "personalizationModal.theseQuestionsHelpUs": "These questions help us tailor n8n to you", - "personalizationModal.whichIndustriesIsYourCompanyIn": "Which industries is your company in?", + "personalizationModal.whatAreYouLookingToAutomate": "What are you looking to automate?", "personalizationModal.whatBestDescribesYourCompany": "What best describes your company?", "personalizationModal.whatDoesYourCompanyFocusOn": "Which services does your company focus on?", - "personalizationModal.pleaseSpecifyYourCompanyFocus": "Please specify your company focus", - "personalizationModal.whatAreYouLookingToAutomate": "What are you looking to automate?", - "personalizationModal.customerIntegrations": "Customer integrations", - "personalizationModal.customerSupport": "Customer support", - "personalizationModal.financeOrAccounting": "Finance / Accounting", - "personalizationModal.product": "Product (e.g. fast prototyping)", - "personalizationModal.salesAndMarketing": "Sales and Marketing", - "personalizationModal.notSureYet": "Not sure yet", - "personalizationModal.specifyYourAutomationGoal": "Please specify your automation goal", + "personalizationModal.whatKindOfCustomersDoYouServe": "What kind of customers do you serve?", + "personalizationModal.whichIndustriesIsYourCompanyIn": "Which industries is your company in?", "pushConnection.showMessage.title": "Workflow executed successfully", "pushConnectionTracker.cannotConnectToServer": "You have a connection issue or the server is down.
n8n should reconnect automatically once the issue is resolved.", "pushConnectionTracker.connectionLost": "Connection lost", + "reusableBaseText.cancel": "Cancel", + "reusableBaseText.name": "Name", + "reusableBaseText.save": "Save", + "reusableDynamicText.oauth2.clientId": "Client ID", + "reusableDynamicText.oauth2.clientSecret": "Client Secret", "runData.binary": "Binary", "runData.copyItemPath": "Copy Item Path", "runData.copyParameterPath": "Copy Parameter Path", @@ -484,6 +554,7 @@ "runData.dataOfExecution": "Data of Execution", "runData.dataReturnedByThisNodeWillDisplayHere": "Data returned by this node will display here", "runData.displayDataAnyway": "Display Data Anyway", + "runData.downloadBinaryData": "Download", "runData.entriesExistButThey": "Entries exist but they do not contain any JSON data", "runData.executeNode": "Execute Node", "runData.executesThisNodeAfterExecuting": "Executes this {nodeName} node after executing any previous nodes that have not yet returned data", @@ -499,7 +570,6 @@ "runData.noTextDataFound": "No text data found", "runData.nodeReturnedALargeAmountOfData": "Node returned a large amount of data", "runData.output": "Output", - "runData.downloadBinaryData": "Download", "runData.showBinaryData": "View", "runData.startTime": "Start Time", "runData.table": "Table", @@ -507,9 +577,57 @@ "saveButton.save": "@:reusableBaseText.save", "saveButton.saved": "Saved", "saveButton.saving": "Saving", + "settings": "Settings", + "settings.goBack": "Go back", + "settings.personal": "Personal", + "settings.personal.basicInformation": "Basic Information", + "settings.personal.personalSettings": "Personal Settings", + "settings.personal.personalSettingsUpdated": "Personal details updated", + "settings.personal.personalSettingsUpdatedError": "Problem updating your details", + "settings.personal.save": "Save", + "settings.personal.security": "Security", + "settings.signup.signUpInviterInfo": "{firstName} {lastName} has invited you to n8n", + "settings.users": "Users", + "settings.users.confirmDataHandlingAfterDeletion": "What should we do with their data?", + "settings.users.confirmUserDeletion": "Are you sure you want to delete this invited user?", + "settings.users.delete": "Delete", + "settings.users.deleteConfirmationMessage": "Type “delete all data” to confirm", + "settings.users.deleteConfirmationText": "delete all data", + "settings.users.deleteUser": "Delete {user}", + "settings.users.deleteWorkflowsAndCredentials": "Delete their workflows and credentials", + "settings.users.emailInvitesSent": "An invite email was sent to {emails}", + "settings.users.emailInvitesSentError": "Could not invite {emails}", + "settings.users.emailSentTo": "Email sent to {email}", + "settings.users.invalidEmailError": "{email} is not a valid email", + "settings.users.invite": "Invite", + "settings.users.inviteNewUsers": "Invite new users", + "settings.users.inviteResent": "Invite resent", + "settings.users.inviteUser": "Invite user", + "settings.users.inviteXUser": "Invite {count} users", + "settings.users.newEmailsToInvite": "New User Email Addresses", + "settings.users.noUsersToInvite": "No users to invite", + "settings.users.setupMyAccount": "Set up my owner account", + "settings.users.setupSMTPToInviteUsers": "Set up SMTP to invite users. Instructions", + "settings.users.setupToInviteUsers": "To invite users, set up your own account", + "settings.users.setupToInviteUsersInfo": "Invited users won’t be able to see workflows and credentials of other users. More info

You will need details of an SMTP server to complete the setup.", + "settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). Instructions", + "settings.users.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another user", + "settings.users.transferredToUser": "Data transferred to {user}", + "settings.users.userDeleted": "User deleted", + "settings.users.userDeletedError": "Problem while deleting user", + "settings.users.userInvited": "User invited", + "settings.users.userInvitedError": "User could not be invited", + "settings.users.userReinviteError": "Could not reinvite user", + "settings.users.userToTransferTo": "User to transfer to", + "settings.users.usersEmailedError": "Couldn't send invite email", + "settings.users.usersInvited": "Users invited", + "settings.users.usersInvitedError": "Could not invite users", + "settings.version": "Version", "showMessage.cancel": "@:reusableBaseText.cancel", "showMessage.ok": "OK", "showMessage.showDetails": "Show Details", + "startupError": "Error connecting to n8n", + "startupError.message": "Could not connect to server. Refresh to try again", "tagsDropdown.createTag": "Create tag \"{filter}\"", "tagsDropdown.manageTags": "Manage tags", "tagsDropdown.noMatchingTagsExist": "No matching tags exist", @@ -544,8 +662,8 @@ "tagsView.notBeingUsed": "Not being used", "template.buttons.goBackButton": "Go back", "template.buttons.useThisWorkflowButton": "Use this workflow", - "template.details.appsInTheWorkflow": "Apps in this workflow", "template.details.appsInTheCollection": "This collection features", + "template.details.appsInTheWorkflow": "Apps in this workflow", "template.details.by": "by", "template.details.categories": "Categories", "template.details.created": "Created", @@ -557,6 +675,7 @@ "templates.collection": "Collection", "templates.collections": "Collections", "templates.collectionsNotFound": "Collection could not be found", + "templates.connectionWarning": "⚠️ There was a problem fetching workflow templates. Check your internet connection.", "templates.endResult": "Share your own useful workflows through your n8n.io account", "templates.heading": "Workflow templates", "templates.newButton": "New blank workflow", @@ -565,7 +684,6 @@ "templates.workflow": "Workflow", "templates.workflows": "Workflows", "templates.workflowsNotFound": "Workflow could not be found", - "templates.connectionWarning": "⚠️ There was a problem fetching workflow templates. Check your internet connection.", "textEdit.edit": "Edit", "timeAgo.daysAgo": "%s days ago", "timeAgo.hoursAgo": "%s hours ago", @@ -628,25 +746,29 @@ "workflowActivator.thisWorkflowHasNoTriggerNodes": "This workflow has no trigger nodes that require activation", "workflowDetails.active": "Active", "workflowDetails.addTag": "Add tag", + "workflowDetails.chooseOrCreateATag": "Choose or create a tag", "workflowDetails.showMessage.message": "Please enter a name, or press 'esc' to go back to the old one", "workflowDetails.showMessage.title": "Name missing", - "workflowDetails.chooseOrCreateATag": "Choose or create a tag", "workflowHelpers.showMessage.title": "Problem saving workflow", "workflowOpen.active": "Active", "workflowOpen.confirmMessage.cancelButtonText": "Leave without saving", "workflowOpen.confirmMessage.confirmButtonText": "Save", "workflowOpen.confirmMessage.headline": "Save changes before leaving?", "workflowOpen.confirmMessage.message": "If you don't save, you will lose your changes.", + "workflowOpen.couldNotLoadActiveWorkflows": "Could not load active workflows", "workflowOpen.created": "Created", + "workflowOpen.filterWorkflows": "Filter by tags", "workflowOpen.name": "@:reusableBaseText.name", "workflowOpen.openWorkflow": "Open Workflow", - "workflowOpen.filterWorkflows": "Filter by tags", "workflowOpen.searchWorkflows": "Search workflows...", "workflowOpen.showError.title": "Problem loading workflows", "workflowOpen.showMessage.message": "This is the current workflow", "workflowOpen.showMessage.title": "Workflow already open", "workflowOpen.updated": "Updated", - "workflowOpen.couldNotLoadActiveWorkflows": "Could not load active workflows", + "workflowPreview.showError.arrayEmpty": "Must have an array of nodes", + "workflowPreview.showError.missingWorkflow": "Missing workflow", + "workflowPreview.showError.previewError.message": "Unable to preview workflow", + "workflowPreview.showError.previewError.title": "Preview error", "workflowRun.noActiveConnectionToTheServer": "Lost connection to the server", "workflowRun.showError.title": "Problem running workflow", "workflowRun.showMessage.message": "Please fix them before executing", @@ -695,127 +817,5 @@ "workflowSettings.showMessage.saveSettings.title": "Workflow settings saved", "workflowSettings.timeoutAfter": "Timeout After", "workflowSettings.timeoutWorkflow": "Timeout Workflow", - "workflowSettings.timezone": "Timezone", - "activationModal.workflowActivated": "Workflow activated", - "activationModal.theseExecutionsWillNotShowUp": "These executions will not show up immediately in the editor,", - "activationModal.butYouCanSeeThem": "but you can see them in the", - "activationModal.executionList": "execution list", - "activationModal.ifYouChooseTo": "if you choose to", - "activationModal.saveExecutions": "save executions.", - "activationModal.dontShowAgain": "Don't show again", - "activationModal.yourTriggersWillNowFire": "Your triggers will now fire production executions automatically.", - "activationModal.yourTriggerWillNowFire": "Your trigger will now fire production executions automatically.", - "activationModal.yourWorkflowWillNowRegularlyCheck": "Your workflow will now regularly check {serviceName} for events and trigger executions for them.", - "activationModal.yourWorkflowWillNowListenForEvents": "Your workflow will now listen for events from {serviceName} and trigger executions.", - "activationModal.gotIt": "Got it", - "workflowPreview.showError.previewError.message": "Unable to preview workflow", - "workflowPreview.showError.previewError.title": "Preview error", - "workflowPreview.showError.missingWorkflow": "Missing workflow", - "workflowPreview.showError.arrayEmpty": "Must have an array of nodes", - "auth.changePassword": "Change Password", - "auth.changePassword.currentPassword": "Current Password", - "auth.changePassword.error": "Problem changing the password", - "auth.changePassword.missingTokenError": "Missing token", - "auth.changePassword.missingUserIdError": "Missing user ID", - "auth.changePassword.passwordUpdated": "Password updated", - "auth.changePassword.passwordUpdatedMessage": "You can now sign in with your new password", - "auth.changePassword.passwordsMustMatchError": "Passwords must match", - "auth.changePassword.reenterNewPassword": "Re-enter New Password", - "auth.changePassword.tokenValidationError": "Issue validating invite token", - "auth.defaultPasswordRequirements": "8+ characters, at least 1 number and 1 capital letter", - "auth.email": "Email", - "auth.firstName": "First Name", - "auth.lastName": "Last Name", - "auth.newPassword": "New Password", - "auth.password": "Password", - "auth.role": "Role", - "auth.roles.member": "Member", - "auth.roles.owner": "Owner", - "auth.setup.setupOwner": "Set up owner account", - "auth.setup.skipOwnerSetupQuestion": "Skip owner account setup?", - "auth.setup.skipSetup": "Skip setup", - "auth.setup.goBack": "Go back", - "auth.setup.confirmOwnerSetup": "Set up owner account?", - "auth.setup.setupConfirmation.oneWorkflowCount": "{count} existing workflow", - "auth.setup.setupConfirmation.oneCredentialCount": "{count} credential", - "auth.setup.setupConfirmation.workflowsCount": "{count} existing workflows", - "auth.setup.setupConfirmation.credentialsCount": "{count} credentials", - "auth.setup.setupConfirmation.concatEntities": "{workflows} and {credentials}", - "auth.setup.confirmOwnerSetupMessage": "To give others access to your {entities}, you’ll need to share these account details with them. Or you can continue as before with no account, by going back and skipping this setup. More info", - "auth.setup.createAccount": "Create account", - "auth.setup.skipSetupTemporarily": "Skip setup for now", - "auth.setup.next": "Next", - "auth.setup.ownerAccountBenefits": "Setting up an owner account allows you to invite others, and prevents people using n8n without an account", - "auth.setup.settingUpOwnerError": "Problem setting up owner", - "auth.signin": "Sign in", - "auth.signin.error": "Problem logging in", - "auth.signout": "Sign out", - "auth.signout.error": "Could not sign out", - "auth.signup.finishAccountSetup": "Finish account setup", - "auth.signup.missingTokenError": "Missing token", - "auth.signup.setupYourAccount": "Set up your account", - "auth.signup.setupYourAccountError": "Problem setting up your account", - "auth.signup.tokenValidationError": "Issue validating invite token", - "error": "Error", - "error.goBack": "Go back", - "error.pageNotFound": "Oops, couldn’t find that", - "forgotPassword": "Forgot my password", - "forgotPassword.emailSentIfExists": "We’ve emailed {email} (if there’s a matching account)", - "forgotPassword.getRecoveryLink": "Email me a recovery link", - "forgotPassword.noSMTPToSendEmailWarning": "Please contact your admin. n8n isn’t set up to send email right now.", - "forgotPassword.recoverPassword": "Recover password", - "forgotPassword.recoveryEmailSent": "Recovery email sent", - "forgotPassword.returnToSignIn": "Back to sign in", - "forgotPassword.sendingEmailError": "Problem sending email", - "forgotPassword.smtpErrorContactAdministrator": "Please contact your administrator (problem with your SMTP setup)", - "openWorkflow.workflowImportError": "Could not import workflow", - "openWorkflow.workflowNotFoundError": "Could not find workflow", - "settings": "Settings", - "settings.goBack": "Go back", - "settings.personal": "Personal", - "settings.personal.basicInformation": "Basic Information", - "settings.personal.personalSettings": "Personal Settings", - "settings.personal.personalSettingsUpdated": "Personal details updated", - "settings.personal.personalSettingsUpdatedError": "Problem updating your details", - "settings.personal.save": "Save", - "settings.personal.security": "Security", - "settings.signup.signUpInviterInfo": "{firstName} {lastName} has invited you to n8n", - "settings.users": "Users", - "settings.users.confirmDataHandlingAfterDeletion": "What should we do with their data?", - "settings.users.confirmUserDeletion": "Are you sure you want to delete this invited user?", - "settings.users.delete": "Delete", - "settings.users.deleteConfirmationMessage": "Type “delete all data” to confirm", - "settings.users.deleteConfirmationText": "delete all data", - "settings.users.deleteUser": "Delete {user}", - "settings.users.deleteWorkflowsAndCredentials": "Delete their workflows and credentials", - "settings.users.emailInvitesSent": "An invite email was sent to {emails}", - "settings.users.emailInvitesSentError": "Could not invite {emails}", - "settings.users.emailSentTo": "Email sent to {email}", - "settings.users.invalidEmailError": "{email} is not a valid email", - "settings.users.invite": "Invite", - "settings.users.inviteNewUsers": "Invite new users", - "settings.users.inviteResent": "Invite resent", - "settings.users.inviteUser": "Invite user", - "settings.users.inviteXUser": "Invite {count} users", - "settings.users.newEmailsToInvite": "New User Email Addresses", - "settings.users.noUsersToInvite": "No users to invite", - "settings.users.setupMyAccount": "Set up my owner account", - "settings.users.setupSMTPToInviteUsers": "Set up SMTP to invite users. Instructions", - "settings.users.setupToInviteUsers": "To invite users, set up your own account", - "settings.users.setupToInviteUsersInfo": "Invited users won’t be able to see workflows and credentials of other users. More info

You will need details of an SMTP server to complete the setup.", - "settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). Instructions", - "settings.users.transferWorkflowsAndCredentials": "Transfer their workflows and credentials to another user", - "settings.users.transferredToUser": "Data transferred to {user}", - "settings.users.userDeleted": "User deleted", - "settings.users.userDeletedError": "Problem while deleting user", - "settings.users.userInvited": "User invited", - "settings.users.userInvitedError": "User could not be invited", - "settings.users.userReinviteError": "Could not reinvite user", - "settings.users.userToTransferTo": "User to transfer to", - "settings.users.usersInvited": "Users invited", - "settings.users.usersInvitedError": "Could not invite users", - "settings.users.usersEmailedError": "Couldn't send invite email", - "settings.version": "Version", - "startupError": "Error connecting to n8n", - "startupError.message": "Could not connect to server. Refresh to try again" + "workflowSettings.timezone": "Timezone" } From 175e63f7ec819f9f486ccbfeb2327d3fddda900f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Mon, 11 Apr 2022 12:31:14 +0200 Subject: [PATCH 13/14] :zap: Switch to async check --- packages/cli/src/Server.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 22be162689a5a..14aff9569f762 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -30,7 +30,7 @@ /* eslint-disable no-await-in-loop */ import express from 'express'; -import { readFileSync, existsSync } from 'fs'; +import { readFileSync, promises } from 'fs'; import { readFile } from 'fs/promises'; import _, { cloneDeep } from 'lodash'; import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path'; @@ -1504,7 +1504,11 @@ class App { const packagesPath = pathJoin(__dirname, '..', '..', '..'); const headersPath = pathJoin(packagesPath, 'nodes-base', 'dist', 'nodes', 'headers'); - if (!existsSync(`${headersPath}.js`)) return; + try { + await promises.access(`${headersPath}.js`); + } catch (_) { + return; // no headers available + } try { return require(headersPath); From aaf259f1ca5211eeca974b5915cb7e7f4285d0da Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Mon, 11 Apr 2022 15:09:14 +0200 Subject: [PATCH 14/14] feat(editor): Refactor Output Panel + fix i18n issues (#3097) * update main panel * finish up tabs * fix docs link * add icon * update node settings * clean up settings * add rename modal * fix component styles * fix spacing * truncate name * remove mixin * fix spacing * fix spacing * hide docs url * fix bug * fix renaming * refactor tabs out * refactor execute button * refactor header * add more views * fix error view * fix workflow rename bug * rename component * fix small screen bug * move items, fix positions * add hover state * show selector on empty state * add empty run state * fix binary view * 1 item * add vjs styles * show empty row for every item * refactor tabs * add branch names * fix spacing * fix up spacing * add run selector * fix positioning * clean up * increase width of selector * fix up spacing * fix copy button * fix branch naming; type issues * fix docs in custom nodes * add type * hide items when run selector is shown * increase selector size * add select prepend * clean up a bit * Add pagination * add stale icon * enable stale data in execution run * Revert "enable stale data in execution run" 8edb68dbffa0aa0d8189117e1a53381cb2c27608 * move metadata to its own state * fix smaller size * add scroll buttons * update tabs on resize * update stale data on rename * remove metadata on delete * hide x * change title colors * binary data classes * remove duplicate css * add colors * delete unused keys * use event bus * update styles of pagination * fix ts issues * fix ts issues * use chevron icons * fix design with download button * add back to canvas button * add trigger warning disabled * show trigger warning tooltip * update button labels for triggers * update node output message * fix add-option bug * add page selector * fix pagination selector bug * fix executions bug * remove hint * add json colors * add colors for json * add color json keys * fix select options bug * update keys * address comments * update name limit * align pencil * update icon size * update radio buttons height * address comments * fix pencil bug * change buttons alignment * fully center * change order of buttons * add no output message in branch * scroll to top * change active state * fix page size * all items * update expression background * update naming * align pencil * update modal background * add schedule group * update schedule nodes messages * use ellpises for last chars * fix spacing * fix tabs issue * fix too far data bug * fix executions bug * fix table wrapping * fix rename bug * add padding * handle unkown errors * add sticky header * ignore empty input, trim node name * nudge lightness of color * center buttons * update pagination * set colors of title * increase table font, fix alignment * fix pencil bug * fix spacing * use date now * address pagination issues * delete unused keys * update keys sort * fix prepend * fix radio button position * Revert "fix radio button position" ae42781786f2e6dcfb00d1be770b19a67f533bdf --- .../src/components/N8nIcon/Icon.vue | 3 + .../components/N8nInfoTip/InfoTip.stories.js | 10 +- .../src/components/N8nInfoTip/InfoTip.vue | 63 +- .../N8nRadioButtons/RadioButton.vue | 66 ++ .../N8nRadioButtons/RadioButtons.stories.js | 51 + .../N8nRadioButtons/RadioButtons.vue | 49 + .../src/components/N8nRadioButtons/index.js | 3 + .../src/components/N8nSelect/Select.vue | 71 +- .../src/components/N8nTabs/Tabs.stories.js | 54 + .../src/components/N8nTabs/Tabs.vue | 192 ++++ .../src/components/N8nTabs/index.js | 3 + .../design-system/src/components/index.js | 8 + .../src/components/utils/helpers.ts | 2 +- .../src/styleguide/colors.stories.mdx | 12 + packages/design-system/theme/src/_tokens.scss | 15 +- .../design-system/theme/src/common/var.scss | 4 +- packages/design-system/theme/src/index.scss | 2 +- .../design-system/theme/src/pagination.scss | 20 +- packages/editor-ui/src/Interface.ts | 13 + .../editor-ui/src/components/DataDisplay.vue | 149 ++- .../src/components/DisplayWithChange.vue | 128 --- .../src/components/ExpressionEdit.vue | 2 +- .../ExecutionDetails/ExecutionDetails.vue | 8 +- .../components/MainHeader/WorkflowDetails.vue | 8 +- .../src/components/NodeExecuteButton.vue | 65 ++ .../editor-ui/src/components/NodeSettings.vue | 162 +-- .../editor-ui/src/components/NodeTabs.vue | 85 ++ .../editor-ui/src/components/NodeTitle.vue | 135 +++ packages/editor-ui/src/components/RunData.vue | 1011 ++++++++++------- ...{WorkflowNameShort.vue => ShortenName.vue} | 2 +- .../src/components/mixins/nodeHelpers.ts | 3 + packages/editor-ui/src/n8n-theme.scss | 30 +- packages/editor-ui/src/plugins/components.ts | 11 +- .../src/plugins/i18n/locales/en.json | 40 +- packages/editor-ui/src/plugins/icons.ts | 2 + packages/editor-ui/src/store.ts | 15 + packages/editor-ui/src/views/NodeView.vue | 15 +- packages/nodes-base/nodes/Cron/Cron.node.ts | 2 +- .../nodes/Interval/Interval.node.ts | 2 +- 39 files changed, 1739 insertions(+), 777 deletions(-) create mode 100644 packages/design-system/src/components/N8nRadioButtons/RadioButton.vue create mode 100644 packages/design-system/src/components/N8nRadioButtons/RadioButtons.stories.js create mode 100644 packages/design-system/src/components/N8nRadioButtons/RadioButtons.vue create mode 100644 packages/design-system/src/components/N8nRadioButtons/index.js create mode 100644 packages/design-system/src/components/N8nTabs/Tabs.stories.js create mode 100644 packages/design-system/src/components/N8nTabs/Tabs.vue create mode 100644 packages/design-system/src/components/N8nTabs/index.js delete mode 100644 packages/editor-ui/src/components/DisplayWithChange.vue create mode 100644 packages/editor-ui/src/components/NodeExecuteButton.vue create mode 100644 packages/editor-ui/src/components/NodeTabs.vue create mode 100644 packages/editor-ui/src/components/NodeTitle.vue rename packages/editor-ui/src/components/{WorkflowNameShort.vue => ShortenName.vue} (96%) diff --git a/packages/design-system/src/components/N8nIcon/Icon.vue b/packages/design-system/src/components/N8nIcon/Icon.vue index 8ffe65edeccf4..b89b43a57eee3 100644 --- a/packages/design-system/src/components/N8nIcon/Icon.vue +++ b/packages/design-system/src/components/N8nIcon/Icon.vue @@ -2,6 +2,7 @@ diff --git a/packages/design-system/src/components/N8nInfoTip/InfoTip.stories.js b/packages/design-system/src/components/N8nInfoTip/InfoTip.stories.js index 382a46dac6ffa..f61d1d9f9d17a 100644 --- a/packages/design-system/src/components/N8nInfoTip/InfoTip.stories.js +++ b/packages/design-system/src/components/N8nInfoTip/InfoTip.stories.js @@ -11,7 +11,13 @@ const Template = (args, { argTypes }) => ({ N8nInfoTip, }, template: - 'Need help doing something? Open docs', + 'Need help doing something? Open docs', }); -export const InputLabel = Template.bind({}); +export const Note = Template.bind({}); + +export const Tooltip = Template.bind({}); +Tooltip.args = { + type: 'tooltip', + tooltipPlacement: 'right', +}; diff --git a/packages/design-system/src/components/N8nInfoTip/InfoTip.vue b/packages/design-system/src/components/N8nInfoTip/InfoTip.vue index 75a56f49e4bfb..4d26093f473e5 100644 --- a/packages/design-system/src/components/N8nInfoTip/InfoTip.vue +++ b/packages/design-system/src/components/N8nInfoTip/InfoTip.vue @@ -1,23 +1,48 @@ - @@ -81,7 +81,7 @@ import mixins from "vue-typed-mixins"; import { mapGetters } from "vuex"; import { MAX_WORKFLOW_NAME_LENGTH } from "@/constants"; -import WorkflowNameShort from "@/components/WorkflowNameShort.vue"; +import ShortenName from "@/components/ShortenName.vue"; import TagsContainer from "@/components/TagsContainer.vue"; import PushConnectionTracker from "@/components/PushConnectionTracker.vue"; import WorkflowActivator from "@/components/WorkflowActivator.vue"; @@ -105,7 +105,7 @@ export default mixins(workflowHelpers).extend({ components: { TagsContainer, PushConnectionTracker, - WorkflowNameShort, + ShortenName, WorkflowActivator, SaveButton, TagsDropdown, diff --git a/packages/editor-ui/src/components/NodeExecuteButton.vue b/packages/editor-ui/src/components/NodeExecuteButton.vue new file mode 100644 index 0000000000000..e478da18e1991 --- /dev/null +++ b/packages/editor-ui/src/components/NodeExecuteButton.vue @@ -0,0 +1,65 @@ + + + diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 0d59691cf9a62..5f4a76c0dc625 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -1,17 +1,15 @@ @@ -59,12 +55,11 @@ import { IUpdateInformation, } from '@/Interface'; -import { ElTabPane } from "element-ui/types/tab-pane"; - -import DisplayWithChange from '@/components/DisplayWithChange.vue'; +import NodeTitle from '@/components/NodeTitle.vue'; import ParameterInputFull from '@/components/ParameterInputFull.vue'; import ParameterInputList from '@/components/ParameterInputList.vue'; import NodeCredentials from '@/components/NodeCredentials.vue'; +import NodeTabs from '@/components/NodeTabs.vue'; import NodeWebhooks from '@/components/NodeWebhooks.vue'; import { get, set, unset } from 'lodash'; @@ -73,21 +68,23 @@ import { genericHelpers } from '@/components/mixins/genericHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers'; import mixins from 'vue-typed-mixins'; +import NodeExecuteButton from './NodeExecuteButton.vue'; export default mixins( externalHooks, genericHelpers, nodeHelpers, ) - .extend({ name: 'NodeSettings', components: { - DisplayWithChange, + NodeTitle, NodeCredentials, ParameterInputFull, ParameterInputList, + NodeTabs, NodeWebhooks, + NodeExecuteButton, }, computed: { nodeType (): INodeTypeDescription | null { @@ -150,14 +147,16 @@ export default mixins( return this.nodeType.properties; }, - workflowRunning (): boolean { - return this.$store.getters.isActionActive('workflowRunning'); + }, + props: { + eventBus: { }, }, data () { return { nodeValid: true, nodeColor: null, + openPanel: 'params', nodeValues: { color: '#ff0000', alwaysOutputData: false, @@ -271,7 +270,9 @@ export default mixins( }, }, methods: { - noOp () {}, + onNodeExecute () { + this.$emit('execute'); + }, setValue (name: string, value: NodeParameterValue) { const nameParts = name.split('.'); let lastNamePart: string | undefined = nameParts.pop(); @@ -337,6 +338,13 @@ export default mixins( this.$externalHooks().run('nodeSettings.credentialSelected', { updateInformation }); }, + nameChanged(name: string) { + // @ts-ignore + this.valueChanged({ + value: name, + name: 'name', + }); + }, valueChanged (parameterData: IUpdateInformation) { let newValue: NodeParameterValue; if (parameterData.hasOwnProperty('value')) { @@ -362,7 +370,6 @@ export default mixins( }; this.$emit('valueChanged', sendData); - this.$store.commit('setActiveNode', newValue); } else if (parameterData.name.startsWith('parameters.')) { // A node parameter changed @@ -514,20 +521,25 @@ export default mixins( this.nodeValid = false; } }, - handleTabClick(tab: ElTabPane) { - if(tab.label === 'Settings') { - this.$telemetry.track('User viewed node settings', { node_type: this.node ? this.node.type : '', workflow_id: this.$store.getters.workflowId }); - } - }, }, mounted () { this.setNodeValues(); + if (this.eventBus) { + (this.eventBus as Vue).$on('openSettings', () => { + this.openPanel = 'settings'; + }); + } }, }); - + diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 43091926db53c..ad2348b26687a 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -1,73 +1,52 @@ @@ -225,6 +241,7 @@ import { IBinaryDisplayData, IExecutionResponse, INodeUi, + ITab, ITableData, } from '@/Interface'; @@ -234,15 +251,16 @@ import { } from '@/constants'; import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue'; +import WarningTooltip from '@/components/WarningTooltip.vue'; import NodeErrorView from '@/components/Error/NodeErrorView.vue'; import { copyPaste } from '@/components/mixins/copyPaste'; import { externalHooks } from "@/components/mixins/externalHooks"; import { genericHelpers } from '@/components/mixins/genericHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers'; -import { workflowRun } from '@/components/mixins/workflowRun'; import mixins from 'vue-typed-mixins'; +import Vue from 'vue/types/umd'; import { saveAs } from 'file-saver'; @@ -254,7 +272,6 @@ export default mixins( externalHooks, genericHelpers, nodeHelpers, - workflowRun, ) .extend({ name: 'RunData', @@ -262,13 +279,14 @@ export default mixins( BinaryDataDisplay, NodeErrorView, VueJsonPretty, + WarningTooltip, }, data () { return { binaryDataPreviewActive: false, dataSize: 0, deselectedPlaceholder, - displayMode: this.$locale.baseText('runData.table'), + displayMode: 'table', state: { value: '' as object | number | string, path: deselectedPlaceholder, @@ -276,18 +294,48 @@ export default mixins( runIndex: 0, showData: false, outputIndex: 0, - maxDisplayItems: 25 as number | null, binaryDataDisplayVisible: false, binaryDataDisplayData: null as IBinaryDisplayData | null, MAX_DISPLAY_DATA_SIZE, MAX_DISPLAY_ITEMS_AUTO_ALL, + currentPage: 1, + pageSize: 10, + pageSizes: [10, 25, 50, 100], }; }, mounted() { this.init(); }, computed: { + nodeType (): INodeTypeDescription | null { + if (this.node) { + return this.$store.getters.nodeType(this.node.type, this.node.typeVersion); + } + return null; + }, + isTriggerNode (): boolean { + return !!(this.nodeType && this.nodeType.group.includes('trigger')); + }, + isPollingTypeNode (): boolean { + return !!(this.nodeType && this.nodeType.polling); + }, + isScheduleTrigger (): boolean { + return !!(this.nodeType && this.nodeType.group.includes('schedule')); + }, + buttons(): Array<{label: string, value: string}> { + const defaults = [ + { label: this.$locale.baseText('runData.table'), value: 'table'}, + { label: this.$locale.baseText('runData.json'), value: 'json'}, + ]; + if (this.binaryData.length) { + return [ ...defaults, + { label: this.$locale.baseText('runData.binary'), value: 'binary'}, + ]; + } + + return defaults; + }, hasNodeRun(): boolean { return Boolean(this.node && this.workflowRunData && this.workflowRunData.hasOwnProperty(this.node.name)); }, @@ -305,19 +353,15 @@ export default mixins( return null; } const executionData: IRunExecutionData = this.workflowExecution.data; - return executionData.resultData.runData; - }, - maxDisplayItemsOptions (): number[] { - const options = [25, 50, 100, 250, 500, 1000].filter(option => option <= this.dataCount); - if (!options.includes(this.dataCount)) { - options.push(this.dataCount); + if (executionData && executionData.resultData) { + return executionData.resultData.runData; } - return options; + return null; }, node (): INodeUi | null { return this.$store.getters.activeNode; }, - runMetadata () { + runTaskData (): ITaskData | null { if (!this.node || this.workflowExecution === null) { return null; } @@ -332,40 +376,33 @@ export default mixins( return null; } - const taskData: ITaskData = runData[this.node.name][this.runIndex]; + return runData[this.node.name][this.runIndex]; + }, + runMetadata (): {executionTime: number, startTime: string} | null { + if (!this.runTaskData) { + return null; + } return { - executionTime: taskData.executionTime, - startTime: new Date(taskData.startTime).toLocaleString(), + executionTime: this.runTaskData.executionTime, + startTime: new Date(this.runTaskData.startTime).toLocaleString(), }; }, - dataCount (): number { - if (this.node === null) { - return 0; - } - - const runData: IRunData | null = this.workflowRunData; - - if (runData === null || !runData.hasOwnProperty(this.node.name)) { - return 0; - } - - if (runData[this.node.name].length <= this.runIndex) { - return 0; + staleData(): boolean { + if (!this.node) { + return false; } - - if (runData[this.node.name][this.runIndex].hasOwnProperty('error')) { - return 1; - } - - if (!runData[this.node.name][this.runIndex].hasOwnProperty('data') || - runData[this.node.name][this.runIndex].data === undefined - ) { - return 0; + const updatedAt = this.$store.getters.getParametersLastUpdated(this.node.name); + if (!updatedAt || !this.runTaskData) { + return false; } - - const inputData = this.getMainInputData(runData[this.node.name][this.runIndex].data!, this.outputIndex); - - return inputData.length; + const runAt = this.runTaskData.startTime; + return updatedAt > runAt; + }, + dataCount (): number { + return this.getDataCount(this.runIndex, this.outputIndex); + }, + dataSizeInMB(): string { + return (this.dataSize / 1024 / 1000).toLocaleString(); }, maxOutputIndex (): number { if (this.node === null) { @@ -407,29 +444,22 @@ export default mixins( return 0; }, - jsonData (): IDataObject[] { + inputData (): INodeExecutionData[] { let inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex); if (inputData.length === 0 || !Array.isArray(inputData)) { return []; } - if (this.maxDisplayItems !== null) { - inputData = inputData.slice(0, this.maxDisplayItems); - } + const offset = this.pageSize * (this.currentPage - 1); + inputData = inputData.slice(offset, offset + this.pageSize); - return this.convertToJson(inputData); + return inputData; + }, + jsonData (): IDataObject[] { + return this.convertToJson(this.inputData); }, tableData (): ITableData | undefined { - let inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex); - if (inputData.length === 0) { - return undefined; - } - - if (this.maxDisplayItems !== null) { - inputData = inputData.slice(0,this.maxDisplayItems); - } - - return this.convertToTable(inputData); + return this.convertToTable(this.inputData); }, binaryData (): IBinaryKeyData[] { if (this.node === null) { @@ -438,17 +468,106 @@ export default mixins( return this.getBinaryData(this.workflowRunData, this.node.name, this.runIndex, this.outputIndex); }, + branches (): ITab[] { + function capitalize(name: string) { + return name.charAt(0).toLocaleUpperCase() + name.slice(1); + } + const branches: ITab[] = []; + for (let i = 0; i <= this.maxOutputIndex; i++) { + const itemsCount = this.getDataCount(this.runIndex, i); + const items = this.$locale.baseText(itemsCount === 1 ? 'ndv.output.item': 'ndv.output.items'); + let outputName = this.getOutputName(i); + if (`${outputName}` === `${i}`) { + outputName = `${this.$locale.baseText('ndv.output')} ${outputName}`; + } + else { + outputName = capitalize(`${this.getOutputName(i)} ${this.$locale.baseText('ndv.output.branch')}`); + } + branches.push({ + label: itemsCount ? `${outputName} (${itemsCount} ${items})` : outputName, + value: i, + }); + } + return branches; + }, }, methods: { + onPageSizeChange(pageSize: number) { + this.pageSize = pageSize; + const maxPage = Math.ceil(this.dataCount / this.pageSize); + if (maxPage < this.currentPage) { + this.currentPage = maxPage; + } + }, + onDisplayModeChange(displayMode: string) { + const previous = this.displayMode; + this.displayMode = displayMode; + + const dataContainer = this.$refs.dataContainer; + if (dataContainer) { + const dataDisplay = (dataContainer as Element).children[0]; + + if (dataDisplay){ + dataDisplay.scrollTo(0, 0); + } + } + + this.closeBinaryDataDisplay(); + this.$externalHooks().run('runData.displayModeChanged', { newValue: displayMode, oldValue: previous }); + if(this.node) { + const nodeType = this.node ? this.node.type : ''; + this.$telemetry.track('User changed node output view mode', { old_mode: previous, new_mode: displayMode, node_type: nodeType, workflow_id: this.$store.getters.workflowId }); + } + }, + getRunLabel(option: number) { + let itemsCount = 0; + for (let i = 0; i <= this.maxOutputIndex; i++) { + itemsCount += this.getDataCount(option - 1, i); + } + const items = this.$locale.baseText(itemsCount === 1 ? 'ndv.output.item': 'ndv.output.items'); + const itemsLabel = itemsCount > 0 ? ` (${itemsCount} ${items})` : ''; + return option + this.$locale.baseText('ndv.output.of') + (this.maxRunIndex+1) + itemsLabel; + }, + getDataCount(runIndex: number, outputIndex: number) { + if (this.node === null) { + return 0; + } + + const runData: IRunData | null = this.workflowRunData; + + if (runData === null || !runData.hasOwnProperty(this.node.name)) { + return 0; + } + + if (runData[this.node.name].length <= runIndex) { + return 0; + } + + if (runData[this.node.name][runIndex].hasOwnProperty('error')) { + return 1; + } + + if (!runData[this.node.name][runIndex].hasOwnProperty('data') || + runData[this.node.name][runIndex].data === undefined + ) { + return 0; + } + + const inputData = this.getMainInputData(runData[this.node.name][runIndex].data!, outputIndex); + + return inputData.length; + }, + openSettings() { + this.$emit('openSettings'); + }, init() { // Reset the selected output index every time another node gets selected this.outputIndex = 0; - this.maxDisplayItems = 25; this.refreshDataSize(); - if (this.displayMode === this.$locale.baseText('runData.binary')) { + if (this.displayMode === 'binary') { this.closeBinaryDataDisplay(); if (this.binaryData.length === 0) { - this.displayMode = this.$locale.baseText('runData.table'); + this.displayMode = 'table'; } } }, @@ -562,7 +681,7 @@ export default mixins( return outputIndex + 1; } - const nodeType = this.$store.getters.nodeType(this.node.type, this.node.typeVersion) as INodeTypeDescription | null; + const nodeType = this.nodeType; if (!nodeType || !nodeType.outputNames || nodeType.outputNames.length <= outputIndex) { return outputIndex + 1; } @@ -643,7 +762,8 @@ export default mixins( // Check how much data there is to display const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex); - const jsonItems = inputData.slice(0, this.maxDisplayItems || inputData.length).map(item => item.json); + const offset = this.pageSize * (this.currentPage - 1); + const jsonItems = inputData.slice(offset, offset + this.pageSize).map(item => item.json); this.dataSize = JSON.stringify(jsonItems).length; @@ -660,14 +780,6 @@ export default mixins( jsonData () { this.refreshDataSize(); }, - displayMode (newValue, oldValue) { - this.closeBinaryDataDisplay(); - this.$externalHooks().run('runData.displayModeChanged', { newValue, oldValue }); - if(this.node) { - const nodeType = this.node ? this.node.type : ''; - this.$telemetry.track('User changed node output view mode', { old_mode: oldValue, new_mode: newValue, node_type: nodeType, workflow_id: this.$store.getters.workflowId }); - } - }, maxRunIndex () { this.runIndex = Math.min(this.runIndex, this.maxRunIndex); }, @@ -675,188 +787,279 @@ export default mixins( }); - + + diff --git a/packages/editor-ui/src/components/WorkflowNameShort.vue b/packages/editor-ui/src/components/ShortenName.vue similarity index 96% rename from packages/editor-ui/src/components/WorkflowNameShort.vue rename to packages/editor-ui/src/components/ShortenName.vue index eb9b73c063752..dca88204b1b03 100644 --- a/packages/editor-ui/src/components/WorkflowNameShort.vue +++ b/packages/editor-ui/src/components/ShortenName.vue @@ -11,7 +11,7 @@ const DEFAULT_WORKFLOW_NAME_LIMIT = 25; const WORKFLOW_NAME_END_COUNT_TO_KEEP = 4; export default Vue.extend({ - name: "WorkflowNameShort", + name: "ShortenName", props: ["name", "limit"], computed: { shortenedName(): string { diff --git a/packages/editor-ui/src/components/mixins/nodeHelpers.ts b/packages/editor-ui/src/components/mixins/nodeHelpers.ts index f4c33408cbf05..6335a7e4c5b04 100644 --- a/packages/editor-ui/src/components/mixins/nodeHelpers.ts +++ b/packages/editor-ui/src/components/mixins/nodeHelpers.ts @@ -287,6 +287,9 @@ export const nodeHelpers = mixins( return []; } const executionData: IRunExecutionData = this.$store.getters.getWorkflowExecution.data; + if (!executionData || !executionData.resultData) { // unknown status + return []; + } const runData = executionData.resultData.runData; if (runData === null || runData[node.name] === undefined || diff --git a/packages/editor-ui/src/n8n-theme.scss b/packages/editor-ui/src/n8n-theme.scss index acd8ab1c75554..120c2cb8a8128 100644 --- a/packages/editor-ui/src/n8n-theme.scss +++ b/packages/editor-ui/src/n8n-theme.scss @@ -120,9 +120,6 @@ .el-tabs__nav:focus { outline: none; } -.el-tabs__item { - color: #555; -} .el-tabs__item.is-active { font-weight: bold; } @@ -185,3 +182,30 @@ } } } + +.add-option { + > * { + border: none; + } + + i.el-select__caret { + color: var(--color-foreground-xlight); + } + .el-input .el-input__inner { + &, + &:hover, + &:focus { + border-radius: 20px; + color: var(--color-foreground-xlight); + font-weight: 600; + background-color: var(--color-primary); + border-color: var(--color-primary); + text-align: center; + } + + &::placeholder { + color: var(--color-foreground-xlight); + opacity: 1; /** Firefox */ + } + } +} diff --git a/packages/editor-ui/src/plugins/components.ts b/packages/editor-ui/src/plugins/components.ts index 9a6cca9964ac8..e902e1580c45d 100644 --- a/packages/editor-ui/src/plugins/components.ts +++ b/packages/editor-ui/src/plugins/components.ts @@ -41,6 +41,8 @@ import { Message, Notification, CollapseTransition, + Pagination, + Popover, N8nActionBox, N8nAvatar, @@ -60,8 +62,10 @@ import { N8nMenu, N8nMenuItem, N8nOption, + N8nRadioButtons, N8nSelect, N8nSpinner, + N8nTabs, N8nFormInputs, N8nFormBox, N8nSquareButton, @@ -81,7 +85,7 @@ Vue.use(N8nAvatar); Vue.use(N8nButton); Vue.component('n8n-form-box', N8nFormBox); Vue.component('n8n-form-inputs', N8nFormInputs); -Vue.use('n8n-icon', N8nIcon); +Vue.component('n8n-icon', N8nIcon); Vue.use(N8nIconButton); Vue.use(N8nInfoTip); Vue.use(N8nInput); @@ -96,8 +100,10 @@ Vue.use(N8nMenuItem); Vue.use(N8nOption); Vue.use(N8nSelect); Vue.use(N8nSpinner); +Vue.use(N8nRadioButtons); Vue.component('n8n-square-button', N8nSquareButton); Vue.use(N8nTags); +Vue.component('n8n-tabs', N8nTabs); Vue.use(N8nTag); Vue.component('n8n-text', N8nText); Vue.use(N8nTooltip); @@ -130,6 +136,9 @@ Vue.use(Badge); Vue.use(Card); Vue.use(ColorPicker); Vue.use(Container); +Vue.use(Pagination); +Vue.use(Popover); + Vue.use(VueAgile); Vue.component(CollapseTransition.name, CollapseTransition); diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index 6a715c9c3748b..b37ca0f446c38 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -288,6 +288,38 @@ "multipleParameter.deleteItem": "Delete item", "multipleParameter.moveDown": "Move down", "multipleParameter.moveUp": "Move up", + "ndv.backToCanvas": "Back to canvas", + "ndv.backToCanvas.waitingForTriggerWarning": "Waiting for a Trigger node to execute. Close this view to see the Workflow Canvas.", + "ndv.execute.executeNode": "Execute node", + "ndv.execute.executing": "Executing", + "ndv.execute.fetchEvent": "Fetch Event", + "ndv.execute.listenForEvent": "Listen For Event", + "ndv.output": "Output", + "ndv.output.all": "all", + "ndv.output.branch": "Branch", + "ndv.output.emptyInput": "This input item is empty. {name} will still execute when it recieves an empty item.", + "ndv.output.emptyOutput": "This output item is empty.", + "ndv.output.executing": "Executing node...", + "ndv.output.item": "item", + "ndv.output.items": "items", + "ndv.output.noOutputData.message": "n8n stops executing the workflow when a node has no output data. You can change this default behaviour via", + "ndv.output.noOutputData.message.settings": "Settings", + "ndv.output.noOutputData.message.settingsOption": "> “Always Output Data”.", + "ndv.output.noOutputData.title": "No output data returned", + "ndv.output.noOutputDataInBranch": "No output data in this branch", + "ndv.output.of": " of ", + "ndv.output.pageSize": "Page Size", + "ndv.output.pollEventNodeHint": "Fetch an event to output data", + "ndv.output.run": "Run", + "ndv.output.runNodeHint": "Execute this node to output data", + "ndv.output.staleDataWarning": "Node parameters have changed.
Execute node again to refresh output.", + "ndv.output.tooMuchData.message": "The node contains {size} MB of data. Displaying it may cause problems.
If you do decide to display it, avoid the JSON view.", + "ndv.output.tooMuchData.showDataAnyway": "Show data anyway", + "ndv.output.tooMuchData.title": "Output data is huge!", + "ndv.output.triggerEventNodeHint": "Listen for an event to output data", + "ndv.title.cancel": "Cancel", + "ndv.title.rename": "Rename", + "ndv.title.renameNode": "Rename node", "noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?", "noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows", "node.activateDeactivateNode": "Activate/Deactivate Node", @@ -357,6 +389,7 @@ "nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io", "nodeSettings.continueOnFail.description": "If active, the workflow continues even if this node's execution fails. When this occurs, the node passes along input data from previous nodes - so your workflow should account for unexpected output data.", "nodeSettings.continueOnFail.displayName": "Continue On Fail", + "nodeSettings.docs": "Docs", "nodeSettings.executeOnce.description": "If active, the node executes only once, with data from the first item it receives", "nodeSettings.executeOnce.displayName": "Execute Once", "nodeSettings.maxTries.description": "Number of times to attempt to execute the node before failing the execution", @@ -370,7 +403,6 @@ "nodeSettings.parameters": "Parameters", "nodeSettings.retryOnFail.description": "If active, the node tries to execute again when it fails", "nodeSettings.retryOnFail.displayName": "Retry On Fail", - "nodeSettings.settings": "Settings", "nodeSettings.theNodeIsNotValidAsItsTypeIsUnknown": "The node is not valid as its type ({nodeType}) is unknown", "nodeSettings.thisNodeDoesNotHaveAnyParameters": "This node does not have any parameters", "nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)", @@ -551,13 +583,8 @@ "runData.copyParameterPath": "Copy Parameter Path", "runData.copyToClipboard": "Copy to Clipboard", "runData.copyValue": "Copy Value", - "runData.dataOfExecution": "Data of Execution", - "runData.dataReturnedByThisNodeWillDisplayHere": "Data returned by this node will display here", - "runData.displayDataAnyway": "Display Data Anyway", "runData.downloadBinaryData": "Download", - "runData.entriesExistButThey": "Entries exist but they do not contain any JSON data", "runData.executeNode": "Execute Node", - "runData.executesThisNodeAfterExecuting": "Executes this {nodeName} node after executing any previous nodes that have not yet returned data", "runData.executionTime": "Execution Time", "runData.fileExtension": "File Extension", "runData.fileName": "File Name", @@ -573,7 +600,6 @@ "runData.showBinaryData": "View", "runData.startTime": "Start Time", "runData.table": "Table", - "runData.theNodeContains": "The node contains {numberOfKb} KB of data.
Displaying it could cause problems.

If you do decide to display it, consider avoiding the JSON view.", "saveButton.save": "@:reusableBaseText.save", "saveButton.saved": "Saved", "saveButton.saving": "Saving", diff --git a/packages/editor-ui/src/plugins/icons.ts b/packages/editor-ui/src/plugins/icons.ts index 22908d518dce0..f7c86d9ec4c8d 100644 --- a/packages/editor-ui/src/plugins/icons.ts +++ b/packages/editor-ui/src/plugins/icons.ts @@ -61,6 +61,7 @@ import { faPause, faPauseCircle, faPen, + faPencilAlt, faPlay, faPlayCircle, faPlus, @@ -158,6 +159,7 @@ addIcon(faNetworkWired); addIcon(faPause); addIcon(faPauseCircle); addIcon(faPen); +addIcon(faPencilAlt); addIcon(faPlay); addIcon(faPlayCircle); addIcon(faPlus); diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index 418a22a33fe76..d465d9fce76b0 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -90,6 +90,7 @@ const state: IRootState = { }, sidebarMenuItems: [], instanceId: '', + nodeMetadata: {}, }; const modules = { @@ -328,6 +329,9 @@ export const store = new Vuex.Store({ if (state.lastSelectedNode === nameData.old) { state.lastSelectedNode = nameData.new; } + + Vue.set(state.nodeMetadata, nameData.new, state.nodeMetadata[nameData.old]); + Vue.delete(state.nodeMetadata, nameData.old); }, resetAllNodesIssues (state) { @@ -418,6 +422,8 @@ export const store = new Vuex.Store({ state.workflow.nodes.push(nodeData); }, removeNode (state, node: INodeUi) { + Vue.delete(state.nodeMetadata, node.name); + for (let i = 0; i < state.workflow.nodes.length; i++) { if (state.workflow.nodes[i].name === node.name) { state.workflow.nodes.splice(i, 1); @@ -470,6 +476,11 @@ export const store = new Vuex.Store({ state.stateIsDirty = true; Vue.set(node, 'parameters', updateInformation.value); + + if (!state.nodeMetadata[node.name]) { + Vue.set(state.nodeMetadata, node.name, {}); + } + Vue.set(state.nodeMetadata[node.name], 'parametersLastUpdatedAt', Date.now()); }, // Node-Index @@ -666,6 +677,10 @@ export const store = new Vuex.Store({ return state.activeExecutions; }, + getParametersLastUpdated: (state): ((name: string) => number | undefined) => { + return (nodeName: string) => state.nodeMetadata[nodeName] && state.nodeMetadata[nodeName].parametersLastUpdatedAt; + }, + getBaseUrl: (state): string => { return state.baseUrl; }, diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index f6f8123ddd289..18c4b83c73974 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -33,7 +33,7 @@ > - +
@@ -344,6 +344,7 @@ export default mixins( pullConnActiveNodeName: null as string | null, pullConnActive: false, dropPrevented: false, + renamingActive: false, }; }, beforeDestroy () { @@ -2235,6 +2236,13 @@ export default mixins( if (currentName === newName) { return; } + + const activeNodeName = this.activeNode && this.activeNode.name; + const isActive = activeNodeName === currentName; + if (isActive) { + this.renamingActive = true; + } + // Check if node-name is unique else find one that is newName = this.getUniqueNodeName({ originalName: newName, @@ -2262,6 +2270,11 @@ export default mixins( // Make sure that the node is selected again this.deselectAllNodes(); this.nodeSelectedByName(newName); + + if (isActive) { + this.$store.commit('setActiveNode', newName); + this.renamingActive = false; + } }, deleteEveryEndpoint () { // Check as it does not exist on first load diff --git a/packages/nodes-base/nodes/Cron/Cron.node.ts b/packages/nodes-base/nodes/Cron/Cron.node.ts index 4a6cfa7d02669..54782b08f5fe6 100644 --- a/packages/nodes-base/nodes/Cron/Cron.node.ts +++ b/packages/nodes-base/nodes/Cron/Cron.node.ts @@ -22,7 +22,7 @@ export class Cron implements INodeType { displayName: 'Cron', name: 'cron', icon: 'fa:calendar', - group: ['trigger'], + group: ['trigger', 'schedule'], version: 1, description: 'Triggers the workflow at a specific time', eventTriggerDescription: '', diff --git a/packages/nodes-base/nodes/Interval/Interval.node.ts b/packages/nodes-base/nodes/Interval/Interval.node.ts index c71317bdd012d..61b78455b6471 100644 --- a/packages/nodes-base/nodes/Interval/Interval.node.ts +++ b/packages/nodes-base/nodes/Interval/Interval.node.ts @@ -12,7 +12,7 @@ export class Interval implements INodeType { displayName: 'Interval', name: 'interval', icon: 'fa:hourglass', - group: ['trigger'], + group: ['trigger', 'schedule'], version: 1, description: 'Triggers the workflow in a given interval', eventTriggerDescription: '',