mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
[fix] Automatically fix malformed node def translations (#4042)
This commit is contained in:
3
.github/workflows/i18n-node-defs.yaml
vendored
3
.github/workflows/i18n-node-defs.yaml
vendored
@@ -32,6 +32,9 @@ jobs:
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Fix malformed outputs in translations
|
||||
run: node scripts/fix-translated-outputs.cjs
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
|
||||
37
scripts/README-node-def-translations.md
Normal file
37
scripts/README-node-def-translations.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Node Definition Translation Collection Script
|
||||
|
||||
## Overview
|
||||
|
||||
The `collect-i18n-node-defs.ts` script automatically extracts translatable content from ComfyUI node definitions to generate structured JSON files for internationalization (i18n).
|
||||
|
||||
## What It Does
|
||||
|
||||
- Uses Playwright to load ComfyUI frontend and fetch node definitions via the ComfyUI HTTP API
|
||||
- Extracts data types, node categories, input/output names, and descriptions
|
||||
- Discovers runtime widget labels by creating actual node instances
|
||||
- Normalizes keys for i18n compatibility (replaces dots with underscores)
|
||||
- Generates `src/locales/en/main.json` (data types & categories) and `src/locales/en/nodeDefs.json`
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Browser Setup**: Uses Playwright to load ComfyUI frontend and access the HTTP API
|
||||
2. **Data Collection**: Fetches node definitions via API and filters out DevTools nodes
|
||||
3. **Widget Discovery**: Creates LiteGraph node instances to find runtime-generated widgets
|
||||
4. **Output Generation**: Writes structured translation files
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Runtime Widget Detection**: Captures dynamically created widgets not in static definitions
|
||||
- **Data Type Deduplication**: Skips output names that already exist as data types
|
||||
- **Special Character Handling**: Normalizes keys with dots for i18n compatibility
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm run collect:i18n:nodeDefs
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
- **main.json**: Updates `dataTypes` and `nodeCategories` sections
|
||||
- **nodeDefs.json**: Complete node translation structure with inputs, outputs, and metadata
|
||||
143
scripts/fix-translated-outputs.cjs
Normal file
143
scripts/fix-translated-outputs.cjs
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Fix malformed outputs arrays in translated nodeDefs.json files
|
||||
*
|
||||
* The translation service sometimes converts object structures to arrays with null values:
|
||||
*
|
||||
* Expected: { "outputs": { "0": { "name": "image" }, "1": { "name": "mask" } } }
|
||||
* Actual: { "outputs": [ null, null, { "name": "normal" }, { "name": "info" } ] }
|
||||
*
|
||||
* This script converts malformed arrays back to the correct object structure.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Fix malformed outputs in a node definition object
|
||||
* @param {Object} nodeDef - Node definition object
|
||||
* @returns {Object} Fixed node definition
|
||||
*/
|
||||
function fixNodeDefOutputs(nodeDef) {
|
||||
if (!nodeDef.outputs) {
|
||||
return nodeDef;
|
||||
}
|
||||
|
||||
// If outputs is already an object, no fix needed
|
||||
if (!Array.isArray(nodeDef.outputs)) {
|
||||
return nodeDef;
|
||||
}
|
||||
|
||||
// Convert array to object, filtering out nulls
|
||||
const outputsObject = {};
|
||||
nodeDef.outputs.forEach((output, index) => {
|
||||
if (output !== null && output !== undefined) {
|
||||
outputsObject[index.toString()] = output;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...nodeDef,
|
||||
outputs: outputsObject
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix malformed outputs in all node definitions in a locale file
|
||||
* @param {Object} localeData - Parsed locale JSON data
|
||||
* @returns {Object} Fixed locale data
|
||||
*/
|
||||
function fixLocaleOutputs(localeData) {
|
||||
const fixed = {};
|
||||
|
||||
for (const [nodeKey, nodeDef] of Object.entries(localeData)) {
|
||||
fixed[nodeKey] = fixNodeDefOutputs(nodeDef);
|
||||
}
|
||||
|
||||
return fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single nodeDefs.json file
|
||||
* @param {string} filePath - Path to the file
|
||||
*/
|
||||
function processFile(filePath) {
|
||||
try {
|
||||
console.log(`Processing: ${filePath}`);
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const data = JSON.parse(content);
|
||||
|
||||
const fixed = fixLocaleOutputs(data);
|
||||
const fixedContent = JSON.stringify(fixed, null, 2);
|
||||
|
||||
// Only write if content changed
|
||||
if (content !== fixedContent) {
|
||||
fs.writeFileSync(filePath, fixedContent);
|
||||
console.log(` ✓ Fixed malformed outputs in ${filePath}`);
|
||||
} else {
|
||||
console.log(` - No changes needed in ${filePath}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(` ✗ Error processing ${filePath}:`, error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all nodeDefs.json files except the English source
|
||||
* @returns {string[]} Array of file paths
|
||||
*/
|
||||
function findTranslatedLocaleFiles() {
|
||||
const localesDir = path.join(process.cwd(), 'src', 'locales');
|
||||
|
||||
if (!fs.existsSync(localesDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = [];
|
||||
const locales = fs.readdirSync(localesDir, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory() && dirent.name !== 'en')
|
||||
.map(dirent => dirent.name);
|
||||
|
||||
for (const locale of locales) {
|
||||
const nodeDefsPath = path.join(localesDir, locale, 'nodeDefs.json');
|
||||
if (fs.existsSync(nodeDefsPath)) {
|
||||
files.push(nodeDefsPath);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main execution
|
||||
*/
|
||||
function main() {
|
||||
try {
|
||||
const files = findTranslatedLocaleFiles();
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log('No translated nodeDefs.json files found to process.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${files.length} translated locale files to process:`);
|
||||
files.forEach(file => console.log(` - ${path.relative(process.cwd(), file)}`));
|
||||
console.log('');
|
||||
|
||||
files.forEach(processFile);
|
||||
|
||||
console.log('\n✓ All files processed successfully');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error finding files:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
Reference in New Issue
Block a user