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

View file

@ -66,9 +66,9 @@ const startNewSession = async () => {
</template> </template>
<template #footer> <template #footer>
<div :class="$style.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 <n8n-button
:label="$locale.baseText('aiAssistant.newSessionModal.confirm')" :label="i18n.baseText('aiAssistant.newSessionModal.confirm')"
@click="startNewSession" @click="startNewSession"
/> />
</div> </div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -164,7 +164,7 @@ function valueChanged(parameterData: IUpdateInformation) {
<div class="collection-parameter" @keydown.stop> <div class="collection-parameter" @keydown.stop>
<div class="collection-parameter-wrapper"> <div class="collection-parameter-wrapper">
<div v-if="getProperties.length === 0" class="no-items-exist"> <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> </div>
<Suspense> <Suspense>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -689,15 +689,15 @@ function onInputBlur() {
<template #error> <template #error>
<div :class="$style.error" data-test-id="rlc-error-container"> <div :class="$style.error" data-test-id="rlc-error-container">
<n8n-text color="text-dark" align="center" tag="div"> <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>
<n8n-text v-if="hasCredential || credentialsNotSet" size="small" color="text-base"> <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">{{ <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>
<a v-else-if="hasCredential" @click="openCredential">{{ <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> }}</a>
</n8n-text> </n8n-text>
</div> </div>
@ -714,7 +714,7 @@ function onInputBlur() {
:model-value="selectedMode" :model-value="selectedMode"
:size="inputSize" :size="inputSize"
:disabled="isReadOnly" :disabled="isReadOnly"
:placeholder="$locale.baseText('resourceLocator.modeSelector.placeholder')" :placeholder="i18n.baseText('resourceLocator.modeSelector.placeholder')"
data-test-id="rlc-mode-selector" data-test-id="rlc-mode-selector"
@update:model-value="onModeSelected" @update:model-value="onModeSelected"
> >
@ -726,7 +726,7 @@ function onInputBlur() {
:disabled="isValueExpression && mode.name === 'list'" :disabled="isValueExpression && mode.name === 'list'"
:title=" :title="
isValueExpression && mode.name === 'list' 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"> <script setup lang="ts">
import { useI18n } from '@/composables/useI18n';
import type { IResourceLocatorResultExpanded } from '@/Interface'; import type { IResourceLocatorResultExpanded } from '@/Interface';
import { N8nLoading } from 'n8n-design-system'; import { N8nLoading } from 'n8n-design-system';
import type { EventBus } from 'n8n-design-system/utils'; import type { EventBus } from 'n8n-design-system/utils';
@ -43,6 +44,8 @@ const emit = defineEmits<{
filter: [filter: string]; filter: [filter: string];
}>(); }>();
const i18n = useI18n();
const hoverIndex = ref(0); const hoverIndex = ref(0);
const showHoverUrl = ref(false); const showHoverUrl = ref(false);
const searchRef = ref<HTMLInputElement>(); const searchRef = ref<HTMLInputElement>();
@ -209,7 +212,7 @@ function onResultsEnd() {
ref="searchRef" ref="searchRef"
:model-value="filter" :model-value="filter"
:clearable="true" :clearable="true"
:placeholder="$locale.baseText('resourceLocator.search.placeholder')" :placeholder="i18n.baseText('resourceLocator.search.placeholder')"
data-test-id="rlc-search" data-test-id="rlc-search"
@update:model-value="onFilterInput" @update:model-value="onFilterInput"
> >
@ -219,13 +222,13 @@ function onResultsEnd() {
</N8nInput> </N8nInput>
</div> </div>
<div v-if="filterRequired && !filter && !errorView && !loading" :class="$style.searchRequired"> <div v-if="filterRequired && !filter && !errorView && !loading" :class="$style.searchRequired">
{{ $locale.baseText('resourceLocator.mode.list.searchRequired') }} {{ i18n.baseText('resourceLocator.mode.list.searchRequired') }}
</div> </div>
<div <div
v-else-if="!errorView && sortedResources.length === 0 && !loading" v-else-if="!errorView && sortedResources.length === 0 && !loading"
:class="$style.messageContainer" :class="$style.messageContainer"
> >
{{ $locale.baseText('resourceLocator.mode.list.noResults') }} {{ i18n.baseText('resourceLocator.mode.list.noResults') }}
</div> </div>
<div <div
v-else-if="!errorView" v-else-if="!errorView"
@ -254,7 +257,7 @@ function onResultsEnd() {
<font-awesome-icon <font-awesome-icon
v-if="showHoverUrl && result.url && hoverIndex === i" v-if="showHoverUrl && result.url && hoverIndex === i"
icon="external-link-alt" 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)" @click="openUrl($event, result.url)"
/> />
</div> </div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,7 @@ const shortcutTooltipLabel = computed(() => {
<template> <template>
<span :class="$style.container" data-test-id="save-button"> <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> <template v-else>
<KeyboardShortcutTooltip <KeyboardShortcutTooltip
v-if="withShortcut" v-if="withShortcut"

View file

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

View file

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

View file

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

View file

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

View file

@ -63,7 +63,7 @@ const redirectToSearchPage = (node: ITemplatesNode) => {
<TemplateDetailsBlock <TemplateDetailsBlock
v-if="!loading && isFullTemplatesCollection(template) && template.categories.length > 0" 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" /> <n8n-tags :tags="template.categories" @click:tag="redirectToCategory" />
</TemplateDetailsBlock> </TemplateDetailsBlock>

View file

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

View file

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

View file

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

View file

@ -8,11 +8,14 @@ import WorkerJobAccordion from './WorkerJobAccordion.ee.vue';
import WorkerNetAccordion from './WorkerNetAccordion.ee.vue'; import WorkerNetAccordion from './WorkerNetAccordion.ee.vue';
import WorkerChartsAccordion from './WorkerChartsAccordion.ee.vue'; import WorkerChartsAccordion from './WorkerChartsAccordion.ee.vue';
import { sortByProperty } from '@/utils/sortUtils'; import { sortByProperty } from '@/utils/sortUtils';
import { useI18n } from '@/composables/useI18n';
let interval: NodeJS.Timer; let interval: NodeJS.Timer;
const orchestrationStore = useOrchestrationStore(); const orchestrationStore = useOrchestrationStore();
const i18n = useI18n();
const props = defineProps<{ const props = defineProps<{
workerId: string; workerId: string;
}>(); }>();
@ -73,7 +76,7 @@ onBeforeUnmount(() => {
<div :class="$style.cardDescription"> <div :class="$style.cardDescription">
<n8n-text color="text-light" size="small" :class="$style.container"> <n8n-text color="text-light" size="small" :class="$style.container">
<span <span
>{{ $locale.baseText('workerList.item.lastUpdated') }} {{ secondsSinceLastUpdateString }}s >{{ i18n.baseText('workerList.item.lastUpdated') }} {{ secondsSinceLastUpdateString }}s
ago | n8n-Version: {{ worker.version }} | Architecture: {{ worker.arch }} ( ago | n8n-Version: {{ worker.version }} | Architecture: {{ worker.arch }} (
{{ worker.platform }}) | Uptime: {{ upTime(worker.uptime) }}</span {{ 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 type { ChartComponentRef } from 'vue-chartjs';
import { Chart } from 'vue-chartjs'; import { Chart } from 'vue-chartjs';
import { averageWorkerLoadFromLoads, memAsGb } from '@/utils/workerUtils'; import { averageWorkerLoadFromLoads, memAsGb } from '@/utils/workerUtils';
import { useI18n } from '@/composables/useI18n';
const props = defineProps<{ const props = defineProps<{
workerId: string; workerId: string;
}>(); }>();
const i18n = useI18n();
const blankDataSet = (label: string, color: string, prefill: number = 0) => ({ const blankDataSet = (label: string, color: string, prefill: number = 0) => ({
datasets: [ datasets: [
{ {
@ -94,7 +97,7 @@ orchestrationStore.$onAction(({ name, store }) => {
<template> <template>
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="false"> <WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="false">
<template #title> <template #title>
{{ $locale.baseText('workerList.item.chartsTitle') }} {{ i18n.baseText('workerList.item.chartsTitle') }}
</template> </template>
<template #content> <template #content>
<div :class="$style.charts"> <div :class="$style.charts">

View file

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

View file

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

View file

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

View file

@ -4,6 +4,7 @@ import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { useBugReporting } from '@/composables/useBugReporting'; import { useBugReporting } from '@/composables/useBugReporting';
import { useTelemetry } from '@/composables/useTelemetry'; import { useTelemetry } from '@/composables/useTelemetry';
import { useI18n } from '@/composables/useI18n';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -25,6 +26,7 @@ const emit = defineEmits<{
const { getReportingURL } = useBugReporting(); const { getReportingURL } = useBugReporting();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
const i18n = useI18n();
const isResetZoomVisible = computed(() => props.zoom !== 1); const isResetZoomVisible = computed(() => props.zoom !== 1);
@ -51,7 +53,7 @@ function trackBugReport() {
<template> <template>
<Controls :show-zoom="false" :show-fit-view="false"> <Controls :show-zoom="false" :show-fit-view="false">
<KeyboardShortcutTooltip <KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomToFit')" :label="i18n.baseText('nodeView.zoomToFit')"
:shortcut="{ keys: ['1'] }" :shortcut="{ keys: ['1'] }"
> >
<N8nIconButton <N8nIconButton
@ -62,10 +64,7 @@ function trackBugReport() {
@click="onZoomToFit" @click="onZoomToFit"
/> />
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<KeyboardShortcutTooltip <KeyboardShortcutTooltip :label="i18n.baseText('nodeView.zoomIn')" :shortcut="{ keys: ['+'] }">
:label="$locale.baseText('nodeView.zoomIn')"
:shortcut="{ keys: ['+'] }"
>
<N8nIconButton <N8nIconButton
type="tertiary" type="tertiary"
size="large" size="large"
@ -74,10 +73,7 @@ function trackBugReport() {
@click="onZoomIn" @click="onZoomIn"
/> />
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<KeyboardShortcutTooltip <KeyboardShortcutTooltip :label="i18n.baseText('nodeView.zoomOut')" :shortcut="{ keys: ['-'] }">
:label="$locale.baseText('nodeView.zoomOut')"
:shortcut="{ keys: ['-'] }"
>
<N8nIconButton <N8nIconButton
type="tertiary" type="tertiary"
size="large" size="large"
@ -88,7 +84,7 @@ function trackBugReport() {
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<KeyboardShortcutTooltip <KeyboardShortcutTooltip
v-if="isResetZoomVisible" v-if="isResetZoomVisible"
:label="$locale.baseText('nodeView.resetZoom')" :label="i18n.baseText('nodeView.resetZoom')"
:shortcut="{ keys: ['0'] }" :shortcut="{ keys: ['0'] }"
> >
<N8nIconButton <N8nIconButton
@ -101,7 +97,7 @@ function trackBugReport() {
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<KeyboardShortcutTooltip <KeyboardShortcutTooltip
v-if="props.showBugReportingButton" v-if="props.showBugReportingButton"
:label="$locale.baseText('nodeView.reportBug')" :label="i18n.baseText('nodeView.reportBug')"
> >
<a :href="getReportingURL()" target="_blank" @click="trackBugReport"> <a :href="getReportingURL()" target="_blank" @click="trackBugReport">
<N8nIconButton type="tertiary" size="large" icon="bug" data-test-id="report-bug" /> <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 { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import { NODE_CREATOR_OPEN_SOURCES } from '@/constants'; import { NODE_CREATOR_OPEN_SOURCES } from '@/constants';
import { nodeViewEventBus } from '@/event-bus'; import { nodeViewEventBus } from '@/event-bus';
import { useI18n } from '@/composables/useI18n';
const nodeCreatorStore = useNodeCreatorStore(); const nodeCreatorStore = useNodeCreatorStore();
const i18n = useI18n();
const isTooltipVisible = ref(false); const isTooltipVisible = ref(false);
@ -45,10 +47,10 @@ function onClick() {
<FontAwesomeIcon icon="plus" size="lg" /> <FontAwesomeIcon icon="plus" size="lg" />
</button> </button>
<template #content> <template #content>
{{ $locale.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting') }} {{ i18n.baseText('nodeView.canvasAddButton.addATriggerNodeBeforeExecuting') }}
</template> </template>
</N8nTooltip> </N8nTooltip>
<p :class="$style.label" v-text="$locale.baseText('nodeView.canvasAddButton.addFirstStep')" /> <p :class="$style.label" v-text="i18n.baseText('nodeView.canvasAddButton.addFirstStep')" />
</div> </div>
</template> </template>

View file

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

View file

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

View file

@ -16,7 +16,7 @@ const time = computed(() => {
return '...'; return '...';
} }
const msPassed = nowTime.value - new Date(props.startTime).getTime(); 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(() => { onMounted(() => {

View file

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

View file

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

View file

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

View file

@ -21,7 +21,7 @@ import App from '@/App.vue';
import router from './router'; import router from './router';
import { TelemetryPlugin } from './plugins/telemetry'; import { TelemetryPlugin } from './plugins/telemetry';
import { I18nPlugin, i18nInstance } from './plugins/i18n'; import { i18nInstance } from './plugins/i18n';
import { GlobalComponentsPlugin } from './plugins/components'; import { GlobalComponentsPlugin } from './plugins/components';
import { GlobalDirectivesPlugin } from './plugins/directives'; import { GlobalDirectivesPlugin } from './plugins/directives';
import { FontAwesomePlugin } from './plugins/icons'; import { FontAwesomePlugin } from './plugins/icons';
@ -38,7 +38,6 @@ const app = createApp(App);
app.use(SentryPlugin); app.use(SentryPlugin);
app.use(TelemetryPlugin); app.use(TelemetryPlugin);
app.use(PiniaVuePlugin); app.use(PiniaVuePlugin);
app.use(I18nPlugin);
app.use(FontAwesomePlugin); app.use(FontAwesomePlugin);
app.use(GlobalComponentsPlugin); app.use(GlobalComponentsPlugin);
app.use(GlobalDirectivesPlugin); 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 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. > **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 axios from 'axios';
import { createI18n } from 'vue-i18n'; 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 { INodeProperties, INodePropertyCollection, INodePropertyOptions } from 'n8n-workflow';
import type { INodeTranslationHeaders } from '@/Interface'; import type { INodeTranslationHeaders } from '@/Interface';
@ -437,17 +436,6 @@ export function addHeaders(headers: INodeTranslationHeaders, language: string) {
export const i18n: I18nClass = new I18nClass(); 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 // typings
// ---------------------------------- // ----------------------------------

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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