diff --git a/.github/workflows/i18n-node-defs.yaml b/.github/workflows/i18n-node-defs.yaml index b9c9af6e3..faa4e343c 100644 --- a/.github/workflows/i18n-node-defs.yaml +++ b/.github/workflows/i18n-node-defs.yaml @@ -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: diff --git a/scripts/README-node-def-translations.md b/scripts/README-node-def-translations.md new file mode 100644 index 000000000..84349eca9 --- /dev/null +++ b/scripts/README-node-def-translations.md @@ -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 \ No newline at end of file diff --git a/scripts/fix-translated-outputs.cjs b/scripts/fix-translated-outputs.cjs new file mode 100644 index 000000000..d9df5fc7b --- /dev/null +++ b/scripts/fix-translated-outputs.cjs @@ -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(); +} \ No newline at end of file