Skip to content

Commit ec4ced2

Browse files
[fix] Automatically fix malformed node def translations (#4042)
1 parent 40cfc43 commit ec4ced2

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)