Skip to content

Commit 01a6ac4

Browse files
[fix] Prevent null values in translated node definition outputs
Adds cleanup step to i18n workflow to remove null entries that can occur when translation service encounters timing issues with node output changes. Also adds concise documentation for the node definition translation script.
1 parent 54055e7 commit 01a6ac4

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

.github/workflows/i18n-node-defs.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
env:
3333
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
3434
working-directory: ComfyUI_frontend
35+
- name: Fix malformed outputs in translations
36+
run: node scripts/fix-translated-outputs.cjs
37+
working-directory: ComfyUI_frontend
3538
- name: Create Pull Request
3639
uses: peter-evans/create-pull-request@v7
3740
with:
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Node Definition Translation Collection Script
2+
3+
## Overview
4+
5+
The `collect-i18n-node-defs.ts` script automatically extracts translatable content from ComfyUI node definitions to generate structured JSON files for internationalization (i18n).
6+
7+
## What It Does
8+
9+
- Uses Playwright to load ComfyUI frontend and fetch node definitions via the ComfyUI HTTP API
10+
- Extracts data types, node categories, input/output names, and descriptions
11+
- Discovers runtime widget labels by creating actual node instances
12+
- Normalizes keys for i18n compatibility (replaces dots with underscores)
13+
- Generates `src/locales/en/main.json` (data types & categories) and `src/locales/en/nodeDefs.json`
14+
15+
## How It Works
16+
17+
1. **Browser Setup**: Uses Playwright to load ComfyUI frontend and access the HTTP API
18+
2. **Data Collection**: Fetches node definitions via API and filters out DevTools nodes
19+
3. **Widget Discovery**: Creates LiteGraph node instances to find runtime-generated widgets
20+
4. **Output Generation**: Writes structured translation files
21+
22+
## Key Features
23+
24+
- **Runtime Widget Detection**: Captures dynamically created widgets not in static definitions
25+
- **Data Type Deduplication**: Skips output names that already exist as data types
26+
- **Special Character Handling**: Normalizes keys with dots for i18n compatibility
27+
28+
## Usage
29+
30+
```bash
31+
npm run collect:i18n:nodeDefs
32+
```
33+
34+
## Output Structure
35+
36+
- **main.json**: Updates `dataTypes` and `nodeCategories` sections
37+
- **nodeDefs.json**: Complete node translation structure with inputs, outputs, and metadata

scripts/fix-translated-outputs.cjs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Fix malformed outputs arrays in translated nodeDefs.json files
5+
*
6+
* The translation service sometimes converts object structures to arrays with null values:
7+
*
8+
* Expected: { "outputs": { "0": { "name": "image" }, "1": { "name": "mask" } } }
9+
* Actual: { "outputs": [ null, null, { "name": "normal" }, { "name": "info" } ] }
10+
*
11+
* This script converts malformed arrays back to the correct object structure.
12+
*/
13+
14+
const fs = require('fs');
15+
const path = require('path');
16+
17+
/**
18+
* Fix malformed outputs in a node definition object
19+
* @param {Object} nodeDef - Node definition object
20+
* @returns {Object} Fixed node definition
21+
*/
22+
function fixNodeDefOutputs(nodeDef) {
23+
if (!nodeDef.outputs) {
24+
return nodeDef;
25+
}
26+
27+
// If outputs is already an object, no fix needed
28+
if (!Array.isArray(nodeDef.outputs)) {
29+
return nodeDef;
30+
}
31+
32+
// Convert array to object, filtering out nulls
33+
const outputsObject = {};
34+
nodeDef.outputs.forEach((output, index) => {
35+
if (output !== null && output !== undefined) {
36+
outputsObject[index.toString()] = output;
37+
}
38+
});
39+
40+
return {
41+
...nodeDef,
42+
outputs: outputsObject
43+
};
44+
}
45+
46+
/**
47+
* Fix malformed outputs in all node definitions in a locale file
48+
* @param {Object} localeData - Parsed locale JSON data
49+
* @returns {Object} Fixed locale data
50+
*/
51+
function fixLocaleOutputs(localeData) {
52+
const fixed = {};
53+
54+
for (const [nodeKey, nodeDef] of Object.entries(localeData)) {
55+
fixed[nodeKey] = fixNodeDefOutputs(nodeDef);
56+
}
57+
58+
return fixed;
59+
}
60+
61+
/**
62+
* Process a single nodeDefs.json file
63+
* @param {string} filePath - Path to the file
64+
*/
65+
function processFile(filePath) {
66+
try {
67+
console.log(`Processing: ${filePath}`);
68+
69+
const content = fs.readFileSync(filePath, 'utf8');
70+
const data = JSON.parse(content);
71+
72+
const fixed = fixLocaleOutputs(data);
73+
const fixedContent = JSON.stringify(fixed, null, 2);
74+
75+
// Only write if content changed
76+
if (content !== fixedContent) {
77+
fs.writeFileSync(filePath, fixedContent);
78+
console.log(` ✓ Fixed malformed outputs in ${filePath}`);
79+
} else {
80+
console.log(` - No changes needed in ${filePath}`);
81+
}
82+
83+
} catch (error) {
84+
console.error(` ✗ Error processing ${filePath}:`, error.message);
85+
process.exit(1);
86+
}
87+
}
88+
89+
/**
90+
* Find all nodeDefs.json files except the English source
91+
* @returns {string[]} Array of file paths
92+
*/
93+
function findTranslatedLocaleFiles() {
94+
const localesDir = path.join(process.cwd(), 'src', 'locales');
95+
96+
if (!fs.existsSync(localesDir)) {
97+
return [];
98+
}
99+
100+
const files = [];
101+
const locales = fs.readdirSync(localesDir, { withFileTypes: true })
102+
.filter(dirent => dirent.isDirectory() && dirent.name !== 'en')
103+
.map(dirent => dirent.name);
104+
105+
for (const locale of locales) {
106+
const nodeDefsPath = path.join(localesDir, locale, 'nodeDefs.json');
107+
if (fs.existsSync(nodeDefsPath)) {
108+
files.push(nodeDefsPath);
109+
}
110+
}
111+
112+
return files;
113+
}
114+
115+
/**
116+
* Main execution
117+
*/
118+
function main() {
119+
try {
120+
const files = findTranslatedLocaleFiles();
121+
122+
if (files.length === 0) {
123+
console.log('No translated nodeDefs.json files found to process.');
124+
return;
125+
}
126+
127+
console.log(`Found ${files.length} translated locale files to process:`);
128+
files.forEach(file => console.log(` - ${path.relative(process.cwd(), file)}`));
129+
console.log('');
130+
131+
files.forEach(processFile);
132+
133+
console.log('\n✓ All files processed successfully');
134+
135+
} catch (error) {
136+
console.error('Error finding files:', error.message);
137+
process.exit(1);
138+
}
139+
}
140+
141+
if (require.main === module) {
142+
main();
143+
}

0 commit comments

Comments
 (0)