diff --git a/.github/workflows/i18n-update-core.yaml b/.github/workflows/i18n-update-core.yaml index 5f0985b93..38898f014 100644 --- a/.github/workflows/i18n-update-core.yaml +++ b/.github/workflows/i18n-update-core.yaml @@ -43,7 +43,7 @@ jobs: env: PLAYWRIGHT_TEST_URL: http://localhost:5173 - name: Update translations - run: pnpm locale && pnpm format + run: pnpm locale && pnpm fix-i18n-node-defs && pnpm format env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Commit updated locales diff --git a/.github/workflows/i18n-update-custom-nodes.yaml b/.github/workflows/i18n-update-custom-nodes.yaml index 225c1b3e3..4c6788fad 100644 --- a/.github/workflows/i18n-update-custom-nodes.yaml +++ b/.github/workflows/i18n-update-custom-nodes.yaml @@ -67,7 +67,7 @@ jobs: env: PLAYWRIGHT_TEST_URL: http://localhost:5173 - name: Update translations - run: pnpm locale + run: pnpm locale && pnpm fix-i18n-node-defs env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Diff base vs updated i18n diff --git a/.github/workflows/i18n-update-nodes.yaml b/.github/workflows/i18n-update-nodes.yaml index 5a72e5b10..8c974addb 100644 --- a/.github/workflows/i18n-update-nodes.yaml +++ b/.github/workflows/i18n-update-nodes.yaml @@ -36,7 +36,7 @@ jobs: env: PLAYWRIGHT_TEST_URL: http://localhost:5173 - name: Update translations - run: pnpm locale + run: pnpm locale && pnpm fix-i18n-node-defs env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Create Pull Request diff --git a/package.json b/package.json index 643b3c71a..5dff07bd5 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,12 @@ "collect-i18n": "pnpm exec playwright test --config=playwright.i18n.config.ts", "dev:cloud": "cross-env DEV_SERVER_COMFYUI_URL='https://testcloud.comfy.org/' nx serve", "dev:desktop": "nx dev @comfyorg/desktop-ui", - "dev:electron": "nx serve --config vite.electron.config.mts", + "dev:electron": "cross-env DISTRIBUTION=desktop nx serve --config vite.electron.config.mts", "dev:no-vue": "cross-env DISABLE_VUE_PLUGINS=true nx serve", "dev": "nx serve", "devtools:pycheck": "python3 -m compileall -q tools/devtools", "format:check": "oxfmt --check", + "fix-i18n-node-defs": "tsx scripts/fix-i18n-node-defs.ts", "format": "oxfmt --write", "json-schema": "tsx scripts/generate-json-schema.ts", "knip:no-cache": "knip", diff --git a/scripts/fix-i18n-node-defs.ts b/scripts/fix-i18n-node-defs.ts new file mode 100644 index 000000000..569e79be6 --- /dev/null +++ b/scripts/fix-i18n-node-defs.ts @@ -0,0 +1,66 @@ +import { readFileSync, readdirSync, writeFileSync } from 'fs' +import { join } from 'path' + +const LOCALES_DIR = 'src/locales' + +/** + * Convert arrays with numeric indices back to objects. + * GPT-4.1 (used by @lobehub/i18n-cli) sometimes converts + * {"0": {...}, "1": {...}} objects into JSON arrays with null gaps. + */ +function fixArraysToObjects(value: unknown): Record | unknown { + if (!value || typeof value !== 'object') return value + + if (Array.isArray(value)) { + const obj: Record = {} + for (let i = 0; i < value.length; i++) { + if (value[i] != null) { + obj[String(i)] = fixArraysToObjects(value[i]) + } + } + return obj + } + + const record = value as Record + const result: Record = {} + for (const key of Object.keys(record)) { + result[key] = fixArraysToObjects(record[key]) + } + return result +} + +function run() { + const locales = readdirSync(LOCALES_DIR, { withFileTypes: true }) + .filter((d) => d.isDirectory() && d.name !== 'en') + .map((d) => d.name) + + let totalFixes = 0 + + for (const locale of locales) { + const filePath = join(LOCALES_DIR, locale, 'nodeDefs.json') + let raw: string + try { + raw = readFileSync(filePath, 'utf-8') + } catch { + continue + } + + const data = JSON.parse(raw) + const fixed = fixArraysToObjects(data) as Record + const fixedJson = JSON.stringify(fixed, null, 2) + '\n' + + if (fixedJson !== raw) { + writeFileSync(filePath, fixedJson) + totalFixes++ + console.warn(`Fixed: ${filePath}`) + } + } + + if (totalFixes === 0) { + console.warn('No fixes needed') + } else { + console.warn(`Fixed ${totalFixes} file(s)`) + } +} + +run() diff --git a/src/App.vue b/src/App.vue index f843a7347..f9e34fc68 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,7 +19,8 @@ import config from '@/config' import { useWorkspaceStore } from '@/stores/workspaceStore' import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection' -import { electronAPI, isElectron } from './utils/envUtil' +import { electronAPI } from '@/utils/envUtil' +import { isDesktop } from '@/platform/distribution/types' import { app } from '@/scripts/app' const workspaceStore = useWorkspaceStore() @@ -42,7 +43,7 @@ const showContextMenu = (event: MouseEvent) => { onMounted(() => { window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version - if (isElectron()) { + if (isDesktop) { document.addEventListener('contextmenu', showContextMenu) } diff --git a/src/components/TopMenuSection.test.ts b/src/components/TopMenuSection.test.ts index b2bf7041c..ade4a8ae6 100644 --- a/src/components/TopMenuSection.test.ts +++ b/src/components/TopMenuSection.test.ts @@ -18,9 +18,8 @@ import { useCommandStore } from '@/stores/commandStore' import { useExecutionStore } from '@/stores/executionStore' import { TaskItemImpl, useQueueStore } from '@/stores/queueStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' -import { isElectron } from '@/utils/envUtil' -const mockData = vi.hoisted(() => ({ isLoggedIn: false })) +const mockData = vi.hoisted(() => ({ isLoggedIn: false, isDesktop: false })) vi.mock('@/composables/auth/useCurrentUser', () => ({ useCurrentUser: () => { @@ -30,7 +29,13 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({ } })) -vi.mock('@/utils/envUtil') +vi.mock('@/platform/distribution/types', () => ({ + isCloud: false, + isNightly: false, + get isDesktop() { + return mockData.isDesktop + } +})) vi.mock('@/stores/firebaseAuthStore', () => ({ useFirebaseAuthStore: vi.fn(() => ({ currentUser: null, @@ -107,6 +112,8 @@ describe('TopMenuSection', () => { beforeEach(() => { vi.resetAllMocks() localStorage.clear() + mockData.isDesktop = false + mockData.isLoggedIn = false }) describe('authentication state', () => { @@ -129,7 +136,7 @@ describe('TopMenuSection', () => { describe('on desktop platform', () => { it('should display LoginButton and not display CurrentUserButton', () => { - vi.mocked(isElectron).mockReturnValue(true) + mockData.isDesktop = true const wrapper = createWrapper() expect(wrapper.findComponent(LoginButton).exists()).toBe(true) expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false) diff --git a/src/components/TopMenuSection.vue b/src/components/TopMenuSection.vue index 13b28e904..85a01d1a1 100644 --- a/src/components/TopMenuSection.vue +++ b/src/components/TopMenuSection.vue @@ -153,7 +153,7 @@ import { useQueueStore, useQueueUIStore } from '@/stores/queueStore' import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' import { useWorkspaceStore } from '@/stores/workspaceStore' -import { isElectron } from '@/utils/envUtil' +import { isDesktop } from '@/platform/distribution/types' import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment' import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState' import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes' @@ -163,7 +163,6 @@ const workspaceStore = useWorkspaceStore() const rightSidePanelStore = useRightSidePanelStore() const managerState = useManagerState() const { isLoggedIn } = useCurrentUser() -const isDesktop = isElectron() const { t, n } = useI18n() const { toastErrorHandler } = useErrorHandling() const commandStore = useCommandStore() diff --git a/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts b/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts index b99e54ea1..a651475fb 100644 --- a/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts +++ b/src/components/bottomPanel/tabs/terminal/BaseTerminal.test.ts @@ -55,10 +55,17 @@ vi.mock('@/composables/bottomPanelTabs/useTerminal', () => ({ })) vi.mock('@/utils/envUtil', () => ({ - isElectron: vi.fn(() => false), electronAPI: vi.fn(() => null) })) +const mockData = vi.hoisted(() => ({ isDesktop: false })) + +vi.mock('@/platform/distribution/types', () => ({ + get isDesktop() { + return mockData.isDesktop + } +})) + // Mock clipboard API Object.defineProperty(navigator, 'clipboard', { value: { diff --git a/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue b/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue index 655df0b65..54b337c20 100644 --- a/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue +++ b/src/components/bottomPanel/tabs/terminal/BaseTerminal.vue @@ -35,7 +35,8 @@ import { useI18n } from 'vue-i18n' import Button from '@/components/ui/button/Button.vue' import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal' -import { electronAPI, isElectron } from '@/utils/envUtil' +import { electronAPI } from '@/utils/envUtil' +import { isDesktop } from '@/platform/distribution/types' import { cn } from '@/utils/tailwindUtil' const { t } = useI18n() @@ -85,7 +86,7 @@ const showContextMenu = (event: MouseEvent) => { electronAPI()?.showContextMenu({ type: 'text' }) } -if (isElectron()) { +if (isDesktop) { useEventListener(terminalEl, 'contextmenu', showContextMenu) } diff --git a/src/components/dialog/content/MissingModelsWarning.vue b/src/components/dialog/content/MissingModelsWarning.vue index 1b044e4d3..f939b6c6c 100644 --- a/src/components/dialog/content/MissingModelsWarning.vue +++ b/src/components/dialog/content/MissingModelsWarning.vue @@ -13,7 +13,7 @@ @@ -52,7 +52,7 @@ import { useProgressFavicon } from '@/composables/useProgressFavicon' import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig' import { i18n, loadLocale } from '@/i18n' import ModelImportProgressDialog from '@/platform/assets/components/ModelImportProgressDialog.vue' -import { isCloud } from '@/platform/distribution/types' +import { isCloud, isDesktop } from '@/platform/distribution/types' import { useSettingStore } from '@/platform/settings/settingStore' import { useTelemetry } from '@/platform/telemetry' import { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning' @@ -78,7 +78,7 @@ import { useServerConfigStore } from '@/stores/serverConfigStore' import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' -import { electronAPI, isElectron } from '@/utils/envUtil' +import { electronAPI } from '@/utils/envUtil' import LinearView from '@/views/LinearView.vue' import ManagerProgressToast from '@/workbench/extensions/manager/components/ManagerProgressToast.vue' @@ -111,7 +111,7 @@ watch( document.body.classList.add(DARK_THEME_CLASS) } - if (isElectron()) { + if (isDesktop) { electronAPI().changeTheme({ color: 'rgba(0, 0, 0, 0)', symbolColor: newTheme.colors.comfy_base['input-text'] @@ -121,7 +121,7 @@ watch( { immediate: true } ) -if (isElectron()) { +if (isDesktop) { watch( () => queueStore.tasks, (newTasks, oldTasks) => { diff --git a/src/views/templates/BaseViewTemplate.vue b/src/views/templates/BaseViewTemplate.vue index 786cd77ac..b6b245cf4 100644 --- a/src/views/templates/BaseViewTemplate.vue +++ b/src/views/templates/BaseViewTemplate.vue @@ -22,7 +22,8 @@