diff --git a/.github/workflows/i18n-custom-nodes.yaml b/.github/workflows/i18n-custom-nodes.yaml new file mode 100644 index 000000000..03666452c --- /dev/null +++ b/.github/workflows/i18n-custom-nodes.yaml @@ -0,0 +1,127 @@ +name: Update Locales for given custom node repository +on: + workflow_dispatch: + inputs: + owner: + description: 'Owner of the repository to update locales for' + required: true + type: string + repository: + description: 'Repository to update locales for' + required: true + type: string + fork_owner: + description: 'Owner of the forked repository' + required: false + type: string + default: 'Comfy-Org' + +jobs: + update-locales: + runs-on: ubuntu-latest + steps: + - name: Checkout ComfyUI + uses: actions/checkout@v4 + with: + repository: 'comfyanonymous/ComfyUI' + path: 'ComfyUI' + ref: master + - name: Checkout ComfyUI_frontend + uses: actions/checkout@v4 + with: + repository: 'Comfy-Org/ComfyUI_frontend' + path: 'ComfyUI_frontend' + - name: Checkout ComfyUI_devtools + uses: actions/checkout@v4 + with: + repository: 'Comfy-Org/ComfyUI_devtools' + path: 'ComfyUI/custom_nodes/ComfyUI_devtools' + - name: Checkout custom node repository + uses: actions/checkout@v4 + with: + repository: ${{ inputs.owner }}/${{ inputs.repository }} + path: 'ComfyUI/custom_nodes/${{ inputs.repository }}' + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install ComfyUI requirements + run: | + python -m pip install --upgrade pip + pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu + pip install -r requirements.txt + pip install wait-for-it + shell: bash + working-directory: ComfyUI + - name: Install custom node requirements + run: | + if [ -f "requirements.txt" ]; then + pip install -r requirements.txt + fi + shell: bash + working-directory: ComfyUI/custom_nodes/${{ inputs.repository }} + - name: Build & Install ComfyUI_frontend + run: | + npm ci + npm run build + rm -rf ../ComfyUI/web/* + mv dist/* ../ComfyUI/web/ + shell: bash + working-directory: ComfyUI_frontend + - name: Start ComfyUI server + run: | + python main.py --cpu --multi-user & + wait-for-it --service 127.0.0.1:8188 -t 600 + working-directory: ComfyUI + shell: bash + - name: Install Playwright Browsers + run: npx playwright install chromium --with-deps + working-directory: ComfyUI_frontend + - name: Start dev server + # Run electron dev server as it is a superset of the web dev server + # We do want electron specific UIs to be translated. + run: npm run dev:electron & + working-directory: ComfyUI_frontend + - name: Capture base i18n + run: npx tsx scripts/diff-i18n capture + working-directory: ComfyUI_frontend + - name: Update en.json + run: npm run collect-i18n + env: + PLAYWRIGHT_TEST_URL: http://localhost:5173 + working-directory: ComfyUI_frontend + - name: Update translations + run: npm run locale + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + working-directory: ComfyUI_frontend + - name: Diff base vs updated i18n + run: npx tsx scripts/diff-i18n diff + working-directory: ComfyUI_frontend + - name: Update i18n in custom node repository + run: | + LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/ + install -d "$LOCALE_DIR" + cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR" + - name: Check and create fork of custom node repository + run: | + gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || { + echo "Fork failed - repository might already be forked" + # Exit 0 to prevent the workflow from failing + exit 0 + } + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + - name: Commit and push changes + run: | + git checkout -b update-locales + git add -A + git commit -m "Update locales" + git remote add org_fork git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git + git push org_fork update-locales + gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales + working-directory: ComfyUI/custom_nodes/${{ inputs.repository }} + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.gitignore b/.gitignore index 0579796ec..5fc7f8c41 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,6 @@ browser_tests/*/*-win32.png .env -dist.zip \ No newline at end of file +dist.zip + +/temp/ \ No newline at end of file diff --git a/scripts/diff-i18n.ts b/scripts/diff-i18n.ts new file mode 100644 index 000000000..b365bf45a --- /dev/null +++ b/scripts/diff-i18n.ts @@ -0,0 +1,124 @@ +import { + readFileSync, + writeFileSync, + readdirSync, + mkdirSync, + existsSync, + rmSync +} from 'fs' +import { join, dirname } from 'path' + +// Ensure directories exist +function ensureDir(dir: string) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }) + } +} + +// Read JSON file +function readJsonFile(path: string) { + try { + return JSON.parse(readFileSync(path, 'utf-8')) + } catch { + return {} + } +} + +// Get all JSON files recursively +function getAllJsonFiles(dir: string): string[] { + const files: string[] = [] + const items = readdirSync(dir, { withFileTypes: true }) + + for (const item of items) { + const path = join(dir, item.name) + if (item.isDirectory()) { + files.push(...getAllJsonFiles(path)) + } else if (item.name.endsWith('.json')) { + files.push(path) + } + } + return files +} + +// Find additions in new object compared to base +function findAdditions(base: any, updated: any): Record { + const additions: Record = {} + + for (const key in updated) { + if (!(key in base)) { + additions[key] = updated[key] + } else if ( + typeof updated[key] === 'object' && + !Array.isArray(updated[key]) && + typeof base[key] === 'object' && + !Array.isArray(base[key]) + ) { + const nestedAdditions = findAdditions(base[key], updated[key]) + if (Object.keys(nestedAdditions).length > 0) { + additions[key] = nestedAdditions + } + } + } + + return additions +} + +// Capture command +function capture(srcLocaleDir: string, tempBaseDir: string) { + ensureDir(tempBaseDir) + const files = getAllJsonFiles(srcLocaleDir) + + for (const file of files) { + const relativePath = file.replace(srcLocaleDir, '') + const targetPath = join(tempBaseDir, relativePath) + ensureDir(dirname(targetPath)) + writeFileSync(targetPath, readFileSync(file)) + } + console.log('Captured current locale files to temp/base/') +} + +// Diff command +function diff(srcLocaleDir: string, tempBaseDir: string, tempDiffDir: string) { + ensureDir(tempDiffDir) + const files = getAllJsonFiles(srcLocaleDir) + + for (const file of files) { + const relativePath = file.replace(srcLocaleDir, '') + const basePath = join(tempBaseDir, relativePath) + const diffPath = join(tempDiffDir, relativePath) + + const baseContent = readJsonFile(basePath) + const updatedContent = readJsonFile(file) + + const additions = findAdditions(baseContent, updatedContent) + if (Object.keys(additions).length > 0) { + ensureDir(dirname(diffPath)) + writeFileSync(diffPath, JSON.stringify(additions, null, 2)) + console.log(`Wrote diff to ${diffPath}`) + } + } +} + +// Command handling +const command = process.argv[2] +const SRC_LOCALE_DIR = 'src/locales' +const TEMP_BASE_DIR = 'temp/base' +const TEMP_DIFF_DIR = 'temp/diff' + +switch (command) { + case 'capture': + capture(SRC_LOCALE_DIR, TEMP_BASE_DIR) + break + case 'diff': + diff(SRC_LOCALE_DIR, TEMP_BASE_DIR, TEMP_DIFF_DIR) + break + case 'clean': + // Remove temp directory recursively + if (existsSync('temp')) { + rmSync('temp', { recursive: true, force: true }) + console.log('Removed temp directory') + } + break + default: + console.log('Please specify either "capture" or "diff" command') +}