@arronqzy/vue-blueprint 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,19 @@ const selectedNodeId = ref<string | null>(null);
47
47
  import "@arronqzy/vue-blueprint/blueprint.css";
48
48
  ```
49
49
 
50
+ ## 本地存储
51
+
52
+ 蓝图库、执行日志、接口集合存在 IndexedDB,函数最后一个参数都是 `nameSpace`:
53
+
54
+ | 数据 | 函数 |
55
+ |------|------|
56
+ | 蓝图库 | `listBlueprintLibrary` / `getBlueprintLibraryRecord` / `putBlueprintLibraryRecord` / `deleteBlueprintLibraryRecord` / `clearBlueprintLibrary` |
57
+ | 执行日志 | `listExecutionRunRecords` / `getExecutionRunRecord` / `putExecutionRunRecord` / `deleteExecutionRunRecord` / `clearAllExecutionRunRecords` / `purgeExecutionRunsOlderThan` / `trimExecutionRunRecordsToMax` / `downloadExecutionRunExport` |
58
+ | 执行日志保留策略 | `readExecutionLogSettings` / `writeExecutionLogSettings` / `DEFAULT_EXECUTION_LOG_SETTINGS` |
59
+ | 接口集合 | `listApiCollectionRecords` / `deleteApiCollection` / `clearApiCollections`(同时更新内存中的接口列表) |
60
+
61
+ 这一层不负责通知已挂载的视图面板。宿主清理数据请用 `@arronqzy/abuilder` / `@arronqzy/abuilder-vue` 导出的同名函数,删除蓝图库后面板会自动刷新。
62
+
50
63
  ## 和 React 版
51
64
 
52
65
  图数据结构(`BlueprintGraph`、文档同步)是独立 TypeScript 实现,形状与 `react-blueprint` 对齐,所以同一份蓝图 JSON 两边都能打开。画布实现不共享。长期可以抽一层 `blueprint-core`,目前还没有。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/vue-blueprint",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Vue 3 blueprint editor for Abuilder (Vue Flow + Ant Design Vue)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,9 +19,9 @@
19
19
  "ant-design-vue": "^4.2.6",
20
20
  "dayjs": "^1.11.13",
21
21
  "vue": "^3.5.13",
22
- "@arronqzy/blueprint-dsl": "1.0.16",
23
- "@arronqzy/vue-rx-store": "0.1.6",
24
- "@arronqzy/i18n": "0.1.16"
22
+ "@arronqzy/blueprint-dsl": "1.0.17",
23
+ "@arronqzy/i18n": "0.1.17",
24
+ "@arronqzy/vue-rx-store": "0.1.6"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "vue": "^3.4.0"
@@ -31,8 +31,8 @@
31
31
  "eslint": "^8.57.0",
32
32
  "typescript": "^5.5.4",
33
33
  "vue-tsc": "^2.2.0",
34
- "@arronqzy/eslint-config": "0.0.0",
35
- "@arronqzy/typescript-config": "1.0.0"
34
+ "@arronqzy/typescript-config": "1.0.0",
35
+ "@arronqzy/eslint-config": "0.0.0"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -281,15 +281,17 @@ function toggleEventType(eventType: ViewEventType, checked: boolean) {
281
281
  <p v-if="!node.libraryBlueprintId" class="text-[11px] text-muted-foreground">
282
282
  {{ t("blueprint.config.selectLibraryFirst") }}
283
283
  </p>
284
- <label class="flex items-start gap-2 pt-1">
284
+ <label class="flex items-center gap-2 pt-1">
285
285
  <Checkbox
286
286
  :checked="allowFalseSignalPropagation"
287
- class="mt-0.5"
288
287
  @update:checked="(v) => onUpdateAllowFalseSignalPropagation?.(Boolean(v))"
289
288
  />
290
- <span class="text-[11px] leading-relaxed text-muted-foreground">
291
- {{ t("blueprint.config.allowFalsePropagateBlueprint") }}
289
+ <span class="text-[11px] text-muted-foreground">
290
+ {{ t("blueprint.config.allowFalsePropagate") }}
292
291
  </span>
292
+ <ConfigHintIcon :label="t('blueprint.config.allowFalsePropagate')">
293
+ {{ t("blueprint.config.allowFalsePropagateBlueprint") }}
294
+ </ConfigHintIcon>
293
295
  </label>
294
296
  </div>
295
297
 
@@ -426,15 +428,17 @@ function toggleEventType(eventType: ViewEventType, checked: boolean) {
426
428
  class="space-y-2 rounded-md border border-border/70 bg-muted/20 p-2.5"
427
429
  >
428
430
  <div class="font-medium text-foreground">{{ t("blueprint.config.taskChain") }}</div>
429
- <label class="flex items-start gap-2">
431
+ <label class="flex items-center gap-2">
430
432
  <Checkbox
431
433
  :checked="allowFalseSignalPropagation"
432
- class="mt-0.5"
433
434
  @update:checked="(v) => onUpdateAllowFalseSignalPropagation?.(Boolean(v))"
434
435
  />
435
- <span class="text-[11px] leading-relaxed text-muted-foreground">
436
- {{ t("blueprint.config.allowFalsePropagateDefault") }}
436
+ <span class="text-[11px] text-muted-foreground">
437
+ {{ t("blueprint.config.allowFalsePropagate") }}
437
438
  </span>
439
+ <ConfigHintIcon :label="t('blueprint.config.allowFalsePropagate')">
440
+ {{ t("blueprint.config.allowFalsePropagateDefault") }}
441
+ </ConfigHintIcon>
438
442
  </label>
439
443
  </div>
440
444
  </div>
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from "vue";
3
+ import ConfigHintIcon from "./ConfigHintIcon.vue";
3
4
  import { useI18n } from "@arronqzy/i18n/vue";
4
5
  import { Button, Checkbox, Input, Modal } from "ant-design-vue";
5
6
  import {
@@ -263,6 +264,9 @@ async function handleFiles(files: FileList | null) {
263
264
  <label class="ml-auto flex cursor-pointer items-center gap-2 text-[11px] text-muted-foreground">
264
265
  <Checkbox v-model:checked="persistLocalDefault" />
265
266
  {{ t("blueprint.apiCollection.persistLocal") }}
267
+ <ConfigHintIcon :label="t('blueprint.apiCollection.persistLocal')">
268
+ {{ t("blueprint.apiCollection.persistLocalHint") }}
269
+ </ConfigHintIcon>
266
270
  </label>
267
271
  <input
268
272
  ref="fileInputRef"
@@ -273,11 +277,6 @@ async function handleFiles(files: FileList | null) {
273
277
  @change="(e) => void handleFiles((e.target as HTMLInputElement).files)"
274
278
  />
275
279
  </div>
276
-
277
- <p class="mb-2 text-[11px] text-muted-foreground">
278
- {{ t("blueprint.apiCollection.persistLocalHint") }}
279
- </p>
280
-
281
280
  <pre
282
281
  v-if="error"
283
282
  class="mb-2 max-h-24 overflow-auto whitespace-pre-wrap rounded border border-destructive/40 bg-destructive/5 p-2 text-[11px] text-destructive"
@@ -2,6 +2,9 @@
2
2
  import type { BlueprintGraph } from "../graph/blueprint-graph";
3
3
  import type { BlueprintExecutionOverlay } from "../runtime/execution-overlay";
4
4
  import BlueprintCanvas from "./BlueprintCanvas.vue";
5
+ import { useBlueprintDslLocaleSync } from "../composables/useBlueprintDslLocaleSync";
6
+
7
+ useBlueprintDslLocaleSync();
5
8
 
6
9
  const props = withDefaults(
7
10
  defineProps<{
@@ -102,15 +102,17 @@ function handleEmitImmediatelyChange(checked: boolean) {
102
102
  />
103
103
  </label>
104
104
 
105
- <label class="flex items-start gap-2">
105
+ <label class="flex items-center gap-2">
106
106
  <Checkbox
107
107
  :checked="clockConfig.emitImmediately"
108
- class="mt-0.5"
109
108
  @update:checked="handleEmitImmediatelyChange"
110
109
  />
111
- <span class="text-[11px] leading-relaxed text-muted-foreground">
110
+ <span class="text-[11px] text-muted-foreground">
112
111
  {{ t("blueprint.config.emitImmediately") }}
113
112
  </span>
113
+ <ConfigHintIcon :label="t('blueprint.config.emitImmediately')">
114
+ {{ t("blueprint.config.emitImmediatelyHint") }}
115
+ </ConfigHintIcon>
114
116
  </label>
115
117
 
116
118
  <label class="block space-y-1">
@@ -28,11 +28,24 @@ const resolvedLabel = computed(() => props.label || t("common.hint"));
28
28
  type="button"
29
29
  :class="
30
30
  buttonClass ??
31
- 'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-border text-[10px] leading-none text-muted-foreground hover:bg-accent/50'
31
+ 'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring'
32
32
  "
33
33
  :aria-label="t('common.hintAria', { label: resolvedLabel })"
34
34
  >
35
- ?
35
+ <svg
36
+ viewBox="0 0 24 24"
37
+ fill="none"
38
+ stroke="currentColor"
39
+ stroke-width="2"
40
+ stroke-linecap="round"
41
+ stroke-linejoin="round"
42
+ class="h-3.5 w-3.5"
43
+ aria-hidden="true"
44
+ >
45
+ <circle cx="12" cy="12" r="10" />
46
+ <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
47
+ <path d="M12 17h.01" />
48
+ </svg>
36
49
  </button>
37
50
  </Tooltip>
38
51
  </template>
@@ -45,7 +45,7 @@ import ConfigHintIcon from "./ConfigHintIcon.vue";
45
45
  import FetchUrlAutocomplete from "./FetchUrlAutocomplete.vue";
46
46
  import ScopeTemplateAutocompleteHost from "./ScopeTemplateAutocompleteHost.vue";
47
47
 
48
- const { t } = useI18n();
48
+ const { t, locale } = useI18n();
49
49
 
50
50
  export type FetchNodeConfigPanelProps = {
51
51
  node: BlueprintGraphNode;
@@ -113,12 +113,14 @@ const resolvedBodyPreview = computed(() => {
113
113
  if (!usesScopeTemplate.value) return "";
114
114
  return resolvedFetchConfig.value.body?.trim() ?? "";
115
115
  });
116
- const headersJsonError = computed(() =>
117
- getFetchHeadersValidationError(fetchConfig.value, incomingScope.value)
118
- );
119
- const bodyJsonError = computed(() =>
120
- getFetchBodyValidationError(fetchConfig.value.body, resolvedFetchConfig.value.body)
121
- );
116
+ const headersJsonError = computed(() => {
117
+ void locale.value;
118
+ return getFetchHeadersValidationError(fetchConfig.value, incomingScope.value);
119
+ });
120
+ const bodyJsonError = computed(() => {
121
+ void locale.value;
122
+ return getFetchBodyValidationError(fetchConfig.value.body, resolvedFetchConfig.value.body);
123
+ });
122
124
  const incomingHasPendingValue = computed(() => {
123
125
  const scope = incomingScope.value;
124
126
  if (!scope || typeof scope !== "object") return false;
@@ -411,8 +413,15 @@ function handleHeadersInput(event: Event) {
411
413
  </div>
412
414
 
413
415
  <div class="space-y-1">
414
- <span class="text-[11px] text-muted-foreground">
416
+ <span class="inline-flex items-center gap-1 text-[11px] text-muted-foreground">
415
417
  {{ t("blueprint.config.fetchSourceMode") }}
418
+ <ConfigHintIcon :label="t('blueprint.config.fetchSourceMode')">
419
+ {{
420
+ usingCollection
421
+ ? t("blueprint.config.fetchSourceCollectionHint")
422
+ : t("blueprint.config.fetchSourceDirectHint")
423
+ }}
424
+ </ConfigHintIcon>
416
425
  </span>
417
426
  <div class="flex rounded-md border border-border p-0.5">
418
427
  <button
@@ -440,13 +449,6 @@ function handleHeadersInput(event: Event) {
440
449
  {{ t("blueprint.config.fetchSourceCollection") }}
441
450
  </button>
442
451
  </div>
443
- <p class="text-[10px] text-muted-foreground">
444
- {{
445
- usingCollection
446
- ? t("blueprint.config.fetchSourceCollectionHint")
447
- : t("blueprint.config.fetchSourceDirectHint")
448
- }}
449
- </p>
450
452
  </div>
451
453
 
452
454
  <div class="space-y-1 rounded-md border border-gray-200/80 bg-white/80 p-2">
@@ -491,7 +493,12 @@ function handleHeadersInput(event: Event) {
491
493
  </Select>
492
494
  </label>
493
495
  <label class="block space-y-1">
494
- <span class="text-muted-foreground">{{ t("blueprint.config.apiCollectionEndpoint") }}</span>
496
+ <span class="inline-flex items-center gap-1 text-muted-foreground">
497
+ {{ t("blueprint.config.apiCollectionEndpoint") }}
498
+ <ConfigHintIcon :label="t('blueprint.config.apiCollectionEndpoint')">
499
+ {{ t("blueprint.config.apiCollectionFilledHint") }}
500
+ </ConfigHintIcon>
501
+ </span>
495
502
  <div class="flex gap-1.5">
496
503
  <Tooltip
497
504
  :title="selectedEndpointDescription || undefined"
@@ -544,9 +551,6 @@ function handleHeadersInput(event: Event) {
544
551
  </Button>
545
552
  </div>
546
553
  </label>
547
- <p v-if="selectedCollection" class="text-[10px] text-muted-foreground">
548
- {{ t("blueprint.config.apiCollectionFilledHint") }}
549
- </p>
550
554
  <label class="block space-y-1">
551
555
  <span class="text-muted-foreground">{{ t("blueprint.config.requestUrl") }}</span>
552
556
  <Input
@@ -8,7 +8,7 @@ import ConfigHintIcon from "./ConfigHintIcon.vue";
8
8
  import type { BlueprintGraphNode } from "../graph/document";
9
9
  import { resolveNodeLogicConfig } from "../graph/document";
10
10
 
11
- const { t } = useI18n();
11
+ const { t, locale } = useI18n();
12
12
 
13
13
  export type LogicNodeConfigPanelProps = {
14
14
  node: BlueprintGraphNode;
@@ -31,9 +31,10 @@ function patchLogicConfig(node: BlueprintGraphNode, patch: Partial<LogicNodeConf
31
31
 
32
32
  const logicConfig = computed(() => resolveNodeLogicConfig(props.node));
33
33
 
34
- const storedValidation = computed(() =>
35
- validateLogicSourceCode(logicConfig.value.sourceCode)
36
- );
34
+ const storedValidation = computed(() => {
35
+ void locale.value;
36
+ return validateLogicSourceCode(logicConfig.value.sourceCode);
37
+ });
37
38
 
38
39
  const parseError = computed(
39
40
  () => draftError.value ?? (storedValidation.value.ok ? null : storedValidation.value.error)
@@ -0,0 +1,12 @@
1
+ import { setBlueprintDslLocale } from "@arronqzy/blueprint-dsl";
2
+ import { useI18nOptional } from "@arronqzy/i18n/vue";
3
+ import { watch } from "vue";
4
+
5
+ /**
6
+ * Keeps blueprint-dsl validation / runtime messages in the app locale. `flush: "sync"`
7
+ * updates it before computeds that track the locale are re-evaluated.
8
+ */
9
+ export function useBlueprintDslLocaleSync(): void {
10
+ const { locale } = useI18nOptional();
11
+ watch(locale, (next) => setBlueprintDslLocale(next), { immediate: true, flush: "sync" });
12
+ }
@@ -207,7 +207,7 @@ export function resolveRunnableNodeType(
207
207
  }
208
208
 
209
209
  import type { TranslateFn } from "@arronqzy/i18n";
210
- import { resolveLocale, tForLocale } from "@arronqzy/i18n";
210
+ import { matchMessageInAnyLocale, relocalizeMessage, resolveLocale, tForLocale } from "@arronqzy/i18n";
211
211
 
212
212
  /** 画布节点顶部(拖拽区)配置类型对应的 i18n key */
213
213
  export const BLUEPRINT_CONFIG_TYPE_LABEL_KEYS: Record<
@@ -289,16 +289,23 @@ export function defaultBlueprintNodeLabel(
289
289
  return getBlueprintNodeTypeLabel(t, configSource);
290
290
  }
291
291
 
292
- /** 当前名称是否仍是「类型默认名」(空或等于任一类型文案) */
292
+ const BLUEPRINT_TYPE_LABEL_KEY_LIST = Object.values(BLUEPRINT_CONFIG_TYPE_LABEL_KEYS);
293
+
294
+ /** 当前名称是否仍是「类型默认名」(空或等于任一语言下的任一类型文案) */
293
295
  export function isDefaultBlueprintNodeLabel(
294
296
  label: string | undefined | null,
295
297
  t: TranslateFn = defaultTranslate()
296
298
  ): boolean {
297
299
  const trimmed = (label ?? "").trim();
298
300
  if (!trimmed) return true;
299
- return (
300
- Object.keys(BLUEPRINT_CONFIG_TYPE_LABEL_KEYS) as BlueprintConfigSource[]
301
- ).some((src) => getBlueprintNodeTypeLabel(t, src) === trimmed);
301
+ return BLUEPRINT_TYPE_LABEL_KEY_LIST.some(
302
+ (key) => t(key) === trimmed || matchMessageInAnyLocale(trimmed, key) !== null
303
+ );
304
+ }
305
+
306
+ /** 类型默认名按当前语言显示;用户自定义名称保持不变 */
307
+ export function localizeBlueprintNodeLabel(label: string, t: TranslateFn): string {
308
+ return relocalizeMessage(label, BLUEPRINT_TYPE_LABEL_KEY_LIST, t);
302
309
  }
303
310
 
304
311
  export function resolveNodeFetchConfig(
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export { documentToRunnableGraph } from "./runtime/document-to-runnable-graph";
4
4
  export type { BlueprintExecutionOverlay } from "./runtime/execution-overlay";
5
5
  export type { BlueprintFlowNodeData } from "./types";
6
6
  export { default as BluePrintVueRoot } from "./components/BluePrintVueRoot.vue";
7
+ export { useBlueprintDslLocaleSync } from "./composables/useBlueprintDslLocaleSync";
7
8
  export { default as BlueprintCanvas } from "./components/BlueprintCanvas.vue";
8
9
  export { default as BlueprintNodeConfigSidebar } from "./BlueprintNodeConfigSidebar.vue";
9
10
  export type {
@@ -28,6 +29,8 @@ export {
28
29
  getApiCollectionListItems,
29
30
  upsertApiCollectionFromJson,
30
31
  removeApiCollection,
32
+ deleteApiCollection,
33
+ clearApiCollections,
31
34
  subscribeApiCollections,
32
35
  useApiCollections,
33
36
  useApiCollectionListItems,
@@ -41,7 +44,23 @@ export {
41
44
  getApiCollectionRecord,
42
45
  putApiCollectionRecord,
43
46
  deleteApiCollectionRecord,
47
+ clearApiCollectionRecords,
44
48
  } from "./library/api-collection-db";
49
+ export {
50
+ listExecutionRunRecords,
51
+ getExecutionRunRecord,
52
+ putExecutionRunRecord,
53
+ deleteExecutionRunRecord,
54
+ purgeExecutionRunsOlderThan,
55
+ clearAllExecutionRunRecords,
56
+ trimExecutionRunRecordsToMax,
57
+ downloadExecutionRunExport,
58
+ } from "./library/execution-log-db";
59
+ export {
60
+ DEFAULT_EXECUTION_LOG_SETTINGS,
61
+ readExecutionLogSettings,
62
+ writeExecutionLogSettings,
63
+ } from "./library/execution-log-settings";
45
64
  export { default as BlueprintNodeSwitchTaskDialog } from "./components/BlueprintNodeSwitchTaskDialog.vue";
46
65
  export type { BlueprintNodeSwitchTaskDialogProps } from "./components/BlueprintNodeSwitchTaskDialog.vue";
47
66
  export type { ExecutionLogSettings } from "./library/execution-log-settings";
@@ -96,3 +96,7 @@ export async function deleteApiCollectionRecord(
96
96
  nameSpace
97
97
  );
98
98
  }
99
+
100
+ export async function clearApiCollectionRecords(nameSpace?: string | null): Promise<void> {
101
+ await runTransaction<undefined>("readwrite", (store) => store.clear(), nameSpace);
102
+ }
@@ -6,9 +6,11 @@ import {
6
6
  type ApiCollectionDocument,
7
7
  type ApiCollectionListItem,
8
8
  type ApiCollectionRecord,
9
+ normalizeAppNameSpace,
9
10
  } from "@arronqzy/blueprint-dsl";
10
11
 
11
12
  import {
13
+ clearApiCollectionRecords,
12
14
  deleteApiCollectionRecord,
13
15
  listApiCollectionRecords,
14
16
  putApiCollectionRecord,
@@ -127,6 +129,34 @@ export async function removeApiCollection(id: string): Promise<void> {
127
129
  emit();
128
130
  }
129
131
 
132
+ function isCurrentNameSpace(nameSpace?: string | null): boolean {
133
+ return normalizeAppNameSpace(nameSpace) === normalizeAppNameSpace(nameSpaceRef);
134
+ }
135
+
136
+ /** 按 nameSpace 删除接口集合;若该 nameSpace 正在被编辑器使用,内存列表同步移除 */
137
+ export async function deleteApiCollection(
138
+ id: string,
139
+ nameSpace?: string | null
140
+ ): Promise<void> {
141
+ await deleteApiCollectionRecord(id, nameSpace);
142
+ if (!isCurrentNameSpace(nameSpace)) return;
143
+ records = records.filter((item) => item.id !== id);
144
+ emit();
145
+ }
146
+
147
+ /** 清空该 nameSpace 下的接口集合(含仅存内存的集合),返回删除数量 */
148
+ export async function clearApiCollections(nameSpace?: string | null): Promise<number> {
149
+ const persisted = await listApiCollectionRecords(nameSpace);
150
+ await clearApiCollectionRecords(nameSpace);
151
+ const removedIds = new Set(persisted.map((item) => item.id));
152
+ if (isCurrentNameSpace(nameSpace)) {
153
+ records.forEach((item) => removedIds.add(item.id));
154
+ records = [];
155
+ emit();
156
+ }
157
+ return removedIds.size;
158
+ }
159
+
130
160
  /** Vue composable:返回响应式集合列表 */
131
161
  export function useApiCollections(): Ref<ApiCollectionRecord[]> {
132
162
  const list = ref<ApiCollectionRecord[]>([...records]);
@@ -124,3 +124,10 @@ export async function deleteBlueprintLibraryRecord(
124
124
  ): Promise<void> {
125
125
  await runTransaction<undefined>("readwrite", (store) => store.delete(id), nameSpace);
126
126
  }
127
+
128
+ /** 清空该 nameSpace 下的蓝图库,返回删除数量 */
129
+ export async function clearBlueprintLibrary(nameSpace?: string | null): Promise<number> {
130
+ const count = await runTransaction<number>("readonly", (store) => store.count(), nameSpace);
131
+ await runTransaction<undefined>("readwrite", (store) => store.clear(), nameSpace);
132
+ return count;
133
+ }
@@ -2,8 +2,9 @@
2
2
  import { useI18n } from "@arronqzy/i18n/vue";
3
3
  import { computed } from "vue";
4
4
  import { cn } from "../utils/cn";
5
+ import { localizeBlueprintNodeLabel } from "../graph/document";
5
6
 
6
- const { t } = useI18n();
7
+ const { t, locale } = useI18n();
7
8
 
8
9
  export type BlueprintNodeCardProps = {
9
10
  nodeId: string;
@@ -26,7 +27,11 @@ const emit = defineEmits<{
26
27
  select: [nodeId: string];
27
28
  }>();
28
29
 
29
- const showMeta = computed(() => Boolean(props.meta && props.meta !== props.label));
30
+ const displayLabel = computed(() => {
31
+ void locale.value;
32
+ return localizeBlueprintNodeLabel(props.label, t);
33
+ });
34
+ const showMeta = computed(() => Boolean(props.meta && props.meta !== displayLabel.value));
30
35
  </script>
31
36
 
32
37
  <template>
@@ -108,7 +113,7 @@ const showMeta = computed(() => Boolean(props.meta && props.meta !== props.label
108
113
  </span>
109
114
  <div class="bp-node-card__content">
110
115
  <div class="bp-node-card__title-row">
111
- <div class="bp-node-card__title">{{ label }}</div>
116
+ <div class="bp-node-card__title">{{ displayLabel }}</div>
112
117
  <span
113
118
  v-if="progressLabel"
114
119
  :class="cn('bp-node-card__badge', `bp-node-card__badge--${variant}`)"