feat(editor): Use i18n component instead od v-html for localization

* feat(editor): Export i18n instance and bind it to Vue instance

* feat(editor): Audit usage of v-html and replace with alternatives where possible

* 🔀 Fix conflicted element in RunDataTable

* ♻️ Refactor issues elements with the new TitledList component

* 🐛 Fixing unknown node modal dialog content rendering

Co-authored-by: Milorad Filipovic <milorad@n8n.io>
This commit is contained in:
OlegIvaniv
2022-09-05 16:36:22 +02:00
committed by GitHub
parent bbd967bbdf
commit 287533e6c8
16 changed files with 141 additions and 65 deletions

View File

@@ -50,7 +50,7 @@
<span <span
size="small" size="small"
:class="[$style.infoText, infoTextErrorMessage ? $style.error : '']" :class="[$style.infoText, infoTextErrorMessage ? $style.error : '']"
v-html="infoTextErrorMessage" v-text="infoTextErrorMessage"
></span> ></span>
</div> </div>
<el-checkbox <el-checkbox

View File

@@ -13,7 +13,7 @@
<div :class="$style.descriptionContainer" v-if="this.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE"> <div :class="$style.descriptionContainer" v-if="this.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE">
<n8n-info-tip theme="info" type="note" :bold="false"> <n8n-info-tip theme="info" type="note" :bold="false">
<template> <template>
<span v-html="getModalContent.description"></span> <span v-text="getModalContent.description"></span>
</template> </template>
</n8n-info-tip> </n8n-info-tip>
</div> </div>

View File

@@ -5,7 +5,7 @@
</div> </div>
<div> <div>
<font-awesome-icon icon="exclamation-triangle" /> <font-awesome-icon icon="exclamation-triangle" />
<span v-html="$locale.baseText('executionDetails.readOnly.readOnly')"></span> <span v-text="$locale.baseText('executionDetails.readOnly.readOnly')"></span>
</div> </div>
</n8n-tooltip> </n8n-tooltip>
</template> </template>

View File

@@ -6,13 +6,13 @@
<div v-if="!data.disabled" :class="{'node-info-icon': true, 'shift-icon': shiftOutputCount}"> <div v-if="!data.disabled" :class="{'node-info-icon': true, 'shift-icon': shiftOutputCount}">
<div v-if="hasIssues" class="node-issues"> <div v-if="hasIssues" class="node-issues">
<n8n-tooltip placement="bottom" > <n8n-tooltip placement="bottom" >
<div slot="content" v-html="nodeIssues"></div> <titled-list slot="content" :title="`${$locale.baseText('node.issues')}:`" :items="nodeIssues" />
<font-awesome-icon icon="exclamation-triangle" /> <font-awesome-icon icon="exclamation-triangle" />
</n8n-tooltip> </n8n-tooltip>
</div> </div>
<div v-else-if="waiting" class="waiting"> <div v-else-if="waiting" class="waiting">
<n8n-tooltip placement="bottom"> <n8n-tooltip placement="bottom">
<div slot="content" v-html="waiting"></div> <div slot="content" v-text="waiting"></div>
<font-awesome-icon icon="clock" /> <font-awesome-icon icon="clock" />
</n8n-tooltip> </n8n-tooltip>
</div> </div>
@@ -105,6 +105,7 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import NodeIcon from '@/components/NodeIcon.vue'; import NodeIcon from '@/components/NodeIcon.vue';
import TitledList from '@/components/TitledList.vue';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
@@ -121,6 +122,7 @@ export default mixins(
).extend({ ).extend({
name: 'Node', name: 'Node',
components: { components: {
TitledList,
NodeIcon, NodeIcon,
}, },
computed: { computed: {
@@ -200,14 +202,12 @@ export default mixins(
executing: this.isExecuting, executing: this.isExecuting,
}; };
}, },
nodeIssues (): string { nodeIssues (): string[] {
if (this.data.issues === undefined) { if (this.data.issues === undefined) {
return ''; return [];
} }
const nodeIssues = NodeHelpers.nodeIssuesToString(this.data.issues, this.data); return NodeHelpers.nodeIssuesToString(this.data.issues, this.data);
return `${this.$locale.baseText('node.issues')}:<br />&nbsp;&nbsp;- ` + nodeIssues.join('<br />&nbsp;&nbsp;- ');
}, },
nodeDisabledIcon (): string { nodeDisabledIcon (): string {
if (this.data.disabled === false) { if (this.data.disabled === false) {

View File

@@ -42,7 +42,7 @@
<div :class="$style.warning" v-if="issues.length"> <div :class="$style.warning" v-if="issues.length">
<n8n-tooltip placement="top" > <n8n-tooltip placement="top" >
<div slot="content" v-html="`${$locale.baseText('nodeCredentials.issues')}:<br />&nbsp;&nbsp;- ` + issues.join('<br />&nbsp;&nbsp;- ')"></div> <titled-list slot="content" :title="`${$locale.baseText('nodeCredentials.issues')}:`" :items="issues" />
<font-awesome-icon icon="exclamation-triangle" /> <font-awesome-icon icon="exclamation-triangle" />
</n8n-tooltip> </n8n-tooltip>
</div> </div>
@@ -74,6 +74,8 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
import { nodeHelpers } from '@/components/mixins/nodeHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import { showMessage } from '@/components/mixins/showMessage'; import { showMessage } from '@/components/mixins/showMessage';
import TitledList from '@/components/TitledList.vue';
import { mapGetters } from "vuex"; import { mapGetters } from "vuex";
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
@@ -89,6 +91,9 @@ export default mixins(
'node', // INodeUi 'node', // INodeUi
'overrideCredType', // cred type 'overrideCredType', // cred type
], ],
components: {
TitledList,
},
data () { data () {
return { return {
NEW_CREDENTIALS_TEXT: `- ${this.$locale.baseText('nodeCredentials.createNew')} -`, NEW_CREDENTIALS_TEXT: `- ${this.$locale.baseText('nodeCredentials.createNew')} -`,

View File

@@ -28,11 +28,14 @@
</div> </div>
<div v-if="isCommunityNode" :class="$style.descriptionContainer"> <div v-if="isCommunityNode" :class="$style.descriptionContainer">
<div class="mb-l"> <div class="mb-l">
<span <i18n path="nodeSettings.communityNodeUnknown.description" tag="span" @click="onMissingNodeTextClick">
v-html="$locale.baseText('nodeSettings.communityNodeUnknown.description', { interpolate: { packageName: node.type.split('.')[0] } })" <template #action>
@click="onMissingNodeTextClick" <a
> :href="`https://www.npmjs.com/package/${node.type.split('.')[0]}`"
</span> target="_blank"
>{{ node.type.split('.')[0] }}</a>
</template>
</i18n>
</div> </div>
<n8n-link <n8n-link
:to="COMMUNITY_NODES_INSTALLATION_DOCS_URL" :to="COMMUNITY_NODES_INSTALLATION_DOCS_URL"
@@ -41,14 +44,15 @@
{{ $locale.baseText('nodeSettings.communityNodeUnknown.installLink.text') }} {{ $locale.baseText('nodeSettings.communityNodeUnknown.installLink.text') }}
</n8n-link> </n8n-link>
</div> </div>
<span v-else <i18n v-else path="nodeSettings.nodeTypeUnknown.description" tag="span">
v-html=" <template #action>
$locale.baseText('nodeSettings.nodeTypeUnknown.description', <a
{ :href="CUSTOM_NODES_DOCS_URL"
interpolate: { docURL: CUSTOM_NODES_DOCS_URL } target="_blank"
}) v-text="$locale.baseText('nodeSettings.nodeTypeUnknown.description.customNode')"
"> />
</span> </template>
</i18n>
</div> </div>
<div class="node-parameters-wrapper" v-if="node && nodeValid"> <div class="node-parameters-wrapper" v-if="node && nodeValid">
<div v-show="openPanel === 'params'"> <div v-show="openPanel === 'params'">

View File

@@ -1,7 +1,7 @@
<template> <template>
<div :class="$style['parameter-issues']" v-if="issues.length"> <div :class="$style['parameter-issues']" v-if="issues.length">
<n8n-tooltip placement="top" > <n8n-tooltip placement="top" >
<div slot="content" v-html="`${$locale.baseText('parameterInput.issues')}:<br />&nbsp;&nbsp;- ` + issues.join('<br />&nbsp;&nbsp;- ')"></div> <titled-list slot="content" :title="`${$locale.baseText('parameterInput.issues')}:`" :items="issues" />
<font-awesome-icon icon="exclamation-triangle" /> <font-awesome-icon icon="exclamation-triangle" />
</n8n-tooltip> </n8n-tooltip>
</div> </div>
@@ -9,9 +9,13 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import TitledList from '@/components/TitledList.vue';
export default Vue.extend({ export default Vue.extend({
name: 'ParameterIssues', name: 'ParameterIssues',
components: {
TitledList,
},
props: [ props: [
'issues', 'issues',
], ],

View File

@@ -21,10 +21,10 @@
:disabled="!mappingEnabled || showHintWithDelay" :disabled="!mappingEnabled || showHintWithDelay"
:open-delay="1000" :open-delay="1000"
> >
<div <div slot="content">
slot="content" <img src='/static/data-mapping-gif.gif'/>
v-html="$locale.baseText('dataMapping.dragColumnToFieldHint')" {{ $locale.baseText('dataMapping.dragColumnToFieldHint') }}
></div> </div>
<Draggable <Draggable
type="mapping" type="mapping"
:data="getExpression(column)" :data="getExpression(column)"
@@ -48,12 +48,11 @@
:class="{ :class="{
[$style.header]: true, [$style.header]: true,
[$style.draggableHeader]: mappingEnabled, [$style.draggableHeader]: mappingEnabled,
[$style.activeHeader]: [$style.activeHeader]: (i === activeColumn || forceShowGrip) && mappingEnabled,
(i === activeColumn || forceShowGrip) && mappingEnabled,
[$style.draggingHeader]: isDragging, [$style.draggingHeader]: isDragging,
}" }"
> >
<span> <span>{{ column || '&nbsp;' }}</span>
<n8n-tooltip <n8n-tooltip
v-if="mappingEnabled" v-if="mappingEnabled"
placement="bottom-start" placement="bottom-start"
@@ -61,25 +60,22 @@
:value="i === 0 && showHintWithDelay" :value="i === 0 && showHintWithDelay"
> >
<div <div
v-if="focusedMappableInput"
slot="content" slot="content"
v-html=" v-html="
$locale.baseText( $locale.baseText('dataMapping.tableHint', {
focusedMappableInput
? 'dataMapping.tableHint'
: 'dataMapping.dragColumnToFieldHint',
{
interpolate: { name: focusedMappableInput }, interpolate: { name: focusedMappableInput },
}, })
)
" "
></div> ></div>
<span>{{ column || '&nbsp;' }}</span> <div v-else slot="content">
</n8n-tooltip> <img src='/static/data-mapping-gif.gif'/>
<span v-else>{{ column || '&nbsp;' }}</span> {{ $locale.baseText('dataMapping.dragColumnToFieldHint') }}
</span> </div>
<div :class="$style.dragButton"> <div :class="$style.dragButton">
<font-awesome-icon icon="grip-vertical" /> <font-awesome-icon icon="grip-vertical" />
</div> </div>
</n8n-tooltip>
</div> </div>
</template> </template>
</Draggable> </Draggable>

View File

@@ -0,0 +1,39 @@
<template>
<div class="titled-list">
<p v-text="title" />
<ul>
<li v-for="item in items" class="titled-list-item" :key="item" v-text="item" />
</ul>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: "TitledList",
props: {
title: {
type: String,
},
items: {
type: Array,
default: () => [],
},
},
});
</script>
<style lang="scss" scoped>
.titled-list {
display: flex;
flex-direction: column;
.titled-list-item {
list-style: none;
padding-left: var(--spacing-3xs);
&::before {
content: "- ";
}
}
}
</style>

View File

@@ -55,7 +55,7 @@
{{ header }} {{ header }}
</n8n-heading> </n8n-heading>
<n8n-text v-if="subheader"> <n8n-text v-if="subheader">
<span v-html="subheader"></span> <span v-text="subheader" />
</n8n-text> </n8n-text>
</div> </div>

View File

@@ -32,7 +32,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.timezone') + ":" }} {{ $locale.baseText('workflowSettings.timezone') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.timezone"></div> <div slot="content" v-text="helpTexts.timezone"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>
@@ -51,7 +51,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveDataErrorExecution') + ":" }} {{ $locale.baseText('workflowSettings.saveDataErrorExecution') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.saveDataErrorExecution"></div> <div slot="content" v-text="helpTexts.saveDataErrorExecution"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>
@@ -70,7 +70,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveDataSuccessExecution') + ":" }} {{ $locale.baseText('workflowSettings.saveDataSuccessExecution') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.saveDataSuccessExecution"></div> <div slot="content" v-text="helpTexts.saveDataSuccessExecution"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>
@@ -89,7 +89,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveManualExecutions') + ":" }} {{ $locale.baseText('workflowSettings.saveManualExecutions') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.saveManualExecutions"></div> <div slot="content" v-text="helpTexts.saveManualExecutions"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>
@@ -108,7 +108,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveExecutionProgress') + ":" }} {{ $locale.baseText('workflowSettings.saveExecutionProgress') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.saveExecutionProgress"></div> <div slot="content" v-text="helpTexts.saveExecutionProgress"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>
@@ -127,7 +127,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.timeoutWorkflow') + ":" }} {{ $locale.baseText('workflowSettings.timeoutWorkflow') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.executionTimeoutToggle"></div> <div slot="content" v-text="helpTexts.executionTimeoutToggle"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>
@@ -142,7 +142,7 @@
<el-col :span="10" class="setting-name"> <el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.timeoutAfter') + ":" }} {{ $locale.baseText('workflowSettings.timeoutAfter') + ":" }}
<n8n-tooltip class="setting-info" placement="top" > <n8n-tooltip class="setting-info" placement="top" >
<div slot="content" v-html="helpTexts.executionTimeout"></div> <div slot="content" v-text="helpTexts.executionTimeout"></div>
<font-awesome-icon icon="question-circle" /> <font-awesome-icon icon="question-circle" />
</n8n-tooltip> </n8n-tooltip>
</el-col> </el-col>

View File

@@ -19,7 +19,7 @@ import router from './router';
import { runExternalHook } from './components/mixins/externalHooks'; import { runExternalHook } from './components/mixins/externalHooks';
import { TelemetryPlugin } from './plugins/telemetry'; import { TelemetryPlugin } from './plugins/telemetry';
import { I18nPlugin } from './plugins/i18n'; import { I18nPlugin, i18nInstance } from './plugins/i18n';
import { store } from './store'; import { store } from './store';
@@ -32,6 +32,7 @@ Vue.use(TelemetryPlugin);
Vue.use((vue) => I18nPlugin(vue, store)); Vue.use((vue) => I18nPlugin(vue, store));
new Vue({ new Vue({
i18n: i18nInstance,
router, router,
store, store,
render: h => h(App), render: h => h(App),

View File

@@ -355,7 +355,7 @@ export class I18nClass {
} }
} }
const i18nInstance = new VueI18n({ export const i18nInstance = new VueI18n({
locale: 'en', locale: 'en',
fallbackLocale: 'en', fallbackLocale: 'en',
messages: { en: englishBaseText }, messages: { en: englishBaseText },

View File

@@ -162,8 +162,8 @@
"dataDisplay.needHelp": "Need help?", "dataDisplay.needHelp": "Need help?",
"dataDisplay.nodeDocumentation": "Node Documentation", "dataDisplay.nodeDocumentation": "Node Documentation",
"dataDisplay.openDocumentationFor": "Open {nodeTypeDisplayName} documentation", "dataDisplay.openDocumentationFor": "Open {nodeTypeDisplayName} documentation",
"dataMapping.dragColumnToFieldHint": "<img src='/static/data-mapping-gif.gif'/> Drag onto a field to map column to that field", "dataMapping.dragColumnToFieldHint": "Drag onto a field to map column to that field",
"dataMapping.dragFromPreviousHint": "Map data from previous nodes to <b>{name}</b> by first clicking this button", "dataMapping.dragFromPreviousHint": "Map data from previous nodes to <b>{name}</b><br/> by first clicking this button",
"dataMapping.success.title": "You just mapped some data!", "dataMapping.success.title": "You just mapped some data!",
"dataMapping.success.moreInfo": "Check out our <a href=\"https://docs.n8n.io/data/data-mapping\" target=\"_blank\">docs</a> for more details on mapping data in n8n", "dataMapping.success.moreInfo": "Check out our <a href=\"https://docs.n8n.io/data/data-mapping\" target=\"_blank\">docs</a> for more details on mapping data in n8n",
"dataMapping.tableHint": "<img src='/static/data-mapping-gif.gif'/> Drag a column onto <b>{name}</b> to map it", "dataMapping.tableHint": "<img src='/static/data-mapping-gif.gif'/> Drag a column onto <b>{name}</b> to map it",
@@ -495,9 +495,10 @@
"nodeSettings.scopes.notice": "<a data-key=\"toggle-expand\">{count} scope</a> available for {activeCredential} credentials | <a data-key=\"toggle-expand\">{count} scopes</a> available for {activeCredential} credentials", "nodeSettings.scopes.notice": "<a data-key=\"toggle-expand\">{count} scope</a> available for {activeCredential} credentials | <a data-key=\"toggle-expand\">{count} scopes</a> available for {activeCredential} credentials",
"nodeSettings.theNodeIsNotValidAsItsTypeIsUnknown": "The node is not valid as its type ({nodeType}) is unknown", "nodeSettings.theNodeIsNotValidAsItsTypeIsUnknown": "The node is not valid as its type ({nodeType}) is unknown",
"nodeSettings.communityNodeUnknown.title": "Install this node to use it", "nodeSettings.communityNodeUnknown.title": "Install this node to use it",
"nodeSettings.communityNodeUnknown.description": "This node is not currently installed. It's part of the <a href=\"https://www.npmjs.com/package/{packageName}\" target=\"_blank\"/>{packageName}</a> community package.", "nodeSettings.communityNodeUnknown.description": "This node is not currently installed. It's part of the {action} community package.",
"nodeSettings.communityNodeUnknown.installLink.text": "How to install community nodes", "nodeSettings.communityNodeUnknown.installLink.text": "How to install community nodes",
"nodeSettings.nodeTypeUnknown.description": "This node is not currently installed. It is either from a newer version of n8n, a <a href=\"{docURL}\" target=\"_blank\"/>custom node</a>, or has an invalid structure", "nodeSettings.nodeTypeUnknown.description": "This node is not currently installed. It is either from a newer version of n8n, a {action}, or has an invalid structure",
"nodeSettings.nodeTypeUnknown.description.customNode": "custom node",
"nodeSettings.thisNodeDoesNotHaveAnyParameters": "This node does not have any parameters", "nodeSettings.thisNodeDoesNotHaveAnyParameters": "This node does not have any parameters",
"nodeSettings.useTheHttpRequestNode": "Use the <b>HTTP Request</b> node to make a custom API call. We'll take care of the {nodeTypeDisplayName} auth for you. <a target=\"_blank\" href=\"https://docs.n8n.io/integrations/custom-operations/\">Learn more</a>", "nodeSettings.useTheHttpRequestNode": "Use the <b>HTTP Request</b> node to make a custom API call. We'll take care of the {nodeTypeDisplayName} auth for you. <a target=\"_blank\" href=\"https://docs.n8n.io/integrations/custom-operations/\">Learn more</a>",
"nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)", "nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)",
@@ -795,7 +796,8 @@
"settings.users.newEmailsToInvite": "New User Email Addresses", "settings.users.newEmailsToInvite": "New User Email Addresses",
"settings.users.noUsersToInvite": "No users to invite", "settings.users.noUsersToInvite": "No users to invite",
"settings.users.setupMyAccount": "Set up my owner account", "settings.users.setupMyAccount": "Set up my owner account",
"settings.users.setupSMTPToInviteUsers": "Set up SMTP to invite users. <a href=\"https://docs.n8n.io/reference/user-management.html#step-one-smtp\" target=\"_blank\">Instructions</a>", "settings.users.setupSMTPToInviteUsers": "Set up SMTP to invite users. {action}",
"settings.users.setupSMTPToInviteUsers.instructions": "Instructions",
"settings.users.setupToInviteUsers": "To invite users, set up your own account", "settings.users.setupToInviteUsers": "To invite users, set up your own account",
"settings.users.setupToInviteUsersInfo": "Invited users wont be able to see workflows and credentials of other users. <a href=\"https://docs.n8n.io/reference/user-management.html\" target=\"_blank\">More info</a> <br /> <br /> You will need details of an <a href=\"https://docs.n8n.io/reference/user-management.html#step-one-smtp\" target=\"_blank\">SMTP server</a> to complete the setup.", "settings.users.setupToInviteUsersInfo": "Invited users wont be able to see workflows and credentials of other users. <a href=\"https://docs.n8n.io/reference/user-management.html\" target=\"_blank\">More info</a> <br /> <br /> You will need details of an <a href=\"https://docs.n8n.io/reference/user-management.html#step-one-smtp\" target=\"_blank\">SMTP server</a> to complete the setup.",
"settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). <a target=\"_blank\" href=\"https://docs.n8n.io/reference/user-management.html#step-one-smtp\">Instructions</a>", "settings.users.smtpToAddUsersWarning": "Set up SMTP before adding users (so that n8n can send them invitation emails). <a target=\"_blank\" href=\"https://docs.n8n.io/reference/user-management.html#step-one-smtp\">Instructions</a>",
@@ -823,7 +825,9 @@
"settings.api.delete.toast": "API Key deleted", "settings.api.delete.toast": "API Key deleted",
"settings.api.view.copy.toast": "API Key copied to clipboard", "settings.api.view.copy.toast": "API Key copied to clipboard",
"settings.api.view.apiPlayground": "API Playground", "settings.api.view.apiPlayground": "API Playground",
"settings.api.view.info": "Use your API Key to control n8n programmatically using the <a href=\"https://docs.n8n.io/api\" target=\"_blank\">n8n API</a>. But if you only want to trigger workflows, consider using the <a href=\"https://docs.n8n.io/integrations/core-nodes/n8n-nodes-base.webhook/\" target=\"_blank\">webhook node</a> instead.", "settings.api.view.info": "Use your API Key to control n8n programmatically using the {apiAction}. But if you only want to trigger workflows, consider using the {webhookAction} instead.",
"settings.api.view.info.api": "n8n API",
"settings.api.view.info.webhook": "webhook node",
"settings.api.view.myKey": "My API Key", "settings.api.view.myKey": "My API Key",
"settings.api.view.tryapi": "Try it out using the", "settings.api.view.tryapi": "Try it out using the",
"settings.api.view.error": "Could not check if an api key already exists.", "settings.api.view.error": "Could not check if an api key already exists.",

View File

@@ -13,7 +13,22 @@
<div v-if="apiKey"> <div v-if="apiKey">
<p class="mb-s"> <p class="mb-s">
<n8n-info-tip :bold="false"> <n8n-info-tip :bold="false">
<span v-html="$locale.baseText('settings.api.view.info')"></span> <i18n path="settings.api.view.info" tag="span">
<template #apiAction>
<a
href="https://docs.n8n.io/api"
target="_blank"
v-text="$locale.baseText('settings.api.view.info.api')"
/>
</template>
<template #webhookAction>
<a
href="https://docs.n8n.io/integrations/core-nodes/n8n-nodes-base.webhook/"
target="_blank"
v-text="$locale.baseText('settings.api.view.info.webhook')"
/>
</template>
</i18n>
</n8n-info-tip> </n8n-info-tip>
</p> </p>
<n8n-card class="mb-4xs" :class="$style.card"> <n8n-card class="mb-4xs" :class="$style.card">

View File

@@ -5,7 +5,15 @@
<n8n-heading size="2xlarge">{{ $locale.baseText('settings.users') }}</n8n-heading> <n8n-heading size="2xlarge">{{ $locale.baseText('settings.users') }}</n8n-heading>
<div :class="$style.buttonContainer" v-if="!showUMSetupWarning"> <div :class="$style.buttonContainer" v-if="!showUMSetupWarning">
<n8n-tooltip :disabled="isSmtpSetup" placement="bottom"> <n8n-tooltip :disabled="isSmtpSetup" placement="bottom">
<div slot="content" v-html="$locale.baseText('settings.users.setupSMTPToInviteUsers')"></div> <i18n slot="content" path="settings.users.setupSMTPToInviteUsers" tag="span">
<template #action>
<a
href="https://docs.n8n.io/reference/user-management.html#step-one-smtp"
target="_blank"
v-text="$locale.baseText('settings.users.setupSMTPToInviteUsers.instructions')"
/>
</template>
</i18n>
<div> <div>
<n8n-button :label="$locale.baseText('settings.users.invite')" @click="onInvite" size="large" :disabled="!isSmtpSetup" /> <n8n-button :label="$locale.baseText('settings.users.invite')" @click="onInvite" size="large" :disabled="!isSmtpSetup" />
</div> </div>