mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 00:32:14 +03:00
✨ Render node strings
This commit is contained in:
parent
2d8e158012
commit
7fc0395e95
@ -5,6 +5,7 @@
|
||||
import {
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
INodeTypeDescription,
|
||||
INodeTypes,
|
||||
INodeVersionedType,
|
||||
NodeHelpers,
|
||||
@ -18,7 +19,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
// polling nodes the polling times
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const nodeTypeData of Object.values(nodeTypes)) {
|
||||
const nodeType = NodeHelpers.getVersionedTypeNode(nodeTypeData.type);
|
||||
const nodeType = NodeHelpers.getVersionedNodeType(nodeTypeData.type);
|
||||
const applyParameters = NodeHelpers.getSpecialNodeParameters(nodeType);
|
||||
|
||||
if (applyParameters.length) {
|
||||
@ -39,11 +40,26 @@ class NodeTypesClass implements INodeTypes {
|
||||
return this.nodeTypes[nodeType].type;
|
||||
}
|
||||
|
||||
getWithPath(
|
||||
nodeTypeName: string,
|
||||
version: number,
|
||||
): { description: INodeTypeDescription } & { sourcePath: string } {
|
||||
const nodeType = this.nodeTypes[nodeTypeName];
|
||||
|
||||
if (!nodeType) {
|
||||
throw new Error(`Unknown node type: ${nodeTypeName}`);
|
||||
}
|
||||
|
||||
const { description } = NodeHelpers.getVersionedNodeType(nodeType.type, version);
|
||||
|
||||
return { description: { ...description }, sourcePath: nodeType.sourcePath };
|
||||
}
|
||||
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
if (this.nodeTypes[nodeType] === undefined) {
|
||||
throw new Error(`The node-type "${nodeType}" is not known!`);
|
||||
}
|
||||
return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version);
|
||||
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
import * as express from 'express';
|
||||
import { readFileSync } from 'fs';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
||||
import { FindManyOptions, getConnectionManager, In, IsNull, LessThanOrEqual, Not } from 'typeorm';
|
||||
import * as bodyParser from 'body-parser';
|
||||
@ -144,6 +145,7 @@ import { InternalHooksManager } from './InternalHooksManager';
|
||||
import { TagEntity } from './databases/entities/TagEntity';
|
||||
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
||||
import { NameRequest } from './WorkflowHelpers';
|
||||
import { getTranslationPath } from './TranslationHelpers';
|
||||
|
||||
require('body-parser-xml')(bodyParser);
|
||||
|
||||
@ -1152,13 +1154,13 @@ class App {
|
||||
|
||||
if (onlyLatest) {
|
||||
allNodes.forEach((nodeData) => {
|
||||
const nodeType = NodeHelpers.getVersionedTypeNode(nodeData);
|
||||
const nodeType = NodeHelpers.getVersionedNodeType(nodeData);
|
||||
const nodeInfo: INodeTypeDescription = getNodeDescription(nodeType);
|
||||
returnData.push(nodeInfo);
|
||||
});
|
||||
} else {
|
||||
allNodes.forEach((nodeData) => {
|
||||
const allNodeTypes = NodeHelpers.getVersionedTypeNodeAll(nodeData);
|
||||
const allNodeTypes = NodeHelpers.getVersionedNodeTypeAll(nodeData);
|
||||
allNodeTypes.forEach((element) => {
|
||||
const nodeInfo: INodeTypeDescription = getNodeDescription(element);
|
||||
returnData.push(nodeInfo);
|
||||
@ -1179,15 +1181,28 @@ class App {
|
||||
const nodeInfos = _.get(req, 'body.nodeInfos', []) as INodeTypeNameVersion[];
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
const returnData: INodeTypeDescription[] = [];
|
||||
nodeInfos.forEach((nodeInfo) => {
|
||||
const nodeType = nodeTypes.getByNameAndVersion(nodeInfo.name, nodeInfo.version);
|
||||
if (nodeType?.description) {
|
||||
returnData.push(nodeType.description);
|
||||
}
|
||||
});
|
||||
const language = config.get('defaultLocale') ?? req.headers['accept-language'] ?? 'en';
|
||||
|
||||
return returnData;
|
||||
if (language === 'en') {
|
||||
return nodeInfos.reduce<INodeTypeDescription[]>((acc, { name, version }) => {
|
||||
const { description } = nodeTypes.getByNameAndVersion(name, version);
|
||||
if (description) acc.push(description);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// add node translations where available
|
||||
return nodeInfos.reduce<INodeTypeDescription[]>((acc, { name, version }) => {
|
||||
const { description, sourcePath } = nodeTypes.getWithPath(name, version);
|
||||
const mainTranslationPath = getTranslationPath(sourcePath, language);
|
||||
|
||||
if (description && existsSync(mainTranslationPath)) {
|
||||
description.translation = require(mainTranslationPath);
|
||||
}
|
||||
|
||||
if (description) acc.push(description);
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
8
packages/cli/src/TranslationHelpers.ts
Normal file
8
packages/cli/src/TranslationHelpers.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { join, dirname } from 'path';
|
||||
|
||||
/**
|
||||
* Retrieve the path to the translation file for a node.
|
||||
*/
|
||||
export function getTranslationPath(nodeSourcePath: string, language: string): string {
|
||||
return join(dirname(nodeSourcePath), 'translations', `${language}.js`);
|
||||
}
|
@ -726,7 +726,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
||||
|
||||
getAll(): INodeType[] {
|
||||
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedTypeNode(data.type));
|
||||
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type));
|
||||
}
|
||||
|
||||
getByName(nodeType: string): INodeType {
|
||||
@ -734,7 +734,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
}
|
||||
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version);
|
||||
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -584,6 +584,7 @@ export interface IRootState {
|
||||
activeActions: string[];
|
||||
activeNode: string | null;
|
||||
baseUrl: string;
|
||||
credentialTextRenderKeys: { nodeType: string; credentialType: string; } | null;
|
||||
defaultLocale: string;
|
||||
endpointWebhook: string;
|
||||
endpointWebhookTest: string;
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div v-if="dialogVisible">
|
||||
<el-dialog :visible="dialogVisible" append-to-body :close-on-click-modal="false" width="80%" :title="`Edit ${parameter.displayName}`" :before-close="closeDialog">
|
||||
<el-dialog :visible="dialogVisible" append-to-body :close-on-click-modal="false" width="80%" :title="`${$baseText('codeEdit.edit')} ${$nodeText.topParameterDisplayName(parameter)}`" :before-close="closeDialog">
|
||||
<div class="ignore-key-press">
|
||||
<n8n-input-label :label="parameter.displayName">
|
||||
<n8n-input-label :label="$nodeText.topParameterDisplayName(parameter)">
|
||||
<div :class="$style.editor" @keydown.stop>
|
||||
<prism-editor :lineNumbers="true" :code="value" :readonly="isReadOnly" @change="valueChanged" language="js"></prism-editor>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<n8n-option
|
||||
v-for="item in parameterOptions"
|
||||
:key="item.name"
|
||||
:label="item.displayName"
|
||||
:label="$nodeText.collectionOptionDisplayName(parameter, item)"
|
||||
:value="item.name">
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
@ -67,7 +67,8 @@ export default mixins(
|
||||
},
|
||||
computed: {
|
||||
getPlaceholderText (): string {
|
||||
return this.parameter.placeholder ? this.parameter.placeholder : this.$baseText('collectionParameter.choose');
|
||||
const placeholder = this.$nodeText.placeholder(this.parameter);
|
||||
return placeholder ? placeholder : this.$baseText('collectionParameter.choose');
|
||||
},
|
||||
getProperties (): INodeProperties[] {
|
||||
const returnProperties = [];
|
||||
|
@ -72,7 +72,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ICredentialType } from 'n8n-workflow';
|
||||
import { ICredentialType, INodeTypeDescription } from 'n8n-workflow';
|
||||
import { getAppNameFromCredType } from '../helpers';
|
||||
|
||||
import Vue from 'vue';
|
||||
@ -81,10 +81,11 @@ import CopyInput from '../CopyInput.vue';
|
||||
import CredentialInputs from './CredentialInputs.vue';
|
||||
import OauthButton from './OauthButton.vue';
|
||||
import { renderText } from '../mixins/renderText';
|
||||
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { addNodeTranslation } from '@/i18n';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(renderText).extend({
|
||||
export default mixins(renderText, restApi).extend({
|
||||
name: 'CredentialConfig',
|
||||
components: {
|
||||
Banner,
|
||||
@ -94,6 +95,7 @@ export default mixins(renderText).extend({
|
||||
},
|
||||
props: {
|
||||
credentialType: {
|
||||
type: Object,
|
||||
},
|
||||
credentialProperties: {
|
||||
type: Array,
|
||||
@ -126,6 +128,10 @@ export default mixins(renderText).extend({
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
async beforeMount() {
|
||||
await this.findCredentialTextRenderKeys();
|
||||
await this.addNodeTranslationForCredential();
|
||||
},
|
||||
computed: {
|
||||
appName(): string {
|
||||
if (!this.credentialType) {
|
||||
@ -136,7 +142,7 @@ export default mixins(renderText).extend({
|
||||
(this.credentialType as ICredentialType).displayName,
|
||||
);
|
||||
|
||||
return appName || "the service you're connecting to";
|
||||
return appName || this.$baseText('credentialEdit.credentialConfig.theServiceYouReConnectingTo');
|
||||
},
|
||||
credentialTypeName(): string {
|
||||
return (this.credentialType as ICredentialType).name;
|
||||
@ -170,6 +176,62 @@ export default mixins(renderText).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<Record<string, string>>((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 being viewed.
|
||||
*/
|
||||
async addNodeTranslationForCredential() {
|
||||
// TODO i18n: Check if node translation has already been added (via NodeView)
|
||||
|
||||
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.
|
||||
*/
|
||||
async getCurrentNodeVersion(targetNodeType: string) {
|
||||
const { allNodeTypes }: { allNodeTypes: INodeTypeDescription[] } = this.$store.getters;
|
||||
const found = allNodeTypes.find(nodeType => nodeType.name === targetNodeType);
|
||||
|
||||
return found ? found.version : 1;
|
||||
},
|
||||
|
||||
onDataChange (event: { name: string; value: string | number | boolean | Date | null }): void {
|
||||
this.$emit('change', event);
|
||||
},
|
||||
|
@ -43,7 +43,7 @@
|
||||
<n8n-option
|
||||
v-for="item in parameterOptions"
|
||||
:key="item.name"
|
||||
:label="item.displayName"
|
||||
:label="$nodeText.collectionOptionDisplayName(parameter, item)"
|
||||
:value="item.name">
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
@ -85,7 +85,8 @@ export default mixins(genericHelpers)
|
||||
},
|
||||
computed: {
|
||||
getPlaceholderText (): string {
|
||||
return this.parameter.placeholder ? this.parameter.placeholder : this.$baseText('fixedCollectionParameter.choose');
|
||||
const placeholder = this.$nodeText.placeholder(this.parameter);
|
||||
return placeholder ? placeholder : this.$baseText('fixedCollectionParameter.choose');
|
||||
},
|
||||
getProperties (): INodePropertyCollection[] {
|
||||
const returnProperties = [];
|
||||
|
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div @keydown.stop class="duplicate-parameter">
|
||||
<n8n-input-label
|
||||
:label="parameter.displayName"
|
||||
:tooltipText="parameter.description"
|
||||
:label="$nodeText.topParameterDisplayName(parameter)"
|
||||
:tooltipText="$nodeText.topParameterDescription(parameter)"
|
||||
:underline="true"
|
||||
:labelHoverableOnly="true"
|
||||
size="small"
|
||||
@ -64,7 +64,14 @@ export default mixins(genericHelpers)
|
||||
],
|
||||
computed: {
|
||||
addButtonText (): string {
|
||||
return (this.parameter.typeOptions && this.parameter.typeOptions.multipleValueButtonText) ? this.parameter.typeOptions.multipleValueButtonText : 'Add item';
|
||||
if (
|
||||
!this.parameter.typeOptions &&
|
||||
!this.parameter.typeOptions.multipleValueButtonText
|
||||
) {
|
||||
return this.$baseText('multipleParameter.addItem');
|
||||
}
|
||||
|
||||
return this.$nodeText.multipleValueButtonText(this.parameter);
|
||||
},
|
||||
hideDelete (): boolean {
|
||||
return this.parameter.options.length === 1;
|
||||
|
@ -35,7 +35,7 @@
|
||||
@focus="setFocus"
|
||||
@blur="onBlur"
|
||||
:title="displayTitle"
|
||||
:placeholder="isValueExpression?'':parameter.placeholder"
|
||||
:placeholder="isValueExpression ? '' : getPlaceholder()"
|
||||
>
|
||||
<div slot="suffix" class="expand-input-icon-container">
|
||||
<font-awesome-icon v-if="!isValueExpression && !isReadOnly" icon="external-link-alt" class="edit-window-button clickable" :title="$baseText('parameterInput.openEditWindow')" @click="displayEditDialog()" />
|
||||
@ -78,7 +78,7 @@
|
||||
:value="displayValue"
|
||||
:title="displayTitle"
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="parameter.placeholder?parameter.placeholder:$baseText('parameterInput.selectDateAndTime')"
|
||||
:placeholder="parameter.placeholder ? getPlaceholder() : $baseText('parameterInput.selectDateAndTime')"
|
||||
:picker-options="dateTimePickerOptions"
|
||||
@change="valueChanged"
|
||||
@focus="setFocus"
|
||||
@ -124,11 +124,13 @@
|
||||
v-for="option in parameterOptions"
|
||||
:value="option.value"
|
||||
:key="option.value"
|
||||
:label="option.name"
|
||||
:label="getOptionsOptionDisplayName(option)"
|
||||
>
|
||||
<div class="list-option">
|
||||
<div class="option-headline">{{ option.name }}</div>
|
||||
<div v-if="option.description" class="option-description" v-html="option.description"></div>
|
||||
<div class="option-headline">
|
||||
{{ getOptionsOptionDisplayName(option) }}
|
||||
</div>
|
||||
<div v-if="option.description" class="option-description" v-html="getOptionsOptionDescription(option)"></div>
|
||||
</div>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
@ -148,10 +150,10 @@
|
||||
@blur="onBlur"
|
||||
:title="displayTitle"
|
||||
>
|
||||
<n8n-option v-for="option in parameterOptions" :value="option.value" :key="option.value" :label="option.name" >
|
||||
<n8n-option v-for="option in parameterOptions" :value="option.value" :key="option.value" :label="getOptionsOptionDisplayName(option)">
|
||||
<div class="list-option">
|
||||
<div class="option-headline">{{ option.name }}</div>
|
||||
<div v-if="option.description" class="option-description" v-html="option.description"></div>
|
||||
<div class="option-headline">{{ getOptionsOptionDisplayName(option) }}</div>
|
||||
<div v-if="option.description" class="option-description" v-html="getOptionsOptionDescription(option)"></div>
|
||||
</div>
|
||||
</n8n-option>
|
||||
</n8n-select>
|
||||
@ -240,6 +242,7 @@ export default mixins(
|
||||
'value',
|
||||
'hideIssues', // boolean
|
||||
'errorHighlight',
|
||||
'isForCredential', // boolean
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
@ -255,14 +258,14 @@ export default mixins(
|
||||
dateTimePickerOptions: {
|
||||
shortcuts: [
|
||||
{
|
||||
text: 'Today',
|
||||
text: 'Today', // TODO
|
||||
// tslint:disable-next-line:no-any
|
||||
onClick (picker: any) {
|
||||
picker.$emit('pick', new Date());
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Yesterday',
|
||||
text: 'Yesterday', // TODO
|
||||
// tslint:disable-next-line:no-any
|
||||
onClick (picker: any) {
|
||||
const date = new Date();
|
||||
@ -271,7 +274,7 @@ export default mixins(
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'A week ago',
|
||||
text: 'A week ago', // TODO
|
||||
// tslint:disable-next-line:no-any
|
||||
onClick (picker: any) {
|
||||
const date = new Date();
|
||||
@ -325,20 +328,26 @@ export default mixins(
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
displayTitle (): string {
|
||||
let title = `Parameter: "${this.shortPath}"`;
|
||||
if (this.getIssues.length) {
|
||||
title += ` has issues`;
|
||||
if (this.isValueExpression === true) {
|
||||
title += ` and expression`;
|
||||
}
|
||||
title += `!`;
|
||||
} else {
|
||||
if (this.isValueExpression === true) {
|
||||
title += ` has expression`;
|
||||
}
|
||||
const interpolation = { interpolate: { shortPath: this.shortPath } };
|
||||
|
||||
if (this.getIssues.length && this.isValueExpression) {
|
||||
return this.$baseText(
|
||||
'parameterInput.parameterHasIssuesAndExpression',
|
||||
interpolation,
|
||||
);
|
||||
} else if (this.getIssues.length && !this.isValueExpression) {
|
||||
return this.$baseText(
|
||||
'parameterInput.parameterHasIssues',
|
||||
interpolation,
|
||||
);
|
||||
} else if (!this.getIssues.length && this.isValueExpression) {
|
||||
return this.$baseText(
|
||||
'parameterInput.parameterHasExpression',
|
||||
interpolation,
|
||||
);
|
||||
}
|
||||
|
||||
return title;
|
||||
return this.$baseText('parameterInput.parameter', interpolation);
|
||||
},
|
||||
displayValue (): string | number | boolean | null {
|
||||
if (this.remoteParameterOptionsLoading === true) {
|
||||
@ -346,7 +355,7 @@ export default mixins(
|
||||
// to user that the data is loading. If not it would
|
||||
// display the user the key instead of the value it
|
||||
// represents
|
||||
return 'Loading options...';
|
||||
return this.$baseText('parameterInput.loadingOptions');
|
||||
}
|
||||
|
||||
let returnValue;
|
||||
@ -415,7 +424,7 @@ export default mixins(
|
||||
try {
|
||||
computedValue = this.resolveExpression(this.value) as NodeParameterValue;
|
||||
} catch (error) {
|
||||
computedValue = `[ERROR: ${error.message}]`;
|
||||
computedValue = `[${this.$baseText('parameterInput.error')}}: ${error.message}]`;
|
||||
}
|
||||
|
||||
// Try to convert it into the corret type
|
||||
@ -559,6 +568,22 @@ export default mixins(
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPlaceholder(): string {
|
||||
return this.isForCredential
|
||||
? this.$credText.placeholder(this.parameter)
|
||||
: this.$nodeText.placeholder(this.parameter);
|
||||
},
|
||||
getOptionsOptionDisplayName(option: { value: string; name: string }): string {
|
||||
return this.isForCredential
|
||||
? this.$credText.optionsOptionDisplayName(this.parameter, option)
|
||||
: this.$nodeText.optionsOptionDisplayName(this.parameter, option);
|
||||
},
|
||||
getOptionsOptionDescription(option: { value: string; description: string }): string {
|
||||
return this.isForCredential
|
||||
? this.$credText.optionsOptionDescription(this.parameter, option)
|
||||
: this.$nodeText.optionsOptionDescription(this.parameter, option);
|
||||
},
|
||||
|
||||
async loadRemoteParameterOptions () {
|
||||
if (this.node === null || this.remoteMethod === undefined || this.remoteParameterOptionsLoading) {
|
||||
return;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<n8n-input-label
|
||||
:label="parameter.displayName"
|
||||
:tooltipText="parameter.description"
|
||||
:label="$credText.topParameterDisplayName(parameter)"
|
||||
:tooltipText="$credText.topParameterDescription(parameter)"
|
||||
:required="parameter.required"
|
||||
:showTooltip="focused"
|
||||
>
|
||||
@ -13,6 +13,7 @@
|
||||
:displayOptions="true"
|
||||
:documentationUrl="documentationUrl"
|
||||
:errorHighlight="showRequiredErrors"
|
||||
:isForCredential="true"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@textInput="valueChanged"
|
||||
@ -20,7 +21,7 @@
|
||||
inputSize="large"
|
||||
/>
|
||||
<div class="errors" v-if="showRequiredErrors">
|
||||
{{ $baseText('parameterInputExpanded.thisFieldIsRequired') }} <a v-if="documentationUrl" :href="documentationUrl" target="_blank" @click="onDocumentationUrlClick">Open docs</a>
|
||||
{{ $baseText('parameterInputExpanded.thisFieldIsRequired') }} <a v-if="documentationUrl" :href="documentationUrl" target="_blank" @click="onDocumentationUrlClick">{{ $baseText('parameterInputExpanded.openDocs') }}</a>
|
||||
</div>
|
||||
</n8n-input-label>
|
||||
</template>
|
||||
@ -29,8 +30,10 @@
|
||||
import { IUpdateInformation } from '@/Interface';
|
||||
import ParameterInput from './ParameterInput.vue';
|
||||
import Vue from 'vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { renderText } from './mixins/renderText';
|
||||
|
||||
export default Vue.extend({
|
||||
export default mixins(renderText).extend({
|
||||
name: 'ParameterInputExpanded',
|
||||
components: {
|
||||
ParameterInput,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<n8n-input-label
|
||||
:label="parameter.displayName"
|
||||
:tooltipText="parameter.description"
|
||||
:label="$nodeText.topParameterDisplayName(parameter)"
|
||||
:tooltipText="$nodeText.topParameterDescription(parameter)"
|
||||
:showTooltip="focused"
|
||||
:bold="false"
|
||||
size="small"
|
||||
@ -27,8 +27,10 @@ import {
|
||||
} from '@/Interface';
|
||||
|
||||
import ParameterInput from '@/components/ParameterInput.vue';
|
||||
import { renderText } from '@/components/mixins/renderText';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default Vue
|
||||
export default mixins(renderText)
|
||||
.extend({
|
||||
name: 'ParameterInputFull',
|
||||
components: {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<div v-else-if="parameter.type === 'notice'" class="parameter-item parameter-notice">
|
||||
<n8n-text size="small">
|
||||
<span v-html="parameter.displayName"></span>
|
||||
<span v-html="$nodeText.topParameterDisplayName(parameter)"></span>
|
||||
</n8n-text>
|
||||
</div>
|
||||
|
||||
@ -33,8 +33,8 @@
|
||||
/>
|
||||
</div>
|
||||
<n8n-input-label
|
||||
:label="parameter.displayName"
|
||||
:tooltipText="parameter.description"
|
||||
:label="$nodeText.topParameterDisplayName(parameter)"
|
||||
:tooltipText="$nodeText.topParameterDescription(parameter)"
|
||||
size="small"
|
||||
:underline="true"
|
||||
:labelHoverableOnly="true"
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div v-if="dialogVisible">
|
||||
<el-dialog :visible="dialogVisible" append-to-body width="80%" :title="`Edit ${parameter.displayName}`" :before-close="closeDialog">
|
||||
<el-dialog :visible="dialogVisible" append-to-body width="80%" :title="`${$baseText('textEdit.edit')} ${$nodeText.topParameterDisplayName(parameter)}`" :before-close="closeDialog">
|
||||
|
||||
<div class="ignore-key-press">
|
||||
<n8n-input-label :label="parameter.displayName">
|
||||
<n8n-input-label :label="$nodeText.topParameterDisplayName(parameter)">
|
||||
<div @keydown.stop @keydown.esc="closeDialog()">
|
||||
<n8n-input v-model="tempValue" type="textarea" ref="inputField" :value="value" :placeholder="parameter.placeholder" @change="valueChanged" @keydown.stop="noOp" :rows="15" />
|
||||
<n8n-input v-model="tempValue" type="textarea" ref="inputField" :value="value" :placeholder="$nodeText.placeholder(parameter)" @change="valueChanged" @keydown.stop="noOp" :rows="15" />
|
||||
</div>
|
||||
</n8n-input-label>
|
||||
</div>
|
||||
@ -16,9 +16,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { renderText } from '@/components/mixins/renderText';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default Vue.extend({
|
||||
|
||||
export default mixins(renderText).extend({
|
||||
name: 'TextEdit',
|
||||
props: [
|
||||
'dialogVisible',
|
||||
|
@ -1,191 +1,211 @@
|
||||
/* tslint:disable: variable-name */
|
||||
|
||||
// import { TranslationPath } from '@/Interface';
|
||||
import Vue from 'vue';
|
||||
|
||||
export const renderText = Vue.extend({
|
||||
computed: {
|
||||
/**
|
||||
* Node type for the active node in `NodeView.vue`.
|
||||
*/
|
||||
activeNodeType (): string {
|
||||
return this.$store.getters.activeNode.type;
|
||||
},
|
||||
},
|
||||
const REUSABLE_TEXT_KEY = 'reusableText';
|
||||
const CREDENTIALS_MODAL_KEY = 'credentialsModal';
|
||||
const NODE_VIEW_KEY = 'nodeView';
|
||||
|
||||
export const renderText = Vue.extend({
|
||||
methods: {
|
||||
/**
|
||||
* Render a string of base text, i.e. a string with a **fixed path** to the value in the locale object. Allows for [interpolation](https://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) when the localized value contains a string between curly braces.
|
||||
* ```js
|
||||
* $baseText('fixed.path.to.localized.value');
|
||||
* $baseText('fixed.path.to.localized.value', { interpolate: { var: arg } });
|
||||
* ```
|
||||
* 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.
|
||||
*/
|
||||
$baseText(
|
||||
key: string,
|
||||
options?: { interpolate: { [key: string]: string } },
|
||||
key: string, options?: { interpolate: { [key: string]: string } },
|
||||
): string {
|
||||
return this.$t(key, options && options.interpolate).toString();
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate a node- or credentials-specific string.
|
||||
* Called in-mixin by node- or credentials-specific methods,
|
||||
* which are called directly in Vue templates.
|
||||
* Render a string of dynamic text, i.e. a string with a constructed path to the localized value in the node text object, either in the credentials modal (`$credText`) or in the node view (`$nodeView`). **Private method**, to be called only from the two namespaces within this mixin.
|
||||
*/
|
||||
translateSpecific(
|
||||
__render(
|
||||
{ key, fallback }: { key: string, fallback: string },
|
||||
): string {
|
||||
) {
|
||||
return this.$te(key) ? this.$t(key).toString() : fallback;
|
||||
},
|
||||
},
|
||||
|
||||
// -----------------------------------------
|
||||
// node-specific methods
|
||||
// -----------------------------------------
|
||||
computed: {
|
||||
$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 context = this;
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Translate a top-level node parameter name, i.e. leftmost parameter in `NodeView.vue`.
|
||||
* Display name for a top-level parameter in the credentials modal.
|
||||
*/
|
||||
$translateNodeParameterName(
|
||||
topParameterDisplayName(
|
||||
{ name: parameterName, displayName }: { name: string; displayName: string; },
|
||||
) {
|
||||
return this.translateSpecific({
|
||||
key: `${this.activeNodeType}.parameters.${parameterName}.displayName`,
|
||||
if (['clientId', 'clientSecret'].includes(parameterName)) {
|
||||
return context.__render({
|
||||
key: `${REUSABLE_TEXT_KEY}.oauth2.${parameterName}`,
|
||||
fallback: displayName,
|
||||
});
|
||||
}
|
||||
|
||||
return context.__render({
|
||||
key: `${credentialPrefix}.${parameterName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate a top-level parameter description for a node or for credentials.
|
||||
* Description for a top-level parameter in the credentials modal.
|
||||
*/
|
||||
$translateDescription(
|
||||
topParameterDescription(
|
||||
{ name: parameterName, description }: { name: string; description: string; },
|
||||
) {
|
||||
return this.translateSpecific({
|
||||
key: `${this.activeNodeType}.parameters.${parameterName}.description`,
|
||||
return context.__render({
|
||||
key: `${credentialPrefix}.${parameterName}.description`,
|
||||
fallback: description,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate the name for an option in a `collection` or `fixed collection` parameter,
|
||||
* e.g. an option name in an "Additional Options" fixed collection.
|
||||
* Display name for an option inside an `options` or `multiOptions` parameter in the credentials modal.
|
||||
*/
|
||||
$translateCollectionOptionName(
|
||||
optionsOptionDisplayName(
|
||||
{ name: parameterName }: { name: string; },
|
||||
{ name: optionName, displayName }: { name: string; displayName: string; },
|
||||
{ value: optionName, name: displayName }: { value: string; name: string; },
|
||||
) {
|
||||
return this.translateSpecific({
|
||||
key: `${this.activeNodeType}.parameters.${parameterName}.options.${optionName}.displayName`,
|
||||
return context.__render({
|
||||
key: `${credentialPrefix}.${parameterName}.options.${optionName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate the label for a button that adds another field-input pair to a collection.
|
||||
* Description for an option inside an `options` or `multiOptions` parameter in the credentials modal.
|
||||
*/
|
||||
$translateMultipleValueButtonText(
|
||||
{ name: parameterName, typeOptions: { multipleValueButtonText } }:
|
||||
{ name: string, typeOptions: { multipleValueButtonText: string } },
|
||||
optionsOptionDescription(
|
||||
{ name: parameterName }: { name: string; },
|
||||
{ value: optionName, description }: { value: string; description: string; },
|
||||
) {
|
||||
return this.translateSpecific({
|
||||
key: `${this.activeNodeType}.parameters.${parameterName}.multipleValueButtonText`,
|
||||
return context.__render({
|
||||
key: `${credentialPrefix}.${parameterName}.options.${optionName}.description`,
|
||||
fallback: description,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Placeholder for a `string` or `collection` or `fixedCollection` parameter in the credentials modal.
|
||||
* - 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, displayName }: { name: string; displayName: string; },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${credentialPrefix}.${parameterName}.placeholder`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
$nodeText () {
|
||||
const nodePrefix = `${this.$store.getters.activeNode.type}.${NODE_VIEW_KEY}`;
|
||||
const context = this;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Display name for a top-level parameter in the node view.
|
||||
*/
|
||||
topParameterDisplayName(
|
||||
{ name: parameterName, displayName }: { name: string; displayName: string; },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Description for a top-level parameter in the node view in the node view.
|
||||
*/
|
||||
topParameterDescription(
|
||||
{ name: parameterName, description }: { name: string; description: string; },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.description`,
|
||||
fallback: description,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display name for an option inside a `collection` or `fixedCollection` parameter in the node view.
|
||||
*/
|
||||
collectionOptionDisplayName(
|
||||
{ name: parameterName }: { name: string; },
|
||||
{ name: optionName, displayName }: { name: string; displayName: string; },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.options.${optionName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display name for an option inside an `options` or `multiOptions` parameter in the node view.
|
||||
*/
|
||||
optionsOptionDisplayName(
|
||||
{ name: parameterName }: { name: string; },
|
||||
{ value: optionName, name: displayName }: { value: string; name: string; },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.options.${optionName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Description for an option inside an `options` or `multiOptions` parameter in the node view.
|
||||
*/
|
||||
optionsOptionDescription(
|
||||
{ name: parameterName }: { name: string; },
|
||||
{ value: optionName, description }: { value: string; description: string; },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.options.${optionName}.description`,
|
||||
fallback: description,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Text for a button to add another option inside a `collection` or `fixedCollection` parameter having`multipleValues: true` in the node view.
|
||||
*/
|
||||
multipleValueButtonText(
|
||||
{ name: parameterName, typeOptions: { multipleValueButtonText } }:
|
||||
{ name: string; typeOptions: { multipleValueButtonText: string; } },
|
||||
) {
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.multipleValueButtonText`,
|
||||
fallback: multipleValueButtonText,
|
||||
});
|
||||
},
|
||||
|
||||
// -----------------------------------------
|
||||
// creds-specific methods
|
||||
// -----------------------------------------
|
||||
|
||||
/**
|
||||
* Translate a credentials property name, i.e. leftmost parameter in `CredentialsEdit.vue`.
|
||||
* Placeholder for a `string` or `collection` or `fixedCollection` parameter in the node view.
|
||||
* - For a `string` parameter, the placeholder is unselectable greyed-out sample text.
|
||||
* - For a `collection` or `fixedCollection` parameter, the placeholder is the button text.
|
||||
*/
|
||||
$translateCredentialsPropertyName(
|
||||
{ name: parameterName, displayName }: { name: string; displayName: string; },
|
||||
{ nodeType, credentialsName }: { nodeType: string, credentialsName: string; },
|
||||
) {
|
||||
if (['clientId', 'clientSecret'].includes(parameterName)) {
|
||||
return this.$t(`oauth2.${parameterName}`);
|
||||
}
|
||||
|
||||
return this.translateSpecific({
|
||||
key: `${nodeType}.credentials.${credentialsName}.${parameterName}.displayName`,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate a credentials property description, i.e. label tooltip in `CredentialsEdit.vue`.
|
||||
*/
|
||||
$translateCredentialsPropertyDescription(
|
||||
{ name: parameterName, description }: { name: string; description: string; },
|
||||
{ nodeType, credentialsName }: { nodeType: string, credentialsName: string; },
|
||||
) {
|
||||
return this.translateSpecific({
|
||||
key: `${nodeType}.credentials.${credentialsName}.${parameterName}.description`,
|
||||
fallback: description,
|
||||
});
|
||||
},
|
||||
|
||||
// -----------------------------------------
|
||||
// node- and creds-specific methods
|
||||
// -----------------------------------------
|
||||
|
||||
/**
|
||||
* Translate the placeholder inside the input field for a string-type parameter.
|
||||
*/
|
||||
$translatePlaceholder(
|
||||
placeholder(
|
||||
{ name: parameterName, placeholder }: { name: string; placeholder: string; },
|
||||
isCredential = false,
|
||||
{ nodeType, credentialsName } = { nodeType: '', credentialsName: '' },
|
||||
) {
|
||||
const key = isCredential
|
||||
? `${nodeType}.credentials.${credentialsName}.placeholder`
|
||||
: `${this.activeNodeType}.parameters.${parameterName}.placeholder`;
|
||||
|
||||
return this.translateSpecific({
|
||||
key,
|
||||
return context.__render({
|
||||
key: `${nodePrefix}.${parameterName}.placeholder`,
|
||||
fallback: placeholder,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate the name for an option in an `options` parameter,
|
||||
* e.g. an option name in a "Resource" or "Operation" dropdown menu.
|
||||
*/
|
||||
$translateOptionsOptionName(
|
||||
{ name: parameterName }: { name: string },
|
||||
{ value: optionName, name: displayName }: { value: string; name: string; },
|
||||
isCredential = false,
|
||||
{ nodeType, credentialsName } = { nodeType: '', credentialsName: '' },
|
||||
) {
|
||||
const key = isCredential
|
||||
? `${nodeType}.credentials.${credentialsName}.options.${optionName}.displayName`
|
||||
: `${this.activeNodeType}.parameters.${parameterName}.options.${optionName}.displayName`;
|
||||
|
||||
return this.translateSpecific({
|
||||
key,
|
||||
fallback: displayName,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate the description for an option in an `options` parameter,
|
||||
* e.g. an option name in a "Resource" or "Operation" dropdown menu.
|
||||
*/
|
||||
$translateOptionsOptionDescription(
|
||||
{ name: parameterName }: { name: string },
|
||||
{ value: optionName, description }: { value: string; description: string; },
|
||||
isCredential = false,
|
||||
{ nodeType, credentialsName } = { nodeType: '', credentialsName: '' },
|
||||
) {
|
||||
const key = isCredential
|
||||
? `${nodeType}.credentials.${credentialsName}.options.${optionName}.description`
|
||||
: `${this.activeNodeType}.parameters.${parameterName}.options.${optionName}.description`;
|
||||
|
||||
return this.translateSpecific({
|
||||
key,
|
||||
fallback: description,
|
||||
});
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -2,24 +2,27 @@ import Vue from 'vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import englishBaseText from './locales/en';
|
||||
import axios from 'axios';
|
||||
import path from 'path';
|
||||
|
||||
Vue.use(VueI18n);
|
||||
|
||||
// TODO i18n: Remove next line
|
||||
console.log('About to initialize i18n'); // eslint-disable-line no-console
|
||||
|
||||
export const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: englishBaseText,
|
||||
messages: { en: englishBaseText },
|
||||
silentTranslationWarn: true,
|
||||
});
|
||||
|
||||
const loadedLanguages = ['en'];
|
||||
|
||||
function setLanguage(language: string): string {
|
||||
function setLanguage(language: string) {
|
||||
i18n.locale = language;
|
||||
axios.defaults.headers.common['Accept-Language'] = language;
|
||||
document!.querySelector('html')!.setAttribute('lang', language);
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
@ -36,21 +39,30 @@ export async function loadLanguage(language?: string) {
|
||||
return Promise.resolve(setLanguage(language));
|
||||
}
|
||||
|
||||
const { default: { [language]: messages }} = require(`./locales/${language}`);
|
||||
i18n.setLocaleMessage(language, messages);
|
||||
const baseText = require(`./locales/${language}`).default; // TODO i18n: `path.join()`
|
||||
console.log(baseText);
|
||||
i18n.setLocaleMessage(language, baseText);
|
||||
loadedLanguages.push(language);
|
||||
|
||||
return setLanguage(language);
|
||||
}
|
||||
|
||||
export function addNodeTranslations(translations: { [key: string]: string | object }) {
|
||||
const lang = Object.keys(translations)[0];
|
||||
const messages = translations[lang];
|
||||
export function addNodeTranslation(
|
||||
nodeTranslation: { [key: string]: object },
|
||||
language: string,
|
||||
) {
|
||||
const newNodesBase = {
|
||||
'n8n-nodes-base': Object.assign(
|
||||
i18n.messages[lang]['n8n-nodes-base'],
|
||||
messages,
|
||||
i18n.messages[language]['n8n-nodes-base'] || {},
|
||||
nodeTranslation,
|
||||
),
|
||||
};
|
||||
i18n.setLocaleMessage(lang, Object.assign(i18n.messages[lang], newNodesBase));
|
||||
|
||||
// TODO i18n: Remove next line
|
||||
console.log('newNodesBase', newNodesBase); // eslint-disable-line no-console
|
||||
|
||||
i18n.setLocaleMessage(
|
||||
language,
|
||||
Object.assign(i18n.messages[language], newNodesBase),
|
||||
);
|
||||
}
|
@ -1,5 +1,16 @@
|
||||
export default {
|
||||
de: {
|
||||
reusableText: {
|
||||
oauth2: {
|
||||
clientId: '🇩🇪 Client ID',
|
||||
clientSecret: '🇩🇪 Client Secret',
|
||||
},
|
||||
},
|
||||
textEdit: {
|
||||
edit: '🇩🇪 Edit',
|
||||
},
|
||||
codeEdit: {
|
||||
edit: '🇩🇪 Edit',
|
||||
},
|
||||
versionCard: {
|
||||
thisVersionHasASecurityIssue: '🇩🇪 This version has a security issue.<br/>It is listed here for completeness.',
|
||||
released: '🇩🇪 Released',
|
||||
@ -145,9 +156,10 @@ export default {
|
||||
reconnectOAuth2Credential: '🇩🇪 Reconnect OAuth2 Credential',
|
||||
connectionTestedSuccessfully: '🇩🇪 Connection tested successfully',
|
||||
oAuthRedirectUrl: '🇩🇪 OAuth Redirect URL',
|
||||
clickToCopy: '🇩🇪 ClickToCopy',
|
||||
clickToCopy: '🇩🇪 Click To Copy',
|
||||
subtitle: '🇩🇪 In {appName}, use the URL above when prompted to enter an OAuth callback or redirect URL',
|
||||
redirectUrlCopiedToClipboard: '🇩🇪 Redirect URL copied to clipboard',
|
||||
theServiceYouReConnectingTo: "🇩🇪 the service you're connecting to",
|
||||
},
|
||||
oAuthButton: {
|
||||
signInWithGoogle: '🇩🇪 Sign in with Google',
|
||||
@ -398,6 +410,7 @@ export default {
|
||||
workflows: '🇩🇪 Workflows',
|
||||
},
|
||||
multipleParameter: {
|
||||
addItem: '🇩🇪 Add item',
|
||||
currentlyNoItemsExist: '🇩🇪 Currently no items exist',
|
||||
moveUp: '🇩🇪 Move up',
|
||||
moveDown: '🇩🇪 Move down',
|
||||
@ -619,6 +632,7 @@ export default {
|
||||
withWorkflowTagsYouReFree: '🇩🇪 With workflow tags, you\'re free to create the perfect tagging system for your flows',
|
||||
},
|
||||
parameterInput: {
|
||||
loadingOptions: '🇩🇪 Loading options...',
|
||||
addExpression: '🇩🇪 Add Expression',
|
||||
removeExpression: '🇩🇪 Remove Expression',
|
||||
refreshList: '🇩🇪 Refresh List',
|
||||
@ -627,9 +641,15 @@ export default {
|
||||
openEditWindow: '🇩🇪 Open Edit Window',
|
||||
issues: '🇩🇪 Issues',
|
||||
parameterOptions: '🇩🇪 Parameter Options',
|
||||
parameterHasIssuesAndExpression: '🇩🇪 Parameter: "{shortPath}" has issues and expression!',
|
||||
parameterHasIssues: '🇩🇪 Parameter: "{shortPath}" has issues!',
|
||||
parameterHasExpression: '🇩🇪 Parameter: "{shortPath}" has expression!',
|
||||
parameter: '🇩🇪 Parameter: "{shortPath}"',
|
||||
error: '🇩🇪 ERROR',
|
||||
},
|
||||
parameterInputExpanded: {
|
||||
thisFieldIsRequired: '🇩🇪 This field is required.',
|
||||
openDocs: '🇩🇪 Open docs',
|
||||
},
|
||||
parameterInputList: {
|
||||
delete: '🇩🇪 Delete',
|
||||
@ -933,7 +953,4 @@ export default {
|
||||
timeoutWorkflow: '🇩🇪 Timeout Workflow',
|
||||
timezone: '🇩🇪 Timezone',
|
||||
},
|
||||
|
||||
'n8n-nodes-base': {}, // required for node translation
|
||||
},
|
||||
};
|
@ -1,5 +1,10 @@
|
||||
export default {
|
||||
en: {
|
||||
textEdit: {
|
||||
edit: 'Edit',
|
||||
},
|
||||
codeEdit: {
|
||||
edit: 'Edit',
|
||||
},
|
||||
versionCard: {
|
||||
thisVersionHasASecurityIssue: 'This version has a security issue.<br/>It is listed here for completeness.',
|
||||
released: 'Released',
|
||||
@ -145,9 +150,10 @@ export default {
|
||||
reconnectOAuth2Credential: 'Reconnect OAuth2 Credential',
|
||||
connectionTestedSuccessfully: 'Connection tested successfully',
|
||||
oAuthRedirectUrl: 'OAuth Redirect URL',
|
||||
clickToCopy: 'ClickToCopy',
|
||||
clickToCopy: 'Click To Copy',
|
||||
subtitle: 'In {appName}, use the URL above when prompted to enter an OAuth callback or redirect URL',
|
||||
redirectUrlCopiedToClipboard: 'Redirect URL copied to clipboard',
|
||||
theServiceYouReConnectingTo: "the service you're connecting to",
|
||||
},
|
||||
oAuthButton: {
|
||||
signInWithGoogle: 'Sign in with Google',
|
||||
@ -398,6 +404,7 @@ export default {
|
||||
workflows: 'Workflows',
|
||||
},
|
||||
multipleParameter: {
|
||||
addItem: 'Add item',
|
||||
currentlyNoItemsExist: 'Currently no items exist',
|
||||
moveUp: 'Move up',
|
||||
moveDown: 'Move down',
|
||||
@ -619,6 +626,7 @@ export default {
|
||||
withWorkflowTagsYouReFree: 'With workflow tags, you\'re free to create the perfect tagging system for your flows',
|
||||
},
|
||||
parameterInput: {
|
||||
loadingOptions: 'Loading options...',
|
||||
addExpression: 'Add Expression',
|
||||
removeExpression: 'Remove Expression',
|
||||
refreshList: 'Refresh List',
|
||||
@ -627,9 +635,15 @@ export default {
|
||||
openEditWindow: 'Open Edit Window',
|
||||
issues: 'Issues',
|
||||
parameterOptions: 'Parameter Options',
|
||||
parameterHasIssuesAndExpression: 'Parameter: "{shortPath}" has issues and expression!',
|
||||
parameterHasIssues: 'Parameter: "{shortPath}" has issues!',
|
||||
parameterHasExpression: 'Parameter: "{shortPath}" has expression!',
|
||||
parameter: 'Parameter: "{shortPath}"',
|
||||
error: 'ERROR',
|
||||
},
|
||||
parameterInputExpanded: {
|
||||
thisFieldIsRequired: 'This field is required.',
|
||||
openDocs: 'Open docs',
|
||||
},
|
||||
parameterInputList: {
|
||||
delete: 'Delete',
|
||||
@ -933,7 +947,4 @@ export default {
|
||||
timeoutWorkflow: 'Timeout Workflow',
|
||||
timezone: 'Timezone',
|
||||
},
|
||||
|
||||
'n8n-nodes-base': {}, // required for node translation
|
||||
},
|
||||
};
|
@ -48,6 +48,7 @@ const state: IRootState = {
|
||||
activeNode: 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',
|
||||
@ -559,6 +560,9 @@ export const store = new Vuex.Store({
|
||||
setActiveNode (state, nodeName: string) {
|
||||
state.activeNode = nodeName;
|
||||
},
|
||||
setCredentialTextRenderKeys (state, renderKeys: { nodeType: string; credentialType: string; }) {
|
||||
state.credentialTextRenderKeys = renderKeys;
|
||||
},
|
||||
|
||||
setLastSelectedNode (state, nodeName: string) {
|
||||
state.lastSelectedNode = nodeName;
|
||||
@ -653,6 +657,10 @@ export const store = new Vuex.Store({
|
||||
return state.activeExecutions;
|
||||
},
|
||||
|
||||
credentialTextRenderKeys: (state): object | null => {
|
||||
return state.credentialTextRenderKeys;
|
||||
},
|
||||
|
||||
getBaseUrl: (state): string => {
|
||||
return state.baseUrl;
|
||||
},
|
||||
|
@ -170,7 +170,7 @@ import {
|
||||
IExecutionsSummary,
|
||||
} from '../Interface';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { loadLanguage } from '@/i18n';
|
||||
import { loadLanguage, addNodeTranslation } from '@/i18n';
|
||||
|
||||
const NODE_SIZE = 100;
|
||||
const DEFAULT_START_POSITION_X = 250;
|
||||
@ -2384,8 +2384,16 @@ export default mixins(
|
||||
if (nodesToBeFetched.length > 0) {
|
||||
// Only call API if node information is actually missing
|
||||
this.startLoading();
|
||||
const nodeInfo = await this.restApi().getNodesInformation(nodesToBeFetched);
|
||||
this.$store.commit('updateNodeTypes', nodeInfo);
|
||||
|
||||
const nodesInfo = await this.restApi().getNodesInformation(nodesToBeFetched);
|
||||
|
||||
nodesInfo.forEach(nodeInfo => {
|
||||
if (nodeInfo.translation) {
|
||||
addNodeTranslation(nodeInfo.translation, this.$store.getters.defaultLocale);
|
||||
}
|
||||
});
|
||||
|
||||
this.$store.commit('updateNodeTypes', nodesInfo);
|
||||
this.stopLoading();
|
||||
}
|
||||
},
|
||||
|
22
packages/nodes-base/nodes/Bitwarden/translations/de.ts
Normal file
22
packages/nodes-base/nodes/Bitwarden/translations/de.ts
Normal file
@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
bitwarden: {
|
||||
credentialsModal: {
|
||||
bitwardenApi: {
|
||||
environment: {
|
||||
displayName: '🇩🇪 Environment',
|
||||
description: '🇩🇪 Description for environment',
|
||||
options: {
|
||||
cloudHosted: {
|
||||
displayName: '🇩🇪 Cloud-hosted',
|
||||
description: '🇩🇪 Description for cloud-hosted',
|
||||
},
|
||||
selfHosted: {
|
||||
displayName: '🇩🇪 Self-hosted',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nodeView: {},
|
||||
},
|
||||
};
|
165
packages/nodes-base/nodes/Github/translations/de.ts
Normal file
165
packages/nodes-base/nodes/Github/translations/de.ts
Normal file
@ -0,0 +1,165 @@
|
||||
module.exports = {
|
||||
github: {
|
||||
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: {
|
||||
/**
|
||||
* Examples of `options` parameters.
|
||||
*/
|
||||
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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Examples of `string` parameters.
|
||||
*/
|
||||
owner: {
|
||||
displayName: '🇩🇪 Repository Owner',
|
||||
placeholder: '🇩🇪 n8n-io',
|
||||
description: '🇩🇪 Owner of the repository.',
|
||||
},
|
||||
repository: {
|
||||
displayName: '🇩🇪 Repository Name',
|
||||
placeholder: '🇩🇪 n8n',
|
||||
},
|
||||
title: {
|
||||
displayName: '🇩🇪 Title',
|
||||
},
|
||||
body: {
|
||||
displayName: '🇩🇪 Body',
|
||||
},
|
||||
|
||||
/**
|
||||
* Examples of `collection` parameters.
|
||||
* `multipleValueButtonText` is the button label.
|
||||
*/
|
||||
labels: {
|
||||
displayName: '🇩🇪 Labels',
|
||||
multipleValueButtonText: '🇩🇪 Add Label',
|
||||
},
|
||||
assignees: {
|
||||
displayName: '🇩🇪 Assignees',
|
||||
multipleValueButtonText: '🇩🇪 Add Assignee',
|
||||
},
|
||||
|
||||
/**
|
||||
* Examples of fields in `collection` parameters.
|
||||
* Note: Same level of nesting as `collection`.
|
||||
*/
|
||||
label: {
|
||||
displayName: '🇩🇪 Label',
|
||||
description: '🇩🇪 Label to add to issue.',
|
||||
},
|
||||
assignee: {
|
||||
displayName: '🇩🇪 Assignee',
|
||||
description: '🇩🇪 User to assign issue to.',
|
||||
},
|
||||
|
||||
/**
|
||||
* Example of a `fixedCollection` parameter.
|
||||
* `placeholder` is the button label.
|
||||
*/
|
||||
additionalParameters: {
|
||||
displayName: '🇩🇪 Additional Fields',
|
||||
placeholder: '🇩🇪 Add Field',
|
||||
options: {
|
||||
author: {
|
||||
displayName: '🇩🇪 Author',
|
||||
},
|
||||
branch: {
|
||||
displayName: '🇩🇪 Branch',
|
||||
},
|
||||
committer: {
|
||||
displayName: '🇩🇪 Committer',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Example of a field in a `fixedCollection` parameter.
|
||||
* Note: Same level of nesting as `fixedCollection`.
|
||||
*/
|
||||
committer: {
|
||||
displayName: '🇩🇪 Commit-Macher',
|
||||
description: '🇩🇪 Beschreibung',
|
||||
},
|
||||
|
||||
/**
|
||||
* Examples of options in a field in a `fixedCollection` parameter.
|
||||
* Note: Same level of nesting as `fixedCollection`.
|
||||
*/
|
||||
name: {
|
||||
displayName: '🇩🇪 Name',
|
||||
description: '🇩🇪 The name of the author of the commit.',
|
||||
},
|
||||
email: {
|
||||
displayName: '🇩🇪 Email',
|
||||
description: '🇩🇪 The email of the author of the commit.'
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
};
|
@ -817,6 +817,7 @@ export interface INodeTypeDescription extends INodeTypeBaseDescription {
|
||||
deactivate?: INodeHookDescription[];
|
||||
};
|
||||
webhooks?: IWebhookDescription[];
|
||||
translation?: { [key: string]: object };
|
||||
}
|
||||
|
||||
export interface INodeHookDescription {
|
||||
|
@ -1390,7 +1390,7 @@ export function mergeNodeProperties(
|
||||
}
|
||||
}
|
||||
|
||||
export function getVersionedTypeNode(
|
||||
export function getVersionedNodeType(
|
||||
object: INodeVersionedType | INodeType,
|
||||
version?: number,
|
||||
): INodeType {
|
||||
@ -1400,7 +1400,7 @@ export function getVersionedTypeNode(
|
||||
return object as INodeType;
|
||||
}
|
||||
|
||||
export function getVersionedTypeNodeAll(object: INodeVersionedType | INodeType): INodeType[] {
|
||||
export function getVersionedNodeTypeAll(object: INodeVersionedType | INodeType): INodeType[] {
|
||||
if (isNodeTypeVersioned(object)) {
|
||||
return Object.values((object as INodeVersionedType).nodeVersions).map((element) => {
|
||||
element.description.name = object.description.name;
|
||||
|
@ -99,7 +99,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
async init(nodeTypes: INodeTypeData): Promise<void> {}
|
||||
|
||||
getAll(): INodeType[] {
|
||||
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedTypeNode(data.type));
|
||||
return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type));
|
||||
}
|
||||
|
||||
getByName(nodeType: string): INodeType {
|
||||
@ -107,7 +107,7 @@ class NodeTypesClass implements INodeTypes {
|
||||
}
|
||||
|
||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||
return NodeHelpers.getVersionedTypeNode(this.nodeTypes[nodeType].type, version);
|
||||
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user