refactor(editor): Stop using $locale in favor of the i18n composable (#11731)
Some checks failed
Test Master / install-and-build (push) Has been cancelled
Test Master / Unit tests (18.x) (push) Has been cancelled
Test Master / Unit tests (20.x) (push) Has been cancelled
Test Master / Unit tests (22.4) (push) Has been cancelled
Test Master / Lint (push) Has been cancelled
Test Master / Notify Slack on failure (push) Has been cancelled

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Ricardo Espinoza 2024-11-15 19:11:58 -05:00 committed by GitHub
parent 54e1f62535
commit 3f9127955a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 529 additions and 474 deletions

View file

@ -1,6 +1,6 @@
import type { Plugin } from 'vue';
import { render } from '@testing-library/vue';
import { i18nInstance, I18nPlugin } from '@/plugins/i18n';
import { i18nInstance } from '@/plugins/i18n';
import { GlobalComponentsPlugin } from '@/plugins/components';
import { GlobalDirectivesPlugin } from '@/plugins/directives';
import { FontAwesomePlugin } from '@/plugins/icons';
@ -32,7 +32,6 @@ const defaultOptions = {
'vue-json-pretty': vueJsonPretty,
},
plugins: [
I18nPlugin,
i18nInstance,
PiniaVuePlugin,
FontAwesomePlugin,

View file

@ -66,9 +66,9 @@ const startNewSession = async () => {
</template>
<template #footer>
<div :class="$style.footer">
<n8n-button :label="$locale.baseText('generic.cancel')" type="secondary" @click="close" />
<n8n-button :label="i18n.baseText('generic.cancel')" type="secondary" @click="close" />
<n8n-button
:label="$locale.baseText('aiAssistant.newSessionModal.confirm')"
:label="i18n.baseText('aiAssistant.newSessionModal.confirm')"
@click="startNewSession"
/>
</div>

View file

@ -4,6 +4,7 @@ import type { IBinaryData, IRunData } from 'n8n-workflow';
import BinaryDataDisplayEmbed from '@/components/BinaryDataDisplayEmbed.vue';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useI18n } from '@/composables/useI18n';
const props = defineProps<{
displayData: IBinaryData;
@ -17,6 +18,8 @@ const emit = defineEmits<{
const nodeHelpers = useNodeHelpers();
const workflowsStore = useWorkflowsStore();
const i18n = useI18n();
const workflowRunData = computed<IRunData | null>(() => {
const workflowExecution = workflowsStore.getWorkflowExecution;
if (workflowExecution === null) {
@ -74,15 +77,15 @@ function closeWindow() {
<n8n-button
size="small"
class="binary-data-window-back"
:title="$locale.baseText('binaryDataDisplay.backToOverviewPage')"
:title="i18n.baseText('binaryDataDisplay.backToOverviewPage')"
icon="arrow-left"
:label="$locale.baseText('binaryDataDisplay.backToList')"
:label="i18n.baseText('binaryDataDisplay.backToList')"
@click.stop="closeWindow"
/>
<div class="binary-data-window-wrapper">
<div v-if="!binaryData">
{{ $locale.baseText('binaryDataDisplay.noDataFoundToDisplay') }}
{{ i18n.baseText('binaryDataDisplay.noDataFoundToDisplay') }}
</div>
<BinaryDataDisplayEmbed v-else :binary-data="binaryData" />
</div>

View file

@ -5,6 +5,7 @@ import type { IBinaryData } from 'n8n-workflow';
import { jsonParse } from 'n8n-workflow';
import VueJsonPretty from 'vue-json-pretty';
import RunDataHtml from '@/components/RunDataHtml.vue';
import { useI18n } from '@/composables/useI18n';
const props = defineProps<{
binaryData: IBinaryData;
@ -17,6 +18,8 @@ const data = ref('');
const workflowsStore = useWorkflowsStore();
const i18n = useI18n();
const embedClass = computed(() => {
return [props.binaryData.fileType ?? 'other'];
});
@ -57,11 +60,11 @@ onMounted(async () => {
<span v-else>
<video v-if="binaryData.fileType === 'video'" controls autoplay>
<source :src="embedSource" :type="binaryData.mimeType" />
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
{{ i18n.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</video>
<audio v-else-if="binaryData.fileType === 'audio'" controls autoplay>
<source :src="embedSource" :type="binaryData.mimeType" />
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
{{ i18n.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</audio>
<img v-else-if="binaryData.fileType === 'image'" :src="embedSource" />
<VueJsonPretty

View file

@ -4,11 +4,13 @@ import { storeToRefs } from 'pinia';
import { useCanvasStore } from '@/stores/canvas.store';
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import { useDeviceSupport } from 'n8n-design-system';
import { useI18n } from '@/composables/useI18n';
const canvasStore = useCanvasStore();
const { zoomToFit, zoomIn, zoomOut, resetZoom } = canvasStore;
const { nodeViewScale, isDemo } = storeToRefs(canvasStore);
const deviceSupport = useDeviceSupport();
const i18n = useI18n();
const keyDown = (e: KeyboardEvent) => {
const isCtrlKeyPressed = deviceSupport.isCtrlKeyPressed(e);
@ -41,7 +43,7 @@ onBeforeUnmount(() => {
}"
>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomToFit')"
:label="i18n.baseText('nodeView.zoomToFit')"
:shortcut="{ keys: ['1'] }"
>
<n8n-icon-button
@ -52,10 +54,7 @@ onBeforeUnmount(() => {
@click="zoomToFit"
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomIn')"
:shortcut="{ keys: ['+'] }"
>
<KeyboardShortcutTooltip :label="i18n.baseText('nodeView.zoomIn')" :shortcut="{ keys: ['+'] }">
<n8n-icon-button
type="tertiary"
size="large"
@ -64,10 +63,7 @@ onBeforeUnmount(() => {
@click="zoomIn"
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomOut')"
:shortcut="{ keys: ['-'] }"
>
<KeyboardShortcutTooltip :label="i18n.baseText('nodeView.zoomOut')" :shortcut="{ keys: ['-'] }">
<n8n-icon-button
type="tertiary"
size="large"
@ -77,7 +73,7 @@ onBeforeUnmount(() => {
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.resetZoom')"
:label="i18n.baseText('nodeView.resetZoom')"
:shortcut="{ keys: ['0'] }"
>
<n8n-icon-button

View file

@ -399,7 +399,7 @@ async function onDrop(value: string, event: MouseEvent) {
:class="$style.tabs"
>
<el-tab-pane
:label="$locale.baseText('codeNodeEditor.tabs.code')"
:label="i18n.baseText('codeNodeEditor.tabs.code')"
name="code"
data-test-id="code-node-tab-code"
:class="$style.fillHeight"
@ -426,7 +426,7 @@ async function onDrop(value: string, event: MouseEvent) {
<slot name="suffix" />
</el-tab-pane>
<el-tab-pane
:label="$locale.baseText('codeNodeEditor.tabs.askAi')"
:label="i18n.baseText('codeNodeEditor.tabs.askAi')"
name="ask-ai"
data-test-id="code-node-tab-ai"
>

View file

@ -1,12 +1,10 @@
import type { EditorView } from '@codemirror/view';
import type { I18nClass } from '@/plugins/i18n';
import type { Workflow, CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
import type { Node } from 'estree';
import type { DefineComponent } from 'vue';
export type CodeNodeEditorMixin = InstanceType<
DefineComponent & {
$locale: I18nClass;
editor: EditorView | null;
mode: CodeExecutionMode;
language: CodeNodeEditorLanguage;

View file

@ -164,7 +164,7 @@ function valueChanged(parameterData: IUpdateInformation) {
<div class="collection-parameter" @keydown.stop>
<div class="collection-parameter-wrapper">
<div v-if="getProperties.length === 0" class="no-items-exist">
<n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text>
<n8n-text size="small">{{ i18n.baseText('collectionParameter.noProperties') }}</n8n-text>
</div>
<Suspense>

View file

@ -36,7 +36,7 @@ const validators = ref<{ [key: string]: IValidator }>({
if (!VALID_EMAIL_REGEX.test(value)) {
return {
messageKey: 'settings.users.invalidEmailError',
message: 'settings.users.invalidEmailError',
options: { interpolate: { email: value } },
};
}

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import { useI18n } from '@/composables/useI18n';
import type { IUpdateInformation, NodeAuthenticationOption } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
@ -28,6 +29,8 @@ const emit = defineEmits<{
const nodeTypesStore = useNodeTypesStore();
const ndvStore = useNDVStore();
const i18n = useI18n();
const props = defineProps<Props>();
const selected = ref('');
@ -133,8 +136,8 @@ defineExpose({
</div>
<div>
<n8n-input-label
:label="$locale.baseText('credentialEdit.credentialConfig.authTypeSelectorLabel')"
:tooltip-text="$locale.baseText('credentialEdit.credentialConfig.authTypeSelectorTooltip')"
:label="i18n.baseText('credentialEdit.credentialConfig.authTypeSelectorLabel')"
:tooltip-text="i18n.baseText('credentialEdit.credentialConfig.authTypeSelectorTooltip')"
:required="true"
/>
</div>

View file

@ -241,7 +241,7 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
v-show="showValidationWarning"
theme="danger"
:message="
$locale.baseText(
i18n.baseText(
`credentialEdit.credentialConfig.pleaseCheckTheErrorsBelow${
credentialPermissions.update ? '' : '.sharee'
}`,
@ -254,7 +254,7 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
v-if="authError && !showValidationWarning"
theme="danger"
:message="
$locale.baseText(
i18n.baseText(
`credentialEdit.credentialConfig.couldntConnectWithTheseSettings${
credentialPermissions.update ? '' : '.sharee'
}`,
@ -262,9 +262,9 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
)
"
:details="authError"
:button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
:button-label="i18n.baseText('credentialEdit.credentialConfig.retry')"
button-loading-label="Retrying"
:button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
:button-title="i18n.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
:button-loading="isRetesting"
@click="$emit('retest')"
/>
@ -272,18 +272,16 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
<Banner
v-show="showOAuthSuccessBanner && !showValidationWarning"
theme="success"
:message="$locale.baseText('credentialEdit.credentialConfig.accountConnected')"
:button-label="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
:button-title="
$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')
"
:message="i18n.baseText('credentialEdit.credentialConfig.accountConnected')"
:button-label="i18n.baseText('credentialEdit.credentialConfig.reconnect')"
:button-title="i18n.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')"
data-test-id="oauth-connect-success-banner"
@click="$emit('oauth')"
>
<template v-if="isGoogleOAuthType" #button>
<p
:class="$style.googleReconnectLabel"
v-text="`${$locale.baseText('credentialEdit.credentialConfig.reconnect')}:`"
v-text="`${i18n.baseText('credentialEdit.credentialConfig.reconnect')}:`"
/>
<GoogleAuthButton @click="$emit('oauth')" />
</template>
@ -292,10 +290,10 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
<Banner
v-show="testedSuccessfully && !showValidationWarning"
theme="success"
:message="$locale.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')"
:button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
:button-loading-label="$locale.baseText('credentialEdit.credentialConfig.retrying')"
:button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
:message="i18n.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')"
:button-label="i18n.baseText('credentialEdit.credentialConfig.retry')"
:button-loading-label="i18n.baseText('credentialEdit.credentialConfig.retrying')"
:button-title="i18n.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
:button-loading="isRetesting"
data-test-id="credentials-config-container-test-success"
@click="$emit('retest')"
@ -306,10 +304,10 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
v-if="documentationUrl && credentialProperties.length && !showCredentialDocs"
theme="warning"
>
{{ $locale.baseText('credentialEdit.credentialConfig.needHelpFillingOutTheseFields') }}
{{ i18n.baseText('credentialEdit.credentialConfig.needHelpFillingOutTheseFields') }}
<span class="ml-4xs">
<n8n-link :to="documentationUrl" size="small" bold @click="onDocumentationUrlClick">
{{ $locale.baseText('credentialEdit.credentialConfig.openDocs') }}
{{ i18n.baseText('credentialEdit.credentialConfig.openDocs') }}
</n8n-link>
</span>
</n8n-notice>
@ -331,16 +329,16 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
<CopyInput
v-if="isOAuthType && !allOAuth2BasePropertiesOverridden"
:label="$locale.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')"
:label="i18n.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')"
:value="oAuthCallbackUrl"
:copy-button-text="$locale.baseText('credentialEdit.credentialConfig.clickToCopy')"
:copy-button-text="i18n.baseText('credentialEdit.credentialConfig.clickToCopy')"
:hint="
$locale.baseText('credentialEdit.credentialConfig.subtitle', {
i18n.baseText('credentialEdit.credentialConfig.subtitle', {
interpolate: { appName },
})
"
:toast-title="
$locale.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard')
i18n.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard')
"
:redact-value="true"
/>
@ -349,7 +347,7 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
<div>
<n8n-info-tip :bold="false">
{{
$locale.baseText('credentialEdit.credentialEdit.info.sharee', {
i18n.baseText('credentialEdit.credentialEdit.info.sharee', {
interpolate: { credentialOwnerName },
})
}}
@ -379,15 +377,15 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => {
/>
<n8n-text v-if="isMissingCredentials" color="text-base" size="medium">
{{ $locale.baseText('credentialEdit.credentialConfig.missingCredentialType') }}
{{ i18n.baseText('credentialEdit.credentialConfig.missingCredentialType') }}
</n8n-text>
<EnterpriseEdition :features="[EnterpriseEditionFeature.ExternalSecrets]">
<template #fallback>
<n8n-info-tip class="mt-s">
{{ $locale.baseText('credentialEdit.credentialConfig.externalSecrets') }}
<n8n-link bold :to="$locale.baseText('settings.externalSecrets.docs')" size="small">
{{ $locale.baseText('credentialEdit.credentialConfig.externalSecrets.moreInfo') }}
{{ i18n.baseText('credentialEdit.credentialConfig.externalSecrets') }}
<n8n-link bold :to="i18n.baseText('settings.externalSecrets.docs')" size="small">
{{ i18n.baseText('credentialEdit.credentialConfig.externalSecrets.moreInfo') }}
</n8n-link>
</n8n-info-tip>
</template>

View file

@ -1067,7 +1067,7 @@ function resetCredentialData(): void {
<div :class="$style.credActions">
<n8n-icon-button
v-if="currentCredential && credentialPermissions.delete"
:title="$locale.baseText('credentialEdit.credentialEdit.delete')"
:title="i18n.baseText('credentialEdit.credentialEdit.delete')"
icon="trash"
type="tertiary"
:disabled="isSaving"
@ -1081,8 +1081,8 @@ function resetCredentialData(): void {
:is-saving="isSaving || isTesting"
:saving-label="
isTesting
? $locale.baseText('credentialEdit.credentialEdit.testing')
: $locale.baseText('credentialEdit.credentialEdit.saving')
? i18n.baseText('credentialEdit.credentialEdit.testing')
: i18n.baseText('credentialEdit.credentialEdit.saving')
"
data-test-id="credential-save-button"
@click="saveCredential"

View file

@ -1,9 +1,11 @@
<script lang="ts" setup>
import { useUIStore } from '@/stores/ui.store';
import { useRootStore } from '@/stores/root.store';
import { useI18n } from '@/composables/useI18n';
const { baseUrl } = useRootStore();
const type = useUIStore().appliedTheme === 'dark' ? '.dark.png' : '.png';
const i18n = useI18n();
const googleAuthButtons = {
'--google-auth-btn-normal': `url(${baseUrl}static/google-auth/normal${type}`,
'--google-auth-btn-focus': `url(${baseUrl}static/google-auth/focus${type}`,
@ -15,7 +17,7 @@ const googleAuthButtons = {
<template>
<button
:class="$style.googleAuthBtn"
:title="$locale.baseText('credentialEdit.oAuthButton.signInWithGoogle')"
:title="i18n.baseText('credentialEdit.oAuthButton.signInWithGoogle')"
:style="googleAuthButtons"
/>
</template>

View file

@ -1,9 +1,11 @@
<script lang="ts" setup>
import { useI18n } from '@/composables/useI18n';
import GoogleAuthButton from './GoogleAuthButton.vue';
defineProps<{
isGoogleOAuthType: boolean;
}>();
const i18n = useI18n();
</script>
<template>
@ -11,7 +13,7 @@ defineProps<{
<GoogleAuthButton v-if="isGoogleOAuthType" />
<n8n-button
v-else
:label="$locale.baseText('credentialEdit.oAuthButton.connectMyAccount')"
:label="i18n.baseText('credentialEdit.oAuthButton.connectMyAccount')"
size="large"
/>
</div>

View file

@ -6,6 +6,7 @@ import NodeCredentials from '@/components/NodeCredentials.vue';
import { useCredentialsStore } from '@/stores/credentials.store';
import { N8nOption, N8nSelect } from 'n8n-design-system';
import type { INodeUi, INodeUpdatePropertiesInformation } from '@/Interface';
import { useI18n } from '@/composables/useI18n';
type Props = {
activeCredentialType: string;
@ -28,6 +29,8 @@ const emit = defineEmits<{
const credentialsStore = useCredentialsStore();
const i18n = useI18n();
const innerSelectRef = ref<HTMLSelectElement>();
const allCredentialTypes = computed(() => credentialsStore.allCredentialTypes);
@ -119,7 +122,7 @@ defineExpose({ focus });
:size="inputSize"
filterable
:model-value="displayValue"
:placeholder="$locale.baseText('parameterInput.select')"
:placeholder="i18n.baseText('parameterInput.select')"
:title="displayTitle"
:disabled="isReadOnly"
data-test-id="credential-select"

View file

@ -9,9 +9,11 @@ import { createEventBus } from 'n8n-design-system/utils';
import { onMounted, ref } from 'vue';
import { CREDENTIAL_SELECT_MODAL_KEY } from '../constants';
import Modal from './Modal.vue';
import { useI18n } from '@/composables/useI18n';
const externalHooks = useExternalHooks();
const telemetry = useTelemetry();
const i18n = useI18n();
const modalBus = ref(createEventBus());
const selected = ref('');
@ -68,19 +70,19 @@ function openCredentialType() {
>
<template #header>
<h2 :class="$style.title">
{{ $locale.baseText('credentialSelectModal.addNewCredential') }}
{{ i18n.baseText('credentialSelectModal.addNewCredential') }}
</h2>
</template>
<template #content>
<div>
<div :class="$style.subtitle">
{{ $locale.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }}
{{ i18n.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }}
</div>
<N8nSelect
ref="selectRef"
filterable
default-first-option
:placeholder="$locale.baseText('credentialSelectModal.searchForApp')"
:placeholder="i18n.baseText('credentialSelectModal.searchForApp')"
size="xlarge"
:model-value="selected"
data-test-id="new-credential-type-select"
@ -103,7 +105,7 @@ function openCredentialType() {
<template #footer>
<div :class="$style.footer">
<N8nButton
:label="$locale.baseText('credentialSelectModal.continue')"
:label="i18n.baseText('credentialSelectModal.continue')"
float="right"
size="large"
:disabled="!selected"

View file

@ -1,8 +1,10 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { VIEWS } from '@/constants';
import { useI18n } from '@/composables/useI18n';
const router = useRouter();
const i18n = useI18n();
const navigateTo = () => {
void router.push({ name: VIEWS.TEMPLATES });
@ -12,7 +14,7 @@ const navigateTo = () => {
<template>
<div :class="$style.wrapper" @click="navigateTo">
<font-awesome-icon :class="$style.icon" icon="arrow-left" />
<div :class="$style.text" v-text="$locale.baseText('template.buttons.goBackButton')" />
<div :class="$style.text" v-text="i18n.baseText('template.buttons.goBackButton')" />
</div>
</template>

View file

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { useI18n } from '@/composables/useI18n';
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
import { useUIStore } from '@/stores/ui.store';
@ -7,6 +8,7 @@ defineProps<{
}>();
const uiStore = useUIStore();
const i18n = useI18n();
function onImportCurlClicked() {
uiStore.openModal(IMPORT_CURL_MODAL_KEY);
@ -17,7 +19,7 @@ function onImportCurlClicked() {
<div :class="$style.importSection">
<n8n-button
type="secondary"
:label="$locale.baseText('importCurlParameter.label')"
:label="i18n.baseText('importCurlParameter.label')"
:disabled="isReadOnly"
size="mini"
@click="onImportCurlClicked"

View file

@ -340,10 +340,10 @@ function activatePane() {
:run-index="isMappingMode ? 0 : runIndex"
:linked-runs="linkedRuns"
:can-link-runs="!mappedNode && canLinkRuns"
:too-much-data-title="$locale.baseText('ndv.input.tooMuchData.title')"
:no-data-in-branch-message="$locale.baseText('ndv.input.noOutputDataInBranch')"
:too-much-data-title="i18n.baseText('ndv.input.tooMuchData.title')"
:no-data-in-branch-message="i18n.baseText('ndv.input.noOutputDataInBranch')"
:is-executing="isExecutingPrevious"
:executing-message="$locale.baseText('ndv.input.executingPrevious')"
:executing-message="i18n.baseText('ndv.input.executingPrevious')"
:push-ref="pushRef"
:override-outputs="connectedCurrentNodeOutputs"
:mapping-enabled="isMappingEnabled"
@ -362,7 +362,7 @@ function activatePane() {
>
<template #header>
<div :class="$style.titleSection">
<span :class="$style.title">{{ $locale.baseText('ndv.input') }}</span>
<span :class="$style.title">{{ i18n.baseText('ndv.input') }}</span>
<N8nRadioButtons
v-if="isActiveNodeConfig && !readOnly"
data-test-id="input-panel-mode"
@ -402,13 +402,13 @@ function activatePane() {
:class="$style.noOutputData"
>
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('ndv.input.noOutputData.title')
i18n.baseText('ndv.input.noOutputData.title')
}}</N8nText>
<N8nTooltip v-if="!readOnly" :visible="showDraggableHint && showDraggableHintWithDelay">
<template #content>
<div
v-n8n-html="
$locale.baseText('dataMapping.dragFromPreviousHint', {
i18n.baseText('dataMapping.dragFromPreviousHint', {
interpolate: { name: focusedMappableInput },
})
"
@ -419,14 +419,14 @@ function activatePane() {
hide-icon
:transparent="true"
:node-name="(isActiveNodeConfig ? rootNode : currentNodeName) ?? ''"
:label="$locale.baseText('ndv.input.noOutputData.executePrevious')"
:label="i18n.baseText('ndv.input.noOutputData.executePrevious')"
telemetry-source="inputs"
data-test-id="execute-previous-node"
@execute="onNodeExecute"
/>
</N8nTooltip>
<N8nText v-if="!readOnly" tag="div" size="small">
{{ $locale.baseText('ndv.input.noOutputData.hint') }}
{{ i18n.baseText('ndv.input.noOutputData.hint') }}
</N8nText>
</div>
<div v-else :class="$style.notConnected">
@ -434,16 +434,16 @@ function activatePane() {
<WireMeUp />
</div>
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('ndv.input.notConnected.title')
i18n.baseText('ndv.input.notConnected.title')
}}</N8nText>
<N8nText tag="div">
{{ $locale.baseText('ndv.input.notConnected.message') }}
{{ i18n.baseText('ndv.input.notConnected.message') }}
<a
href="https://docs.n8n.io/workflows/connections/"
target="_blank"
@click="onConnectionHelpClick"
>
{{ $locale.baseText('ndv.input.notConnected.learnMore') }}
{{ i18n.baseText('ndv.input.notConnected.learnMore') }}
</a>
</N8nText>
</div>
@ -456,17 +456,17 @@ function activatePane() {
<template #no-output-data>
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('ndv.input.noOutputData')
i18n.baseText('ndv.input.noOutputData')
}}</N8nText>
</template>
<template #recovered-artificial-output-data>
<div :class="$style.recoveredOutputData">
<N8nText tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('executionDetails.executionFailed.recoveredNodeTitle')
i18n.baseText('executionDetails.executionFailed.recoveredNodeTitle')
}}</N8nText>
<N8nText>
{{ $locale.baseText('executionDetails.executionFailed.recoveredNodeMessage') }}
{{ i18n.baseText('executionDetails.executionFailed.recoveredNodeMessage') }}
</N8nText>
</div>
</template>

View file

@ -80,6 +80,7 @@ const usersStore = useUsersStore();
const workflowsStore = useWorkflowsStore();
const projectsStore = useProjectsStore();
const npsSurveyStore = useNpsSurveyStore();
const i18n = useI18n();
const router = useRouter();
const route = useRoute();
@ -644,7 +645,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
ref="dropdown"
v-model="appliedTagIds"
:event-bus="tagsEventBus"
:placeholder="$locale.baseText('workflowDetails.chooseOrCreateATag')"
:placeholder="i18n.baseText('workflowDetails.chooseOrCreateATag')"
class="tags-edit"
data-test-id="workflow-tags-dropdown"
@blur="onTagsBlur"
@ -656,7 +657,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
"
>
<span class="add-tag clickable" data-test-id="new-tag-link" @click="onTagsEditEnable">
+ {{ $locale.baseText('workflowDetails.addTag') }}
+ {{ i18n.baseText('workflowDetails.addTag') }}
</span>
</div>
<WorkflowTagsContainer
@ -687,13 +688,13 @@ function showCreateWorkflowSuccessToast(id?: string) {
data-test-id="workflow-share-button"
@click="onShareButtonClick"
>
{{ $locale.baseText('workflowDetails.share') }}
{{ i18n.baseText('workflowDetails.share') }}
</N8nButton>
</div>
<template #fallback>
<N8nTooltip>
<N8nButton type="secondary" :class="['mr-2xs', $style.disabledShareButton]">
{{ $locale.baseText('workflowDetails.share') }}
{{ i18n.baseText('workflowDetails.share') }}
</N8nButton>
<template #content>
<i18n-t
@ -706,7 +707,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
<template #action>
<a @click="goToUpgrade">
{{
$locale.baseText(
i18n.baseText(
uiStore.contextBasedTranslationKeys.workflows.sharing.unavailable
.button as BaseTextKey,
)
@ -727,7 +728,7 @@ function showCreateWorkflowSuccessToast(id?: string) {
"
:is-saving="isWorkflowSaving"
:with-shortcut="!readOnly && workflowPermissions.update"
:shortcut-tooltip="$locale.baseText('saveWorkflowButton.hint')"
:shortcut-tooltip="i18n.baseText('saveWorkflowButton.hint')"
data-test-id="workflow-save-button"
@click="onSaveButtonClick"
/>
@ -755,9 +756,9 @@ function showCreateWorkflowSuccessToast(id?: string) {
/>
<template #content>
<div class="mb-4xs">
<N8nBadge>{{ $locale.baseText('menuActions.badge.alpha') }}</N8nBadge>
<N8nBadge>{{ i18n.baseText('menuActions.badge.alpha') }}</N8nBadge>
</div>
{{ $locale.baseText('menuActions.nodeViewDiscovery.tooltip') }}
{{ i18n.baseText('menuActions.nodeViewDiscovery.tooltip') }}
<N8nIcon
:class="$style.closeNodeViewDiscovery"
icon="times-circle"

View file

@ -35,7 +35,7 @@ const workflowsStore = useWorkflowsStore();
const { callDebounced } = useDebounce();
const externalHooks = useExternalHooks();
const locale = useI18n();
const i18n = useI18n();
const route = useRoute();
const router = useRouter();
const telemetry = useTelemetry();
@ -53,11 +53,11 @@ const fullyExpanded = ref(false);
const userMenuItems = ref([
{
id: 'settings',
label: locale.baseText('settings'),
label: i18n.baseText('settings'),
},
{
id: 'logout',
label: locale.baseText('auth.signout'),
label: i18n.baseText('auth.signout'),
},
]);
@ -73,7 +73,7 @@ const mainMenuItems = computed(() => [
// Link to in-app templates, available if custom templates are enabled
id: 'templates',
icon: 'box-open',
label: locale.baseText('mainSidebar.templates'),
label: i18n.baseText('mainSidebar.templates'),
position: 'bottom',
available: settingsStore.isTemplatesEnabled && templatesStore.hasCustomTemplatesHost,
route: { to: { name: VIEWS.TEMPLATES } },
@ -82,7 +82,7 @@ const mainMenuItems = computed(() => [
// Link to website templates, available if custom templates are not enabled
id: 'templates',
icon: 'box-open',
label: locale.baseText('mainSidebar.templates'),
label: i18n.baseText('mainSidebar.templates'),
position: 'bottom',
available: settingsStore.isTemplatesEnabled && !templatesStore.hasCustomTemplatesHost,
link: {
@ -93,7 +93,7 @@ const mainMenuItems = computed(() => [
{
id: 'variables',
icon: 'variable',
label: locale.baseText('mainSidebar.variables'),
label: i18n.baseText('mainSidebar.variables'),
customIconSize: 'medium',
position: 'bottom',
route: { to: { name: VIEWS.VARIABLES } },
@ -101,13 +101,13 @@ const mainMenuItems = computed(() => [
{
id: 'help',
icon: 'question',
label: locale.baseText('mainSidebar.help'),
label: i18n.baseText('mainSidebar.help'),
position: 'bottom',
children: [
{
id: 'quickstart',
icon: 'video',
label: locale.baseText('mainSidebar.helpMenuItems.quickstart'),
label: i18n.baseText('mainSidebar.helpMenuItems.quickstart'),
link: {
href: 'https://www.youtube.com/watch?v=1MwSoB0gnM4',
target: '_blank',
@ -116,7 +116,7 @@ const mainMenuItems = computed(() => [
{
id: 'docs',
icon: 'book',
label: locale.baseText('mainSidebar.helpMenuItems.documentation'),
label: i18n.baseText('mainSidebar.helpMenuItems.documentation'),
link: {
href: 'https://docs.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
target: '_blank',
@ -125,7 +125,7 @@ const mainMenuItems = computed(() => [
{
id: 'forum',
icon: 'users',
label: locale.baseText('mainSidebar.helpMenuItems.forum'),
label: i18n.baseText('mainSidebar.helpMenuItems.forum'),
link: {
href: 'https://community.n8n.io?utm_source=n8n_app&utm_medium=app_sidebar',
target: '_blank',
@ -134,7 +134,7 @@ const mainMenuItems = computed(() => [
{
id: 'examples',
icon: 'graduation-cap',
label: locale.baseText('mainSidebar.helpMenuItems.course'),
label: i18n.baseText('mainSidebar.helpMenuItems.course'),
link: {
href: 'https://docs.n8n.io/courses/',
target: '_blank',
@ -143,7 +143,7 @@ const mainMenuItems = computed(() => [
{
id: 'report-bug',
icon: 'bug',
label: locale.baseText('mainSidebar.helpMenuItems.reportBug'),
label: i18n.baseText('mainSidebar.helpMenuItems.reportBug'),
link: {
href: getReportingURL(),
target: '_blank',
@ -152,7 +152,7 @@ const mainMenuItems = computed(() => [
{
id: 'about',
icon: 'info',
label: locale.baseText('mainSidebar.aboutN8n'),
label: i18n.baseText('mainSidebar.aboutN8n'),
position: 'bottom',
},
],
@ -357,10 +357,10 @@ const checkWidthAndAdjustSidebar = async (width: number) => {
<template v-if="isCollapsed" #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="settings">
{{ $locale.baseText('settings') }}
{{ i18n.baseText('settings') }}
</el-dropdown-item>
<el-dropdown-item command="logout">
{{ $locale.baseText('auth.signout') }}
{{ i18n.baseText('auth.signout') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>

View file

@ -39,7 +39,7 @@ const loadingQrCode = ref(true);
const clipboard = useClipboard();
const userStore = useUsersStore();
const i18 = useI18n();
const i18n = useI18n();
const toast = useToast();
// #endregion
@ -64,15 +64,15 @@ const onInput = (value: string) => {
authenticatorCode.value = value;
})
.catch(() => {
infoTextErrorMessage.value = i18.baseText('mfa.setup.invalidCode');
infoTextErrorMessage.value = i18n.baseText('mfa.setup.invalidCode');
});
};
const onCopySecretToClipboard = () => {
void clipboard.copy(secret.value);
toast.showToast({
title: i18.baseText('mfa.setup.step1.toast.copyToClipboard.title'),
message: i18.baseText('mfa.setup.step1.toast.copyToClipboard.message'),
title: i18n.baseText('mfa.setup.step1.toast.copyToClipboard.title'),
message: i18n.baseText('mfa.setup.step1.toast.copyToClipboard.message'),
type: 'success',
});
};
@ -102,20 +102,20 @@ const onSetupClick = async () => {
closeDialog();
toast.showMessage({
type: 'success',
title: i18.baseText('mfa.setup.step2.toast.setupFinished.message'),
title: i18n.baseText('mfa.setup.step2.toast.setupFinished.message'),
});
} catch (e) {
if (e.errorCode === MFA_AUTHENTICATION_TOKEN_WINDOW_EXPIRED) {
toast.showMessage({
type: 'error',
title: i18.baseText('mfa.setup.step2.toast.tokenExpired.error.message'),
title: i18n.baseText('mfa.setup.step2.toast.tokenExpired.error.message'),
});
return;
}
toast.showMessage({
type: 'error',
title: i18.baseText('mfa.setup.step2.toast.setupFinished.error.message'),
title: i18n.baseText('mfa.setup.step2.toast.setupFinished.error.message'),
});
}
};
@ -127,7 +127,7 @@ const getMfaQR = async () => {
secret.value = response.secret;
recoveryCodes.value = response.recoveryCodes;
} catch (error) {
toast.showError(error, i18.baseText('settings.api.view.error'));
toast.showError(error, i18n.baseText('settings.api.view.error'));
} finally {
loadingQrCode.value = false;
}
@ -153,8 +153,8 @@ onMounted(async () => {
max-height="640px"
:title="
!showRecoveryCodes
? i18.baseText('mfa.setup.step1.title')
: i18.baseText('mfa.setup.step2.title')
? i18n.baseText('mfa.setup.step1.title')
: i18n.baseText('mfa.setup.step2.title')
"
:event-bus="modalBus"
:name="MFA_SETUP_MODAL_KEY_NAME"
@ -165,21 +165,21 @@ onMounted(async () => {
<div v-if="!showRecoveryCodes" :class="[$style.container, $style.modalContent]">
<div :class="$style.textContainer">
<n8n-text size="large" color="text-dark" :bold="true">{{
i18.baseText('mfa.setup.step1.instruction1.title')
i18n.baseText('mfa.setup.step1.instruction1.title')
}}</n8n-text>
</div>
<div>
<n8n-text size="medium" :bold="false">
<i18n-t keypath="mfa.setup.step1.instruction1.subtitle" tag="span">
<template #part1>
{{ i18.baseText('mfa.setup.step1.instruction1.subtitle.part1') }}
{{ i18n.baseText('mfa.setup.step1.instruction1.subtitle.part1') }}
</template>
<template #part2>
<a
:class="$style.secret"
data-test-id="mfa-secret-button"
@click="onCopySecretToClipboard"
>{{ i18.baseText('mfa.setup.step1.instruction1.subtitle.part2') }}</a
>{{ i18n.baseText('mfa.setup.step1.instruction1.subtitle.part2') }}</a
>
</template>
</i18n-t>
@ -190,7 +190,7 @@ onMounted(async () => {
</div>
<div :class="$style.textContainer">
<n8n-text size="large" color="text-dark" :bold="true">{{
i18.baseText('mfa.setup.step1.instruction2.title')
i18n.baseText('mfa.setup.step1.instruction2.title')
}}</n8n-text>
</div>
<div :class="[$style.form, infoTextErrorMessage ? $style.error : '']">
@ -198,13 +198,13 @@ onMounted(async () => {
size="medium"
:bold="false"
:class="$style.labelTooltip"
:label="i18.baseText('mfa.setup.step1.input.label')"
:label="i18n.baseText('mfa.setup.step1.input.label')"
>
<n8n-input
v-model="authenticatorCode"
type="text"
:maxlength="6"
:placeholder="$locale.baseText('mfa.code.input.placeholder')"
:placeholder="i18n.baseText('mfa.code.input.placeholder')"
:required="true"
data-test-id="mfa-token-input"
@input="onInput"
@ -218,7 +218,7 @@ onMounted(async () => {
<div v-else :class="$style.container">
<div>
<n8n-text size="medium" :bold="false">{{
i18.baseText('mfa.setup.step2.description')
i18n.baseText('mfa.setup.step2.description')
}}</n8n-text>
</div>
<div :class="$style.recoveryCodesContainer">
@ -227,23 +227,23 @@ onMounted(async () => {
</div>
</div>
<n8n-info-tip :bold="false" :class="$style['edit-mode-footer-infotip']">
<i18n-t keypath="mfa.setup.step2.infobox.description" tag="span">
<i18nn-t keypath="mfa.setup.step2.infobox.description" tag="span">
<template #part1>
{{ i18.baseText('mfa.setup.step2.infobox.description.part1') }}
{{ i18n.baseText('mfa.setup.step2.infobox.description.part1') }}
</template>
<template #part2>
<n8n-text size="small" :bold="true" :class="$style.loseAccessText">
{{ i18.baseText('mfa.setup.step2.infobox.description.part2') }}
{{ i18n.baseText('mfa.setup.step2.infobox.description.part2') }}
</n8n-text>
</template>
</i18n-t>
</i18nn-t>
</n8n-info-tip>
<div>
<n8n-button
type="primary"
icon="download"
float="right"
:label="i18.baseText('mfa.setup.step2.button.download')"
:label="i18n.baseText('mfa.setup.step2.button.download')"
data-test-id="mfa-recovery-codes-button"
@click="onDownloadClick"
/>
@ -256,7 +256,7 @@ onMounted(async () => {
<n8n-button
float="right"
:disabled="!recoveryCodesDownloaded"
:label="i18.baseText('mfa.setup.step2.button.save')"
:label="i18n.baseText('mfa.setup.step2.button.save')"
size="large"
data-test-id="mfa-save-button"
@click="onSetupClick"
@ -267,7 +267,7 @@ onMounted(async () => {
<div>
<n8n-button
float="right"
:label="i18.baseText('mfa.setup.step1.button.continue')"
:label="i18n.baseText('mfa.setup.step1.button.continue')"
size="large"
:disabled="!readyToSubmit"
@click="onSaveClick"

View file

@ -14,6 +14,7 @@ import type {
} from 'n8n-workflow';
import { useDebounce } from '@/composables/useDebounce';
import { OnClickOutside } from '@vueuse/components';
import { useI18n } from '@/composables/useI18n';
interface Props {
rootNode: INodeUi;
@ -23,6 +24,7 @@ const props = defineProps<Props>();
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const nodeHelpers = useNodeHelpers();
const i18n = useI18n();
const { debounce } = useDebounce();
const emit = defineEmits<{
switchSelectedNode: [nodeName: string];
@ -245,7 +247,7 @@ defineExpose({
Add {{ connection.displayName }}
<template v-if="hasInputIssues(connection.type)">
<TitledList
:title="`${$locale.baseText('node.issues')}:`"
:title="`${i18n.baseText('node.issues')}:`"
:items="nodeInputIssues[connection.type]"
/>
</template>
@ -285,7 +287,7 @@ defineExpose({
{{ node.node.name }}
<template v-if="node.issues">
<TitledList
:title="`${$locale.baseText('node.issues')}:`"
:title="`${i18n.baseText('node.issues')}:`"
:items="node.issues"
/>
</template>

View file

@ -13,6 +13,7 @@ import type { AddedNodesAndConnections, ToggleNodeCreatorOptions } from '@/Inter
import { useActions } from './NodeCreator/composables/useActions';
import { useThrottleFn } from '@vueuse/core';
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import { useI18n } from '@/composables/useI18n';
type Props = {
nodeViewScale: number;
@ -34,6 +35,7 @@ const emit = defineEmits<{
}>();
const uiStore = useUIStore();
const i18n = useI18n();
const { getAddedNodesAndConnections } = useActions();
@ -101,7 +103,7 @@ onBeforeUnmount(() => {
<div v-if="!createNodeActive" :class="$style.nodeButtonsWrapper">
<div :class="$style.nodeCreatorButton" ref="wrapperRef" data-test-id="node-creator-plus-button">
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.openNodesPanel')"
:label="i18n.baseText('nodeView.openNodesPanel')"
:shortcut="{ keys: ['Tab'] }"
placement="left"
>
@ -119,7 +121,7 @@ onBeforeUnmount(() => {
@click="addStickyNote"
>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.addStickyHint')"
:label="i18n.baseText('nodeView.addStickyHint')"
:shortcut="{ keys: ['s'], shiftKey: true }"
placement="left"
>

View file

@ -245,7 +245,7 @@ registerKeyHook('MainViewArrowLeft', {
<CategorizedItemsRenderer
v-if="globalSearchItemsDiff.length > 0"
:elements="globalSearchItemsDiff"
:category="$locale.baseText('nodeCreator.categoryNames.otherCategories')"
:category="i18n.baseText('nodeCreator.categoryNames.otherCategories')"
@selected="onSelected"
>
</CategorizedItemsRenderer>

View file

@ -7,6 +7,7 @@ import {
import type { NodeFilterType } from '@/Interface';
import NoResultsIcon from './NoResultsIcon.vue';
import { useI18n } from '@/composables/useI18n';
export interface Props {
showIcon?: boolean;
@ -15,6 +16,7 @@ export interface Props {
}
defineProps<Props>();
const i18n = useI18n();
</script>
<template>
@ -27,34 +29,34 @@ defineProps<Props>();
</div>
<div :class="$style.title">
<slot name="title" />
<p v-text="$locale.baseText('nodeCreator.noResults.weDidntMakeThatYet')" />
<p v-text="i18n.baseText('nodeCreator.noResults.weDidntMakeThatYet')" />
<div
v-if="rootView === REGULAR_NODE_CREATOR_VIEW || rootView === TRIGGER_NODE_CREATOR_VIEW"
:class="$style.action"
>
{{ $locale.baseText('nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe') }}
{{ i18n.baseText('nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe') }}
<n8n-link v-if="rootView === REGULAR_NODE_CREATOR_VIEW" @click="$emit('addHttpNode')">
{{ $locale.baseText('nodeCreator.noResults.httpRequest') }}
{{ i18n.baseText('nodeCreator.noResults.httpRequest') }}
</n8n-link>
<n8n-link v-if="rootView === TRIGGER_NODE_CREATOR_VIEW" @click="$emit('addWebhookNode')">
{{ $locale.baseText('nodeCreator.noResults.webhook') }}
{{ i18n.baseText('nodeCreator.noResults.webhook') }}
</n8n-link>
{{ $locale.baseText('nodeCreator.noResults.node') }}
{{ i18n.baseText('nodeCreator.noResults.node') }}
</div>
</div>
<div v-if="showRequest" :class="$style.request">
<p v-text="$locale.baseText('nodeCreator.noResults.wantUsToMakeItFaster')" />
<p v-text="i18n.baseText('nodeCreator.noResults.wantUsToMakeItFaster')" />
<div>
<n8n-link :to="REQUEST_NODE_FORM_URL">
<span>{{ $locale.baseText('nodeCreator.noResults.requestTheNode') }}</span
<span>{{ i18n.baseText('nodeCreator.noResults.requestTheNode') }}</span
>&nbsp;
<span>
<font-awesome-icon
:class="$style.external"
icon="external-link-alt"
:title="$locale.baseText('nodeCreator.noResults.requestTheNode')"
:title="i18n.baseText('nodeCreator.noResults.requestTheNode')"
/>
</span>
</n8n-link>

View file

@ -176,9 +176,7 @@ function onBackButton() {
v-if="activeViewStack.hasSearch"
:class="$style.searchBar"
:placeholder="
searchPlaceholder
? searchPlaceholder
: $locale.baseText('nodeCreator.searchBar.searchNodes')
searchPlaceholder ? searchPlaceholder : i18n.baseText('nodeCreator.searchBar.searchNodes')
"
:model-value="activeViewStack.search"
@update:model-value="onSearch"

View file

@ -75,7 +75,6 @@ const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const nodeHelpers = useNodeHelpers();
const toast = useToast();
const subscribedToCredentialType = ref('');
@ -558,7 +557,7 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
<N8nTooltip placement="top">
<template #content>
<TitledList
:title="`${$locale.baseText('nodeCredentials.issues')}:`"
:title="`${i18n.baseText('nodeCredentials.issues')}:`"
:items="getIssues(type.name)"
/>
</template>
@ -574,7 +573,7 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
<font-awesome-icon
icon="pen"
class="clickable"
:title="$locale.baseText('nodeCredentials.updateCredential')"
:title="i18n.baseText('nodeCredentials.updateCredential')"
@click="editCredential(type.name)"
/>
</div>

View file

@ -703,13 +703,13 @@ onBeforeUnmount(() => {
>
<template #content>
<div :class="$style.triggerWarning">
{{ $locale.baseText('ndv.backToCanvas.waitingForTriggerWarning') }}
{{ i18n.baseText('ndv.backToCanvas.waitingForTriggerWarning') }}
</div>
</template>
<div :class="$style.backToCanvas" data-test-id="back-to-canvas" @click="close">
<n8n-icon icon="arrow-left" color="text-xlight" size="medium" />
<n8n-text color="text-xlight" size="medium" :bold="true">
{{ $locale.baseText('ndv.backToCanvas') }}
{{ i18n.baseText('ndv.backToCanvas') }}
</n8n-text>
</div>
</n8n-tooltip>
@ -816,7 +816,7 @@ onBeforeUnmount(() => {
@click="onFeatureRequestClick"
>
<font-awesome-icon icon="lightbulb" />
{{ $locale.baseText('ndv.featureRequest') }}
{{ i18n.baseText('ndv.featureRequest') }}
</a>
</template>
</NDVDraggablePanels>

View file

@ -981,7 +981,7 @@ onBeforeUnmount(() => {
</p>
<div class="missingNodeTitleContainer mt-s mb-xs">
<n8n-text size="large" color="text-dark" bold>
{{ $locale.baseText('nodeSettings.communityNodeUnknown.title') }}
{{ i18n.baseText('nodeSettings.communityNodeUnknown.title') }}
</n8n-text>
</div>
<div v-if="isCommunityNode" :class="$style.descriptionContainer">
@ -1004,7 +1004,7 @@ onBeforeUnmount(() => {
:to="COMMUNITY_NODES_INSTALLATION_DOCS_URL"
@click="onMissingNodeLearnMoreLinkClick"
>
{{ $locale.baseText('nodeSettings.communityNodeUnknown.installLink.text') }}
{{ i18n.baseText('nodeSettings.communityNodeUnknown.installLink.text') }}
</n8n-link>
</div>
<i18n-t v-else keypath="nodeSettings.nodeTypeUnknown.description" tag="span">
@ -1012,7 +1012,7 @@ onBeforeUnmount(() => {
<a
:href="CUSTOM_NODES_DOCS_URL"
target="_blank"
v-text="$locale.baseText('nodeSettings.nodeTypeUnknown.description.customNode')"
v-text="i18n.baseText('nodeSettings.nodeTypeUnknown.description.customNode')"
/>
</template>
</i18n-t>
@ -1021,7 +1021,7 @@ onBeforeUnmount(() => {
<n8n-notice
v-if="hasForeignCredential && !isHomeProjectTeam"
:content="
$locale.baseText('nodeSettings.hasForeignCredential', {
i18n.baseText('nodeSettings.hasForeignCredential', {
interpolate: { owner: credentialOwnerName },
})
"
@ -1053,7 +1053,7 @@ onBeforeUnmount(() => {
</ParameterInputList>
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
<n8n-text>
{{ $locale.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
{{ i18n.baseText('nodeSettings.thisNodeDoesNotHaveAnyParameters') }}
</n8n-text>
</div>
@ -1064,7 +1064,7 @@ onBeforeUnmount(() => {
>
<n8n-notice
:content="
$locale.baseText('nodeSettings.useTheHttpRequestNode', {
i18n.baseText('nodeSettings.useTheHttpRequestNode', {
interpolate: { nodeTypeDisplayName: nodeType?.displayName ?? '' },
})
"
@ -1094,7 +1094,7 @@ onBeforeUnmount(() => {
/>
<div class="node-version" data-test-id="node-version">
{{
$locale.baseText('nodeSettings.nodeVersion', {
i18n.baseText('nodeSettings.nodeVersion', {
interpolate: {
node: nodeType?.displayName as string,
version: (node.typeVersion ?? latestVersion).toString(),

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import NodeIcon from '@/components/NodeIcon.vue';
import { useI18n } from '@/composables/useI18n';
import type { INodeTypeDescription } from 'n8n-workflow';
import { computed, nextTick, ref } from 'vue';
@ -21,6 +22,8 @@ const editName = ref(false);
const newName = ref('');
const input = ref<HTMLInputElement>();
const i18n = useI18n();
const editable = computed(() => !props.readOnly && window === window.parent);
async function onEdit() {
@ -54,21 +57,21 @@ function onRename() {
@keydown.esc="editName = false"
>
<n8n-text :bold="true" color="text-base" tag="div">{{
$locale.baseText('ndv.title.renameNode')
i18n.baseText('ndv.title.renameNode')
}}</n8n-text>
<n8n-input ref="input" v-model="newName" size="small" data-test-id="node-rename-input" />
<div :class="$style.editButtons">
<n8n-button
type="secondary"
size="small"
:label="$locale.baseText('ndv.title.cancel')"
:label="i18n.baseText('ndv.title.cancel')"
@click="editName = false"
@keydown.enter.stop
/>
<n8n-button
type="primary"
size="small"
:label="$locale.baseText('ndv.title.rename')"
:label="i18n.baseText('ndv.title.rename')"
@click="onRename"
/>
</div>

View file

@ -1092,7 +1092,7 @@ onUpdated(async () => {
:model-value="codeEditDialogVisible"
:append-to="`#${APP_MODALS_ELEMENT_ID}`"
width="80%"
:title="`${i18n.baseText('codeEdit.edit')} ${$locale
:title="`${i18n.baseText('codeEdit.edit')} ${i18n
.nodeText()
.inputLabelDisplayName(parameter, path)}`"
:before-close="closeCodeEditDialog"
@ -1181,7 +1181,7 @@ onUpdated(async () => {
icon="external-link-alt"
size="xsmall"
class="textarea-modal-opener"
:title="$locale.baseText('parameterInput.openEditWindow')"
:title="i18n.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
/>
</template>
@ -1203,7 +1203,7 @@ onUpdated(async () => {
icon="external-link-alt"
size="xsmall"
class="textarea-modal-opener"
:title="$locale.baseText('parameterInput.openEditWindow')"
:title="i18n.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
/>
</template>
@ -1224,7 +1224,7 @@ onUpdated(async () => {
icon="external-link-alt"
size="xsmall"
class="textarea-modal-opener"
:title="$locale.baseText('parameterInput.openEditWindow')"
:title="i18n.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
/>
</template>
@ -1246,7 +1246,7 @@ onUpdated(async () => {
icon="external-link-alt"
size="xsmall"
class="textarea-modal-opener"
:title="$locale.baseText('parameterInput.openEditWindow')"
:title="i18n.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
/>
</template>
@ -1266,7 +1266,7 @@ onUpdated(async () => {
icon="external-link-alt"
size="xsmall"
class="textarea-modal-opener"
:title="$locale.baseText('parameterInput.openEditWindow')"
:title="i18n.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
/>
</template>

View file

@ -110,8 +110,8 @@ function onDocumentationUrlClick(): void {
<template>
<n8n-input-label
:label="$locale.credText().inputLabelDisplayName(parameter)"
:tooltip-text="$locale.credText().inputLabelDescription(parameter)"
:label="i18n.credText().inputLabelDisplayName(parameter)"
:tooltip-text="i18n.credText().inputLabelDescription(parameter)"
:required="parameter.required"
:show-tooltip="focused"
:show-options="menuExpanded"
@ -149,7 +149,7 @@ function onDocumentationUrlClick(): void {
/>
<div v-if="showRequiredErrors" :class="$style.errors">
<n8n-text color="danger" size="small">
{{ $locale.baseText('parameterInputExpanded.thisFieldIsRequired') }}
{{ i18n.baseText('parameterInputExpanded.thisFieldIsRequired') }}
<n8n-link
v-if="documentationUrl"
:to="documentationUrl"
@ -157,7 +157,7 @@ function onDocumentationUrlClick(): void {
:underline="true"
@click="onDocumentationUrlClick"
>
{{ $locale.baseText('parameterInputExpanded.openDocs') }}
{{ i18n.baseText('parameterInputExpanded.openDocs') }}
</n8n-link>
</n8n-text>
</div>

View file

@ -36,6 +36,7 @@ import { get, set } from 'lodash-es';
import { useRouter } from 'vue-router';
import { captureException } from '@sentry/vue';
import { N8nNotice, N8nIconButton, N8nInputLabel, N8nText, N8nIcon } from 'n8n-design-system';
import { useI18n } from '@/composables/useI18n';
const LazyFixedCollectionParameter = defineAsyncComponent(
async () => await import('./FixedCollectionParameter.vue'),
@ -69,6 +70,7 @@ const nodeHelpers = useNodeHelpers();
const asyncLoadingError = ref(false);
const router = useRouter();
const workflowHelpers = useWorkflowHelpers({ router });
const i18n = useI18n();
onErrorCaptured((e, component) => {
if (
@ -511,7 +513,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
<N8nNotice
v-else-if="parameter.type === 'notice'"
:class="['parameter-item', parameter.typeOptions?.containerClass ?? '']"
:content="$locale.nodeText().inputLabelDisplayName(parameter, path)"
:content="i18n.nodeText().inputLabelDisplayName(parameter, path)"
@action="onNoticeAction"
/>
@ -536,12 +538,12 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
size="mini"
icon="trash"
class="delete-option"
:title="$locale.baseText('parameterInputList.delete')"
:title="i18n.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)"
></N8nIconButton>
<N8nInputLabel
:label="$locale.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="$locale.nodeText().inputLabelDescription(parameter, path)"
:label="i18n.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="i18n.nodeText().inputLabelDescription(parameter, path)"
size="small"
:underline="true"
color="text-dark"
@ -570,13 +572,13 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
<template #fallback>
<N8nText size="small" class="async-notice">
<N8nIcon icon="sync-alt" size="xsmall" :spin="true" />
{{ $locale.baseText('parameterInputList.loadingFields') }}
{{ i18n.baseText('parameterInputList.loadingFields') }}
</N8nText>
</template>
</Suspense>
<N8nText v-else size="small" color="danger" class="async-notice">
<N8nIcon icon="exclamation-triangle" size="xsmall" />
{{ $locale.baseText('parameterInputList.loadingError') }}
{{ i18n.baseText('parameterInputList.loadingError') }}
</N8nText>
</div>
<ResourceMapper
@ -619,7 +621,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
size="mini"
icon="trash"
class="delete-option"
:title="$locale.baseText('parameterInputList.delete')"
:title="i18n.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)"
></N8nIconButton>

View file

@ -1,16 +1,19 @@
<script setup lang="ts">
import TitledList from '@/components/TitledList.vue';
import { useI18n } from '@/composables/useI18n';
defineProps<{
issues: string[];
}>();
const i18n = useI18n();
</script>
<template>
<div v-if="issues.length" :class="$style['parameter-issues']" data-test-id="parameter-issues">
<n8n-tooltip placement="top">
<template #content>
<TitledList :title="`${$locale.baseText('parameterInput.issues')}:`" :items="issues" />
<TitledList :title="`${i18n.baseText('parameterInput.issues')}:`" :items="issues" />
</template>
<font-awesome-icon icon="exclamation-triangle" />
</n8n-tooltip>

View file

@ -605,8 +605,8 @@ const onSubmit = async (values: IPersonalizationLatestVersion) => {
<template>
<Modal
:name="PERSONALIZATION_MODAL_KEY"
:title="$locale.baseText('personalizationModal.customizeN8n')"
:subtitle="$locale.baseText('personalizationModal.theseQuestionsHelpUs')"
:title="i18n.baseText('personalizationModal.customizeN8n')"
:subtitle="i18n.baseText('personalizationModal.theseQuestionsHelpUs')"
:center-title="true"
:show-close="false"
:event-bus="modalBus"
@ -633,7 +633,7 @@ const onSubmit = async (values: IPersonalizationLatestVersion) => {
<div>
<n8n-button
:loading="isSaving"
:label="$locale.baseText('personalizationModal.getStarted')"
:label="i18n.baseText('personalizationModal.getStarted')"
float="right"
@click="onSave"
/>

View file

@ -689,15 +689,15 @@ function onInputBlur() {
<template #error>
<div :class="$style.error" data-test-id="rlc-error-container">
<n8n-text color="text-dark" align="center" tag="div">
{{ $locale.baseText('resourceLocator.mode.list.error.title') }}
{{ i18n.baseText('resourceLocator.mode.list.error.title') }}
</n8n-text>
<n8n-text v-if="hasCredential || credentialsNotSet" size="small" color="text-base">
{{ $locale.baseText('resourceLocator.mode.list.error.description.part1') }}
{{ i18n.baseText('resourceLocator.mode.list.error.description.part1') }}
<a v-if="credentialsNotSet" @click="createNewCredential">{{
$locale.baseText('resourceLocator.mode.list.error.description.part2.noCredentials')
i18n.baseText('resourceLocator.mode.list.error.description.part2.noCredentials')
}}</a>
<a v-else-if="hasCredential" @click="openCredential">{{
$locale.baseText('resourceLocator.mode.list.error.description.part2.hasCredentials')
i18n.baseText('resourceLocator.mode.list.error.description.part2.hasCredentials')
}}</a>
</n8n-text>
</div>
@ -714,7 +714,7 @@ function onInputBlur() {
:model-value="selectedMode"
:size="inputSize"
:disabled="isReadOnly"
:placeholder="$locale.baseText('resourceLocator.modeSelector.placeholder')"
:placeholder="i18n.baseText('resourceLocator.modeSelector.placeholder')"
data-test-id="rlc-mode-selector"
@update:model-value="onModeSelected"
>
@ -726,7 +726,7 @@ function onInputBlur() {
:disabled="isValueExpression && mode.name === 'list'"
:title="
isValueExpression && mode.name === 'list'
? $locale.baseText('resourceLocator.mode.list.disabled.title')
? i18n.baseText('resourceLocator.mode.list.disabled.title')
: ''
"
>

View file

@ -1,4 +1,5 @@
<script setup lang="ts">
import { useI18n } from '@/composables/useI18n';
import type { IResourceLocatorResultExpanded } from '@/Interface';
import { N8nLoading } from 'n8n-design-system';
import type { EventBus } from 'n8n-design-system/utils';
@ -43,6 +44,8 @@ const emit = defineEmits<{
filter: [filter: string];
}>();
const i18n = useI18n();
const hoverIndex = ref(0);
const showHoverUrl = ref(false);
const searchRef = ref<HTMLInputElement>();
@ -209,7 +212,7 @@ function onResultsEnd() {
ref="searchRef"
:model-value="filter"
:clearable="true"
:placeholder="$locale.baseText('resourceLocator.search.placeholder')"
:placeholder="i18n.baseText('resourceLocator.search.placeholder')"
data-test-id="rlc-search"
@update:model-value="onFilterInput"
>
@ -219,13 +222,13 @@ function onResultsEnd() {
</N8nInput>
</div>
<div v-if="filterRequired && !filter && !errorView && !loading" :class="$style.searchRequired">
{{ $locale.baseText('resourceLocator.mode.list.searchRequired') }}
{{ i18n.baseText('resourceLocator.mode.list.searchRequired') }}
</div>
<div
v-else-if="!errorView && sortedResources.length === 0 && !loading"
:class="$style.messageContainer"
>
{{ $locale.baseText('resourceLocator.mode.list.noResults') }}
{{ i18n.baseText('resourceLocator.mode.list.noResults') }}
</div>
<div
v-else-if="!errorView"
@ -254,7 +257,7 @@ function onResultsEnd() {
<font-awesome-icon
v-if="showHoverUrl && result.url && hoverIndex === i"
icon="external-link-alt"
:title="result.linkAlt || $locale.baseText('resourceLocator.mode.list.openUrl')"
:title="result.linkAlt || i18n.baseText('resourceLocator.mode.list.openUrl')"
@click="openUrl($event, result.url)"
/>
</div>

View file

@ -22,6 +22,7 @@ import {
} from '@/utils/nodeTypesUtils';
import { useNodeSpecificationValues } from '@/composables/useNodeSpecificationValues';
import { N8nIconButton, N8nInputLabel, N8nOption, N8nSelect, N8nTooltip } from 'n8n-design-system';
import { useI18n } from '@/composables/useI18n';
interface Props {
parameter: INodeProperties;
@ -52,6 +53,8 @@ const {
pluralFieldWordCapitalized,
} = useNodeSpecificationValues(props.parameter.typeOptions);
const i18n = useI18n();
const emit = defineEmits<{
fieldValueChanged: [value: IUpdateInformation];
removeField: [field: string];
@ -311,7 +314,7 @@ defineExpose({
</N8nInputLabel>
<div v-if="orderedFields.length === 0" class="mt-3xs mb-xs">
<N8nText size="small">{{
$locale.baseText('fixedCollectionParameter.currentlyNoItemsExist')
i18n.baseText('fixedCollectionParameter.currentlyNoItemsExist')
}}</N8nText>
</div>
<div

View file

@ -1254,7 +1254,7 @@ defineExpose({ enterEditMode });
icon="thumbtack"
:class="$style.pinnedDataCallout"
>
{{ $locale.baseText('runData.pindata.thisDataIsPinned') }}
{{ i18n.baseText('runData.pindata.thisDataIsPinned') }}
<span v-if="!isReadOnlyRoute && !readOnlyEnv" class="ml-4xs">
<N8nLink
theme="secondary"
@ -1264,7 +1264,7 @@ defineExpose({ enterEditMode });
data-test-id="ndv-unpin-data"
@click.stop="onTogglePinData({ source: 'banner-link' })"
>
{{ $locale.baseText('runData.pindata.unpin') }}
{{ i18n.baseText('runData.pindata.unpin') }}
</N8nLink>
</span>
<template #trailingContent>
@ -1276,7 +1276,7 @@ defineExpose({ enterEditMode });
underline
@click="onClickDataPinningDocsLink"
>
{{ $locale.baseText('runData.pindata.learnMore') }}
{{ i18n.baseText('runData.pindata.learnMore') }}
</N8nLink>
</template>
</N8nCallout>
@ -1322,7 +1322,7 @@ defineExpose({ enterEditMode });
<N8nIconButton
v-if="canPinData && !isReadOnlyRoute && !readOnlyEnv"
v-show="!editMode.enabled"
:title="$locale.baseText('runData.editOutput')"
:title="i18n.baseText('runData.editOutput')"
:circle="false"
:disabled="node?.disabled"
icon="pencil-alt"
@ -1347,13 +1347,13 @@ defineExpose({ enterEditMode });
<div v-show="editMode.enabled" :class="$style.editModeActions">
<N8nButton
type="tertiary"
:label="$locale.baseText('runData.editor.cancel')"
:label="i18n.baseText('runData.editor.cancel')"
@click="onClickCancelEdit"
/>
<N8nButton
class="ml-2xs"
type="primary"
:label="$locale.baseText('runData.editor.save')"
:label="i18n.baseText('runData.editor.save')"
@click="onClickSaveEdit"
/>
</div>
@ -1381,7 +1381,7 @@ defineExpose({ enterEditMode });
@update:model-value="onRunIndexChange"
@click.stop
>
<template #prepend>{{ $locale.baseText('ndv.output.run') }}</template>
<template #prepend>{{ i18n.baseText('ndv.output.run') }}</template>
<N8nOption
v-for="option in maxRunIndex + 1"
:key="option"
@ -1392,7 +1392,7 @@ defineExpose({ enterEditMode });
<N8nTooltip v-if="canLinkRuns" placement="right">
<template #content>
{{ $locale.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
{{ i18n.baseText(linkedRuns ? 'runData.unlinking.hint' : 'runData.linking.hint') }}
</template>
<N8nIconButton
:icon="linkedRuns ? 'unlink' : 'link'"
@ -1467,7 +1467,7 @@ defineExpose({ enterEditMode });
<N8nText v-if="search" :class="$style.itemsText">
{{
$locale.baseText('ndv.search.items', {
i18n.baseText('ndv.search.items', {
adjustToNumber: unfilteredDataCount,
interpolate: { matched: dataCount, total: unfilteredDataCount },
})
@ -1476,7 +1476,7 @@ defineExpose({ enterEditMode });
<N8nText v-else :class="$style.itemsText">
<span>
{{
$locale.baseText('ndv.output.items', {
i18n.baseText('ndv.output.items', {
adjustToNumber: dataCount,
interpolate: { count: dataCount },
})
@ -1484,7 +1484,7 @@ defineExpose({ enterEditMode });
</span>
<span v-if="activeTaskMetadata?.subExecutionsCount">
{{
$locale.baseText('ndv.output.andSubExecutions', {
i18n.baseText('ndv.output.andSubExecutions', {
adjustToNumber: activeTaskMetadata.subExecutionsCount,
interpolate: { count: activeTaskMetadata.subExecutionsCount },
})
@ -1523,9 +1523,9 @@ defineExpose({ enterEditMode });
</div>
<div :class="$style.editModeFooter">
<N8nInfoTip :bold="false" :class="$style.editModeFooterInfotip">
{{ $locale.baseText('runData.editor.copyDataInfo') }}
{{ i18n.baseText('runData.editor.copyDataInfo') }}
<N8nLink :to="DATA_EDITING_DOCS_URL" size="small">
{{ $locale.baseText('generic.learnMore') }}
{{ i18n.baseText('generic.learnMore') }}
</N8nLink>
</N8nInfoTip>
</div>
@ -1556,9 +1556,9 @@ defineExpose({ enterEditMode });
:class="$style.center"
>
<N8nText>
{{ $locale.baseText('ndv.input.disabled', { interpolate: { nodeName: node.name } }) }}
{{ i18n.baseText('ndv.input.disabled', { interpolate: { nodeName: node.name } }) }}
<N8nLink @click="enableNode">
{{ $locale.baseText('ndv.input.disabled.cta') }}
{{ i18n.baseText('ndv.input.disabled.cta') }}
</N8nLink>
</N8nText>
</div>
@ -1570,7 +1570,7 @@ defineExpose({ enterEditMode });
<div v-else-if="hasNodeRun && hasRunError" :class="$style.stretchVertically">
<N8nText v-if="isPaneTypeInput" :class="$style.center" size="large" tag="p" bold>
{{
$locale.baseText('nodeErrorView.inputPanel.previousNodeError.title', {
i18n.baseText('nodeErrorView.inputPanel.previousNodeError.title', {
interpolate: { nodeName: node?.name ?? '' },
})
}}
@ -1598,14 +1598,12 @@ defineExpose({ enterEditMode });
:class="$style.center"
>
<div v-if="search">
<N8nText tag="h3" size="large">{{
$locale.baseText('ndv.search.noMatch.title')
}}</N8nText>
<N8nText tag="h3" size="large">{{ i18n.baseText('ndv.search.noMatch.title') }}</N8nText>
<N8nText>
<i18n-t keypath="ndv.search.noMatch.description" tag="span">
<template #link>
<a href="#" @click="onSearchClear">
{{ $locale.baseText('ndv.search.noMatch.description.link') }}
{{ i18n.baseText('ndv.search.noMatch.description.link') }}
</a>
</template>
</i18n-t>
@ -1629,7 +1627,7 @@ defineExpose({ enterEditMode });
<N8nText align="center" tag="div"
><span
v-n8n-html="
$locale.baseText('ndv.output.tooMuchData.message', {
i18n.baseText('ndv.output.tooMuchData.message', {
interpolate: { size: dataSizeInMB },
})
"
@ -1638,13 +1636,13 @@ defineExpose({ enterEditMode });
<N8nButton
outline
:label="$locale.baseText('ndv.output.tooMuchData.showDataAnyway')"
:label="i18n.baseText('ndv.output.tooMuchData.showDataAnyway')"
@click="showTooMuchData"
/>
<N8nButton
size="small"
:label="$locale.baseText('runData.downloadBinaryData')"
:label="i18n.baseText('runData.downloadBinaryData')"
@click="downloadJsonData()"
/>
</div>
@ -1663,20 +1661,20 @@ defineExpose({ enterEditMode });
:class="$style.center"
>
<N8nText>
{{ $locale.baseText('runData.switchToBinary.info') }}
{{ i18n.baseText('runData.switchToBinary.info') }}
<a @click="switchToBinary">
{{ $locale.baseText('runData.switchToBinary.binary') }}
{{ i18n.baseText('runData.switchToBinary.binary') }}
</a>
</N8nText>
</div>
<div v-else-if="showIoSearchNoMatchContent" :class="$style.center">
<N8nText tag="h3" size="large">{{ $locale.baseText('ndv.search.noMatch.title') }}</N8nText>
<N8nText tag="h3" size="large">{{ i18n.baseText('ndv.search.noMatch.title') }}</N8nText>
<N8nText>
<i18n-t keypath="ndv.search.noMatch.description" tag="span">
<template #link>
<a href="#" @click="onSearchClear">
{{ $locale.baseText('ndv.search.noMatch.description.link') }}
{{ i18n.baseText('ndv.search.noMatch.description.link') }}
</a>
</template>
</i18n-t>
@ -1737,9 +1735,7 @@ defineExpose({ enterEditMode });
</Suspense>
<div v-else-if="displayMode === 'binary' && binaryData.length === 0" :class="$style.center">
<N8nText align="center" tag="div">{{
$locale.baseText('runData.noBinaryDataFound')
}}</N8nText>
<N8nText align="center" tag="div">{{ i18n.baseText('runData.noBinaryDataFound') }}</N8nText>
</div>
<div v-else-if="displayMode === 'binary'" :class="$style.dataDisplay">
@ -1763,7 +1759,7 @@ defineExpose({ enterEditMode });
<div v-if="binaryData.fileName">
<div>
<N8nText size="small" :bold="true"
>{{ $locale.baseText('runData.fileName') }}:
>{{ i18n.baseText('runData.fileName') }}:
</N8nText>
</div>
<div :class="$style.binaryValue">{{ binaryData.fileName }}</div>
@ -1771,7 +1767,7 @@ defineExpose({ enterEditMode });
<div v-if="binaryData.directory">
<div>
<N8nText size="small" :bold="true"
>{{ $locale.baseText('runData.directory') }}:
>{{ i18n.baseText('runData.directory') }}:
</N8nText>
</div>
<div :class="$style.binaryValue">{{ binaryData.directory }}</div>
@ -1779,7 +1775,7 @@ defineExpose({ enterEditMode });
<div v-if="binaryData.fileExtension">
<div>
<N8nText size="small" :bold="true"
>{{ $locale.baseText('runData.fileExtension') }}:</N8nText
>{{ i18n.baseText('runData.fileExtension') }}:</N8nText
>
</div>
<div :class="$style.binaryValue">{{ binaryData.fileExtension }}</div>
@ -1787,7 +1783,7 @@ defineExpose({ enterEditMode });
<div v-if="binaryData.mimeType">
<div>
<N8nText size="small" :bold="true"
>{{ $locale.baseText('runData.mimeType') }}:
>{{ i18n.baseText('runData.mimeType') }}:
</N8nText>
</div>
<div :class="$style.binaryValue">{{ binaryData.mimeType }}</div>
@ -1795,7 +1791,7 @@ defineExpose({ enterEditMode });
<div v-if="binaryData.fileSize">
<div>
<N8nText size="small" :bold="true"
>{{ $locale.baseText('runData.fileSize') }}:
>{{ i18n.baseText('runData.fileSize') }}:
</N8nText>
</div>
<div :class="$style.binaryValue">{{ binaryData.fileSize }}</div>
@ -1805,7 +1801,7 @@ defineExpose({ enterEditMode });
<N8nButton
v-if="isViewable(index, key)"
size="small"
:label="$locale.baseText('runData.showBinaryData')"
:label="i18n.baseText('runData.showBinaryData')"
data-test-id="ndv-view-binary-data"
@click="displayBinaryData(index, key)"
/>
@ -1813,7 +1809,7 @@ defineExpose({ enterEditMode });
v-if="isDownloadable(index, key)"
size="small"
type="secondary"
:label="$locale.baseText('runData.downloadBinaryData')"
:label="i18n.baseText('runData.downloadBinaryData')"
data-test-id="ndv-download-binary-data"
@click="downloadBinaryData(index, key)"
/>
@ -1857,9 +1853,9 @@ defineExpose({ enterEditMode });
teleported
@update:model-value="onPageSizeChange"
>
<template #prepend>{{ $locale.baseText('ndv.output.pageSize') }}</template>
<template #prepend>{{ i18n.baseText('ndv.output.pageSize') }}</template>
<N8nOption v-for="size in pageSizes" :key="size" :label="size" :value="size"> </N8nOption>
<N8nOption :label="$locale.baseText('ndv.output.all')" :value="dataCount"> </N8nOption>
<N8nOption :label="i18n.baseText('ndv.output.all')" :value="dataCount"> </N8nOption>
</N8nSelect>
</div>
</div>

View file

@ -207,7 +207,7 @@ onMounted(() => {
size="small"
:class="$style.copyToClipboard"
type="secondary"
:title="$locale.baseText('nodeErrorView.copyToClipboard')"
:title="i18n.baseText('nodeErrorView.copyToClipboard')"
icon="copy"
@click="onCopyToClipboard(raw)"
/>

View file

@ -8,6 +8,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import NodeIcon from '@/components/NodeIcon.vue';
import RunDataAiContent from './RunDataAiContent.vue';
import { ElTree } from 'element-plus';
import { useI18n } from '@/composables/useI18n';
interface AIResult {
node: string;
@ -32,6 +33,9 @@ const props = withDefaults(defineProps<Props>(), { runIndex: 0 });
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const selectedRun: Ref<IAiData[]> = ref([]);
const i18n = useI18n();
function isTreeNodeSelected(node: TreeNode) {
return selectedRun.value.some((run) => run.node === node.node && run.runIndex === node.runIndex);
}
@ -260,7 +264,7 @@ watch(() => props.runIndex, selectFirst, { immediate: true });
<div v-if="selectedRun.length === 0" :class="$style.empty">
<n8n-text size="large">
{{
$locale.baseText('ndv.output.ai.empty', {
i18n.baseText('ndv.output.ai.empty', {
interpolate: {
node: props.node.name,
},
@ -277,7 +281,7 @@ watch(() => props.runIndex, selectFirst, { immediate: true });
</div>
</div>
</template>
<div v-else :class="$style.noData">{{ $locale.baseText('ndv.output.ai.waiting') }}</div>
<div v-else :class="$style.noData">{{ i18n.baseText('ndv.output.ai.waiting') }}</div>
</div>
</template>

View file

@ -12,6 +12,7 @@ import { computed } from 'vue';
import NodeIcon from '@/components/NodeIcon.vue';
import AiRunContentBlock from './AiRunContentBlock.vue';
import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
import { useI18n } from '@/composables/useI18n';
interface RunMeta {
startTimeMs: number;
@ -33,6 +34,7 @@ const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const { trackOpeningRelatedExecution, resolveRelatedExecutionUrl } = useExecutionHelpers();
const i18n = useI18n();
type TokenUsageData = {
completionTokens: number;
@ -131,7 +133,7 @@ const outputError = computed(() => {
{{ new Date(runMeta?.startTimeMs).toLocaleString() }}
</template>
{{
$locale.baseText('runData.aiContentBlock.startedAt', {
i18n.baseText('runData.aiContentBlock.startedAt', {
interpolate: {
startTime: new Date(runMeta?.startTimeMs).toLocaleTimeString(),
},
@ -147,7 +149,7 @@ const outputError = computed(() => {
>
<N8nIcon icon="external-link-alt" size="xsmall" />
{{
$locale.baseText('runData.openSubExecution', {
i18n.baseText('runData.openSubExecution', {
interpolate: {
id: runMeta.subExecution?.executionId,
},
@ -157,7 +159,7 @@ const outputError = computed(() => {
</li>
<li v-if="(consumedTokensSum?.totalTokens ?? 0) > 0" :class="$style.tokensUsage">
{{
$locale.baseText('runData.aiContentBlock.tokens', {
i18n.baseText('runData.aiContentBlock.tokens', {
interpolate: {
count: formatTokenUsageCount(consumedTokensSum?.totalTokens ?? 0),
},
@ -166,9 +168,9 @@ const outputError = computed(() => {
<n8n-info-tip type="tooltip" theme="info-light" tooltip-placement="right">
<div>
<n8n-text :bold="true" size="small">
{{ $locale.baseText('runData.aiContentBlock.tokens.prompt') }}
{{ i18n.baseText('runData.aiContentBlock.tokens.prompt') }}
{{
$locale.baseText('runData.aiContentBlock.tokens', {
i18n.baseText('runData.aiContentBlock.tokens', {
interpolate: {
count: formatTokenUsageCount(consumedTokensSum?.promptTokens ?? 0),
},
@ -177,9 +179,9 @@ const outputError = computed(() => {
</n8n-text>
<br />
<n8n-text :bold="true" size="small">
{{ $locale.baseText('runData.aiContentBlock.tokens.completion') }}
{{ i18n.baseText('runData.aiContentBlock.tokens.completion') }}
{{
$locale.baseText('runData.aiContentBlock.tokens', {
i18n.baseText('runData.aiContentBlock.tokens', {
interpolate: {
count: formatTokenUsageCount(consumedTokensSum?.completionTokens ?? 0),
},

View file

@ -271,14 +271,12 @@ watch(
:class="[$style.schemaWrapper, { highlightSchema: highlight }]"
>
<div v-if="search && nodes.length > 0 && filteredNodes.length === 0" :class="$style.noMatch">
<n8n-text tag="h3" size="large">{{
$locale.baseText('ndv.search.noNodeMatch.title')
}}</n8n-text>
<n8n-text tag="h3" size="large">{{ i18n.baseText('ndv.search.noNodeMatch.title') }}</n8n-text>
<n8n-text>
<i18n-t keypath="ndv.search.noMatch.description" tag="span">
<template #link>
<a href="#" @click="emit('clear:search')">
{{ $locale.baseText('ndv.search.noMatch.description.link') }}
{{ i18n.baseText('ndv.search.noMatch.description.link') }}
</a>
</template>
</i18n-t>
@ -399,19 +397,17 @@ watch(
<div v-else :class="[$style.schemaWrapper, { highlightSchema: highlight }]">
<div v-if="isDataEmpty(nodeSchema) && search" :class="$style.noMatch">
<n8n-text tag="h3" size="large">{{
$locale.baseText('ndv.search.noNodeMatch.title')
}}</n8n-text>
<n8n-text tag="h3" size="large">{{ i18n.baseText('ndv.search.noNodeMatch.title') }}</n8n-text>
<n8n-text>
<i18n-t keypath="ndv.search.noMatch.description" tag="span">
<template #link>
<a href="#" @click="emit('clear:search')">
{{ $locale.baseText('ndv.search.noMatch.description.link') }}
{{ i18n.baseText('ndv.search.noMatch.description.link') }}
</a>
</template>
</i18n-t>
</n8n-text>
<n8n-text>{{ $locale.baseText('ndv.search.noMatchSchema.description') }}</n8n-text>
<n8n-text>{{ i18n.baseText('ndv.search.noMatchSchema.description') }}</n8n-text>
</div>
<div v-else :class="$style.schema" data-test-id="run-data-schema-node-schema">

View file

@ -36,7 +36,7 @@ const shortcutTooltipLabel = computed(() => {
<template>
<span :class="$style.container" data-test-id="save-button">
<span v-if="saved" :class="$style.saved">{{ $locale.baseText('saveButton.saved') }}</span>
<span v-if="saved" :class="$style.saved">{{ i18n.baseText('saveButton.saved') }}</span>
<template v-else>
<KeyboardShortcutTooltip
v-if="withShortcut"

View file

@ -3,8 +3,10 @@ import { ElCheckbox as Checkbox, type CheckboxValueType } from 'element-plus';
import { mapStores } from 'pinia';
import type { BaseTextKey } from '@/plugins/i18n';
import { useLogStreamingStore } from '@/stores/logStreaming.store';
import { defineComponent } from 'vue';
import { useI18n } from '@/composables/useI18n';
export default {
export default defineComponent({
name: 'EventSelection',
components: {
Checkbox,
@ -16,6 +18,13 @@ export default {
},
readonly: Boolean,
},
setup() {
const i18n = useI18n();
return {
i18n,
};
},
data() {
return {
unchanged: true,
@ -42,16 +51,16 @@ export default {
this.$forceUpdate();
},
groupLabelName(t: string): string {
return this.$locale.baseText(`settings.log-streaming.eventGroup.${t}` as BaseTextKey) ?? t;
return this.i18n.baseText(`settings.log-streaming.eventGroup.${t}` as BaseTextKey) ?? t;
},
groupLabelInfo(t: string): string | undefined {
const labelInfo = `settings.log-streaming.eventGroup.${t}.info`;
const infoText = this.$locale.baseText(labelInfo as BaseTextKey);
const infoText = this.i18n.baseText(labelInfo as BaseTextKey);
if (infoText === labelInfo || infoText === '') return;
return infoText;
},
},
};
});
</script>
<template>
@ -89,11 +98,11 @@ export default {
@update:model-value="onInput"
@change="anonymizeAuditMessagesChanged"
>
{{ $locale.baseText('settings.log-streaming.tab.events.anonymize') }}
{{ i18n.baseText('settings.log-streaming.tab.events.anonymize') }}
<n8n-tooltip placement="top" :popper-class="$style.tooltipPopper">
<n8n-icon icon="question-circle" size="small" class="ml-4xs" />
<template #content>
{{ $locale.baseText('settings.log-streaming.tab.events.anonymize.info') }}
{{ i18n.baseText('settings.log-streaming.tab.events.anonymize.info') }}
</template>
</n8n-tooltip>
</Checkbox>

View file

@ -21,6 +21,7 @@ import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
import { useNodeBase } from '@/composables/useNodeBase';
import { useTelemetry } from '@/composables/useTelemetry';
import { useStyles } from '@/composables/useStyles';
import { useI18n } from '@/composables/useI18n';
const props = withDefaults(
defineProps<{
@ -56,6 +57,7 @@ const nodeTypesStore = useNodeTypesStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const { APP_Z_INDEXES } = useStyles();
const i18n = useI18n();
const isResizing = ref<boolean>(false);
const isTouchActive = ref<boolean>(false);
@ -361,7 +363,7 @@ const onContextMenu = (e: MouseEvent): void => {
v-touch:tap="deleteNode"
class="option"
data-test-id="delete-sticky"
:title="$locale.baseText('node.delete')"
:title="i18n.baseText('node.delete')"
>
<font-awesome-icon icon="trash" />
</div>
@ -378,7 +380,7 @@ const onContextMenu = (e: MouseEvent): void => {
<div
class="option"
data-test-id="change-sticky-color"
:title="$locale.baseText('node.changeColor')"
:title="i18n.baseText('node.changeColor')"
@click="() => setColorPopoverVisible(!isColorPopoverVisible)"
>
<font-awesome-icon icon="palette" />

View file

@ -1,4 +1,5 @@
<script setup lang="ts">
import { useI18n } from '@/composables/useI18n';
import type { BaseTextKey } from '@/plugins/i18n';
type Props = {
@ -10,6 +11,8 @@ withDefaults(defineProps<Props>(), {
titleLocaleKey: 'noTagsView.readyToOrganizeYourWorkflows',
descriptionLocaleKey: 'noTagsView.withWorkflowTagsYouReFree',
});
const i18n = useI18n();
</script>
<template>
@ -19,11 +22,11 @@ withDefaults(defineProps<Props>(), {
<div>
<div class="mb-s">
<n8n-heading size="large">
{{ $locale.baseText(titleLocaleKey) }}
{{ i18n.baseText(titleLocaleKey) }}
</n8n-heading>
</div>
<div class="description">
{{ $locale.baseText(descriptionLocaleKey) }}
{{ i18n.baseText(descriptionLocaleKey) }}
</div>
</div>
<n8n-button label="Create a tag" size="large" @click="$emit('enableCreate')" />

View file

@ -5,6 +5,7 @@ import type { ITagRow } from '@/Interface';
import { onMounted, ref, watch } from 'vue';
import { N8nInput } from 'n8n-design-system';
import type { BaseTextKey } from '@/plugins/i18n';
import { useI18n } from '@/composables/useI18n';
interface Props {
rows: ITagRow[];
@ -26,6 +27,8 @@ const emit = defineEmits<{
applyOperation: [];
}>();
const i18n = useI18n();
const INPUT_TRANSITION_TIMEOUT = 350;
const DELETE_TRANSITION_TIMEOUT = 100;
@ -135,12 +138,12 @@ onMounted(() => {
:class="$style['tags-table']"
stripe
max-height="450"
:empty-text="$locale.baseText('tagsTable.noMatchingTagsExist')"
:empty-text="i18n.baseText('tagsTable.noMatchingTagsExist')"
:data="rows"
:span-method="getSpan"
:row-class-name="getRowClasses"
>
<el-table-column :label="$locale.baseText('tagsTable.name')">
<el-table-column :label="i18n.baseText('tagsTable.name')">
<template #default="scope">
<div :key="scope.row.id" :class="$style.name" @keydown.stop>
<transition name="fade" mode="out-in">
@ -152,7 +155,7 @@ onMounted(() => {
@update:model-value="onNewNameChange"
></N8nInput>
<span v-else-if="scope.row.delete">
<span>{{ $locale.baseText('tagsTable.areYouSureYouWantToDeleteThisTag') }}</span>
<span>{{ i18n.baseText('tagsTable.areYouSureYouWantToDeleteThisTag') }}</span>
<input ref="deleteHiddenInput" :class="$style.hidden" />
</span>
<span v-else :class="{ [$style.disabled]: scope.row.disable }">
@ -162,7 +165,7 @@ onMounted(() => {
</div>
</template>
</el-table-column>
<el-table-column :label="$locale.baseText(usageColumnTitleLocaleKey)" width="170">
<el-table-column :label="i18n.baseText(usageColumnTitleLocaleKey)" width="170">
<template #default="scope">
<transition name="fade" mode="out-in">
<div
@ -179,53 +182,53 @@ onMounted(() => {
<transition name="fade" mode="out-in">
<div v-if="scope.row.create" :class="$style.ops">
<n8n-button
:label="$locale.baseText('tagsTable.cancel')"
:label="i18n.baseText('tagsTable.cancel')"
type="secondary"
:disabled="isSaving"
@click.stop="cancel"
/>
<n8n-button
:label="$locale.baseText('tagsTable.createTag')"
:label="i18n.baseText('tagsTable.createTag')"
:loading="isSaving"
@click.stop="apply"
/>
</div>
<div v-else-if="scope.row.update" :class="$style.ops">
<n8n-button
:label="$locale.baseText('tagsTable.cancel')"
:label="i18n.baseText('tagsTable.cancel')"
type="secondary"
:disabled="isSaving"
@click.stop="cancel"
/>
<n8n-button
:label="$locale.baseText('tagsTable.saveChanges')"
:label="i18n.baseText('tagsTable.saveChanges')"
:loading="isSaving"
@click.stop="apply"
/>
</div>
<div v-else-if="scope.row.delete" :class="$style.ops">
<n8n-button
:label="$locale.baseText('tagsTable.cancel')"
:label="i18n.baseText('tagsTable.cancel')"
type="secondary"
:disabled="isSaving"
@click.stop="cancel"
/>
<n8n-button
:label="$locale.baseText('tagsTable.deleteTag')"
:label="i18n.baseText('tagsTable.deleteTag')"
:loading="isSaving"
@click.stop="apply"
/>
</div>
<div v-else-if="!scope.row.disable" :class="[$style.ops, $style.main]">
<n8n-icon-button
:title="$locale.baseText('tagsTable.editTag')"
:title="i18n.baseText('tagsTable.editTag')"
icon="pen"
data-test-id="edit-tag-button"
@click.stop="enableUpdate(scope.row)"
/>
<n8n-icon-button
v-if="scope.row.canDelete"
:title="$locale.baseText('tagsTable.deleteTag')"
:title="i18n.baseText('tagsTable.deleteTag')"
icon="trash"
data-test-id="delete-tag-button"
@click.stop="enableDelete(scope.row)"

View file

@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue';
import { MAX_TAG_NAME_LENGTH } from '@/constants';
import { useI18n } from '@/composables/useI18n';
withDefaults(
defineProps<{
@ -13,6 +14,8 @@ withDefaults(
},
);
const i18n = useI18n();
const emit = defineEmits<{
searchChange: [value: string];
createEnable: [];
@ -33,7 +36,7 @@ const onSearchChange = (search: string) => {
<el-row class="tags-header">
<el-col :span="10">
<n8n-input
:placeholder="$locale.baseText('tagsTableHeader.searchTags')"
:placeholder="i18n.baseText('tagsTableHeader.searchTags')"
:model-value="search"
:disabled="disabled"
:maxlength="maxLength"
@ -49,7 +52,7 @@ const onSearchChange = (search: string) => {
<n8n-button
:disabled="disabled"
icon="plus"
:label="$locale.baseText('tagsTableHeader.addNew')"
:label="i18n.baseText('tagsTableHeader.addNew')"
size="large"
float="right"
@click="onAddNew"

View file

@ -63,7 +63,7 @@ const redirectToSearchPage = (node: ITemplatesNode) => {
<TemplateDetailsBlock
v-if="!loading && isFullTemplatesCollection(template) && template.categories.length > 0"
:title="$locale.baseText('template.details.categories')"
:title="i18n.baseText('template.details.categories')"
>
<n8n-tags :tags="template.categories" @click:tag="redirectToCategory" />
</TemplateDetailsBlock>

View file

@ -2,6 +2,7 @@
import { onBeforeUnmount, onMounted, ref } from 'vue';
import TemplateCard from './TemplateCard.vue';
import type { ITemplatesWorkflow } from '@/Interface';
import { useI18n } from '@/composables/useI18n';
interface Props {
workflows?: ITemplatesWorkflow[];
@ -29,6 +30,8 @@ const props = withDefaults(defineProps<Props>(), {
totalCount: 0,
});
const i18n = useI18n();
const loader = ref<HTMLElement | null>(null);
onMounted(() => {
@ -76,7 +79,7 @@ function onUseWorkflow(event: MouseEvent, id: number) {
<div v-if="loading || workflows.length" :class="$style.list">
<div v-if="!simpleView" :class="$style.header">
<n8n-heading :bold="true" size="medium" color="text-light">
{{ $locale.baseText('templates.workflows') }}
{{ i18n.baseText('templates.workflows') }}
<span v-if="totalCount > 0" data-test-id="template-count-label">({{ totalCount }})</span>
<span v-if="!loading && totalWorkflows" v-text="`(${totalWorkflows})`" />
</n8n-heading>

View file

@ -2,6 +2,7 @@
import { ref, watch, onMounted, nextTick } from 'vue';
import type { INodeProperties } from 'n8n-workflow';
import { APP_MODALS_ELEMENT_ID } from '@/constants';
import { useI18n } from '@/composables/useI18n';
const props = defineProps<{
dialogVisible: boolean;
@ -19,6 +20,8 @@ const emit = defineEmits<{
const inputField = ref<HTMLInputElement | null>(null);
const tempValue = ref('');
const i18n = useI18n();
watch(
() => props.dialogVisible,
async (newValue) => {
@ -63,19 +66,19 @@ const closeDialog = () => {
:model-value="dialogVisible"
:append-to="`#${APP_MODALS_ELEMENT_ID}`"
width="80%"
:title="`${$locale.baseText('textEdit.edit')} ${$locale
:title="`${i18n.baseText('textEdit.edit')} ${i18n
.nodeText()
.inputLabelDisplayName(parameter, path)}`"
:before-close="closeDialog"
>
<div class="ignore-key-press-canvas">
<n8n-input-label :label="$locale.nodeText().inputLabelDisplayName(parameter, path)">
<n8n-input-label :label="i18n.nodeText().inputLabelDisplayName(parameter, path)">
<div @keydown.stop @keydown.esc="onKeyDownEsc">
<n8n-input
ref="inputField"
v-model="tempValue"
type="textarea"
:placeholder="$locale.nodeText().placeholder(parameter, path)"
:placeholder="i18n.nodeText().placeholder(parameter, path)"
:read-only="isReadOnly"
:rows="15"
@update:model-value="valueChanged"

View file

@ -28,24 +28,24 @@ const nodeName = (node: IVersionNode): string => {
<div :class="$style.header">
<div>
<div :class="$style.name">
{{ `${$locale.baseText('versionCard.version')} ${version.name}` }}
{{ `${i18n.baseText('versionCard.version')} ${version.name}` }}
</div>
<WarningTooltip v-if="version.hasSecurityIssue">
<span v-n8n-html="$locale.baseText('versionCard.thisVersionHasASecurityIssue')"></span>
<span v-n8n-html="i18n.baseText('versionCard.thisVersionHasASecurityIssue')"></span>
</WarningTooltip>
<Badge
v-if="version.hasSecurityFix"
:text="$locale.baseText('versionCard.securityUpdate')"
:text="i18n.baseText('versionCard.securityUpdate')"
type="danger"
/>
<Badge
v-if="version.hasBreakingChange"
:text="$locale.baseText('versionCard.breakingChanges')"
:text="i18n.baseText('versionCard.breakingChanges')"
type="warning"
/>
</div>
<div :class="$style['release-date']">
{{ $locale.baseText('versionCard.released') }}&nbsp;<TimeAgo :date="version.createdAt" />
{{ i18n.baseText('versionCard.released') }}&nbsp;<TimeAgo :date="version.createdAt" />
</div>
</div>
<div

View file

@ -8,11 +8,14 @@ import WorkerJobAccordion from './WorkerJobAccordion.ee.vue';
import WorkerNetAccordion from './WorkerNetAccordion.ee.vue';
import WorkerChartsAccordion from './WorkerChartsAccordion.ee.vue';
import { sortByProperty } from '@/utils/sortUtils';
import { useI18n } from '@/composables/useI18n';
let interval: NodeJS.Timer;
const orchestrationStore = useOrchestrationStore();
const i18n = useI18n();
const props = defineProps<{
workerId: string;
}>();
@ -73,7 +76,7 @@ onBeforeUnmount(() => {
<div :class="$style.cardDescription">
<n8n-text color="text-light" size="small" :class="$style.container">
<span
>{{ $locale.baseText('workerList.item.lastUpdated') }} {{ secondsSinceLastUpdateString }}s
>{{ i18n.baseText('workerList.item.lastUpdated') }} {{ secondsSinceLastUpdateString }}s
ago | n8n-Version: {{ worker.version }} | Architecture: {{ worker.arch }} (
{{ worker.platform }}) | Uptime: {{ upTime(worker.uptime) }}</span
>

View file

@ -6,11 +6,14 @@ import type { ChartData, ChartOptions } from 'chart.js';
import type { ChartComponentRef } from 'vue-chartjs';
import { Chart } from 'vue-chartjs';
import { averageWorkerLoadFromLoads, memAsGb } from '@/utils/workerUtils';
import { useI18n } from '@/composables/useI18n';
const props = defineProps<{
workerId: string;
}>();
const i18n = useI18n();
const blankDataSet = (label: string, color: string, prefill: number = 0) => ({
datasets: [
{
@ -94,7 +97,7 @@ orchestrationStore.$onAction(({ name, store }) => {
<template>
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="false">
<template #title>
{{ $locale.baseText('workerList.item.chartsTitle') }}
{{ i18n.baseText('workerList.item.chartsTitle') }}
</template>
<template #content>
<div :class="$style.charts">

View file

@ -1,11 +1,14 @@
<script setup lang="ts">
import type { RunningJobSummary } from '@n8n/api-types';
import WorkerAccordion from './WorkerAccordion.ee.vue';
import { useI18n } from '@/composables/useI18n';
const props = defineProps<{
items: RunningJobSummary[];
}>();
const i18n = useI18n();
function runningSince(started: Date): string {
let seconds = Math.floor((new Date().getTime() - started.getTime()) / 1000);
const hrs = Math.floor(seconds / 3600);
@ -19,7 +22,7 @@ function runningSince(started: Date): string {
<template>
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="true">
<template #title>
{{ $locale.baseText('workerList.item.jobListTitle') }} ({{ items.length }})
{{ i18n.baseText('workerList.item.jobListTitle') }} ({{ items.length }})
</template>
<template #content>
<div v-if="props.items.length > 0" :class="$style.accordionItems">
@ -38,7 +41,7 @@ function runningSince(started: Date): string {
</div>
<div v-else :class="$style.accordionItems">
<span :class="$style.empty">
{{ $locale.baseText('workerList.item.jobList.empty') }}
{{ i18n.baseText('workerList.item.jobList.empty') }}
</span>
</div>
</template>

View file

@ -27,7 +27,7 @@ function onCopyToClipboard(content: string) {
<template>
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="false">
<template #title>
{{ $locale.baseText('workerList.item.netListTitle') }} ({{ items.length }})
{{ i18n.baseText('workerList.item.netListTitle') }} ({{ items.length }})
</template>
<template #content>
<div v-if="props.items.length > 0" :class="$style.accordionItems">

View file

@ -472,7 +472,7 @@ onMounted(async () => {
width="65%"
max-height="80%"
:title="
$locale.baseText('workflowSettings.settingsFor', {
i18n.baseText('workflowSettings.settingsFor', {
interpolate: { workflowName, workflowId },
})
"
@ -483,7 +483,7 @@ onMounted(async () => {
<div v-loading="isLoading" class="workflow-settings" data-test-id="workflow-settings-dialog">
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.executionOrder') + ':' }}
{{ i18n.baseText('workflowSettings.executionOrder') + ':' }}
</el-col>
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
@ -508,7 +508,7 @@ onMounted(async () => {
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.errorWorkflow') + ':' }}
{{ i18n.baseText('workflowSettings.errorWorkflow') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-n8n-html="helpTexts.errorWorkflow"></div>
@ -538,7 +538,7 @@ onMounted(async () => {
<div v-if="isSharingEnabled" data-test-id="workflow-caller-policy">
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.callerPolicy') + ':' }}
{{ i18n.baseText('workflowSettings.callerPolicy') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.workflowCallerPolicy"></div>
@ -551,7 +551,7 @@ onMounted(async () => {
<n8n-select
v-model="workflowSettings.callerPolicy"
:disabled="readOnlyEnv || !workflowPermissions.update"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
:placeholder="i18n.baseText('workflowSettings.selectOption')"
filterable
:limit-popper-width="true"
>
@ -567,7 +567,7 @@ onMounted(async () => {
</el-row>
<el-row v-if="workflowSettings.callerPolicy === 'workflowsFromAList'">
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.callerIds') + ':' }}
{{ i18n.baseText('workflowSettings.callerIds') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.workflowCallerIds"></div>
@ -579,7 +579,7 @@ onMounted(async () => {
<n8n-input
v-model="workflowSettings.callerIds"
:disabled="readOnlyEnv || !workflowPermissions.update"
:placeholder="$locale.baseText('workflowSettings.callerIds.placeholder')"
:placeholder="i18n.baseText('workflowSettings.callerIds.placeholder')"
type="text"
data-test-id="workflow-caller-policy-workflow-ids"
@update:model-value="onCallerIdsInput"
@ -589,7 +589,7 @@ onMounted(async () => {
</div>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.timezone') + ':' }}
{{ i18n.baseText('workflowSettings.timezone') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.timezone"></div>
@ -618,7 +618,7 @@ onMounted(async () => {
</el-row>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveDataErrorExecution') + ':' }}
{{ i18n.baseText('workflowSettings.saveDataErrorExecution') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.saveDataErrorExecution"></div>
@ -629,7 +629,7 @@ onMounted(async () => {
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveDataErrorExecution"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
:placeholder="i18n.baseText('workflowSettings.selectOption')"
filterable
:disabled="readOnlyEnv || !workflowPermissions.update"
:limit-popper-width="true"
@ -647,7 +647,7 @@ onMounted(async () => {
</el-row>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveDataSuccessExecution') + ':' }}
{{ i18n.baseText('workflowSettings.saveDataSuccessExecution') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.saveDataSuccessExecution"></div>
@ -658,7 +658,7 @@ onMounted(async () => {
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveDataSuccessExecution"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
:placeholder="i18n.baseText('workflowSettings.selectOption')"
filterable
:disabled="readOnlyEnv || !workflowPermissions.update"
:limit-popper-width="true"
@ -676,7 +676,7 @@ onMounted(async () => {
</el-row>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveManualExecutions') + ':' }}
{{ i18n.baseText('workflowSettings.saveManualExecutions') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.saveManualExecutions"></div>
@ -687,7 +687,7 @@ onMounted(async () => {
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveManualExecutions"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
:placeholder="i18n.baseText('workflowSettings.selectOption')"
filterable
:disabled="readOnlyEnv || !workflowPermissions.update"
:limit-popper-width="true"
@ -705,7 +705,7 @@ onMounted(async () => {
</el-row>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.saveExecutionProgress') + ':' }}
{{ i18n.baseText('workflowSettings.saveExecutionProgress') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.saveExecutionProgress"></div>
@ -716,7 +716,7 @@ onMounted(async () => {
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveExecutionProgress"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
:placeholder="i18n.baseText('workflowSettings.selectOption')"
filterable
:disabled="readOnlyEnv || !workflowPermissions.update"
:limit-popper-width="true"
@ -734,7 +734,7 @@ onMounted(async () => {
</el-row>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.timeoutWorkflow') + ':' }}
{{ i18n.baseText('workflowSettings.timeoutWorkflow') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.executionTimeoutToggle"></div>
@ -761,7 +761,7 @@ onMounted(async () => {
>
<el-row>
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.timeoutAfter') + ':' }}
{{ i18n.baseText('workflowSettings.timeoutAfter') + ':' }}
<n8n-tooltip placement="top">
<template #content>
<div v-text="helpTexts.executionTimeout"></div>
@ -776,7 +776,7 @@ onMounted(async () => {
:min="0"
@update:model-value="(value: string) => setTheTimeout('hours', value)"
>
<template #append>{{ $locale.baseText('workflowSettings.hours') }}</template>
<template #append>{{ i18n.baseText('workflowSettings.hours') }}</template>
</n8n-input>
</el-col>
<el-col :span="4" class="timeout-input">
@ -787,7 +787,7 @@ onMounted(async () => {
:max="60"
@update:model-value="(value: string) => setTheTimeout('minutes', value)"
>
<template #append>{{ $locale.baseText('workflowSettings.minutes') }}</template>
<template #append>{{ i18n.baseText('workflowSettings.minutes') }}</template>
</n8n-input>
</el-col>
<el-col :span="4" class="timeout-input">
@ -798,7 +798,7 @@ onMounted(async () => {
:max="60"
@update:model-value="(value: string) => setTheTimeout('seconds', value)"
>
<template #append>{{ $locale.baseText('workflowSettings.seconds') }}</template>
<template #append>{{ i18n.baseText('workflowSettings.seconds') }}</template>
</n8n-input>
</el-col>
</el-row>
@ -809,7 +809,7 @@ onMounted(async () => {
<div class="action-buttons" data-test-id="workflow-settings-save-button">
<n8n-button
:disabled="readOnlyEnv || !workflowPermissions.update"
:label="$locale.baseText('workflowSettings.save')"
:label="i18n.baseText('workflowSettings.save')"
size="large"
float="right"
@click="saveSettings"

View file

@ -4,6 +4,7 @@ import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import { computed } from 'vue';
import { useBugReporting } from '@/composables/useBugReporting';
import { useTelemetry } from '@/composables/useTelemetry';
import { useI18n } from '@/composables/useI18n';
const props = withDefaults(
defineProps<{
@ -25,6 +26,7 @@ const emit = defineEmits<{
const { getReportingURL } = useBugReporting();
const telemetry = useTelemetry();
const i18n = useI18n();
const isResetZoomVisible = computed(() => props.zoom !== 1);
@ -51,7 +53,7 @@ function trackBugReport() {
<template>
<Controls :show-zoom="false" :show-fit-view="false">
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomToFit')"
:label="i18n.baseText('nodeView.zoomToFit')"
:shortcut="{ keys: ['1'] }"
>
<N8nIconButton
@ -62,10 +64,7 @@ function trackBugReport() {
@click="onZoomToFit"
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomIn')"
:shortcut="{ keys: ['+'] }"
>
<KeyboardShortcutTooltip :label="i18n.baseText('nodeView.zoomIn')" :shortcut="{ keys: ['+'] }">
<N8nIconButton
type="tertiary"
size="large"
@ -74,10 +73,7 @@ function trackBugReport() {
@click="onZoomIn"
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomOut')"
:shortcut="{ keys: ['-'] }"
>
<KeyboardShortcutTooltip :label="i18n.baseText('nodeView.zoomOut')" :shortcut="{ keys: ['-'] }">
<N8nIconButton
type="tertiary"
size="large"
@ -88,7 +84,7 @@ function trackBugReport() {
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
v-if="isResetZoomVisible"
:label="$locale.baseText('nodeView.resetZoom')"
:label="i18n.baseText('nodeView.resetZoom')"
:shortcut="{ keys: ['0'] }"
>
<N8nIconButton
@ -101,7 +97,7 @@ function trackBugReport() {
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
v-if="props.showBugReportingButton"
:label="$locale.baseText('nodeView.reportBug')"
:label="i18n.baseText('nodeView.reportBug')"
>
<a :href="getReportingURL()" target="_blank" @click="trackBugReport">
<N8nIconButton type="tertiary" size="large" icon="bug" data-test-id="report-bug" />

View file

@ -3,8 +3,10 @@ import { onBeforeUnmount, onMounted, ref } from 'vue';
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import { NODE_CREATOR_OPEN_SOURCES } from '@/constants';
import { nodeViewEventBus } from '@/event-bus';
import { useI18n } from '@/composables/useI18n';
const nodeCreatorStore = useNodeCreatorStore();
const i18n = useI18n();
const isTooltipVisible = ref(false);
@ -45,10 +47,10 @@ function onClick() {
<FontAwesomeIcon icon="plus" size="lg" />
</button>
<template #content>
{{ $locale.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting') }}
{{ i18n.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting') }}
</template>
</N8nTooltip>
<p :class="$style.label" v-text="$locale.baseText('nodeView.canvasAddButton.addFirstStep')" />
<p :class="$style.label" v-text="i18n.baseText('nodeView.canvasAddButton.addFirstStep')" />
</div>
</template>

View file

@ -115,7 +115,7 @@ function openContextMenu(event: MouseEvent) {
<slot />
<N8nTooltip v-if="renderOptions.trigger" placement="bottom">
<template #content>
<span v-n8n-html="$locale.baseText('node.thisIsATriggerNode')" />
<span v-n8n-html="i18n.baseText('node.thisIsATriggerNode')" />
</template>
<div :class="$style.triggerIcon">
<FontAwesomeIcon icon="bolt" size="lg" />

View file

@ -3,8 +3,10 @@ import { computed } from 'vue';
import TitledList from '@/components/TitledList.vue';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useCanvasNode } from '@/composables/useCanvasNode';
import { useI18n } from '@/composables/useI18n';
const nodeHelpers = useNodeHelpers();
const i18n = useI18n();
const {
hasPinnedData,
@ -29,7 +31,7 @@ const hideNodeIssues = computed(() => false); // @TODO Implement this
>
<N8nTooltip :show-after="500" placement="bottom">
<template #content>
<TitledList :title="`${$locale.baseText('node.issues')}:`" :items="issues" />
<TitledList :title="`${i18n.baseText('node.issues')}:`" :items="issues" />
</template>
<FontAwesomeIcon icon="exclamation-triangle" />
</N8nTooltip>

View file

@ -16,7 +16,7 @@ const time = computed(() => {
return '...';
}
const msPassed = nowTime.value - new Date(props.startTime).getTime();
return i18n.displayTimer(msPassed); // Note: Adjust for $locale usage in setup
return i18n.displayTimer(msPassed);
});
onMounted(() => {

View file

@ -6,10 +6,12 @@ import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.ee.vue';
import { createEventBus } from 'n8n-design-system';
import VoteButtons from '@/components/executions/workflow/VoteButtons.vue';
import { useToast } from '@/composables/useToast';
import { useI18n } from '@/composables/useI18n';
const executionsStore = useExecutionsStore();
const { showError } = useToast();
const i18n = useI18n();
const tagsEventBus = createEventBus();
const isTagsEditEnabled = ref(false);
@ -100,7 +102,7 @@ const onTagsEditEsc = () => {
>
<div :class="$style.section">
<div :class="$style.vote">
<div>{{ $locale.baseText('generic.rating') }}</div>
<div>{{ i18n.baseText('generic.rating') }}</div>
<VoteButtons :vote="vote" @vote-click="onVoteClick" />
</div>
<span :class="$style.tags" data-test-id="annotation-tags-container">
@ -110,7 +112,7 @@ const onTagsEditEsc = () => {
v-model="appliedTagIds"
:create-enabled="true"
:event-bus="tagsEventBus"
:placeholder="$locale.baseText('executionAnnotationView.chooseOrCreateATag')"
:placeholder="i18n.baseText('executionAnnotationView.chooseOrCreateATag')"
class="tags-edit"
data-test-id="workflow-tags-dropdown"
@blur="onTagsBlur"
@ -122,7 +124,7 @@ const onTagsEditEsc = () => {
data-test-id="new-tag-link"
@click="onTagsEditEnable"
>
+ {{ $locale.baseText('executionAnnotationView.addTag') }}
+ {{ i18n.baseText('executionAnnotationView.addTag') }}
</span>
</div>
@ -143,7 +145,7 @@ const onTagsEditEsc = () => {
<span :class="$style.addTagWrapper">
<n8n-button
:class="$style.addTag"
:label="`+ ` + $locale.baseText('executionAnnotationView.addTag')"
:label="`+ ` + i18n.baseText('executionAnnotationView.addTag')"
type="secondary"
size="mini"
:outline="false"
@ -157,7 +159,7 @@ const onTagsEditEsc = () => {
<div :class="$style.section">
<div :class="$style.heading">
<n8n-heading tag="h3" size="small" color="text-dark">
{{ $locale.baseText('generic.annotationData') }}
{{ i18n.baseText('generic.annotationData') }}
</n8n-heading>
</div>
<div
@ -179,7 +181,7 @@ const onTagsEditEsc = () => {
</div>
<div v-else :class="$style.noResultsContainer" data-test-id="execution-annotation-data-empty">
<n8n-text color="text-base" size="small" align="center">
<span v-n8n-html="$locale.baseText('executionAnnotationView.data.notFound')" />
<span v-n8n-html="i18n.baseText('executionAnnotationView.data.notFound')" />
</n8n-text>
</div>
</div>

View file

@ -26,6 +26,7 @@ const props = withDefaults(
},
);
const i18n = useI18n();
const router = useRouter();
const workflowHelpers = useWorkflowHelpers({ router });
const locale = useI18n();
@ -190,7 +191,7 @@ async function onSaveWorkflowClick(): Promise<void> {
<template>
<N8nInfoAccordion
:class="[$style.accordion, 'mt-2xl']"
:title="$locale.baseText('executionsLandingPage.emptyState.accordion.title')"
:title="i18n.baseText('executionsLandingPage.emptyState.accordion.title')"
:items="accordionItems"
:initially-expanded="shouldExpandAccordion"
:header-icon="accordionIcon"
@ -199,16 +200,14 @@ async function onSaveWorkflowClick(): Promise<void> {
>
<template #customContent>
<footer class="mt-2xs">
{{ $locale.baseText('executionsLandingPage.emptyState.accordion.footer') }}
{{ i18n.baseText('executionsLandingPage.emptyState.accordion.footer') }}
<N8nTooltip :disabled="!isNewWorkflow">
<template #content>
<div>
<N8nLink @click.prevent="onSaveWorkflowClick">{{
$locale.baseText('executionsLandingPage.emptyState.accordion.footer.tooltipLink')
i18n.baseText('executionsLandingPage.emptyState.accordion.footer.tooltipLink')
}}</N8nLink>
{{
$locale.baseText('executionsLandingPage.emptyState.accordion.footer.tooltipText')
}}
{{ i18n.baseText('executionsLandingPage.emptyState.accordion.footer.tooltipText') }}
</div>
</template>
<N8nLink
@ -216,7 +215,7 @@ async function onSaveWorkflowClick(): Promise<void> {
size="small"
@click.prevent="openWorkflowSettings"
>
{{ $locale.baseText('executionsLandingPage.emptyState.accordion.footer.settingsLink') }}
{{ i18n.baseText('executionsLandingPage.emptyState.accordion.footer.settingsLink') }}
</N8nLink>
</N8nTooltip>
</footer>

View file

@ -12,6 +12,7 @@ import { useExecutionsStore } from '@/stores/executions.store';
import type { ExecutionFilterType, IWorkflowDb } from '@/Interface';
import { isComponentPublicInstance } from '@/utils/typeGuards';
import { getResourcePermissions } from '@/permissions';
import { useI18n } from '@/composables/useI18n';
type AutoScrollDeps = { activeExecutionSet: boolean; cardsMounted: boolean; scroll: boolean };
@ -32,6 +33,7 @@ const emit = defineEmits<{
const route = useRoute();
const router = useRouter();
const i18n = useI18n();
const executionsStore = useExecutionsStore();
@ -170,7 +172,7 @@ function scrollToActiveCard(): void {
>
<div :class="$style.heading">
<n8n-heading tag="h2" size="medium" color="text-dark">
{{ $locale.baseText('generic.executions') }}
{{ i18n.baseText('generic.executions') }}
</n8n-heading>
</div>
<div :class="$style.controls">
@ -179,7 +181,7 @@ function scrollToActiveCard(): void {
data-test-id="auto-refresh-checkbox"
@update:model-value="onAutoRefreshChange"
>
{{ $locale.baseText('executionsList.autoRefresh') }}
{{ i18n.baseText('executionsList.autoRefresh') }}
</el-checkbox>
<ExecutionsFilter popover-placement="left-start" @filter-changed="onFilterChanged" />
</div>
@ -198,7 +200,7 @@ function scrollToActiveCard(): void {
data-test-id="execution-list-empty"
>
<n8n-text color="text-base" size="medium" align="center">
{{ $locale.baseText('executionsLandingPage.noResults') }}
{{ i18n.baseText('executionsLandingPage.noResults') }}
</n8n-text>
</div>
<WorkflowExecutionsCard

View file

@ -21,7 +21,7 @@ import App from '@/App.vue';
import router from './router';
import { TelemetryPlugin } from './plugins/telemetry';
import { I18nPlugin, i18nInstance } from './plugins/i18n';
import { i18nInstance } from './plugins/i18n';
import { GlobalComponentsPlugin } from './plugins/components';
import { GlobalDirectivesPlugin } from './plugins/directives';
import { FontAwesomePlugin } from './plugins/icons';
@ -38,7 +38,6 @@ const app = createApp(App);
app.use(SentryPlugin);
app.use(TelemetryPlugin);
app.use(PiniaVuePlugin);
app.use(I18nPlugin);
app.use(FontAwesomePlugin);
app.use(GlobalComponentsPlugin);
app.use(GlobalDirectivesPlugin);

View file

@ -72,7 +72,7 @@ The base text file for each locale is located at `/packages/editor-ui/src/plugin
cp ./packages/editor-ui/src/plugins/i18n/locales/en.json ./packages/editor-ui/src/plugins/i18n/locales/de.json
```
2. Find in the UI a string to translate, and search for it in the newly created base text file. Alternatively, find in `/editor-ui` a call to `$locale.baseText(key)`, e.g. `$locale.baseText('workflowActivator.deactivateWorkflow')`, and take note of the key and find it in the newly created base text file.
2. Find in the UI a string to translate, and search for it in the newly created base text file. Alternatively, find in `/editor-ui` a call to `i18n.baseText(key)`, e.g. `i18n.baseText('workflowActivator.deactivateWorkflow')`, and take note of the key and find it in the newly created base text file.
> **Note**: If you cannot find a string in the new base text file, either it does not belong to base text (i.e., the string might be part of header text, credential text, or node text), or the string might belong to the backend, where i18n is currently unsupported.

View file

@ -1,7 +1,6 @@
import type { Plugin } from 'vue';
import axios from 'axios';
import { createI18n } from 'vue-i18n';
import { locale, type N8nLocaleTranslateFn } from 'n8n-design-system';
import { locale } from 'n8n-design-system';
import type { INodeProperties, INodePropertyCollection, INodePropertyOptions } from 'n8n-workflow';
import type { INodeTranslationHeaders } from '@/Interface';
@ -437,17 +436,6 @@ export function addHeaders(headers: INodeTranslationHeaders, language: string) {
export const i18n: I18nClass = new I18nClass();
export const I18nPlugin: Plugin = {
async install(app) {
locale.i18n(((key: string, options?: BaseTextOptions) =>
i18nInstance.global.t(key, options?.interpolate ?? {})) as N8nLocaleTranslateFn);
app.config.globalProperties.$locale = i18n;
await locale.use('en');
},
};
// ----------------------------------
// typings
// ----------------------------------

View file

@ -21,7 +21,6 @@ declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$style: Record<string, string>;
$locale: I18nClass;
$telemetry: Telemetry;
$route: RouteLocation;
$router: Router;

View file

@ -2,12 +2,15 @@
import { computed } from 'vue';
import type { XYPosition } from '@/Interface';
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import { useI18n } from '@/composables/useI18n';
export interface Props {
showTooltip: boolean;
position: XYPosition;
}
const i18n = useI18n();
const props = defineProps<Props>();
const nodeCreatorStore = useNodeCreatorStore();
@ -35,10 +38,10 @@ const containerCssVars = computed(() => ({
<font-awesome-icon icon="plus" size="lg" />
</button>
<template #content>
{{ $locale.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting') }}
{{ i18n.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting') }}
</template>
</n8n-tooltip>
<p :class="$style.label" v-text="$locale.baseText('nodeView.canvasAddButton.addFirstStep')" />
<p :class="$style.label" v-text="i18n.baseText('nodeView.canvasAddButton.addFirstStep')" />
</div>
</template>

View file

@ -2,6 +2,7 @@
import type { BaseTextKey } from '@/plugins/i18n';
import { useRouter } from 'vue-router';
import { VIEWS } from '@/constants';
import { useI18n } from '@/composables/useI18n';
const router = useRouter();
const props = defineProps<{
@ -11,6 +12,8 @@ const props = defineProps<{
redirectPage?: keyof typeof VIEWS;
}>();
const i18n = useI18n();
function onButtonClick() {
void router.push({ name: props.redirectPage ?? VIEWS.HOMEPAGE });
}
@ -22,16 +25,16 @@ function onButtonClick() {
<div :class="$style.message">
<div>
<n8n-heading size="2xlarge">
{{ $locale.baseText(messageKey) }}
{{ i18n.baseText(messageKey) }}
</n8n-heading>
</div>
<div>
<n8n-text v-if="errorCode" size="large">
{{ errorCode }} {{ $locale.baseText('error') }}
{{ errorCode }} {{ i18n.baseText('error') }}
</n8n-text>
</div>
</div>
<n8n-button :label="$locale.baseText(redirectTextKey)" @click="onButtonClick" />
<n8n-button :label="i18n.baseText(redirectTextKey)" @click="onButtonClick" />
</div>
</template>

View file

@ -221,7 +221,7 @@ export default defineComponent({
const ndvStore = useNDVStore();
const externalHooks = useExternalHooks();
const locale = useI18n();
const i18n = useI18n();
const contextMenu = useContextMenu();
const dataSchema = useDataSchema();
const nodeHelpers = useNodeHelpers();
@ -240,7 +240,7 @@ export default defineComponent({
});
return {
locale,
i18n,
contextMenu,
dataSchema,
nodeHelpers,
@ -366,14 +366,14 @@ export default defineComponent({
},
runButtonText(): string {
if (!this.workflowRunning) {
return this.$locale.baseText('nodeView.runButtonText.executeWorkflow');
return this.i18n.baseText('nodeView.runButtonText.executeWorkflow');
}
if (this.executionWaitingForWebhook) {
return this.$locale.baseText('nodeView.runButtonText.waitingForTriggerEvent');
return this.i18n.baseText('nodeView.runButtonText.waitingForTriggerEvent');
}
return this.$locale.baseText('nodeView.runButtonText.executingWorkflow');
return this.i18n.baseText('nodeView.runButtonText.executingWorkflow');
},
workflowStyle() {
const offsetPosition = this.uiStore.nodeViewOffsetPosition;
@ -589,8 +589,8 @@ export default defineComponent({
if (!this.nodeViewRef) {
this.showError(
new Error('NodeView reference not found'),
this.$locale.baseText('nodeView.showError.mounted1.title'),
this.$locale.baseText('nodeView.showError.mounted1.message') + ':',
this.i18n.baseText('nodeView.showError.mounted1.title'),
this.i18n.baseText('nodeView.showError.mounted1.message') + ':',
);
return;
}
@ -624,8 +624,8 @@ export default defineComponent({
} catch (error) {
this.showError(
error,
this.$locale.baseText('nodeView.showError.mounted1.title'),
this.$locale.baseText('nodeView.showError.mounted1.message') + ':',
this.i18n.baseText('nodeView.showError.mounted1.title'),
this.i18n.baseText('nodeView.showError.mounted1.message') + ':',
);
return;
}
@ -645,8 +645,8 @@ export default defineComponent({
} catch (error) {
this.showError(
error,
this.$locale.baseText('nodeView.showError.mounted2.title'),
this.$locale.baseText('nodeView.showError.mounted2.message') + ':',
this.i18n.baseText('nodeView.showError.mounted2.title'),
this.i18n.baseText('nodeView.showError.mounted2.message') + ':',
);
}
this.canvasStore.stopLoading();
@ -814,12 +814,12 @@ export default defineComponent({
}
if (this.isReadOnlyRoute || this.readOnlyEnv) {
this.readOnlyNotification = this.showMessage({
title: this.$locale.baseText(
title: this.i18n.baseText(
this.readOnlyEnv
? `readOnlyEnv.showMessage.${this.isReadOnlyRoute ? 'executions' : 'workflows'}.title`
: 'readOnly.showMessage.executions.title',
),
message: this.$locale.baseText(
message: this.i18n.baseText(
this.readOnlyEnv
? `readOnlyEnv.showMessage.${
this.isReadOnlyRoute ? 'executions' : 'workflows'
@ -931,12 +931,12 @@ export default defineComponent({
const message =
this.containsTrigger && this.allTriggersDisabled
? this.$locale.baseText('nodeView.addOrEnableTriggerNode')
: this.$locale.baseText('nodeView.addATriggerNodeFirst');
? this.i18n.baseText('nodeView.addOrEnableTriggerNode')
: this.i18n.baseText('nodeView.addATriggerNodeFirst');
const notice = this.showMessage({
type: 'info',
title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'),
title: this.i18n.baseText('nodeView.cantExecuteNoTrigger'),
message,
duration: 3000,
onClick: () =>
@ -973,7 +973,7 @@ export default defineComponent({
}
if (saved) {
this.showMessage({
title: this.$locale.baseText('generic.workflowSaved'),
title: this.i18n.baseText('generic.workflowSaved'),
type: 'success',
});
}
@ -1001,7 +1001,7 @@ export default defineComponent({
try {
data = await this.workflowsStore.getExecution(executionId);
} catch (error) {
this.showError(error, this.$locale.baseText('nodeView.showError.openExecution.title'));
this.showError(error, this.i18n.baseText('nodeView.showError.openExecution.title'));
return;
}
if (data === undefined) {
@ -1069,7 +1069,7 @@ export default defineComponent({
console.error(`Execution ${executionId} error:`);
console.error(data.data.resultData.error.stack);
this.showMessage({
title: this.$locale.baseText('nodeView.showError.workflowError'),
title: this.i18n.baseText('nodeView.showError.workflowError'),
message: data.data.resultData.error.message,
type: 'error',
duration: 0,
@ -1078,7 +1078,7 @@ export default defineComponent({
}
if ((data as ExecutionSummary).waitTill) {
this.showMessage({
title: this.$locale.baseText('nodeView.thisExecutionHasntFinishedYet'),
title: this.i18n.baseText('nodeView.thisExecutionHasntFinishedYet'),
message: h(NodeViewUnfinishedWorkflowMessage),
type: 'warning',
duration: 0,
@ -1103,7 +1103,7 @@ export default defineComponent({
},
async openWorkflowTemplate(templateId: string) {
this.canvasStore.startLoading();
this.canvasStore.setLoadingText(this.$locale.baseText('nodeView.loadingTemplate'));
this.canvasStore.setLoadingText(this.i18n.baseText('nodeView.loadingTemplate'));
this.resetWorkspace();
this.workflowsStore.currentWorkflowExecutions = [];
@ -1116,13 +1116,13 @@ export default defineComponent({
if (!data) {
throw new Error(
this.$locale.baseText('nodeView.workflowTemplateWithIdCouldNotBeFound', {
this.i18n.baseText('nodeView.workflowTemplateWithIdCouldNotBeFound', {
interpolate: { templateId },
}),
);
}
} catch (error) {
this.showError(error, this.$locale.baseText('nodeView.couldntImportWorkflow'));
this.showError(error, this.i18n.baseText('nodeView.couldntImportWorkflow'));
await this.$router.replace({ name: VIEWS.NEW_WORKFLOW });
return;
}
@ -1367,7 +1367,7 @@ export default defineComponent({
}
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.keyDown.title'),
title: this.i18n.baseText('nodeView.showMessage.keyDown.title'),
type: 'success',
});
} else if (e.key === 'Enter' && noModifierKeys) {
@ -1699,7 +1699,7 @@ export default defineComponent({
if (data.nodes.length > 0) {
if (!isCut) {
this.showMessage({
title: this.$locale.baseText('generic.copiedToClipboard'),
title: this.i18n.baseText('generic.copiedToClipboard'),
message: '',
type: 'success',
});
@ -1732,10 +1732,8 @@ export default defineComponent({
this.workflowHelpers.setDocumentTitle(this.workflowsStore.workflowName, 'IDLE');
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
message: this.$locale.baseText(
'nodeView.showMessage.stopExecutionCatch.unsaved.message',
),
title: this.i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
message: this.i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.message'),
type: 'success',
});
} else if (execution?.finished) {
@ -1753,12 +1751,12 @@ export default defineComponent({
this.workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse);
this.uiStore.removeActiveAction('workflowRunning');
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.title'),
message: this.$locale.baseText('nodeView.showMessage.stopExecutionCatch.message'),
title: this.i18n.baseText('nodeView.showMessage.stopExecutionCatch.title'),
message: this.i18n.baseText('nodeView.showMessage.stopExecutionCatch.message'),
type: 'success',
});
} else {
this.showError(error, this.$locale.baseText('nodeView.showError.stopExecution.title'));
this.showError(error, this.i18n.baseText('nodeView.showError.stopExecution.title'));
}
}
this.stopExecutionInProgress = false;
@ -1782,10 +1780,7 @@ export default defineComponent({
try {
await this.workflowsStore.removeTestWebhook(this.workflowsStore.workflowId);
} catch (error) {
this.showError(
error,
this.$locale.baseText('nodeView.showError.stopWaitingForWebhook.title'),
);
this.showError(error, this.i18n.baseText('nodeView.showError.stopWaitingForWebhook.title'));
return;
}
},
@ -1816,16 +1811,16 @@ export default defineComponent({
}
const importConfirm = await this.confirm(
this.$locale.baseText('nodeView.confirmMessage.onClipboardPasteEvent.message', {
this.i18n.baseText('nodeView.confirmMessage.onClipboardPasteEvent.message', {
interpolate: { plainTextData },
}),
this.$locale.baseText('nodeView.confirmMessage.onClipboardPasteEvent.headline'),
this.i18n.baseText('nodeView.confirmMessage.onClipboardPasteEvent.headline'),
{
type: 'warning',
confirmButtonText: this.$locale.baseText(
confirmButtonText: this.i18n.baseText(
'nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText',
),
cancelButtonText: this.$locale.baseText(
cancelButtonText: this.i18n.baseText(
'nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText',
),
dangerouslyUseHTMLString: true,
@ -1875,7 +1870,7 @@ export default defineComponent({
this.canvasStore.stopLoading();
this.showError(
error,
this.$locale.baseText('nodeView.showError.getWorkflowDataFromUrl.title'),
this.i18n.baseText('nodeView.showError.getWorkflowDataFromUrl.title'),
);
return;
}
@ -2025,7 +2020,7 @@ export default defineComponent({
});
}
} catch (error) {
this.showError(error, this.$locale.baseText('nodeView.showError.importWorkflowData.title'));
this.showError(error, this.i18n.baseText('nodeView.showError.importWorkflowData.title'));
}
},
@ -2098,8 +2093,8 @@ export default defineComponent({
showMaxNodeTypeError(nodeTypeData: INodeTypeDescription) {
const maxNodes = nodeTypeData.maxNodes;
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.showMaxNodeTypeError.title'),
message: this.$locale.baseText('nodeView.showMessage.showMaxNodeTypeError.message', {
title: this.i18n.baseText('nodeView.showMessage.showMaxNodeTypeError.title'),
message: this.i18n.baseText('nodeView.showMessage.showMaxNodeTypeError.message', {
adjustToNumber: maxNodes,
interpolate: { nodeTypeDataDisplayName: nodeTypeData.displayName },
}),
@ -2206,8 +2201,8 @@ export default defineComponent({
if (nodeTypeData === null) {
this.showMessage({
title: this.$locale.baseText('nodeView.showMessage.addNodeButton.title'),
message: this.$locale.baseText('nodeView.showMessage.addNodeButton.message', {
title: this.i18n.baseText('nodeView.showMessage.addNodeButton.title'),
message: this.i18n.baseText('nodeView.showMessage.addNodeButton.message', {
interpolate: { nodeTypeName },
}),
type: 'error',
@ -2374,7 +2369,7 @@ export default defineComponent({
newNodeData.position = NodeViewUtils.getNewNodePosition(this.nodes, position);
}
const localizedName = this.locale.localizeNodeName(newNodeData.name, newNodeData.type);
const localizedName = this.i18n.localizeNodeName(newNodeData.name, newNodeData.type);
newNodeData.name = this.uniqueNodeName(localizedName);
@ -2725,8 +2720,8 @@ export default defineComponent({
if (!input.filter.nodes.includes(sourceNode.type)) {
this.dropPrevented = true;
this.showToast({
title: this.$locale.baseText('nodeView.showError.nodeNodeCompatible.title'),
message: this.$locale.baseText('nodeView.showError.nodeNodeCompatible.message', {
title: this.i18n.baseText('nodeView.showError.nodeNodeCompatible.title'),
message: this.i18n.baseText('nodeView.showError.nodeNodeCompatible.message', {
interpolate: { sourceNodeName: sourceNode.name, targetNodeName: targetNode.name },
}),
type: 'error',
@ -3376,14 +3371,14 @@ export default defineComponent({
(this.workflowPermissions.update ?? this.projectPermissions.workflow.update)
) {
const confirmModal = await this.confirm(
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
this.i18n.baseText('generic.unsavedWork.confirmMessage.message'),
{
title: this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'),
title: this.i18n.baseText('generic.unsavedWork.confirmMessage.headline'),
type: 'warning',
confirmButtonText: this.$locale.baseText(
confirmButtonText: this.i18n.baseText(
'generic.unsavedWork.confirmMessage.confirmButtonText',
),
cancelButtonText: this.$locale.baseText(
cancelButtonText: this.i18n.baseText(
'generic.unsavedWork.confirmMessage.cancelButtonText',
),
showClose: true,
@ -3406,7 +3401,7 @@ export default defineComponent({
try {
workflow = await this.workflowsStore.fetchWorkflow(workflowId);
} catch (error) {
this.showError(error, this.$locale.baseText('openWorkflow.workflowNotFoundError'));
this.showError(error, this.i18n.baseText('openWorkflow.workflowNotFoundError'));
void this.$router.push({
name: VIEWS.NEW_WORKFLOW,
@ -3704,17 +3699,17 @@ export default defineComponent({
async renameNodePrompt(currentName: string) {
try {
const promptResponsePromise = this.prompt(
this.$locale.baseText('nodeView.prompt.newName') + ':',
this.$locale.baseText('nodeView.prompt.renameNode') + `: ${currentName}`,
this.i18n.baseText('nodeView.prompt.newName') + ':',
this.i18n.baseText('nodeView.prompt.renameNode') + `: ${currentName}`,
{
customClass: 'rename-prompt',
confirmButtonText: this.$locale.baseText('nodeView.prompt.rename'),
cancelButtonText: this.$locale.baseText('nodeView.prompt.cancel'),
inputErrorMessage: this.$locale.baseText('nodeView.prompt.invalidName'),
confirmButtonText: this.i18n.baseText('nodeView.prompt.rename'),
cancelButtonText: this.i18n.baseText('nodeView.prompt.cancel'),
inputErrorMessage: this.i18n.baseText('nodeView.prompt.invalidName'),
inputValue: currentName,
inputValidator: (value: string) => {
if (!value.trim()) {
return this.$locale.baseText('nodeView.prompt.invalidName');
return this.i18n.baseText('nodeView.prompt.invalidName');
}
return true;
},
@ -3826,7 +3821,7 @@ export default defineComponent({
if (!data.nodes) {
// No nodes to add
throw new Error(this.$locale.baseText('nodeView.noNodesGivenToAdd'));
throw new Error(this.i18n.baseText('nodeView.noNodesGivenToAdd'));
}
// Get how many of the nodes of the types which have
@ -3860,7 +3855,7 @@ export default defineComponent({
oldName = node.name;
const localized = this.locale.localizeNodeName(node.name, node.type);
const localized = this.i18n.localizeNodeName(node.name, node.type);
newName = this.uniqueNodeName(localized, newNodeNames);
@ -3933,8 +3928,8 @@ export default defineComponent({
// Pin data limit reached
if (!pinDataSuccess) {
this.showError(
new Error(this.$locale.baseText('ndv.pinData.error.tooLarge.description')),
this.$locale.baseText('ndv.pinData.error.tooLarge.title'),
new Error(this.i18n.baseText('ndv.pinData.error.tooLarge.description')),
this.i18n.baseText('ndv.pinData.error.tooLarge.title'),
);
continue;
}
@ -4142,13 +4137,13 @@ export default defineComponent({
window.top.postMessage(
JSON.stringify({
command: 'error',
message: this.$locale.baseText('openWorkflow.workflowImportError'),
message: this.i18n.baseText('openWorkflow.workflowImportError'),
}),
'*',
);
}
this.showMessage({
title: this.$locale.baseText('openWorkflow.workflowImportError'),
title: this.i18n.baseText('openWorkflow.workflowImportError'),
message: (e as Error).message,
type: 'error',
});
@ -4168,13 +4163,13 @@ export default defineComponent({
window.top.postMessage(
JSON.stringify({
command: 'error',
message: this.$locale.baseText('nodeView.showError.openExecution.title'),
message: this.i18n.baseText('nodeView.showError.openExecution.title'),
}),
'*',
);
}
this.showMessage({
title: this.$locale.baseText('nodeView.showError.openExecution.title'),
title: this.i18n.baseText('nodeView.showError.openExecution.title'),
message: (e as Error).message,
type: 'error',
});
@ -4539,7 +4534,7 @@ export default defineComponent({
>
<template #custom-tooltip>
<span
v-text="$locale.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting')"
v-text="i18n.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting')"
/>
</template>
</Node>
@ -4655,8 +4650,8 @@ export default defineComponent({
type="secondary"
:title="
stopExecutionInProgress
? $locale.baseText('nodeView.stoppingCurrentExecution')
: $locale.baseText('nodeView.stopCurrentExecution')
? i18n.baseText('nodeView.stoppingCurrentExecution')
: i18n.baseText('nodeView.stopCurrentExecution')
"
:loading="stopExecutionInProgress"
data-test-id="stop-execution-button"
@ -4668,7 +4663,7 @@ export default defineComponent({
class="stop-execution"
icon="stop"
size="large"
:title="$locale.baseText('nodeView.stopWaitingForWebhookCall')"
:title="i18n.baseText('nodeView.stopWaitingForWebhookCall')"
type="secondary"
data-test-id="stop-execution-waiting-for-webhook-button"
@click.stop="stopWaitingForWebhook"
@ -4676,7 +4671,7 @@ export default defineComponent({
<n8n-icon-button
v-if="workflowExecution && !workflowRunning && !allTriggersDisabled"
:title="$locale.baseText('nodeView.deletesTheCurrentExecutionData')"
:title="i18n.baseText('nodeView.deletesTheCurrentExecutionData')"
icon="trash"
size="large"
data-test-id="clear-execution-data-button"

View file

@ -27,7 +27,7 @@ type FormDataDiff = {
};
const usersStore = useUsersStore();
const locale = useI18n();
const i18n = useI18n();
const projectsStore = useProjectsStore();
const rolesStore = useRolesStore();
const cloudPlanStore = useCloudPlanStore();
@ -35,6 +35,7 @@ const toast = useToast();
const router = useRouter();
const telemetry = useTelemetry();
const documentTitle = useDocumentTitle();
const dialogVisible = ref(false);
const upgradeDialogVisible = ref(false);
@ -45,9 +46,9 @@ const formData = ref<Pick<Project, 'name' | 'relations'>>({
relations: [],
});
const projectRoleTranslations = ref<{ [key: string]: string }>({
'project:viewer': locale.baseText('projects.settings.role.viewer'),
'project:editor': locale.baseText('projects.settings.role.editor'),
'project:admin': locale.baseText('projects.settings.role.admin'),
'project:viewer': i18n.baseText('projects.settings.role.viewer'),
'project:editor': i18n.baseText('projects.settings.role.editor'),
'project:admin': i18n.baseText('projects.settings.role.admin'),
});
const nameInput = ref<InstanceType<typeof N8nFormInput> | null>(null);
@ -192,14 +193,14 @@ const onSubmit = async () => {
sendTelemetry(diff);
isDirty.value = false;
toast.showMessage({
title: locale.baseText('projects.settings.save.successful.title', {
title: i18n.baseText('projects.settings.save.successful.title', {
interpolate: { projectName: formData.value.name ?? '' },
}),
type: 'success',
});
}
} catch (error) {
toast.showError(error, locale.baseText('projects.settings.save.error.title'));
toast.showError(error, i18n.baseText('projects.settings.save.error.title'));
}
};
@ -215,7 +216,7 @@ const onConfirmDelete = async (transferId?: string) => {
await projectsStore.deleteProject(projectsStore.currentProject.id, transferId);
await router.push({ name: VIEWS.HOMEPAGE });
toast.showMessage({
title: locale.baseText('projects.settings.delete.successful.title', {
title: i18n.baseText('projects.settings.delete.successful.title', {
interpolate: { projectName },
}),
type: 'success',
@ -223,12 +224,12 @@ const onConfirmDelete = async (transferId?: string) => {
dialogVisible.value = true;
}
} catch (error) {
toast.showError(error, locale.baseText('projects.settings.delete.error.title'));
toast.showError(error, i18n.baseText('projects.settings.delete.error.title'));
}
};
const selectProjectNameIfMatchesDefault = () => {
if (formData.value.name === locale.baseText('projects.settings.newProjectName')) {
if (formData.value.name === i18n.baseText('projects.settings.newProjectName')) {
nameInput.value?.inputRef?.focus();
nameInput.value?.inputRef?.select();
}
@ -252,7 +253,7 @@ onBeforeMount(async () => {
});
onMounted(() => {
documentTitle.set(locale.baseText('projects.settings'));
documentTitle.set(i18n.baseText('projects.settings'));
selectProjectNameIfMatchesDefault();
});
</script>
@ -264,7 +265,7 @@ onMounted(() => {
</div>
<form @submit.prevent="onSubmit">
<fieldset>
<label for="projectName">{{ locale.baseText('projects.settings.name') }}</label>
<label for="projectName">{{ i18n.baseText('projects.settings.name') }}</label>
<N8nFormInput
id="projectName"
ref="nameInput"
@ -279,16 +280,14 @@ onMounted(() => {
/>
</fieldset>
<fieldset>
<label for="projectMembers">{{
locale.baseText('projects.settings.projectMembers')
}}</label>
<label for="projectMembers">{{ i18n.baseText('projects.settings.projectMembers') }}</label>
<N8nUserSelect
id="projectMembers"
class="mb-s"
size="large"
:users="usersList"
:current-user-id="usersStore.currentUser?.id"
:placeholder="$locale.baseText('workflows.shareModal.select.placeholder')"
:placeholder="i18n.baseText('workflows.shareModal.select.placeholder')"
data-test-id="project-members-select"
@update:model-value="onAddMember"
>
@ -300,7 +299,7 @@ onMounted(() => {
:actions="[]"
:users="formData.relations"
:current-user-id="usersStore.currentUser?.id"
:delete-label="$locale.baseText('workflows.shareModal.list.delete')"
:delete-label="i18n.baseText('workflows.shareModal.list.delete')"
>
<template #actions="{ user }">
<div :class="$style.buttons">
@ -324,7 +323,7 @@ onMounted(() => {
:class="$style.upgrade"
@click="upgradeDialogVisible = true"
>
&nbsp;-&nbsp;{{ locale.baseText('generic.upgrade') }}
&nbsp;-&nbsp;{{ i18n.baseText('generic.upgrade') }}
</span>
</N8nOption>
</N8nSelect>
@ -343,7 +342,7 @@ onMounted(() => {
<fieldset :class="$style.buttons">
<div>
<small v-if="isDirty" class="mr-2xs">{{
locale.baseText('projects.settings.message.unsavedChanges')
i18n.baseText('projects.settings.message.unsavedChanges')
}}</small>
<N8nButton
:disabled="!isDirty"
@ -352,20 +351,20 @@ onMounted(() => {
class="mr-2xs"
data-test-id="project-settings-cancel-button"
@click.stop.prevent="onCancel"
>{{ locale.baseText('projects.settings.button.cancel') }}</N8nButton
>{{ i18n.baseText('projects.settings.button.cancel') }}</N8nButton
>
</div>
<N8nButton
:disabled="!isDirty || !isValid"
type="primary"
data-test-id="project-settings-save-button"
>{{ locale.baseText('projects.settings.button.save') }}</N8nButton
>{{ i18n.baseText('projects.settings.button.save') }}</N8nButton
>
</fieldset>
<fieldset>
<hr class="mb-2xl" />
<h3 class="mb-xs">{{ locale.baseText('projects.settings.danger.title') }}</h3>
<small>{{ locale.baseText('projects.settings.danger.message') }}</small>
<h3 class="mb-xs">{{ i18n.baseText('projects.settings.danger.title') }}</h3>
<small>{{ i18n.baseText('projects.settings.danger.message') }}</small>
<br />
<N8nButton
type="tertiary"
@ -373,7 +372,7 @@ onMounted(() => {
class="mt-s"
data-test-id="project-settings-delete-button"
@click.stop.prevent="onDelete"
>{{ locale.baseText('projects.settings.danger.deleteProject') }}</N8nButton
>{{ i18n.baseText('projects.settings.danger.deleteProject') }}</N8nButton
>
</fieldset>
</form>

View file

@ -292,7 +292,7 @@ onMounted(() => {
data-test-id="resources-list-add"
@click="addTemporaryVariable"
>
{{ $locale.baseText(`variables.add`) }}
{{ i18n.baseText(`variables.add`) }}
</n8n-button>
</div>
<template #content>
@ -309,15 +309,15 @@ onMounted(() => {
data-test-id="unavailable-resources-list"
emoji="👋"
:heading="
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.title as BaseTextKey)
i18n.baseText(contextBasedTranslationKeys.variables.unavailable.title as BaseTextKey)
"
:description="
$locale.baseText(
i18n.baseText(
contextBasedTranslationKeys.variables.unavailable.description as BaseTextKey,
)
"
:button-text="
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.button as BaseTextKey)
i18n.baseText(contextBasedTranslationKeys.variables.unavailable.button as BaseTextKey)
"
button-type="secondary"
@click:button="goToUpgrade"
@ -329,15 +329,15 @@ onMounted(() => {
data-test-id="unavailable-resources-list"
emoji="👋"
:heading="
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.title as BaseTextKey)
i18n.baseText(contextBasedTranslationKeys.variables.unavailable.title as BaseTextKey)
"
:description="
$locale.baseText(
i18n.baseText(
contextBasedTranslationKeys.variables.unavailable.description as BaseTextKey,
)
"
:button-text="
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.button as BaseTextKey)
i18n.baseText(contextBasedTranslationKeys.variables.unavailable.button as BaseTextKey)
"
button-type="secondary"
@click:button="goToUpgrade"
@ -347,11 +347,11 @@ onMounted(() => {
data-test-id="cannot-create-variables"
emoji="👋"
:heading="
$locale.baseText('variables.empty.notAllowedToCreate.heading', {
i18n.baseText('variables.empty.notAllowedToCreate.heading', {
interpolate: { name: usersStore.currentUser?.firstName ?? '' },
})
"
:description="$locale.baseText('variables.empty.notAllowedToCreate.description')"
:description="i18n.baseText('variables.empty.notAllowedToCreate.description')"
@click="goToUpgrade"
/>
</template>

View file

@ -2,9 +2,11 @@
import WorkerList from '@/components/WorkerList.ee.vue';
import { useSettingsStore } from '@/stores/settings.store';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import { useI18n } from '@/composables/useI18n';
const settingsStore = useSettingsStore();
const pageRedirectionHelper = usePageRedirectionHelper();
const i18n = useI18n();
const goToUpgrade = () => {
void pageRedirectionHelper.goToUpgrade('worker-view', 'upgrade-worker-view');
@ -20,17 +22,17 @@ const goToUpgrade = () => {
v-else
data-test-id="worker-view-unlicensed"
:class="$style.actionBox"
:description="$locale.baseText('workerList.actionBox.description')"
:button-text="$locale.baseText('workerList.actionBox.buttonText')"
:description="i18n.baseText('workerList.actionBox.description')"
:button-text="i18n.baseText('workerList.actionBox.buttonText')"
@click:button="goToUpgrade"
>
<template #heading>
<span>{{ $locale.baseText('workerList.actionBox.title') }}</span>
<span>{{ i18n.baseText('workerList.actionBox.title') }}</span>
</template>
<template #description>
{{ $locale.baseText('workerList.actionBox.description') }}
<a :href="$locale.baseText('workerList.docs.url')" target="_blank">
{{ $locale.baseText('workerList.actionBox.description.link') }}
{{ i18n.baseText('workerList.actionBox.description') }}
<a :href="i18n.baseText('workerList.docs.url')" target="_blank">
{{ i18n.baseText('workerList.actionBox.description.link') }}
</a>
</template>
</n8n-action-box>