Render node strings

This commit is contained in:
Iván Ovejero
2021-11-15 11:19:43 +01:00
parent 2d8e158012
commit 7fc0395e95
26 changed files with 2442 additions and 2036 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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 = [];

View File

@@ -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);
},

View File

@@ -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 = [];

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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: {

View File

@@ -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"

View File

@@ -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',

View File

@@ -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;
/**
* Translate a top-level node parameter name, i.e. leftmost parameter in `NodeView.vue`.
*/
$translateNodeParameterName(
{ name: parameterName, displayName }: { name: string; displayName: string; },
) {
return this.translateSpecific({
key: `${this.activeNodeType}.parameters.${parameterName}.displayName`,
fallback: displayName,
});
return {
/**
* Display name for a top-level parameter in the credentials modal.
*/
topParameterDisplayName(
{ name: parameterName, displayName }: { name: string; displayName: string; },
) {
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,
});
},
/**
* Description for a top-level parameter in the credentials modal.
*/
topParameterDescription(
{ name: parameterName, description }: { name: string; description: string; },
) {
return context.__render({
key: `${credentialPrefix}.${parameterName}.description`,
fallback: description,
});
},
/**
* Display name for an option inside an `options` or `multiOptions` parameter in the credentials modal.
*/
optionsOptionDisplayName(
{ name: parameterName }: { name: string; },
{ value: optionName, name: displayName }: { value: string; name: string; },
) {
return context.__render({
key: `${credentialPrefix}.${parameterName}.options.${optionName}.displayName`,
fallback: displayName,
});
},
/**
* Description for an option inside an `options` or `multiOptions` parameter in the credentials modal.
*/
optionsOptionDescription(
{ name: parameterName }: { name: string; },
{ value: optionName, description }: { value: string; description: string; },
) {
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,
});
},
};
},
/**
* Translate a top-level parameter description for a node or for credentials.
*/
$translateDescription(
{ name: parameterName, description }: { name: string; description: string; },
) {
return this.translateSpecific({
key: `${this.activeNodeType}.parameters.${parameterName}.description`,
fallback: description,
});
},
$nodeText () {
const nodePrefix = `${this.$store.getters.activeNode.type}.${NODE_VIEW_KEY}`;
const context = this;
/**
* Translate the name for an option in a `collection` or `fixed collection` parameter,
* e.g. an option name in an "Additional Options" fixed collection.
*/
$translateCollectionOptionName(
{ name: parameterName }: { name: string; },
{ name: optionName, displayName }: { name: string; displayName: string; },
) {
return this.translateSpecific({
key: `${this.activeNodeType}.parameters.${parameterName}.options.${optionName}.displayName`,
fallback: displayName,
});
},
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,
});
},
/**
* Translate the label for a button that adds another field-input pair to a collection.
*/
$translateMultipleValueButtonText(
{ name: parameterName, typeOptions: { multipleValueButtonText } }:
{ name: string, typeOptions: { multipleValueButtonText: string } },
) {
return this.translateSpecific({
key: `${this.activeNodeType}.parameters.${parameterName}.multipleValueButtonText`,
fallback: multipleValueButtonText,
});
},
/**
* 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,
});
},
// -----------------------------------------
// creds-specific methods
// -----------------------------------------
/**
* 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,
});
},
/**
* Translate a credentials property name, i.e. leftmost parameter in `CredentialsEdit.vue`.
*/
$translateCredentialsPropertyName(
{ name: parameterName, displayName }: { name: string; displayName: string; },
{ nodeType, credentialsName }: { nodeType: string, credentialsName: string; },
) {
if (['clientId', 'clientSecret'].includes(parameterName)) {
return this.$t(`oauth2.${parameterName}`);
}
/**
* 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,
});
},
return this.translateSpecific({
key: `${nodeType}.credentials.${credentialsName}.${parameterName}.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,
});
},
/**
* 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,
});
},
/**
* 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,
});
},
// -----------------------------------------
// node- and creds-specific methods
// -----------------------------------------
/**
* Translate the placeholder inside the input field for a string-type parameter.
*/
$translatePlaceholder(
{ 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,
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,
});
/**
* 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.
*/
placeholder(
{ name: parameterName, placeholder }: { name: string; placeholder: string; },
) {
return context.__render({
key: `${nodePrefix}.${parameterName}.placeholder`,
fallback: placeholder,
});
},
};
},
},
});

View File

@@ -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),
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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;
},

View File

@@ -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();
}
},