/** * ComfyUI Multi-Language Locale Toggle Extension (Fixed) * Proper ComfyUI extension registration with 20 language support */ import { app } from "../../scripts/app.js"; console.log('🌐 ComfyUI Locale Toggle Extension loading...'); // 20言語対応 const languages = { 'en': { name: 'English', flag: '🇺🇸', native: 'English', code: 'EN' }, 'ja': { name: 'Japanese', flag: '🇯🇵', native: '日本語', code: 'JA' }, 'zh': { name: 'Chinese', flag: '🇨🇳', native: '中文', code: 'ZH' }, 'ko': { name: 'Korean', flag: '🇰🇷', native: '한국어', code: 'KO' }, 'es': { name: 'Spanish', flag: '🇪🇸', native: 'Español', code: 'ES' }, 'fr': { name: 'French', flag: '🇫🇷', native: 'Français', code: 'FR' }, 'de': { name: 'German', flag: '🇩🇪', native: 'Deutsch', code: 'DE' }, 'ru': { name: 'Russian', flag: '🇷🇺', native: 'Русский', code: 'RU' }, 'it': { name: 'Italian', flag: '🇮🇹', native: 'Italiano', code: 'IT' }, 'pt': { name: 'Portuguese', flag: '🇵🇹', native: 'Português', code: 'PT' }, 'nl': { name: 'Dutch', flag: '🇳🇱', native: 'Nederlands', code: 'NL' }, 'sv': { name: 'Swedish', flag: '🇸🇪', native: 'Svenska', code: 'SV' }, 'no': { name: 'Norwegian', flag: '🇳🇴', native: 'Norsk', code: 'NO' }, 'da': { name: 'Danish', flag: '🇩🇰', native: 'Dansk', code: 'DA' }, 'fi': { name: 'Finnish', flag: '🇫🇮', native: 'Suomi', code: 'FI' }, 'pl': { name: 'Polish', flag: '🇵🇱', native: 'Polski', code: 'PL' }, 'cs': { name: 'Czech', flag: '🇨🇿', native: 'Čeština', code: 'CS' }, 'hu': { name: 'Hungarian', flag: '🇭🇺', native: 'Magyar', code: 'HU' }, 'tr': { name: 'Turkish', flag: '🇹🇷', native: 'Türkçe', code: 'TR' }, 'th': { name: 'Thai', flag: '🇹🇭', native: 'ไทย', code: 'TH' } }; let currentLanguage = 'en'; let lastLanguage = 'ja'; let floatingButton = null; let languageMenu = null; // 言語を変更する関数 async function changeLanguage(newLang) { if (newLang === currentLanguage) return; console.log(`🔄 Changing language from ${currentLanguage} to ${newLang}`); // 最後の言語を記録(英語以外の場合) if (currentLanguage !== 'en') { lastLanguage = currentLanguage; } currentLanguage = newLang; // ローカルストレージに保存 localStorage.setItem('comfyui_locale', newLang); localStorage.setItem('comfyui_last_locale', lastLanguage); // ComfyUIのロケール設定を更新 if (window.Comfy && window.Comfy.Locale) { window.Comfy.Locale.locale = newLang; } // ボタンの表示を更新 updateButtonDisplay(); // サーバーに言語設定を送信(可能であれば) try { await fetch('/locale_toggle/set', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ locale: newLang }) }); } catch (e) { console.log('Note: Server locale update not available'); } // ページをリロード setTimeout(() => { window.location.reload(); }, 300); } // ボタンの表示を更新 function updateButtonDisplay() { if (!floatingButton) return; const lang = languages[currentLanguage] || languages['en']; floatingButton.innerHTML = `🌐 ${lang.code}`; floatingButton.title = `Current: ${lang.native}\nLeft-click: Toggle EN ↔ ${languages[lastLanguage]?.native || '日本語'}\nRight-click: All languages`; } // 言語メニューを作成 function createLanguageMenu() { const menu = document.createElement('div'); menu.id = 'locale-menu'; menu.style.cssText = ` position: fixed; background: rgba(20, 20, 20, 0.95); border: 1px solid #555; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); z-index: 10000; padding: 8px; max-height: 400px; overflow-y: auto; min-width: 200px; font-family: 'Segoe UI', Arial, sans-serif; backdrop-filter: blur(10px); display: none; `; // 言語リストを作成 Object.entries(languages).forEach(([code, lang]) => { const item = document.createElement('div'); const isCurrent = code === currentLanguage; item.style.cssText = ` display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; color: ${isCurrent ? '#4da6ff' : 'white'}; background: ${isCurrent ? 'rgba(77, 166, 255, 0.1)' : 'transparent'}; border-radius: 4px; font-size: 14px; transition: background 0.2s ease; border-left: ${isCurrent ? '3px solid #4da6ff' : '3px solid transparent'}; `; item.innerHTML = ` ${lang.flag}
${lang.native}
${lang.name}
${isCurrent ? '✓' : ''} `; item.addEventListener('mouseenter', () => { if (!isCurrent) { item.style.background = 'rgba(255, 255, 255, 0.1)'; } }); item.addEventListener('mouseleave', () => { if (!isCurrent) { item.style.background = 'transparent'; } }); item.addEventListener('click', () => { changeLanguage(code); hideLanguageMenu(); }); menu.appendChild(item); }); document.body.appendChild(menu); return menu; } // メニューを表示 function showLanguageMenu(x, y) { if (!languageMenu) { languageMenu = createLanguageMenu(); } // 画面境界チェック const rect = languageMenu.getBoundingClientRect(); const maxX = window.innerWidth - rect.width - 10; const maxY = window.innerHeight - rect.height - 10; x = Math.min(x, maxX); y = Math.min(y, maxY); languageMenu.style.left = x + 'px'; languageMenu.style.top = y + 'px'; languageMenu.style.display = 'block'; // 外側クリックで閉じる setTimeout(() => { document.addEventListener('click', hideLanguageMenu, { once: true }); }, 0); } // メニューを隠す function hideLanguageMenu() { if (languageMenu) { languageMenu.style.display = 'none'; } } // フローティングボタンを作成 function createFloatingButton() { if (floatingButton) return; console.log('🎈 Creating floating locale toggle button...'); floatingButton = document.createElement('button'); floatingButton.id = 'locale-toggle-floating'; floatingButton.style.cssText = ` position: fixed; top: 20px; right: 80px; z-index: 9999; background: rgba(40, 40, 40, 0.9); color: white; border: 1px solid #555; border-radius: 6px; padding: 8px 14px; cursor: move; font-size: 14px; font-weight: bold; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); transition: all 0.2s ease; user-select: none; backdrop-filter: blur(5px); min-width: 64px; text-align: center; `; // 保存された位置を復元 const savedX = localStorage.getItem('locale_button_x'); const savedY = localStorage.getItem('locale_button_y'); if (savedX && savedY) { floatingButton.style.left = savedX + 'px'; floatingButton.style.top = savedY + 'px'; floatingButton.style.right = 'auto'; } // ドラッグ機能 let isDragging = false; let dragOffsetX = 0; let dragOffsetY = 0; floatingButton.addEventListener('mousedown', (e) => { if (e.button === 0) { // 左クリック isDragging = true; const rect = floatingButton.getBoundingClientRect(); dragOffsetX = e.clientX - rect.left; dragOffsetY = e.clientY - rect.top; floatingButton.style.cursor = 'grabbing'; e.preventDefault(); } }); document.addEventListener('mousemove', (e) => { if (isDragging) { const x = e.clientX - dragOffsetX; const y = e.clientY - dragOffsetY; floatingButton.style.left = Math.max(0, Math.min(x, window.innerWidth - floatingButton.offsetWidth)) + 'px'; floatingButton.style.top = Math.max(0, Math.min(y, window.innerHeight - floatingButton.offsetHeight)) + 'px'; floatingButton.style.right = 'auto'; } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; floatingButton.style.cursor = 'move'; // 位置を保存 localStorage.setItem('locale_button_x', floatingButton.style.left.replace('px', '')); localStorage.setItem('locale_button_y', floatingButton.style.top.replace('px', '')); } }); // クリックイベント floatingButton.addEventListener('click', (e) => { if (!isDragging) { e.preventDefault(); e.stopPropagation(); // 左クリック: 英語 ↔ 最後の言語 if (currentLanguage === 'en') { changeLanguage(lastLanguage); } else { changeLanguage('en'); } } }); // 右クリック: 全言語メニュー floatingButton.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); showLanguageMenu(e.clientX, e.clientY); }); // ホバー効果 floatingButton.addEventListener('mouseenter', () => { if (!isDragging) { floatingButton.style.background = 'rgba(60, 60, 60, 0.9)'; floatingButton.style.transform = 'scale(1.05)'; } }); floatingButton.addEventListener('mouseleave', () => { floatingButton.style.background = 'rgba(40, 40, 40, 0.9)'; floatingButton.style.transform = 'scale(1)'; }); updateButtonDisplay(); document.body.appendChild(floatingButton); console.log('✅ Floating locale toggle button created'); } // 初期化 function initializeLocaleToggle() { // 保存された言語設定を復元 const savedLocale = localStorage.getItem('comfyui_locale'); const savedLastLocale = localStorage.getItem('comfyui_last_locale'); if (savedLocale && languages[savedLocale]) { currentLanguage = savedLocale; } if (savedLastLocale && languages[savedLastLocale]) { lastLanguage = savedLastLocale; } console.log(`🌐 Current language: ${currentLanguage}, Last: ${lastLanguage}`); // ComfyUIロケール設定 if (window.Comfy && window.Comfy.Locale) { window.Comfy.Locale.locale = currentLanguage; } createFloatingButton(); } // ComfyUI拡張として登録 app.registerExtension({ name: "BuimenLabo.LocaleToggle", async setup() { console.log("🚀 BuimenLabo Locale Toggle extension setup"); // CSSスタイルを追加 const style = document.createElement("style"); style.textContent = ` #locale-toggle-floating:active { transform: scale(0.95) !important; } #locale-menu { scrollbar-width: thin; scrollbar-color: #555 transparent; } #locale-menu::-webkit-scrollbar { width: 6px; } #locale-menu::-webkit-scrollbar-track { background: transparent; } #locale-menu::-webkit-scrollbar-thumb { background: #555; border-radius: 3px; } #locale-menu::-webkit-scrollbar-thumb:hover { background: #777; } `; document.head.appendChild(style); // DOMが準備できたら初期化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeLocaleToggle); } else { // 少し遅延させてComfyUIの初期化を待つ setTimeout(initializeLocaleToggle, 500); } console.log("📦 Locale Toggle CSS loaded and initialization scheduled"); } }); console.log("🎉 BuimenLabo Locale Toggle Extension loaded successfully");