From a1013f244abe02e45b8334d8a8713c17cd6d4767 Mon Sep 17 00:00:00 2001 From: jaeone94 <89377375+jaeone94@users.noreply.github.com> Date: Wed, 27 May 2026 06:01:20 +0900 Subject: [PATCH] fix: avoid compiling node tooltip metadata --- src/components/graph/NodeTooltip.vue | 8 +- src/i18n.ts | 15 +++ .../composables/useNodeTooltips.test.ts | 91 +++++++++++++++++++ .../vueNodes/composables/useNodeTooltips.ts | 8 +- 4 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts diff --git a/src/components/graph/NodeTooltip.vue b/src/components/graph/NodeTooltip.vue index 51948afe6e..fd21640c68 100644 --- a/src/components/graph/NodeTooltip.vue +++ b/src/components/graph/NodeTooltip.vue @@ -13,7 +13,7 @@ import { useEventListener } from '@vueuse/core' import { nextTick, ref } from 'vue' -import { st } from '@/i18n' +import { stRaw } from '@/i18n' import { LiteGraph, isOverNodeInput, @@ -84,7 +84,7 @@ function onIdle() { ) if (inputSlot !== -1) { const inputName = node.inputs[inputSlot].name - const translatedTooltip = st( + const translatedTooltip = stRaw( `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(inputName)}.tooltip`, nodeDef?.inputs[inputName]?.tooltip ?? '' ) @@ -98,7 +98,7 @@ function onIdle() { [0, 0] ) if (outputSlot !== -1) { - const translatedTooltip = st( + const translatedTooltip = stRaw( `nodeDefs.${normalizeI18nKey(node.type ?? '')}.outputs.${outputSlot}.tooltip`, nodeDef?.outputs[outputSlot]?.tooltip ?? '' ) @@ -108,7 +108,7 @@ function onIdle() { const widget = comfyApp.canvas.getWidgetAtCursor() // Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these if (widget && !isDOMWidget(widget)) { - const translatedTooltip = st( + const translatedTooltip = stRaw( `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(widget.name)}.tooltip`, nodeDef?.inputs[widget.name]?.tooltip ?? '' ) diff --git a/src/i18n.ts b/src/i18n.ts index c3fc651820..e52ec704d4 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -155,6 +155,7 @@ export const i18n = createI18n({ /** Convenience shorthand: i18n.global */ export const { t, te, d } = i18n.global +const { tm } = i18n.global /** * Safe translation function that returns the fallback message if the key is not found. @@ -166,3 +167,17 @@ export function st(key: string, fallbackMessage: string) { // The normal defaultMsg overload fails in some cases for custom nodes return te(key) ? t(key) : fallbackMessage } + +/** + * Safe raw translation function for strings that may contain i18n syntax. + * + * @param key - The key for the raw locale message. + * @param fallbackMessage - The fallback message to use if the key is not found + * or the locale message is not a string. + */ +export function stRaw(key: string, fallbackMessage: string) { + if (!te(key)) return fallbackMessage + + const message = tm(key) + return typeof message === 'string' ? message : fallbackMessage +} diff --git a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts new file mode 100644 index 0000000000..64d7733a36 --- /dev/null +++ b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts @@ -0,0 +1,91 @@ +import { createTestingPinia } from '@pinia/testing' +import { setActivePinia } from 'pinia' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager' +import { te } from '@/i18n' +import { useSettingStore } from '@/platform/settings/settingStore' +import type { Settings } from '@/schemas/apiSchema' +import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' +import { useNodeDefStore } from '@/stores/nodeDefStore' + +import { useNodeTooltips } from './useNodeTooltips' + +const jsonTooltip = + 'Positive point prompts as JSON [{"x": int, "y": int}, ...] (pixel coords)' + +const positiveCoordsTooltipKey = + 'nodeDefs.SAM3_Detect.inputs.positive_coords.tooltip' + +const positiveCoordsWidget: SafeWidgetData = { + name: 'positive_coords', + type: 'STRING' +} + +const sam3DetectNodeDef: ComfyNodeDef = { + name: 'SAM3_Detect', + display_name: 'SAM3 Detect', + category: 'detection/', + python_module: 'comfy_extras.nodes_sam3', + description: '', + input: { + required: {}, + optional: { + positive_coords: [ + 'STRING', + { + tooltip: jsonTooltip, + forceInput: true + } + ] + } + }, + output: [], + output_node: false, + deprecated: false, + experimental: false +} + +describe('useNodeTooltips', () => { + beforeEach(() => { + setActivePinia(createTestingPinia({ stubActions: false })) + + vi.spyOn(useSettingStore(), 'get').mockImplementation( + (key: K): Settings[K] => { + switch (key) { + case 'Comfy.EnableTooltips': + return true as Settings[K] + case 'LiteGraph.Node.TooltipDelay': + return 500 as Settings[K] + default: + return undefined as Settings[K] + } + } + ) + + useNodeDefStore().addNodeDef(sam3DetectNodeDef) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('reads JSON examples in node metadata without i18n placeholder errors', () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + const { getInputSlotTooltip } = useNodeTooltips('SAM3_Detect') + + // Ensure this exercises the bundled i18n path, not only metadata fallback. + expect(te(positiveCoordsTooltipKey)).toBe(true) + expect(getInputSlotTooltip('positive_coords')).toBe(jsonTooltip) + expect(consoleError).not.toHaveBeenCalled() + }) + + it('reads input-based widget tooltips without i18n placeholder errors', () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + const { getWidgetTooltip } = useNodeTooltips('SAM3_Detect') + + expect(te(positiveCoordsTooltipKey)).toBe(true) + expect(getWidgetTooltip(positiveCoordsWidget)).toBe(jsonTooltip) + expect(consoleError).not.toHaveBeenCalled() + }) +}) diff --git a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts index f884146dad..22ae1c406b 100644 --- a/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts +++ b/src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts @@ -6,7 +6,7 @@ import { computed, ref, unref } from 'vue' import type { MaybeRef } from 'vue' import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager' -import { st } from '@/i18n' +import { st, stRaw } from '@/i18n' import { useSettingStore } from '@/platform/settings/settingStore' import { useNodeDefStore } from '@/stores/nodeDefStore' import { normalizeI18nKey } from '@/utils/formatUtil' @@ -119,7 +119,7 @@ export function useNodeTooltips(nodeType: MaybeRef) { const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.inputs.${normalizeI18nKey(slotName)}.tooltip` const inputTooltip = nodeDef.value.inputs?.[slotName]?.tooltip ?? '' - return st(key, inputTooltip) + return stRaw(key, inputTooltip) } /** @@ -130,7 +130,7 @@ export function useNodeTooltips(nodeType: MaybeRef) { const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.outputs.${slotIndex}.tooltip` const outputTooltip = nodeDef.value.outputs?.[slotIndex]?.tooltip ?? '' - return st(key, outputTooltip) + return stRaw(key, outputTooltip) } /** @@ -146,7 +146,7 @@ export function useNodeTooltips(nodeType: MaybeRef) { // Then try input-based tooltip lookup const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.inputs.${normalizeI18nKey(widget.name)}.tooltip` const inputTooltip = nodeDef.value.inputs?.[widget.name]?.tooltip ?? '' - return st(key, inputTooltip) + return stRaw(key, inputTooltip) } /**