384 lines
13 KiB
JavaScript
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: 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"); |