Files
2025-07-23 21:14:31 +09:00

384 lines
13 KiB
JavaScript

/**
* 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 = `
<span style="font-size: 16px;">${lang.flag}</span>
<div style="flex: 1;">
<div style="font-weight: ${isCurrent ? 'bold' : 'normal'};">${lang.native}</div>
<div style="font-size: 11px; opacity: 0.7;">${lang.name}</div>
</div>
${isCurrent ? '<span style="color: #4da6ff; font-weight: bold;">✓</span>' : ''}
`;
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: 6px 12px;
cursor: move;
font-size: 12px;
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: 60px;
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");