Skip to content
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d8ff3df
reset branch
snomiao Sep 17, 2025
c5c54df
fix: remove unused dependencies and files flagged by knip
snomiao Sep 18, 2025
c66843e
fix: resolve knip issues - remove unused babel-plugin-module-resolver…
snomiao Sep 22, 2025
c4cb359
istall babel plugins
snomiao Sep 23, 2025
c3e2588
fix: resolve collect-i18n babel transformation issues
snomiao Sep 24, 2025
20e1cb0
fix: resolve TypeScript errors in collect-i18n-general.ts
snomiao Sep 24, 2025
66706f8
fix: remove scripts/collect-i18n-*.ts from tsconfig includes
snomiao Sep 24, 2025
14bc5ad
fix: revert unnecessary package.json version changes
snomiao Sep 24, 2025
1b87f29
fix: update pnpm-lock.yaml to match package.json
snomiao Sep 24, 2025
81314b6
Update locales [skip ci]
invalid-email-address Sep 24, 2025
7002a17
fix: add saveImmediately option to i18n configuration
snomiao Sep 24, 2025
1741677
Update locales [skip ci]
invalid-email-address Sep 24, 2025
7ec9f0b
Merge branch 'main' into sno-fix-playwright-babel-config
snomiao Sep 24, 2025
247937f
Update locales [skip ci]
invalid-email-address Sep 24, 2025
b98458d
fix: remove unused imports and streamline node definitions collection
snomiao Sep 24, 2025
23ba2fe
Merge branch 'sno-fix-playwright-babel-config' of github.com:Comfy-Or…
snomiao Sep 24, 2025
12a130b
Merge branch 'main' into sno-fix-playwright-babel-config
snomiao Sep 24, 2025
c785ccf
Update locales [skip ci]
invalid-email-address Sep 24, 2025
916170e
Update locales [skip ci]
invalid-email-address Sep 24, 2025
89465de
Revert "Update locales [skip ci]"
snomiao Sep 24, 2025
8560646
chore(babel-plugin-stub-vue-imports): remove unused Babel plugin for …
snomiao Sep 24, 2025
82c6f02
Merge branch 'main' into sno-fix-playwright-babel-config
snomiao Sep 24, 2025
8fdee1b
Merge branch 'main' into sno-fix-playwright-babel-config
snomiao Sep 25, 2025
7abda29
Update locales [skip ci]
invalid-email-address Sep 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ on:
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened]

jobs:
update-locales:
# Branch detection: Only run for manual dispatch or version-bump-* branches from main repo
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && (startsWith(github.head_ref, 'version-bump-') || startsWith(github.head_ref, 'sno-fix-playwright-')))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for testing, can be resotred after/before merge this pr

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably before right?

runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
Expand Down
1 change: 1 addition & 0 deletions .i18nrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = defineConfig({
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
saveImmediately: true,
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
'latent' is the short form of 'latent space'.
'mask' is in the context of image processing.
Expand Down
5 changes: 4 additions & 1 deletion knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const config: KnipConfig = {
'@primeuix/utils',
'@primevue/icons',
// Dev
'@trivago/prettier-plugin-sort-imports'
'@trivago/prettier-plugin-sort-imports',
// Used by playwright.i18n.config.ts babel plugins
'@babel/plugin-transform-typescript',
'babel-plugin-module-resolver'
],
ignore: [
// Auto generated manager types
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@babel/plugin-transform-typescript": "^7.28.0",
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@iconify/json": "^2.2.380",
"@primeuix/forms": "0.0.2",
Expand All @@ -128,6 +129,7 @@
"@xterm/xterm": "^5.5.0",
"algoliasearch": "^5.21.0",
"axios": "^1.8.2",
"babel-plugin-module-resolver": "^5.0.2",
"chart.js": "^4.5.0",
"clsx": "^2.1.1",
"dompurify": "^3.2.5",
Expand Down
53 changes: 49 additions & 4 deletions playwright.i18n.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,57 @@
import { defineConfig } from '@playwright/test'
import path from 'path'
import { fileURLToPath } from 'url'

export default defineConfig({
const __dirname = path.dirname(fileURLToPath(import.meta.url))

const config: any = defineConfig({
testDir: './scripts',
use: {
baseURL: 'http://localhost:5173',
headless: true
baseURL: 'http://localhost:5173'
},
reporter: 'list',
timeout: 60000,
testMatch: /collect-i18n-.*\.ts/
workers: 1, // Run tests serially to avoid duplicate user creation
testMatch: /collect-i18n-.*\.ts/,
// Start dev server before running tests
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: true,
timeout: 60000
}
})

// Configure babel plugins for TypeScript with declare fields and module resolution
config['@playwright/test'] = {
babelPlugins: [
// Module resolver to handle @ alias
[
'babel-plugin-module-resolver',
{
root: ['./'],
alias: {
'@': './src'
}
}
],
// TypeScript transformation with declare field support
[
'@babel/plugin-transform-typescript',
{
allowDeclareFields: true,
onlyRemoveTypeImports: true
}
],
// Inject browser globals AFTER TypeScript transformation
[
path.join(__dirname, 'scripts/babel-plugin-inject-globals.cjs'),
{
filenamePattern: 'collect-i18n-',
setupFile: './setup-browser-globals.mjs'
}
]
]
}

export default config
1,414 changes: 835 additions & 579 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions scripts/babel-plugin-inject-globals.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = function(babel) {
const { types: t } = babel;

return {
visitor: {
Program(path, state) {
// Get options from plugin configuration
const opts = state.opts || {};
const filenamePattern = opts.filenamePattern || DIE('filenamePattern option is required');
const setupFile = opts.setupFile || DIE('setupFile option is required');

// Only inject the setup for matching test files
if (state.filename?.match(filenamePattern)) {
// Create an import statement for the setup file
const importDeclaration = t.importDeclaration(
[],
t.stringLiteral(setupFile)
);

// Insert the import at the beginning of the file
path.node.body.unshift(importDeclaration);
}
}
}
};
};

function DIE(msg) {
throw new Error(msg);
}
42 changes: 34 additions & 8 deletions scripts/collect-i18n-general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,38 @@ test('collect-i18n-general', async ({ comfyPage }) => {
Array.from(allLabels).map((label) => [normalizeI18nKey(label), label])
)

const allCommandsLocale = Object.fromEntries(
commands.map((command) => [
// Load existing commands to preserve Desktop commands
const existingCommands = JSON.parse(fs.readFileSync(commandsPath, 'utf-8'))

// Filter out Desktop commands from existing commands
const desktopCommands = Object.fromEntries(
Object.entries(existingCommands).filter(([key]) =>
key.startsWith('Comfy-Desktop')
)
)

const allCommandsLocale = Object.fromEntries([
// Keep Desktop commands that aren't in the current collection
...Object.entries(desktopCommands),
// Add/update commands from current collection
...commands.map((command) => [
normalizeI18nKey(command.id),
{
label: command.label,
tooltip: command.tooltip
}
])
)
])

// Settings
const settings = await comfyPage.page.evaluate(() => {
const workspace = window['app'].extensionManager
const settings = workspace.setting.settings as Record<string, SettingParams>
return Object.values(settings)
.sort((a, b) => a.id.localeCompare(b.id))
.sort((a, b) => String(a.id).localeCompare(String(b.id)))
.filter((setting) => setting.type !== 'hidden')
.map((setting) => ({
id: setting.id,
id: String(setting.id),
name: setting.name,
tooltip: setting.tooltip,
category: setting.category,
Expand All @@ -79,8 +92,21 @@ test('collect-i18n-general', async ({ comfyPage }) => {
}))
})

const allSettingsLocale = Object.fromEntries(
settings.map((setting) => [
// Load existing settings to preserve Desktop settings
const existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))

// Filter out Desktop settings from existing settings
const desktopSettings = Object.fromEntries(
Object.entries(existingSettings).filter(([key]) =>
key.startsWith('Comfy-Desktop')
)
)

const allSettingsLocale = Object.fromEntries([
// Keep Desktop settings that aren't in the current collection
...Object.entries(desktopSettings),
// Add/update settings from current collection
...settings.map((setting) => [
normalizeI18nKey(setting.id),
{
name: setting.name,
Expand All @@ -99,7 +125,7 @@ test('collect-i18n-general', async ({ comfyPage }) => {
: undefined
}
])
)
])

const allSettingCategoriesLocale = Object.fromEntries(
settings
Expand Down
120 changes: 85 additions & 35 deletions scripts/collect-i18n-node-defs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import * as fs from 'fs'

import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
import type { ComfyNodeDef } from '../src/schemas/nodeDefSchema'
import type { ComfyApi } from '../src/scripts/api'
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
import type { ComfyNodeDef, InputSpec } from '../src/schemas/nodeDefSchema'
import { normalizeI18nKey } from '../src/utils/formatUtil'

const localePath = './src/locales/en/main.json'
Expand All @@ -26,30 +24,74 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
})
})

const nodeDefs: ComfyNodeDefImpl[] = (
Object.values(
await comfyPage.page.evaluate(async () => {
// @ts-expect-error - app is dynamically added to window
const api = window['app'].api as ComfyApi
return await api.getNodeDefs()
})
) as ComfyNodeDef[]
)
// Ignore DevTools nodes (used for internal testing)
.filter((def) => !def.name.startsWith('DevTools'))
.map((def) => new ComfyNodeDefImpl(def))
// Note: Don't mock the object_info API endpoint - let it hit the actual backend

const nodeDefs: ComfyNodeDef[] = await comfyPage.page.evaluate(async () => {
// @ts-expect-error - app is dynamically added to window
const api = window['app'].api
const rawNodeDefs = await api.getNodeDefs()

// @ts-expect-error - ComfyNodeDefImpl is available in browser context
const { ComfyNodeDefImpl } = await import('../src/stores/nodeDefStore')

return (
Object.values(rawNodeDefs)
// Ignore DevTools nodes (used for internal testing)
.filter((def: any) => !def.name.startsWith('DevTools'))
.map((def: any) => {
const nodeDefImpl = new ComfyNodeDefImpl(def)
// Extract properties needed for i18n collection
return {
name: nodeDefImpl.name,
display_name: nodeDefImpl.display_name,
description: nodeDefImpl.description,
category: nodeDefImpl.category,
input: nodeDefImpl.input,
output: nodeDefImpl.output,
output_is_list: nodeDefImpl.output_is_list,
output_name: nodeDefImpl.output_name,
output_node: nodeDefImpl.output_node,
python_module: nodeDefImpl.python_module
} as ComfyNodeDef
})
)
})

console.log(`Collected ${nodeDefs.length} node definitions`)

// If no node definitions were collected (e.g., running without backend),
// create empty locale files to avoid build failures
if (nodeDefs.length === 0) {
console.warn('No node definitions found - creating empty locale files')
const locale = JSON.parse(fs.readFileSync(localePath, 'utf-8'))
fs.writeFileSync(
localePath,
JSON.stringify(
{
...locale,
dataTypes: {},
nodeCategories: {}
},
null,
2
)
)
fs.writeFileSync(nodeDefsPath, JSON.stringify({}, null, 2))
return
}

const allDataTypesLocale = Object.fromEntries(
nodeDefs
.flatMap((nodeDef) => {
const inputDataTypes = Object.values(nodeDef.inputs).map(
(inputSpec) => inputSpec.type
)
const outputDataTypes = nodeDef.outputs.map(
(outputSpec) => outputSpec.type
)
const inputDataTypes = nodeDef.input?.required
? Object.values(nodeDef.input.required).map((inputSpec: InputSpec) =>
typeof inputSpec[0] === 'string' ? inputSpec[0] : 'COMBO'
)
: []
const outputDataTypes =
nodeDef.output?.map((outputSpec) =>
typeof outputSpec === 'string' ? outputSpec : 'COMBO'
) || []
const allDataTypes = [...inputDataTypes, ...outputDataTypes].flatMap(
(type: string) => type.split(',')
)
Expand All @@ -65,9 +107,9 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
const nodeLabels: WidgetLabels = {}

for (const nodeDef of nodeDefs) {
const inputNames = Object.values(nodeDef.inputs).map(
(input) => input.name
)
const inputNames = nodeDef.input?.required
? Object.keys(nodeDef.input.required)
: []

if (!inputNames.length) continue

Expand Down Expand Up @@ -113,21 +155,24 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {

const nodeDefLabels = await extractWidgetLabels()

function extractInputs(nodeDef: ComfyNodeDefImpl) {
function extractInputs(nodeDef: ComfyNodeDef) {
const allInputs = {
...(nodeDef.input?.required || {}),
...(nodeDef.input?.optional || {})
}
const inputs = Object.fromEntries(
Object.values(nodeDef.inputs).flatMap((input) => {
const name = input.name
const tooltip = input.tooltip
Object.entries(allInputs).flatMap(([inputName, inputSpec]) => {
const tooltip = inputSpec[1]?.tooltip

if (name === undefined && tooltip === undefined) {
if (!inputName && !tooltip) {
return []
}

return [
[
normalizeI18nKey(input.name),
normalizeI18nKey(inputName),
{
name,
name: inputName,
tooltip
}
]
Expand All @@ -137,12 +182,17 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
return Object.keys(inputs).length > 0 ? inputs : undefined
}

function extractOutputs(nodeDef: ComfyNodeDefImpl) {
function extractOutputs(nodeDef: ComfyNodeDef) {
const outputs = Object.fromEntries(
nodeDef.outputs.flatMap((output, i) => {
(nodeDef.output || []).flatMap((_, i) => {
const outputName = nodeDef.output_name?.[i]
const outputTooltip = nodeDef.output_tooltips?.[i]
// Ignore data types if they are already translated in allDataTypesLocale.
const name = output.name in allDataTypesLocale ? undefined : output.name
const tooltip = output.tooltip
const name =
outputName && outputName in allDataTypesLocale
? undefined
: outputName
const tooltip = outputTooltip

if (name === undefined && tooltip === undefined) {
return []
Expand Down
Loading
Loading