From 8ac30b127481f23a5fc027a035b60391676b9b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 14:21:45 +0100 Subject: [PATCH 01/63] :zap: Create endpoint for node credential translation --- packages/cli/src/Server.ts | 24 +++++++++++++++++++++++- packages/cli/src/TranslationHelpers.ts | 13 +++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index dbe60af67fd95..4c7a8e56246a9 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -148,7 +148,7 @@ import { InternalHooksManager } from './InternalHooksManager'; import { TagEntity } from './databases/entities/TagEntity'; import { WorkflowEntity } from './databases/entities/WorkflowEntity'; import { NameRequest } from './WorkflowHelpers'; -import { getNodeTranslationPath } from './TranslationHelpers'; +import { getNodeCredentialTranslationPath, getNodeTranslationPath } from './TranslationHelpers'; require('body-parser-xml')(bodyParser); @@ -1176,6 +1176,28 @@ class App { ), ); + this.app.get( + `/${this.restEndpoint}/node-credential-translation`, + ResponseHelper.send( + async ( + req: express.Request & { query: { credentialType: string } }, + res: express.Response, + ): Promise => { + const credTranslationPath = getNodeCredentialTranslationPath({ + locale: this.frontendSettings.defaultLocale, + credentialType: req.query.credentialType, + }); + + try { + const credTranslation = require(credTranslationPath); + return credTranslation; + } catch (error) { + return null; + } + }, + ), + ); + // Returns node information based on node names and versions this.app.post( `/${this.restEndpoint}/node-types`, diff --git a/packages/cli/src/TranslationHelpers.ts b/packages/cli/src/TranslationHelpers.ts index 547c3e9c305aa..c3c8751c90ec2 100644 --- a/packages/cli/src/TranslationHelpers.ts +++ b/packages/cli/src/TranslationHelpers.ts @@ -37,3 +37,16 @@ export async function getNodeTranslationPath( ? join(nodeDir, `v${maxVersion}`, 'translations', `${language}.json`) : join(nodeDir, 'translations', `${language}.json`); } + +export function getNodeCredentialTranslationPath({ + locale, + credentialType, +}: { + locale: string; + credentialType: string; +}): string { + const packagesPath = join(__dirname, '..', '..', '..'); + const credsPath = join(packagesPath, 'nodes-base', 'dist', 'credentials'); + + return join(credsPath, 'translations', locale, `${credentialType}.json`); +} From 4d631a497b8cd5daf153ecbf8a8936e24950dac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 14:22:27 +0100 Subject: [PATCH 02/63] :zap: Add API helper method in FE --- packages/editor-ui/src/Interface.ts | 3 ++- packages/editor-ui/src/components/mixins/restApi.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index a0ab00a93b774..003c0dd049a5d 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -163,6 +163,7 @@ export interface IRestApi { getPastExecutions(filter: object, limit: number, lastId?: string | number, firstId?: string | number): Promise; stopCurrentExecution(executionId: string): Promise; makeRestApiRequest(method: string, endpoint: string, data?: any): Promise; // tslint:disable-line:no-any + getNodeCredentialTranslation(credentialType: string): Promise; getNodeTranslationHeaders(): Promise; getNodeTypes(onlyLatest?: boolean): Promise; getNodesInformation(nodeInfos: INodeTypeNameVersion[]): Promise; @@ -645,9 +646,9 @@ export interface IRootState { activeExecutions: IExecutionsCurrentSummaryExtended[]; activeWorkflows: string[]; activeActions: string[]; + activeCredentialType: string | null; activeNode: string | null; baseUrl: string; - credentialTextRenderKeys: { nodeType: string; credentialType: string; } | null; defaultLocale: string; endpointWebhook: string; endpointWebhookTest: string; diff --git a/packages/editor-ui/src/components/mixins/restApi.ts b/packages/editor-ui/src/components/mixins/restApi.ts index d0df6dc124a27..47b4cf3b238c9 100644 --- a/packages/editor-ui/src/components/mixins/restApi.ts +++ b/packages/editor-ui/src/components/mixins/restApi.ts @@ -79,6 +79,10 @@ export const restApi = Vue.extend({ return self.restApi().makeRestApiRequest('POST', `/executions-current/${executionId}/stop`); }, + getNodeCredentialTranslation: (credentialType): Promise => { + return self.restApi().makeRestApiRequest('GET', '/node-credential-translation', { credentialType }); + }, + getNodeTranslationHeaders: (): Promise => { return self.restApi().makeRestApiRequest('GET', '/node-translation-headers'); }, From f629e6370d1141158758decd48b9d410f63f8a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 14:22:49 +0100 Subject: [PATCH 03/63] :hammer: Add creds JSON files to tsconfig --- packages/nodes-base/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nodes-base/tsconfig.json b/packages/nodes-base/tsconfig.json index cbcbf98937d3d..1d0670252f848 100644 --- a/packages/nodes-base/tsconfig.json +++ b/packages/nodes-base/tsconfig.json @@ -25,6 +25,7 @@ "src/**/*", "nodes/**/*", "nodes/**/*.json", + "credentials/translations/**/*.json", "test/**/*" ], "exclude": [ From 74aacd49af3641066f0c8f093500c24b3800d655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 14:23:16 +0100 Subject: [PATCH 04/63] :zap: Refactor credentials loading --- packages/editor-ui/src/plugins/i18n/index.ts | 34 ++++++++++++++++---- packages/editor-ui/src/store.ts | 13 ++++---- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index c35e5cc8805d6..6b90f3ad64efb 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -9,7 +9,6 @@ const englishBaseText = require('./locales/en'); Vue.use(VueI18n); const REUSABLE_DYNAMIC_TEXT_KEY = 'reusableDynamicText'; -const CREDENTIALS_MODAL_KEY = 'credentialsModal'; const NODE_VIEW_KEY = 'nodeView'; export function I18nPlugin(vue: typeof _Vue, store: Store): void { @@ -81,10 +80,8 @@ export class I18nClass { } credText () { - const { credentialTextRenderKeys: keys } = this.$store.getters; - const nodeType = keys ? keys.nodeType : ''; - const credentialType = keys ? keys.credentialType : ''; - const credentialPrefix = `${nodeType}.${CREDENTIALS_MODAL_KEY}.${credentialType}`; + const credentialType = this.$store.getters.activeCredentialType; + const credentialPrefix = `n8n-nodes-base.credentials.${credentialType}`; const context = this; return { @@ -163,7 +160,7 @@ export class I18nClass { } nodeText () { - const type = this.$store.getters.activeNode.type; + const { type } = this.$store.getters.activeNode; const nodePrefix = `${type}.${NODE_VIEW_KEY}`; const context = this; @@ -319,6 +316,31 @@ export function addNodeTranslation( ); } +export function addNodeCredentialTranslation( + nodeCredentialTranslation: { [key: string]: object }, + language: string, +) { + const oldNodesBase = i18nInstance.messages[language]['n8n-nodes-base'] || {}; + + const newCredentials = { + // @ts-ignore + ...oldNodesBase.credentials, + ...nodeCredentialTranslation, + }; + + const newNodesBase = { + 'n8n-nodes-base': Object.assign( + oldNodesBase, + { credentials: newCredentials }, + ), + }; + + i18nInstance.setLocaleMessage( + language, + Object.assign(i18nInstance.messages[language], newNodesBase), + ); +} + export function addHeaders( headers: INodeTranslationHeaders, language: string, diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index 987ffa87699f8..3944e57f0dbe7 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -46,9 +46,9 @@ const state: IRootState = { activeWorkflows: [], activeActions: [], activeNode: null, + activeCredentialType: null, // @ts-ignore baseUrl: process.env.VUE_APP_URL_BASE_API ? process.env.VUE_APP_URL_BASE_API : (window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH), - credentialTextRenderKeys: null, defaultLocale: 'en', endpointWebhook: 'webhook', endpointWebhookTest: 'webhook-test', @@ -560,8 +560,8 @@ export const store = new Vuex.Store({ setActiveNode (state, nodeName: string) { state.activeNode = nodeName; }, - setCredentialTextRenderKeys (state, renderKeys: { nodeType: string; credentialType: string; }) { - state.credentialTextRenderKeys = renderKeys; + setActiveCredentialType (state, activeCredentialType: string) { + state.activeCredentialType = activeCredentialType; }, setLastSelectedNode (state, nodeName: string) { @@ -647,6 +647,9 @@ export const store = new Vuex.Store({ }, }, getters: { + activeCredentialType: (state): string | null => { + return state.activeCredentialType; + }, isActionActive: (state) => (action: string): boolean => { return state.activeActions.includes(action); @@ -668,10 +671,6 @@ export const store = new Vuex.Store({ return state.activeExecutions; }, - credentialTextRenderKeys: (state): object | null => { - return state.credentialTextRenderKeys; - }, - getBaseUrl: (state): string => { return state.baseUrl; }, From b0da4774b76670e9181b6ea9b4b88f58566e3163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 14:23:40 +0100 Subject: [PATCH 05/63] :zap: Refactor calls in CredentialConfig --- .../CredentialEdit/CredentialConfig.vue | 57 ++++--------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue index 2da9b398144e8..b04cd0e7a27ae 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue @@ -81,7 +81,7 @@ import CopyInput from '../CopyInput.vue'; import CredentialInputs from './CredentialInputs.vue'; import OauthButton from './OauthButton.vue'; import { restApi } from '@/components/mixins/restApi'; -import { addNodeTranslation } from '@/plugins/i18n'; +import { addNodeCredentialTranslation } from '@/plugins/i18n'; import mixins from 'vue-typed-mixins'; export default mixins(restApi).extend({ @@ -128,10 +128,16 @@ export default mixins(restApi).extend({ }, }, async beforeMount() { - if (this.$store.getters.defaultLocale !== 'en') { - await this.findCredentialTextRenderKeys(); - await this.addNodeTranslationForCredential(); - } + if (this.$store.getters.defaultLocale === 'en') return; + + this.$store.commit('setActiveCredentialType', this.credentialType.name); + + const credTranslation = await this.restApi().getNodeCredentialTranslation(this.credentialType.name); + + addNodeCredentialTranslation( + { [this.credentialType.name]: credTranslation }, + this.$store.getters.defaultLocale, + ); }, computed: { appName(): string { @@ -177,47 +183,6 @@ export default mixins(restApi).extend({ }, }, methods: { - /** - * Find the keys needed by the mixin to render credential text, and place them in the Vuex store. - */ - async findCredentialTextRenderKeys() { - const nodeTypes = await this.restApi().getNodeTypes(); - - // credential type name β†’ node type name - const map = nodeTypes.reduce>((acc, cur) => { - if (!cur.credentials) return acc; - - cur.credentials.forEach(cred => { - if (acc[cred.name]) return; - acc[cred.name] = cur.name; - }); - - return acc; - }, {}); - - const renderKeys = { - nodeType: map[this.credentialType.name], - credentialType: this.credentialType.name, - }; - - this.$store.commit('setCredentialTextRenderKeys', renderKeys); - }, - - /** - * Add to the translation object the node translation for the credential in the modal. - */ - async addNodeTranslationForCredential() { - const { nodeType }: { nodeType: string } = this.$store.getters.credentialTextRenderKeys; - const version = await this.getCurrentNodeVersion(nodeType); - const nodeToBeFetched = [{ name: nodeType, version }]; - const nodesInfo = await this.restApi().getNodesInformation(nodeToBeFetched); - const nodeInfo = nodesInfo.pop(); - - if (nodeInfo && nodeInfo.translation) { - addNodeTranslation(nodeInfo.translation, this.$store.getters.defaultLocale); - } - }, - /** * Get the current version for a node type. */ From 0b2afd85f9f361291ced8748543ce4550cda8486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 14:23:56 +0100 Subject: [PATCH 06/63] :pencil2: Add dummy translations --- .../src/plugins/i18n/locales/de.json | 1040 +++++++++++++++++ .../translations/de/githubApi.json | 12 + .../translations/de/githubOAuth2Api.json | 6 + .../nodes/Github/translations/de.json | 147 +++ 4 files changed, 1205 insertions(+) create mode 100644 packages/editor-ui/src/plugins/i18n/locales/de.json create mode 100644 packages/nodes-base/credentials/translations/de/githubApi.json create mode 100644 packages/nodes-base/credentials/translations/de/githubOAuth2Api.json create mode 100644 packages/nodes-base/nodes/Github/translations/de.json diff --git a/packages/editor-ui/src/plugins/i18n/locales/de.json b/packages/editor-ui/src/plugins/i18n/locales/de.json new file mode 100644 index 0000000000000..42ddc38a35fbc --- /dev/null +++ b/packages/editor-ui/src/plugins/i18n/locales/de.json @@ -0,0 +1,1040 @@ +{ + "reusableBaseText": { + "cancel": "πŸ‡©πŸ‡ͺ Cancel", + "name": "πŸ‡©πŸ‡ͺ Name", + "save": "πŸ‡©πŸ‡ͺ Save" + }, + "reusableDynamicText": { + "oauth2": { + "clientId": "πŸ‡©πŸ‡ͺ Client ID", + "clientSecret": "πŸ‡©πŸ‡ͺ Client Secret" + } + }, + "about": { + "aboutN8n": "πŸ‡©πŸ‡ͺ About n8n", + "apacheWithCommons20Clause": "πŸ‡©πŸ‡ͺ Apache 2.0 with Commons Clause", + "close": "πŸ‡©πŸ‡ͺ Close", + "license": "πŸ‡©πŸ‡ͺ License", + "n8nVersion": "πŸ‡©πŸ‡ͺ n8n Version", + "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}\" credential?" + } + }, + "connection": "πŸ‡©πŸ‡ͺ Connection", + "couldNotFindCredentialOfType": "πŸ‡©πŸ‡ͺ Could not find credential of type", + "couldNotFindCredentialWithId": "πŸ‡©πŸ‡ͺ Could not find credential with ID", + "details": "πŸ‡©πŸ‡ͺ Details", + "delete": "πŸ‡©πŸ‡ͺ Delete", + "saving": "πŸ‡©πŸ‡ͺ Saving", + "showError": { + "createCredential": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem creating credential" + }, + "deleteCredential": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem deleting credential" + }, + "generateAuthorizationUrl": { + "message": "πŸ‡©πŸ‡ͺ There was a problem generating the authorization URL", + "title": "πŸ‡©πŸ‡ͺ OAuth Authorization Error" + }, + "loadCredential": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem loading credential" + }, + "updateCredential": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem updating credential" + } + }, + "showMessage": { + "message": "πŸ‡©πŸ‡ͺ The credential {savedCredentialName} was deleted!", + "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} credential?" + }, + "createNewCredential": "πŸ‡©πŸ‡ͺ Create New Credential", + "created": "πŸ‡©πŸ‡ͺ Created", + "credentials": "πŸ‡©πŸ‡ͺ Credentials", + "deleteCredential": "πŸ‡©πŸ‡ͺ Delete Credential", + "editCredential": "πŸ‡©πŸ‡ͺ Edit Credential", + "name": "@:reusableBaseText.name", + "operations": "πŸ‡©πŸ‡ͺ Operations", + "showError": { + "deleteCredential": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem deleting credential" + } + }, + "showMessage": { + "message": "πŸ‡©πŸ‡ͺ The credential {credentialName} got deleted!", + "title": "πŸ‡©πŸ‡ͺ Credential deleted" + }, + "type": "πŸ‡©πŸ‡ͺ Type", + "updated": "πŸ‡©πŸ‡ͺ Updated", + "yourSavedCredentials": "πŸ‡©πŸ‡ͺ Your saved 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 executions?" + }, + "deleteSelected": "πŸ‡©πŸ‡ͺ Delete Selected", + "error": "πŸ‡©πŸ‡ͺ Error", + "filters": "πŸ‡©πŸ‡ͺ Filters", + "loadMore": "πŸ‡©πŸ‡ͺ Load More", + "mode": "πŸ‡©πŸ‡ͺ Mode", + "modes": { + "error": "πŸ‡©πŸ‡ͺ error", + "manual": "πŸ‡©πŸ‡ͺ manual", + "retry": "πŸ‡©πŸ‡ͺ retry", + "trigger": "πŸ‡©πŸ‡ͺ trigger" + }, + "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": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem deleting executions" + }, + "loadMore": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem loading workflows" + }, + "loadWorkflows": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem loading workflows" + }, + "refreshData": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem loading data" + }, + "retryExecution": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem with retry" + }, + "stopExecution": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem stopping execution" + } + }, + "showMessage": { + "handleDeleteSelected": { + "message": "πŸ‡©πŸ‡ͺ The executions got deleted!", + "title": "πŸ‡©πŸ‡ͺ Execution deleted" + }, + "retrySuccessfulFalse": { + "message": "πŸ‡©πŸ‡ͺ The retry was not successful!", + "title": "πŸ‡©πŸ‡ͺ Retry unsuccessful" + }, + "retrySuccessfulTrue": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Retry successful" + }, + "stopExecution": { + "message": "πŸ‡©πŸ‡ͺ The execution with the ID {activeExecutionId} got stopped!", + "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": "πŸ‡©πŸ‡ͺ The workflow cannot be edited as a past execution gets displayed. To make changed either open the original workflow of which the execution gets displayed or save it under a new name first.", + "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 the workflow {workflowName}?" + }, + "workflowNew": { + "cancelButtonText": "", + "confirmButtonText": "πŸ‡©πŸ‡ͺ Yes, switch workflows and forget changes", + "headline": "πŸ‡©πŸ‡ͺ Save your Changes?", + "message": "πŸ‡©πŸ‡ͺ When you switch workflows your current workflow changes will be lost." + } + }, + "credentials": "πŸ‡©πŸ‡ͺ Credentials", + "delete": "πŸ‡©πŸ‡ͺ Delete", + "download": "πŸ‡©πŸ‡ͺ Download", + "duplicate": "πŸ‡©πŸ‡ͺ Duplicate", + "executions": "πŸ‡©πŸ‡ͺ Executions", + "help": "πŸ‡©πŸ‡ͺ Help", + "helpMenuItems": { + "documentation": "πŸ‡©πŸ‡ͺ Documentation", + "forum": "πŸ‡©πŸ‡ͺ Forum", + "workflows": "πŸ‡©πŸ‡ͺ Workflows" + }, + "importFromFile": "πŸ‡©πŸ‡ͺ Import from File", + "importFromUrl": "πŸ‡©πŸ‡ͺ Import from URL", + "new": "πŸ‡©πŸ‡ͺ New", + "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": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem stopping execution" + } + }, + "showMessage": { + "handleFileImport": { + "message": "πŸ‡©πŸ‡ͺ The file does not contain valid JSON data.", + "title": "πŸ‡©πŸ‡ͺ Could not import file" + }, + "handleSelect1": { + "message": "πŸ‡©πŸ‡ͺ The workflow {workflowName} got deleted.", + "title": "πŸ‡©πŸ‡ͺ Workflow got deleted" + }, + "handleSelect2": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Workflow created" + }, + "handleSelect3": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Workflow created" + }, + "stopExecution": { + "message": "πŸ‡©πŸ‡ͺ Execution with ID {executionId} got stopped!", + "title": "πŸ‡©πŸ‡ͺ Execution stopped" + } + }, + "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 till {date} {time}", + "theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall": "πŸ‡©πŸ‡ͺ The node is waiting indefinitely for an incoming webhook call." + }, + "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", + "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": { + "branches": "πŸ‡©πŸ‡ͺ Branches, core triggers, merge data", + "http": "πŸ‡©πŸ‡ͺ HTTP Requests (API calls), date and time, scrape HTML", + "manipulate": "πŸ‡©πŸ‡ͺ Manipulate data fields, run code", + "work": "πŸ‡©πŸ‡ͺ Work with CSV, XML, text, images etc." + }, + "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": { + "message": "", + "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, the node will return an empty item even if the
node returns no data during an initial execution. Be careful setting
this on IF-Nodes as it could cause an infinite loop.", + "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 recieves.", + "displayName": "πŸ‡©πŸ‡ͺ Execute Once" + }, + "maxTries": { + "description": "πŸ‡©πŸ‡ͺ Number of times Retry On Fail should attempt to execute the node
before stopping and returning the execution as failed.", + "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 a failed attempt
multiple times until it succeeds.", + "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. Value in ms.", + "displayName": "πŸ‡©πŸ‡ͺ Wait Between Tries" + } + }, + "nodeView": { + "addNode": "πŸ‡©πŸ‡ͺ Add node", + "confirmMessage": { + "beforeRouteLeave": { + "cancelButtonText": "", + "confirmButtonText": "πŸ‡©πŸ‡ͺ Yes, switch workflows and forget changes", + "headline": "πŸ‡©πŸ‡ͺ Save your Changes?", + "message": "πŸ‡©πŸ‡ͺ When you switch workflows your current workflow changes will be lost." + }, + "initView": { + "cancelButtonText": "", + "confirmButtonText": "πŸ‡©πŸ‡ͺ Yes, switch workflows and forget changes", + "headline": "πŸ‡©πŸ‡ͺ Save your Changes?", + "message": "πŸ‡©πŸ‡ͺ When you switch workflows your current workflow changes will be lost." + }, + "receivedCopyPasteData": { + "cancelButtonText": "", + "confirmButtonText": "πŸ‡©πŸ‡ͺ Yes, import!", + "headline": "πŸ‡©πŸ‡ͺ Import Workflow from URL?", + "message": "πŸ‡©πŸ‡ͺ Import workflow from this URL:
{plainTextData}" + } + }, + "couldntImportWorkflow": "Couldn't import workflow", + "deletesTheCurrentExecutionData": "πŸ‡©πŸ‡ͺ Deletes the current Execution Data.", + "executesTheWorkflowFromTheStartOrWebhookNode": "πŸ‡©πŸ‡ͺ Executes the Workflow from the Start or Webhook Node.", + "itLooksLikeYouHaveBeenEditingSomething": "πŸ‡©πŸ‡ͺ It looks like you have been editing something. If you leave before saving, your changes will be lost.", + "loadingTemplate": "πŸ‡©πŸ‡ͺ Loading template", + "moreInfo": "πŸ‡©πŸ‡ͺ More info", + "noNodesGivenToAdd": "πŸ‡©πŸ‡ͺ No nodes given to add!", + "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": { + "message": "πŸ‡©πŸ‡ͺ There was a problem loading the workflow data from URL", + "title": "πŸ‡©πŸ‡ͺ Problem loading workflow" + }, + "importWorkflowData": { + "message": "πŸ‡©πŸ‡ͺ There was a problem importing workflow data", + "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": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem loading execution" + }, + "openWorkflow": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem opening workflow" + }, + "stopExecution": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem stopping execution" + }, + "stopWaitingForWebhook": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem deleting the test-webhook" + } + }, + "showMessage": { + "addNodeButton": { + "message": "πŸ‡©πŸ‡ͺ Node of type {nodeTypeName} could not be created as it is not known.", + "title": "πŸ‡©πŸ‡ͺ Could not create node!" + }, + "keyDown": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Workflow created" + }, + "showMaxNodeTypeError": { + "message": { + "plural": "πŸ‡©πŸ‡ͺ Node cannot be created because in a workflow max. {maxNodes} nodes of type {nodeTypeDataDisplayName} are allowed!", + "singular": "πŸ‡©πŸ‡ͺ Node cannot be created because in a workflow max. {maxNodes} node of type {nodeTypeDataDisplayName} is allowed!" + }, + "title": "πŸ‡©πŸ‡ͺ Could not create node!" + }, + "stopExecutionCatch": { + "message": "πŸ‡©πŸ‡ͺ Unable to stop operation in time. Workflow finished executing already.", + "title": "πŸ‡©πŸ‡ͺ Workflow finished executing" + }, + "stopExecutionTry": { + "message": "πŸ‡©πŸ‡ͺ The execution with the id {executionId} got stopped!", + "title": "πŸ‡©πŸ‡ͺ Execution stopped" + }, + "stopWaitingForWebhook": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Webhook got deleted" + } + }, + "stopCurrentExecution": "πŸ‡©πŸ‡ͺ Stop current execution", + "stopWaitingForWebhookCall": "πŸ‡©πŸ‡ͺ Stop waiting for Webhook call", + "stoppingCurrentExecution": "πŸ‡©πŸ‡ͺ Stopping current execution", + "thereWasAProblemLoadingTheNodeParametersOfNode": "πŸ‡©πŸ‡ͺ There was a problem loading the node-parameters of 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": { + "message": "πŸ‡©πŸ‡ͺ The webhook URL was successfully copied!", + "title": "πŸ‡©πŸ‡ͺ 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 expression!", + "parameterHasIssues": "πŸ‡©πŸ‡ͺ Parameter: \"{shortPath}\" has issues!", + "parameterHasIssuesAndExpression": "πŸ‡©πŸ‡ͺ Parameter: \"{shortPath}\" has issues and expression!", + "parameterOptions": "πŸ‡©πŸ‡ͺ Parameter Options", + "refreshList": "πŸ‡©πŸ‡ͺ Refresh List", + "removeExpression": "πŸ‡©πŸ‡ͺ Remove Expression", + "resetValue": "πŸ‡©πŸ‡ͺ Reset Value", + "selectDateAndTime": "πŸ‡©πŸ‡ͺ Select date and time" + }, + "parameterInputExpanded": { + "openDocs": "πŸ‡©πŸ‡ͺ Open docs", + "thisFieldIsRequired": "πŸ‡©πŸ‡ͺ This field is required." + }, + "parameterInputList": { + "delete": "πŸ‡©πŸ‡ͺ Delete", + "deleteParameter": "πŸ‡©πŸ‡ͺ Delete Parameter", + "parameterOptions": "πŸ‡©πŸ‡ͺ Parameter Options" + }, + "personalizationModal": { + "automationConsulting": "πŸ‡©πŸ‡ͺ Automation consulting", + "continue": "πŸ‡©πŸ‡ͺ Continue", + "eCommerce": "πŸ‡©πŸ‡ͺ eCommerce", + "errorWhileSubmittingResults": "πŸ‡©πŸ‡ͺ Error while submitting results", + "executiveTeam": "πŸ‡©πŸ‡ͺ Executive Team", + "finance": "πŸ‡©πŸ‡ͺ finance", + "getStarted": "πŸ‡©πŸ‡ͺ Get started", + "government": "πŸ‡©πŸ‡ͺ Government", + "healthcare": "πŸ‡©πŸ‡ͺ Healthcare", + "howAreYourCodingSkills": "πŸ‡©πŸ‡ͺ How are your coding skills", + "howBigIsYourCompany": "πŸ‡©πŸ‡ͺ How big is your company", + "hr": "πŸ‡©πŸ‡ͺ HR", + "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", + "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", + "neverCoded": "πŸ‡©πŸ‡ͺ 0 (Never coded)", + "operations": "πŸ‡©πŸ‡ͺ operations", + "otherPleaseSpecify": "πŸ‡©πŸ‡ͺ Other (please specify)", + "people": "πŸ‡©πŸ‡ͺ people", + "proCoder": "πŸ‡©πŸ‡ͺ Pro coder", + "product": "πŸ‡©πŸ‡ͺ Product", + "saas": "πŸ‡©πŸ‡ͺ SaaS", + "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", + "thanks": "πŸ‡©πŸ‡ͺ Thanks!", + "theseQuestionsHelpUs": "πŸ‡©πŸ‡ͺ These questions help us tailor n8n to you", + "whichIndustriesIsYourCompanyIn": "πŸ‡©πŸ‡ͺ Which industries is your company in?", + "whichOfTheseAreasDoYouMainlyWorkIn": "πŸ‡©πŸ‡ͺ Which of these areas do you mainly work in?" + }, + "pushConnection": { + "showMessage": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Workflow executed successfully" + } + }, + "pushConnectionTracker": { + "cannotConnectToServer": "πŸ‡©πŸ‡ͺ Cannot connect to server.
It is either down or you have a connection issue.
It should reconnect automatically once the issue is resolved.", + "connectionLost": "πŸ‡©πŸ‡ͺ Connection lost" + }, + "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", + "showBinaryData": "πŸ‡©πŸ‡ͺ Show Binary Data", + "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, avoid 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": "πŸ‡©πŸ‡ͺ New tag was not created" + }, + "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 {escapedName} tag", + "title": "πŸ‡©πŸ‡ͺ New tag was not created" + }, + "onDelete": { + "message": "πŸ‡©πŸ‡ͺ A problem occurred when trying to delete the {escapedName} tag", + "title": "πŸ‡©πŸ‡ͺ Tag was not deleted" + }, + "onUpdate": { + "message": "πŸ‡©πŸ‡ͺ A problem occurred when trying to update the {escapedName} tag", + "title": "πŸ‡©πŸ‡ͺ Tag was not updated" + } + }, + "showMessage": { + "onDelete": { + "message": "πŸ‡©πŸ‡ͺ A problem occurred when trying to delete the {escapedName} tag", + "title": "πŸ‡©πŸ‡ͺ Tag was deleted" + }, + "onUpdate": { + "message": "πŸ‡©πŸ‡ͺ The {escapedOldName} tag was successfully updated to {escapedName}", + "title": "πŸ‡©πŸ‡ͺ Tag was 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" + }, + "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", + "confirmMessage": { + "cancelButtonText": "", + "confirmButtonText": "πŸ‡©πŸ‡ͺ Yes, activate and save!", + "headline": "πŸ‡©πŸ‡ͺ Activate and save?", + "message": "πŸ‡©πŸ‡ͺ When you activate the workflow all currently unsaved changes of the workflow will be saved." + }, + "deactivateWorkflow": "πŸ‡©πŸ‡ͺ Deactivate workflow", + "showError": { + "message": "πŸ‡©πŸ‡ͺ There was a problem and the workflow could not be {newStateName}", + "title": "πŸ‡©πŸ‡ͺ Problem" + }, + "showMessage": { + "activeChangedNodesIssuesExistTrue": { + "message": "πŸ‡©πŸ‡ͺ It is only possible to activate a workflow when all issues on all nodes got resolved!", + "title": "πŸ‡©πŸ‡ͺ Problem activating workflow" + }, + "activeChangedWorkflowIdUndefined": { + "message": "πŸ‡©πŸ‡ͺ The workflow did not get saved yet so cannot be set active!", + "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": "πŸ‡©πŸ‡ͺ Sorry there was a problem. No error got found to display." + }, + "title": "πŸ‡©πŸ‡ͺ Problem activating workflow" + } + }, + "theWorkflowIsSetToBeActiveBut": "πŸ‡©πŸ‡ͺ The workflow is set to be active but could not be started.
Click to display error message." + }, + "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": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem saving workflow" + } + }, + "workflowOpen": { + "active": "πŸ‡©πŸ‡ͺ Active", + "confirmMessage": { + "cancelButtonText": "", + "confirmButtonText": "πŸ‡©πŸ‡ͺ Yes, switch workflows and forget changes", + "headline": "πŸ‡©πŸ‡ͺ Save your Changes?", + "message": "πŸ‡©πŸ‡ͺ When you switch workflows your current workflow changes will be lost." + }, + "created": "πŸ‡©πŸ‡ͺ Created", + "name": "@:reusableBaseText.name", + "openWorkflow": "πŸ‡©πŸ‡ͺ Open Workflow", + "searchWorkflows": "πŸ‡©πŸ‡ͺ Search workflows...", + "showError": { + "message": "πŸ‡©πŸ‡ͺ There was a problem loading the workflows", + "title": "πŸ‡©πŸ‡ͺ Problem loading workflows" + }, + "showMessage": { + "message": "πŸ‡©πŸ‡ͺ This is the current workflow", + "title": "πŸ‡©πŸ‡ͺ Already open" + }, + "updated": "πŸ‡©πŸ‡ͺ Updated" + }, + "workflowRun": { + "noActiveConnectionToTheServer": "πŸ‡©πŸ‡ͺ No active connection to server. It is maybe down.", + "showError": { + "message": "", + "title": "πŸ‡©πŸ‡ͺ Problem running workflow" + }, + "showMessage": { + "message": "πŸ‡©πŸ‡ͺ The workflow has issues. Please fix them first", + "title": "πŸ‡©πŸ‡ͺ Workflow cannot be executed" + } + }, + "workflowSettings": { + "defaultTimezone": "πŸ‡©πŸ‡ͺ Default - {defaultTimezoneValue}", + "defaultTimezoneNotValid": "πŸ‡©πŸ‡ͺ Default Timezone not valid", + "errorWorkflow": "πŸ‡©πŸ‡ͺ Error Workflow", + "helpTexts": { + "errorWorkflow": "πŸ‡©πŸ‡ͺ The workflow to run in case the current one fails.
To function correctly that workflow has to contain an 'Error Trigger' node!", + "executionTimeout": "πŸ‡©πŸ‡ͺ After what time the workflow should timeout.", + "executionTimeoutToggle": "πŸ‡©πŸ‡ͺ Cancel workflow execution after defined time", + "saveDataErrorExecution": "πŸ‡©πŸ‡ͺ If data of executions should be saved in case they failed.", + "saveDataSuccessExecution": "πŸ‡©πŸ‡ͺ If data of executions should be saved in case they succeed.", + "saveExecutionProgress": "πŸ‡©πŸ‡ͺ If data should be saved after each node, allowing you to resume in case of errors from where it stopped. May increase latency.", + "saveManualExecutions": "πŸ‡©πŸ‡ͺ If data of executions should be saved when started manually from the editor.", + "timezone": "πŸ‡©πŸ‡ͺ The timezone in which the workflow should run. Gets for example used by 'Cron' node." + }, + "hours": "πŸ‡©πŸ‡ͺ hours", + "minutes": "πŸ‡©πŸ‡ͺ minutes", + "noWorkflow": "πŸ‡©πŸ‡ͺ - No Workflow -", + "save": "@:reusableBaseText.save", + "saveDataErrorExecution": "πŸ‡©πŸ‡ͺ Save Data Error Execution", + "saveDataErrorExecutionOptions": { + "defaultSave": "πŸ‡©πŸ‡ͺ Default - {defaultValue}", + "doNotSave": "πŸ‡©πŸ‡ͺ Do not save", + "save": "@:reusableBaseText.save" + }, + "saveDataSuccessExecution": "πŸ‡©πŸ‡ͺ Save Data Success Execution", + "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": "πŸ‡©πŸ‡ͺ Set timeout is exceeding the maximum timeout!", + "title": "πŸ‡©πŸ‡ͺ Problem saving settings" + }, + "saveSettings3": { + "message": "πŸ‡©πŸ‡ͺ There was a problem saving the settings", + "title": "πŸ‡©πŸ‡ͺ Problem saving settings" + } + }, + "showMessage": { + "saveSettings": { + "message": "πŸ‡©πŸ‡ͺ The workflow settings got saved!", + "title": "πŸ‡©πŸ‡ͺ Settings saved" + } + }, + "timeoutAfter": "πŸ‡©πŸ‡ͺ Timeout After", + "timeoutWorkflow": "πŸ‡©πŸ‡ͺ Timeout Workflow", + "timezone": "πŸ‡©πŸ‡ͺ Timezone" + } +} \ No newline at end of file diff --git a/packages/nodes-base/credentials/translations/de/githubApi.json b/packages/nodes-base/credentials/translations/de/githubApi.json new file mode 100644 index 0000000000000..be542eadd1af8 --- /dev/null +++ b/packages/nodes-base/credentials/translations/de/githubApi.json @@ -0,0 +1,12 @@ +{ + "server": { + "displayName": "πŸ‡©πŸ‡ͺ Github Server", + "description": "πŸ‡©πŸ‡ͺ The server to connect to. Only has to be set if Github Enterprise is used." + }, + "user": { + "placeholder": "πŸ‡©πŸ‡ͺ JΓΌrgen" + }, + "accessToken": { + "placeholder": "πŸ‡©πŸ‡ͺ 123" + } +} \ No newline at end of file diff --git a/packages/nodes-base/credentials/translations/de/githubOAuth2Api.json b/packages/nodes-base/credentials/translations/de/githubOAuth2Api.json new file mode 100644 index 0000000000000..4087092df7eb8 --- /dev/null +++ b/packages/nodes-base/credentials/translations/de/githubOAuth2Api.json @@ -0,0 +1,6 @@ +{ + "server": { + "displayName": "πŸ‡©πŸ‡ͺ Github Server", + "description": "πŸ‡©πŸ‡ͺ The server to connect to. Only has to be set if Github Enterprise is used." + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Github/translations/de.json b/packages/nodes-base/nodes/Github/translations/de.json new file mode 100644 index 0000000000000..6a9ff486f4081 --- /dev/null +++ b/packages/nodes-base/nodes/Github/translations/de.json @@ -0,0 +1,147 @@ +{ + "githubTrigger": { + "header": { + "displayName": "πŸ‡©πŸ‡ͺ GitHub Trigger", + "description": "πŸ‡©πŸ‡ͺ Listen to GitHub events" + } + }, + "github": { + "header": { + "displayName": "πŸ‡©πŸ‡ͺ GitHub", + "description": "πŸ‡©πŸ‡ͺ Consume GitHub API" + }, + "credentialsModal": { + "githubOAuth2Api": { + "server": { + "displayName": "πŸ‡©πŸ‡ͺ Github Server", + "description": "πŸ‡©πŸ‡ͺ The server to connect to. Only has to be set if Github Enterprise is used." + } + }, + "githubApi": { + "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" + } + } + }, + "nodeView": { + "authentication": { + "displayName": "πŸ‡©πŸ‡ͺ Authentication", + "options": { + "accessToken": { + "displayName": "πŸ‡©πŸ‡ͺ Access Token" + }, + "oAuth2": { + "displayName": "πŸ‡©πŸ‡ͺ OAuth2" + } + } + }, + "resource": { + "displayName": "πŸ‡©πŸ‡ͺ Resource", + "description": "πŸ‡©πŸ‡ͺ The resource to operate on.", + "options": { + "issue": { + "displayName": "πŸ‡©πŸ‡ͺ Issue" + }, + "file": { + "displayName": "πŸ‡©πŸ‡ͺ File" + }, + "repository": { + "displayName": "πŸ‡©πŸ‡ͺ Repository" + }, + "release": { + "displayName": "πŸ‡©πŸ‡ͺ Release" + }, + "review": { + "displayName": "πŸ‡©πŸ‡ͺ Review" + }, + "user": { + "displayName": "πŸ‡©πŸ‡ͺ User" + } + } + }, + "operation": { + "displayName": "πŸ‡©πŸ‡ͺ Operation", + "options": { + "create": { + "displayName": "πŸ‡©πŸ‡ͺ Create", + "description": "πŸ‡©πŸ‡ͺ Create a new issue." + }, + "get": { + "displayName": "πŸ‡©πŸ‡ͺ Get", + "description": "πŸ‡©πŸ‡ͺ Get the data of a single issue." + } + } + }, + "owner": { + "displayName": "πŸ‡©πŸ‡ͺ Repository Owner", + "placeholder": "πŸ‡©πŸ‡ͺ n8n-io", + "description": "πŸ‡©πŸ‡ͺ Owner of the repository." + }, + "repository": { + "displayName": "πŸ‡©πŸ‡ͺ Repository Name", + "placeholder": "πŸ‡©πŸ‡ͺ n8n" + }, + "title": { + "displayName": "πŸ‡©πŸ‡ͺ Title" + }, + "body": { + "displayName": "πŸ‡©πŸ‡ͺ Body" + }, + "labels": { + "displayName": "πŸ‡©πŸ‡ͺ Labels", + "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Label" + }, + "assignees": { + "displayName": "πŸ‡©πŸ‡ͺ Assignees", + "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Assignee" + }, + "label": { + "displayName": "πŸ‡©πŸ‡ͺ Label", + "description": "πŸ‡©πŸ‡ͺ Label to add to issue." + }, + "assignee": { + "displayName": "πŸ‡©πŸ‡ͺ Assignee", + "description": "πŸ‡©πŸ‡ͺ User to assign issue to." + }, + "additionalParameters": { + "displayName": "πŸ‡©πŸ‡ͺ Additional Fields", + "placeholder": "πŸ‡©πŸ‡ͺ Add Field", + "options": { + "author": { + "displayName": "πŸ‡©πŸ‡ͺ Author" + }, + "branch": { + "displayName": "πŸ‡©πŸ‡ͺ Branch" + }, + "committer": { + "displayName": "πŸ‡©πŸ‡ͺ Committer" + } + } + }, + "author": { + "displayName": "πŸ‡©πŸ‡ͺ Author" + }, + "branch": { + "displayName": "πŸ‡©πŸ‡ͺ Branch" + }, + "committer": { + "displayName": "πŸ‡©πŸ‡ͺ Committer" + }, + "name": { + "displayName": "πŸ‡©πŸ‡ͺ Name", + "description": "πŸ‡©πŸ‡ͺ The name of the author of the commit." + }, + "email": { + "displayName": "πŸ‡©πŸ‡ͺ Email", + "description": "πŸ‡©πŸ‡ͺ The email of the author of the commit." + } + } + } +} \ No newline at end of file From 70c0aa48d697e49eb153e259268f334873319a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 16:52:34 +0100 Subject: [PATCH 07/63] :zap: Split translations per node --- packages/cli/src/Server.ts | 8 +- packages/cli/src/TranslationHelpers.ts | 10 +- packages/editor-ui/src/plugins/i18n/index.ts | 5 +- packages/editor-ui/src/views/NodeView.vue | 2 +- packages/nodes-base/gulpfile.js | 70 ++++----- .../nodes/Github/translations/de.json | 147 ------------------ .../nodes/Github/translations/de/github.json | 119 ++++++++++++++ .../Github/translations/de/githubTrigger.json | 6 + 8 files changed, 171 insertions(+), 196 deletions(-) delete mode 100644 packages/nodes-base/nodes/Github/translations/de.json create mode 100644 packages/nodes-base/nodes/Github/translations/de/github.json create mode 100644 packages/nodes-base/nodes/Github/translations/de/githubTrigger.json diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 4c7a8e56246a9..540b92cd87291 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -1221,13 +1221,17 @@ class App { nodeTypes: INodeTypeDescription[], ) { const { description, sourcePath } = NodeTypes().getWithSourcePath(name, version); - const translationPath = await getNodeTranslationPath(sourcePath, defaultLocale); + const translationPath = await getNodeTranslationPath( + sourcePath, + description.name, + defaultLocale, + ); try { const translation = await readFile(translationPath, 'utf8'); description.translation = JSON.parse(translation); } catch (error) { - // ignore - no translation at expected translation path + // ignore - no translation exists at path } nodeTypes.push(description); diff --git a/packages/cli/src/TranslationHelpers.ts b/packages/cli/src/TranslationHelpers.ts index c3c8751c90ec2..2a8e7010165ef 100644 --- a/packages/cli/src/TranslationHelpers.ts +++ b/packages/cli/src/TranslationHelpers.ts @@ -27,15 +27,17 @@ async function getMaxVersion(from: string) { } export async function getNodeTranslationPath( - nodeSourcePath: string, + sourcePath: string, + nodeType: string, language: string, ): Promise { - const nodeDir = dirname(nodeSourcePath); + const nodeDir = dirname(sourcePath); + const shortNodeType = nodeType.replace('n8n-nodes-base.', ''); const maxVersion = await getMaxVersion(nodeDir); return maxVersion - ? join(nodeDir, `v${maxVersion}`, 'translations', `${language}.json`) - : join(nodeDir, 'translations', `${language}.json`); + ? join(nodeDir, `v${maxVersion}`, 'translations', language, `${shortNodeType}.json`) + : join(nodeDir, 'translations', language, `${shortNodeType}.json`); } export function getNodeCredentialTranslationPath({ diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index 6b90f3ad64efb..0c3eb4b2714d7 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -301,12 +301,15 @@ export async function loadLanguage(language?: string) { export function addNodeTranslation( nodeTranslation: { [key: string]: object }, + nodeType: string, language: string, ) { + const shortNodeType = nodeType.replace('n8n-nodes-base.', ''); + const newNodesBase = { 'n8n-nodes-base': Object.assign( i18nInstance.messages[language]['n8n-nodes-base'] || {}, - nodeTranslation, + { [shortNodeType]: nodeTranslation }, ), }; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index a477968f4976a..fd76c896821b6 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -2670,7 +2670,7 @@ export default mixins( nodesInfo.forEach(nodeInfo => { if (nodeInfo.translation) { - addNodeTranslation(nodeInfo.translation, this.$store.getters.defaultLocale); + addNodeTranslation(nodeInfo.translation, nodeInfo.name, this.$store.getters.defaultLocale); } }); diff --git a/packages/nodes-base/gulpfile.js b/packages/nodes-base/gulpfile.js index d47044799c5ba..07b01e26ddea3 100644 --- a/packages/nodes-base/gulpfile.js +++ b/packages/nodes-base/gulpfile.js @@ -16,7 +16,7 @@ function copyIcons() { task('build:translations', writeHeaders); /** - * Write all node translation headers at `/dist/nodes/headers.js`. + * Write node translation headers to single file at `/dist/nodes/headers.js`. */ function writeHeaders(done) { const { N8N_DEFAULT_LOCALE: locale } = process.env; @@ -26,65 +26,48 @@ function writeHeaders(done) { if (!locale || locale === 'en') { log('No translation required - Skipping translations build...'); return done(); - }; - - const paths = getTranslationPaths(); - const headers = getHeaders(paths); + } - const headersDestinationPath = path.join(__dirname, 'dist', 'nodes', 'headers.js'); + const nodeTranslationPaths = getNodeTranslationPaths(); + const headers = getHeaders(nodeTranslationPaths); + const headersDistPath = path.join(__dirname, 'dist', 'nodes', 'headers.js'); - writeDestinationFile(headersDestinationPath, headers); + writeDistFile(headers, headersDistPath); - log('Headers translation file written to:'); - log(headersDestinationPath, { bulletpoint: true }); + log('Headers file written to:'); + log(headersDistPath, { bulletpoint: true }); done(); } -function getTranslationPaths() { - const destinationPaths = require('./package.json').n8n.nodes; +function getNodeTranslationPaths() { + const nodeDistPaths = require('./package.json').n8n.nodes; const { N8N_DEFAULT_LOCALE: locale } = process.env; - const seen = {}; - return destinationPaths.reduce((acc, cur) => { - const sourcePath = path.join( + return nodeDistPaths.reduce((acc, cur) => { + const nodeTranslationPath = path.join( __dirname, cur.split('/').slice(1, -1).join('/'), 'translations', - `${locale}.json`, + locale, + toTranslationFile(cur), ); - if (existsSync(sourcePath) && !seen[sourcePath]) { - seen[sourcePath] = true; - - const destinationPath = path.join( - __dirname, - cur.split('/').slice(0, -1).join('/'), - 'translations', - `${locale}.json`, - ); - - acc.push({ - source: sourcePath, - destination: destinationPath, - }); + if (existsSync(nodeTranslationPath)) { + acc.push(nodeTranslationPath); }; return acc; }, []); } -function getHeaders(paths) { - return paths.reduce((acc, cur) => { - const translation = require(cur.source); - const nodeTypes = Object.keys(translation); +function getHeaders(nodeTranslationPaths) { + return nodeTranslationPaths.reduce((acc, cur) => { + const { header } = require(cur); + const nodeType = cur.split('/').pop().replace('.json', ''); - for (const nodeType of nodeTypes) { - const { header } = translation[nodeType]; - - if (isValidHeader(header, ALLOWED_HEADER_KEYS)) { - acc[nodeType] = header; - } + if (isValidHeader(header, ALLOWED_HEADER_KEYS)) { + acc[nodeType] = header; } return acc; @@ -96,6 +79,11 @@ function getHeaders(paths) { // helpers // ---------------------------------- +function toTranslationFile(distPath) { + const raw = distPath.split('/').pop().replace('.node', '') + 'on'; + return raw.charAt(0).toLowerCase() + raw.slice(1); +} + function isValidHeader(header, allowedHeaderKeys) { if (!header) return false; @@ -105,9 +93,9 @@ function isValidHeader(header, allowedHeaderKeys) { headerKeys.every(key => allowedHeaderKeys.includes(key)); } -function writeDestinationFile(destinationPath, data) { +function writeDistFile(data, distPath) { writeFile( - destinationPath, + distPath, `module.exports = ${JSON.stringify(data, null, 2)}`, ); } diff --git a/packages/nodes-base/nodes/Github/translations/de.json b/packages/nodes-base/nodes/Github/translations/de.json deleted file mode 100644 index 6a9ff486f4081..0000000000000 --- a/packages/nodes-base/nodes/Github/translations/de.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "githubTrigger": { - "header": { - "displayName": "πŸ‡©πŸ‡ͺ GitHub Trigger", - "description": "πŸ‡©πŸ‡ͺ Listen to GitHub events" - } - }, - "github": { - "header": { - "displayName": "πŸ‡©πŸ‡ͺ GitHub", - "description": "πŸ‡©πŸ‡ͺ Consume GitHub API" - }, - "credentialsModal": { - "githubOAuth2Api": { - "server": { - "displayName": "πŸ‡©πŸ‡ͺ Github Server", - "description": "πŸ‡©πŸ‡ͺ The server to connect to. Only has to be set if Github Enterprise is used." - } - }, - "githubApi": { - "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" - } - } - }, - "nodeView": { - "authentication": { - "displayName": "πŸ‡©πŸ‡ͺ Authentication", - "options": { - "accessToken": { - "displayName": "πŸ‡©πŸ‡ͺ Access Token" - }, - "oAuth2": { - "displayName": "πŸ‡©πŸ‡ͺ OAuth2" - } - } - }, - "resource": { - "displayName": "πŸ‡©πŸ‡ͺ Resource", - "description": "πŸ‡©πŸ‡ͺ The resource to operate on.", - "options": { - "issue": { - "displayName": "πŸ‡©πŸ‡ͺ Issue" - }, - "file": { - "displayName": "πŸ‡©πŸ‡ͺ File" - }, - "repository": { - "displayName": "πŸ‡©πŸ‡ͺ Repository" - }, - "release": { - "displayName": "πŸ‡©πŸ‡ͺ Release" - }, - "review": { - "displayName": "πŸ‡©πŸ‡ͺ Review" - }, - "user": { - "displayName": "πŸ‡©πŸ‡ͺ User" - } - } - }, - "operation": { - "displayName": "πŸ‡©πŸ‡ͺ Operation", - "options": { - "create": { - "displayName": "πŸ‡©πŸ‡ͺ Create", - "description": "πŸ‡©πŸ‡ͺ Create a new issue." - }, - "get": { - "displayName": "πŸ‡©πŸ‡ͺ Get", - "description": "πŸ‡©πŸ‡ͺ Get the data of a single issue." - } - } - }, - "owner": { - "displayName": "πŸ‡©πŸ‡ͺ Repository Owner", - "placeholder": "πŸ‡©πŸ‡ͺ n8n-io", - "description": "πŸ‡©πŸ‡ͺ Owner of the repository." - }, - "repository": { - "displayName": "πŸ‡©πŸ‡ͺ Repository Name", - "placeholder": "πŸ‡©πŸ‡ͺ n8n" - }, - "title": { - "displayName": "πŸ‡©πŸ‡ͺ Title" - }, - "body": { - "displayName": "πŸ‡©πŸ‡ͺ Body" - }, - "labels": { - "displayName": "πŸ‡©πŸ‡ͺ Labels", - "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Label" - }, - "assignees": { - "displayName": "πŸ‡©πŸ‡ͺ Assignees", - "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Assignee" - }, - "label": { - "displayName": "πŸ‡©πŸ‡ͺ Label", - "description": "πŸ‡©πŸ‡ͺ Label to add to issue." - }, - "assignee": { - "displayName": "πŸ‡©πŸ‡ͺ Assignee", - "description": "πŸ‡©πŸ‡ͺ User to assign issue to." - }, - "additionalParameters": { - "displayName": "πŸ‡©πŸ‡ͺ Additional Fields", - "placeholder": "πŸ‡©πŸ‡ͺ Add Field", - "options": { - "author": { - "displayName": "πŸ‡©πŸ‡ͺ Author" - }, - "branch": { - "displayName": "πŸ‡©πŸ‡ͺ Branch" - }, - "committer": { - "displayName": "πŸ‡©πŸ‡ͺ Committer" - } - } - }, - "author": { - "displayName": "πŸ‡©πŸ‡ͺ Author" - }, - "branch": { - "displayName": "πŸ‡©πŸ‡ͺ Branch" - }, - "committer": { - "displayName": "πŸ‡©πŸ‡ͺ Committer" - }, - "name": { - "displayName": "πŸ‡©πŸ‡ͺ Name", - "description": "πŸ‡©πŸ‡ͺ The name of the author of the commit." - }, - "email": { - "displayName": "πŸ‡©πŸ‡ͺ Email", - "description": "πŸ‡©πŸ‡ͺ The email of the author of the commit." - } - } - } -} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Github/translations/de/github.json b/packages/nodes-base/nodes/Github/translations/de/github.json new file mode 100644 index 0000000000000..4e5f2a95c3252 --- /dev/null +++ b/packages/nodes-base/nodes/Github/translations/de/github.json @@ -0,0 +1,119 @@ +{ + "header": { + "displayName": "πŸ‡©πŸ‡ͺ GitHub", + "description": "πŸ‡©πŸ‡ͺ Consume GitHub API" + }, + "nodeView": { + "authentication": { + "displayName": "πŸ‡©πŸ‡ͺ Authentication", + "options": { + "accessToken": { + "displayName": "πŸ‡©πŸ‡ͺ Access Token" + }, + "oAuth2": { + "displayName": "πŸ‡©πŸ‡ͺ OAuth2" + } + } + }, + "resource": { + "displayName": "πŸ‡©πŸ‡ͺ Resource", + "description": "πŸ‡©πŸ‡ͺ The resource to operate on.", + "options": { + "issue": { + "displayName": "πŸ‡©πŸ‡ͺ Issue" + }, + "file": { + "displayName": "πŸ‡©πŸ‡ͺ File" + }, + "repository": { + "displayName": "πŸ‡©πŸ‡ͺ Repository" + }, + "release": { + "displayName": "πŸ‡©πŸ‡ͺ Release" + }, + "review": { + "displayName": "πŸ‡©πŸ‡ͺ Review" + }, + "user": { + "displayName": "πŸ‡©πŸ‡ͺ User" + } + } + }, + "operation": { + "displayName": "πŸ‡©πŸ‡ͺ Operation", + "options": { + "create": { + "displayName": "πŸ‡©πŸ‡ͺ Create", + "description": "πŸ‡©πŸ‡ͺ Create a new issue." + }, + "get": { + "displayName": "πŸ‡©πŸ‡ͺ Get", + "description": "πŸ‡©πŸ‡ͺ Get the data of a single issue." + } + } + }, + "owner": { + "displayName": "πŸ‡©πŸ‡ͺ Repository Owner", + "placeholder": "πŸ‡©πŸ‡ͺ n8n-io", + "description": "πŸ‡©πŸ‡ͺ Owner of the repository." + }, + "repository": { + "displayName": "πŸ‡©πŸ‡ͺ Repository Name", + "placeholder": "πŸ‡©πŸ‡ͺ n8n" + }, + "title": { + "displayName": "πŸ‡©πŸ‡ͺ Title" + }, + "body": { + "displayName": "πŸ‡©πŸ‡ͺ Body" + }, + "labels": { + "displayName": "πŸ‡©πŸ‡ͺ Labels", + "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Label" + }, + "assignees": { + "displayName": "πŸ‡©πŸ‡ͺ Assignees", + "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Assignee" + }, + "label": { + "displayName": "πŸ‡©πŸ‡ͺ Label", + "description": "πŸ‡©πŸ‡ͺ Label to add to issue." + }, + "assignee": { + "displayName": "πŸ‡©πŸ‡ͺ Assignee", + "description": "πŸ‡©πŸ‡ͺ User to assign issue to." + }, + "additionalParameters": { + "displayName": "πŸ‡©πŸ‡ͺ Additional Fields", + "placeholder": "πŸ‡©πŸ‡ͺ Add Field", + "options": { + "author": { + "displayName": "πŸ‡©πŸ‡ͺ Author" + }, + "branch": { + "displayName": "πŸ‡©πŸ‡ͺ Branch" + }, + "committer": { + "displayName": "πŸ‡©πŸ‡ͺ Committer" + } + } + }, + "author": { + "displayName": "πŸ‡©πŸ‡ͺ Author" + }, + "branch": { + "displayName": "πŸ‡©πŸ‡ͺ Branch" + }, + "committer": { + "displayName": "πŸ‡©πŸ‡ͺ Committer" + }, + "name": { + "displayName": "πŸ‡©πŸ‡ͺ Name", + "description": "πŸ‡©πŸ‡ͺ The name of the author of the commit." + }, + "email": { + "displayName": "πŸ‡©πŸ‡ͺ Email", + "description": "πŸ‡©πŸ‡ͺ The email of the author of the commit." + } + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Github/translations/de/githubTrigger.json b/packages/nodes-base/nodes/Github/translations/de/githubTrigger.json new file mode 100644 index 0000000000000..01e38caa708ec --- /dev/null +++ b/packages/nodes-base/nodes/Github/translations/de/githubTrigger.json @@ -0,0 +1,6 @@ +{ + "header": { + "displayName": "πŸ‡©πŸ‡ͺ GitHub Trigger", + "description": "πŸ‡©πŸ‡ͺ Listen to GitHub events" + } +} \ No newline at end of file From 788bf9153707a468f4600c416a33e86fecdb22ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Dec 2021 17:32:31 +0100 Subject: [PATCH 08/63] :fire: Remove deprecated method --- packages/editor-ui/src/plugins/i18n/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index 0c3eb4b2714d7..8b09258351680 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -42,10 +42,6 @@ export class I18nClass { return this.i18n.te(key); } - number(value: number, options: VueI18n.FormattedNumberPartType) { - return this.i18n.n(value, options); - } - shortNodeType(longNodeType: string) { return longNodeType.replace('n8n-nodes-base.', ''); } From 86149db254af048fa687fb53677c1c1817826e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 23 Dec 2021 10:06:52 +0100 Subject: [PATCH 09/63] :zap: Refactor nesting in collections --- .../components/FixedCollectionParameter.vue | 2 +- .../src/components/ParameterInput.vue | 2 +- .../src/components/ParameterInputFull.vue | 4 +- packages/editor-ui/src/plugins/i18n/index.ts | 116 ++++++++++++------ packages/editor-ui/src/plugins/i18n/utils.ts | 50 ++++++++ packages/editor-ui/src/views/NodeView.vue | 7 +- .../nodes-base/nodes/Github/Github.node.ts | 3 + .../nodes/Github/translations/de/github.json | 79 +++++++----- 8 files changed, 191 insertions(+), 72 deletions(-) create mode 100644 packages/editor-ui/src/plugins/i18n/utils.ts diff --git a/packages/editor-ui/src/components/FixedCollectionParameter.vue b/packages/editor-ui/src/components/FixedCollectionParameter.vue index eec60d2cf2155..e950f986e6fb4 100644 --- a/packages/editor-ui/src/components/FixedCollectionParameter.vue +++ b/packages/editor-ui/src/components/FixedCollectionParameter.vue @@ -6,7 +6,7 @@
): void { const i18n = new I18nClass(store); @@ -51,16 +56,17 @@ export class I18nClass { // ---------------------------------- /** - * Render a string of base text, i.e. a string with a fixed path to the localized value in the base text object. Optionally allows for [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) when the localized value contains a string between curly braces. + * Render a string of base text, i.e. a string with a fixed path to the localized value. Optionally allows for [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) when the localized value contains a string between curly braces. */ baseText( - key: string, options?: { interpolate: { [key: string]: string } }, + key: string, + options?: { interpolate: { [key: string]: string } }, ): string { return this.i18n.t(key, options && options.interpolate).toString(); } /** - * Render a string of dynamic text, i.e. a string with a constructed path to the localized value in the node text object, in the credentials modal, in the node view, or in the headers. Unlike in `baseText`, the fallback has to be set manually for dynamic text. + * Render a string of dynamic text, i.e. a string with a constructed path to the localized value. */ private dynamicRender( { key, fallback }: { key: string; fallback: string; }, @@ -68,13 +74,13 @@ export class I18nClass { return this.i18n.te(key) ? this.i18n.t(key).toString() : fallback; } - /** - * Render a string of dynamic header text, used in the nodes panel and in the node view. - */ headerText(arg: { key: string; fallback: string; }) { return this.dynamicRender(arg); } + /** + * Namespace for methods to render text in the credentials modal. + */ credText () { const credentialType = this.$store.getters.activeCredentialType; const credentialPrefix = `n8n-nodes-base.credentials.${credentialType}`; @@ -83,14 +89,14 @@ export class I18nClass { return { /** - * Display name for a top-level parameter in the credentials modal. + * Display name for a top-level param. */ topParameterDisplayName( { name: parameterName, displayName }: { name: string; displayName: string; }, ) { if (['clientId', 'clientSecret'].includes(parameterName)) { return context.dynamicRender({ - key: `${REUSABLE_DYNAMIC_TEXT_KEY}.oauth2.${parameterName}`, + key: `reusableDynamicText.oauth2.${parameterName}`, fallback: displayName, }); } @@ -102,7 +108,7 @@ export class I18nClass { }, /** - * Description for a top-level parameter in the credentials modal. + * Description for a top-level param. */ topParameterDescription( { name: parameterName, description }: { name: string; description: string; }, @@ -114,7 +120,7 @@ export class I18nClass { }, /** - * Display name for an option inside an `options` or `multiOptions` parameter in the credentials modal. + * Display name for an option inside an `options` or `multiOptions` param. */ optionsOptionDisplayName( { name: parameterName }: { name: string; }, @@ -127,7 +133,7 @@ export class I18nClass { }, /** - * Description for an option inside an `options` or `multiOptions` parameter in the credentials modal. + * Description for an option inside an `options` or `multiOptions` param. */ optionsOptionDescription( { name: parameterName }: { name: string; }, @@ -140,7 +146,7 @@ export class I18nClass { }, /** - * Placeholder for a `string` or `collection` or `fixedCollection` parameter in the credentials modal. + * Placeholder for a `string` or `collection` or `fixedCollection` param. * - For a `string` parameter, the placeholder is unselectable greyed-out sample text. * - For a `collection` or `fixedCollection` parameter, the placeholder is the button text. */ @@ -155,38 +161,62 @@ export class I18nClass { }; } + /** + * Namespace for methods to render text in the node view. + */ nodeText () { - const { type } = this.$store.getters.activeNode; - const nodePrefix = `${type}.${NODE_VIEW_KEY}`; + const nodeType = this.shortNodeType(this.$store.getters.activeNode.type); + const nodePrefix = `n8n-nodes-base.nodes.${nodeType}.nodeView`; const context = this; return { /** - * Display name for a top-level parameter in the node view. + * Display name for a top-level param. */ topParameterDisplayName( { name: parameterName, displayName }: { name: string; displayName: string; }, + path?: string, + { isSectionTitle } = { isSectionTitle: false }, ) { + let middleKey = parameterName; + + if (isSectionTitle) { + middleKey = toSectionTitleKey(path!, parameterName); + } else if (isForFixedCollection(path)) { + middleKey = toFixedCollectionKey(path); + } else if (isForCollection(path)) { + middleKey = toCollectionKey(path); + } + return context.dynamicRender({ - key: `${nodePrefix}.${parameterName}.displayName`, + key: `${nodePrefix}.${middleKey}.displayName`, fallback: displayName, }); }, /** - * Description for a top-level parameter in the node view in the node view. + * Description for a top-level parameter. */ topParameterDescription( { name: parameterName, description }: { name: string; description: string; }, + path?: string, ) { + let middleKey = parameterName; + + if (isForFixedCollection(path)) { + middleKey = toFixedCollectionKey(path); + } else if (isForCollection(path)) { + middleKey = toCollectionKey(path); + } + return context.dynamicRender({ - key: `${nodePrefix}.${parameterName}.description`, + key: `${nodePrefix}.${middleKey}.description`, fallback: description, }); }, /** - * Display name for an option inside a `collection` or `fixedCollection` parameter in the node view. + * Display name for an option inside a `collection` or `fixedCollection` param. */ collectionOptionDisplayName( { name: parameterName }: { name: string; }, @@ -199,7 +229,7 @@ export class I18nClass { }, /** - * Display name for an option inside an `options` or `multiOptions` parameter in the node view. + * Display name for an option inside an `options` or `multiOptions` param. */ optionsOptionDisplayName( { name: parameterName }: { name: string; }, @@ -212,7 +242,7 @@ export class I18nClass { }, /** - * Description for an option inside an `options` or `multiOptions` parameter in the node view. + * Description for an option inside an `options` or `multiOptions` param. */ optionsOptionDescription( { name: parameterName }: { name: string; }, @@ -225,7 +255,7 @@ export class I18nClass { }, /** - * Text for a button to add another option inside a `collection` or `fixedCollection` parameter having`multipleValues: true` in the node view. + * Text for a button to add another option inside a `collection` or `fixedCollection` param having `multipleValues: true`. */ multipleValueButtonText( { name: parameterName, typeOptions: { multipleValueButtonText } }: @@ -238,15 +268,24 @@ export class I18nClass { }, /** - * Placeholder for a `string` or `collection` or `fixedCollection` parameter in the node view. + * Placeholder for a `string` or `collection` or `fixedCollection` param. * - For a `string` parameter, the placeholder is unselectable greyed-out sample text. * - For a `collection` or `fixedCollection` parameter, the placeholder is the button text. */ placeholder( { name: parameterName, placeholder }: { name: string; placeholder: string; }, + path?: string, ) { + let middleKey = parameterName; + + if (isForFixedCollection(path)) { + middleKey = toFixedCollectionKey(path); + } else if (isForCollection(path)) { + middleKey = toCollectionKey(path); + } + return context.dynamicRender({ - key: `${nodePrefix}.${parameterName}.placeholder`, + key: `${nodePrefix}.${middleKey}.placeholder`, fallback: placeholder, }); }, @@ -296,16 +335,21 @@ export async function loadLanguage(language?: string) { } export function addNodeTranslation( - nodeTranslation: { [key: string]: object }, - nodeType: string, + nodeTranslation: { [nodeType: string]: object }, language: string, ) { - const shortNodeType = nodeType.replace('n8n-nodes-base.', ''); + const oldNodesBase = i18nInstance.messages[language]['n8n-nodes-base'] || {}; + + const updatedNodes = { + // @ts-ignore + ...oldNodesBase.nodes, + ...nodeTranslation, + }; const newNodesBase = { 'n8n-nodes-base': Object.assign( - i18nInstance.messages[language]['n8n-nodes-base'] || {}, - { [shortNodeType]: nodeTranslation }, + oldNodesBase, + { nodes: updatedNodes }, ), }; @@ -316,12 +360,12 @@ export function addNodeTranslation( } export function addNodeCredentialTranslation( - nodeCredentialTranslation: { [key: string]: object }, + nodeCredentialTranslation: { [credentialType: string]: object }, language: string, ) { const oldNodesBase = i18nInstance.messages[language]['n8n-nodes-base'] || {}; - const newCredentials = { + const updatedCredentials = { // @ts-ignore ...oldNodesBase.credentials, ...nodeCredentialTranslation, @@ -330,7 +374,7 @@ export function addNodeCredentialTranslation( const newNodesBase = { 'n8n-nodes-base': Object.assign( oldNodesBase, - { credentials: newCredentials }, + { credentials: updatedCredentials }, ), }; @@ -348,4 +392,4 @@ export function addHeaders( language, Object.assign(i18nInstance.messages[language], { headers }), ); -} \ No newline at end of file +} diff --git a/packages/editor-ui/src/plugins/i18n/utils.ts b/packages/editor-ui/src/plugins/i18n/utils.ts new file mode 100644 index 0000000000000..6c35d5d27ffae --- /dev/null +++ b/packages/editor-ui/src/plugins/i18n/utils.ts @@ -0,0 +1,50 @@ +/** + * Check if a param path indicates that the param is inside a `collection` param. + * Example: `label` in `parameters.labels[0].label` + */ +export function isForCollection(path: string | undefined): path is string { + if (!path) return false; + + return /[\]\]]/.test(path); +} + +/** + * Check if a param path indicates that the param is inside a `fixedCollection` param. + * Example: `email` in `parameters.additionalParameters.author.email` + * // TODO i18n: deeper nesting e.g. slack node + */ +export function isForFixedCollection(path: string | undefined): path is string { + if (!path) return false; + + console.log(path); + + return path.split('.').length === 4; +} + +/** + * Generate the render key for the section title inside a `fixedCollection` param. + */ +export function toSectionTitleKey(path: string, optionName: string) { + const fixedCollectionName = removeParams(path); + + return `${fixedCollectionName}.options.${optionName}`; +} + +/** + * Generate the render key for a param inside a `collection` param. + */ +export function toCollectionKey(path: string) { + return removeParams(path).replace(/\[\d\]/, '.options'); +} + +/** + * Generate the render key for a param inside a `fixedCollection` param. + * TODO i18n: deeper nesting e.g. slack node + */ +export function toFixedCollectionKey(path: string) { + const [ fixedCollectionName, optionName, valueName ] = removeParams(path).split('.'); + + return `${fixedCollectionName}.options.${optionName}.values.${valueName}`; +} + +const removeParams = (path: string) => path.replace('parameters.', ''); diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index fd76c896821b6..cd08d30ee0fd9 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -2670,7 +2670,12 @@ export default mixins( nodesInfo.forEach(nodeInfo => { if (nodeInfo.translation) { - addNodeTranslation(nodeInfo.translation, nodeInfo.name, this.$store.getters.defaultLocale); + const shortNodeType = nodeInfo.name.replace('n8n-nodes-base.', ''); + + addNodeTranslation( + { [shortNodeType]: nodeInfo.translation }, + this.$store.getters.defaultLocale, + ); } }); diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index 9228f58dc3271..522c2d372fd3a 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -539,6 +539,7 @@ export class Github implements INodeType { type: 'string', default: '', description: 'The name of the author of the commit.', + placeholder: 'TODO DELETE Sample name', }, { displayName: 'Email', @@ -546,6 +547,7 @@ export class Github implements INodeType { type: 'string', default: '', description: 'The email of the author of the commit.', + placeholder: 'TODO DELETE Sample placeholder', }, ], }, @@ -700,6 +702,7 @@ export class Github implements INodeType { displayName: 'Label', name: 'label', type: 'string', + placeholder: 'TODO DELETE Sample placeholder', default: '', description: 'Label to add to issue.', }, diff --git a/packages/nodes-base/nodes/Github/translations/de/github.json b/packages/nodes-base/nodes/Github/translations/de/github.json index 4e5f2a95c3252..056b911e94856 100644 --- a/packages/nodes-base/nodes/Github/translations/de/github.json +++ b/packages/nodes-base/nodes/Github/translations/de/github.json @@ -69,51 +69,68 @@ }, "labels": { "displayName": "πŸ‡©πŸ‡ͺ Labels", - "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Label" + "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Label", + "options": { + "label": { + "displayName": "πŸ‡©πŸ‡ͺ Label", + "description": "πŸ‡©πŸ‡ͺ Label to add to issue.", + "placeholder": "πŸ‡©πŸ‡ͺ Some placeholder" + } + } }, "assignees": { "displayName": "πŸ‡©πŸ‡ͺ Assignees", - "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Assignee" - }, - "label": { - "displayName": "πŸ‡©πŸ‡ͺ Label", - "description": "πŸ‡©πŸ‡ͺ Label to add to issue." - }, - "assignee": { - "displayName": "πŸ‡©πŸ‡ͺ Assignee", - "description": "πŸ‡©πŸ‡ͺ User to assign issue to." + "multipleValueButtonText": "πŸ‡©πŸ‡ͺ Add Assignee", + "options": { + "assignee": { + "displayName": "πŸ‡©πŸ‡ͺ Assignee", + "description": "πŸ‡©πŸ‡ͺ User to assign issue to.", + "placeholder": "πŸ‡©πŸ‡ͺ Some placeholder" + } + } }, "additionalParameters": { - "displayName": "πŸ‡©πŸ‡ͺ Additional Fields", + "displayName": "πŸ‡©πŸ‡ͺ Additional Parameters", "placeholder": "πŸ‡©πŸ‡ͺ Add Field", "options": { "author": { - "displayName": "πŸ‡©πŸ‡ͺ Author" + "displayName": "πŸ‡©πŸ‡ͺ Author", + "values": { + "name": { + "displayName": "πŸ‡©πŸ‡ͺ Name", + "description": "πŸ‡©πŸ‡ͺ The name of the author of the commit.", + "placeholder": "πŸ‡©πŸ‡ͺ Hans" + }, + "email": { + "displayName": "πŸ‡©πŸ‡ͺ Email", + "description": "πŸ‡©πŸ‡ͺ The email of the author of the commit.", + "placeholder": "πŸ‡©πŸ‡ͺ hans@n8n.io" + } + } }, "branch": { - "displayName": "πŸ‡©πŸ‡ͺ Branch" + "displayName": "πŸ‡©πŸ‡ͺ Branch", + "values": { + "branch": { + "displayName": "πŸ‡©πŸ‡ͺ Branch", + "description": "πŸ‡©πŸ‡ͺ The branch to commit to." + } + } }, "committer": { - "displayName": "πŸ‡©πŸ‡ͺ Committer" + "displayName": "πŸ‡©πŸ‡ͺ Committer", + "values": { + "name": { + "displayName": "πŸ‡©πŸ‡ͺ Name", + "description": "πŸ‡©πŸ‡ͺ The name of the author of the commit." + }, + "email": { + "displayName": "πŸ‡©πŸ‡ͺ Email", + "description": "πŸ‡©πŸ‡ͺ The email of the author of the commit." + } + } } } - }, - "author": { - "displayName": "πŸ‡©πŸ‡ͺ Author" - }, - "branch": { - "displayName": "πŸ‡©πŸ‡ͺ Branch" - }, - "committer": { - "displayName": "πŸ‡©πŸ‡ͺ Committer" - }, - "name": { - "displayName": "πŸ‡©πŸ‡ͺ Name", - "description": "πŸ‡©πŸ‡ͺ The name of the author of the commit." - }, - "email": { - "displayName": "πŸ‡©πŸ‡ͺ Email", - "description": "πŸ‡©πŸ‡ͺ The email of the author of the commit." } } } \ No newline at end of file From 8072ea7b1f86a95da8884fcebc36bf3148616c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 23 Dec 2021 10:45:25 +0100 Subject: [PATCH 10/63] :truck: Rename topParameter methods for accuracy --- packages/editor-ui/src/components/CodeEdit.vue | 4 ++-- .../src/components/FixedCollectionParameter.vue | 2 +- .../editor-ui/src/components/MultipleParameter.vue | 4 ++-- .../src/components/ParameterInputExpanded.vue | 4 ++-- .../editor-ui/src/components/ParameterInputFull.vue | 4 ++-- .../editor-ui/src/components/ParameterInputList.vue | 6 +++--- packages/editor-ui/src/components/TextEdit.vue | 4 ++-- packages/editor-ui/src/plugins/i18n/index.ts | 12 ++++++------ 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/editor-ui/src/components/CodeEdit.vue b/packages/editor-ui/src/components/CodeEdit.vue index 7d29bc0a0317e..90117e2d3321d 100644 --- a/packages/editor-ui/src/components/CodeEdit.vue +++ b/packages/editor-ui/src/components/CodeEdit.vue @@ -1,8 +1,8 @@