From cfdc1ab13da5128234542d2bcc81f306994e168b Mon Sep 17 00:00:00 2001 From: Staninna <26458805+Staninna@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:50:41 +0200 Subject: [PATCH] refactor: enhance language loading and translation fallback mechanism --- Auto-Image.js | 1021 ++++++------------------------------------------- 1 file changed, 109 insertions(+), 912 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 2c97f18..3d633c7 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -211,917 +211,86 @@ } } - const loadLanguagePreference = () => { + const loadLanguagePreference = async () => { const savedLanguage = localStorage.getItem("wplace_language") const browserLocale = navigator.language const browserLanguage = browserLocale.split("-")[0] - if (TEXT[savedLanguage]) { // trying to load saved language + // Check if we have the saved language available + if (savedLanguage && AVAILABLE_LANGUAGES.includes(savedLanguage)) { state.language = savedLanguage - } else if (TEXT[browserLocale]) { // trying to load full locale match (e.g. "zh-CN", "zh-TW" etc) + await loadTranslations(savedLanguage) + return + } + + // Try full locale match (e.g. "zh-CN", "zh-TW" etc) + if (AVAILABLE_LANGUAGES.includes(browserLocale)) { state.language = browserLocale localStorage.setItem("wplace_language", browserLocale) - } else if (TEXT[browserLanguage]) { // trying to load base language match (e.g. "en" for "en-US" or "en-GB" etc) - state.language = browserLanguage - localStorage.setItem("wplace_language", browserLanguage) + await loadTranslations(browserLocale) + return } + + // Try base language match (e.g. "en" for "en-US" or "en-GB" etc) + if (AVAILABLE_LANGUAGES.includes(browserLanguage)) { + state.language = browserLanguage + localStorage.setItem("wplace_language", browserLanguage) + await loadTranslations(browserLanguage) + return + } + + // Fallback to English + state.language = "en" + await loadTranslations("en") } // Simple translation cache const translationCache = new Map(); - // BILINGUAL TEXT STRINGS (FALLBACK):) - const TEXT = { + // Dynamically loaded translations + let loadedTranslations = {}; + + // Available languages + const AVAILABLE_LANGUAGES = ['en', 'ru', 'pt', 'vi', 'fr', 'id', 'tr', 'zh-CN', 'zh-TW', 'ja', 'ko', 'uk']; + + // Function to load translations from JSON file + const loadTranslations = async (language) => { + if (loadedTranslations[language]) { + return loadedTranslations[language]; + } + + // Load translations from CDN + const url = `https://staninna.github.io/WPlace-AutoBOT/decoupled-translations/lang/${language}.json`; + + try { + console.log(`🔄 Loading ${language} translations from CDN...`); + const response = await fetch(url); + if (response.ok) { + const translations = await response.json(); + loadedTranslations[language] = translations; + console.log(`📚 Loaded ${language} translations successfully from CDN`); + return translations; + } else { + console.warn(`❌ CDN returned HTTP ${response.status}: ${response.statusText} for ${language} translations`); + } + } catch (error) { + console.error(`❌ Failed to load ${language} translations from CDN:`, error); + } + + return null; + }; + + // Emergency fallback TEXT (minimal) + const FALLBACK_TEXT = { en: { title: "WPlace Auto-Image", - toggleOverlay: "Toggle Overlay", - scanColors: "Scan Colors", - uploadImage: "Upload Image", - resizeImage: "Resize Image", - selectPosition: "Select Position", - startPainting: "Start Painting", - stopPainting: "Stop Painting", - checkingColors: "🔍 Checking available colors...", - noColorsFound: "❌ Open the color palette on the site and try again!", - colorsFound: "✅ {count} available colors found. Ready to upload.", - loadingImage: "🖼️ Loading image...", - imageLoaded: "✅ Image loaded with {count} valid pixels", - imageError: "❌ Error loading image", - selectPositionAlert: "Paint the first pixel at the location where you want the art to start!", - waitingPosition: "👆 Waiting for you to paint the reference pixel...", - positionSet: "✅ Position set successfully!", - positionTimeout: "❌ Timeout for position selection", - startPaintingMsg: "🎨 Starting painting...", - paintingProgress: "🧱 Progress: {painted}/{total} pixels...", - noCharges: "⌛ No charges. Waiting {time}...", - paintingStopped: "⏹️ Painting stopped by user", - paintingComplete: "✅ Painting complete! {count} pixels painted.", - paintingError: "❌ Error during painting", - missingRequirements: "❌ Load an image and select a position first", - progress: "Progress", - pixels: "Pixels", - charges: "Charges", - estimatedTime: "Estimated time", - initMessage: "Click 'Upload Image' to begin", - waitingInit: "Waiting for initialization...", - initializingToken: "🔧 Initializing Turnstile token generator...", - tokenReady: "✅ Token generator ready - you can now start painting!", - tokenRetryLater: "⚠️ Token generator will retry when needed", - resizeSuccess: "✅ Image resized to {width}x{height}", - paintingPaused: "⏸️ Painting paused at position X: {x}, Y: {y}", - captchaNeeded: "❗ Token generation failed. Please try again in a moment.", - saveData: "Save Progress", - loadData: "Load Progress", - saveToFile: "Save to File", - loadFromFile: "Load from File", - dataManager: "Data Manager", - autoSaved: "✅ Progress saved automatically", - dataLoaded: "✅ Progress loaded successfully", - fileSaved: "✅ Progress saved to file successfully", - fileLoaded: "✅ Progress loaded from file successfully", - noSavedData: "❌ No saved progress found", - savedDataFound: "✅ Saved progress found! Load to continue?", - savedDate: "Saved on: {date}", - clickLoadToContinue: "Click 'Load Progress' to continue.", - fileError: "❌ Error processing file", - invalidFileFormat: "❌ Invalid file format", - paintingSpeed: "Painting Speed", - pixelsPerSecond: "pixels/second", - speedSetting: "Speed: {speed} pixels/sec", - settings: "Settings", - botSettings: "Bot Settings", + settings: "Settings", close: "Close", language: "Language", - themeSettings: "Theme Settings", - themeSettingsDesc: "Choose your preferred color theme for the interface.", - languageSelectDesc: "Select your preferred language. Changes will take effect immediately.", - autoCaptcha: "Auto-CAPTCHA Solver (Turnstile)", - autoCaptchaDesc: "Automatically generates Turnstile tokens using integrated generator. Falls back to browser automation if needed.", - applySettings: "Apply Settings", - settingsSaved: "✅ Settings saved successfully!", - cooldownSettings: "Cooldown Settings", - waitCharges: "Wait until charges reach", - captchaSolving: "🔑 Generating Turnstile token...", - captchaFailed: "❌ Turnstile token generation failed. Trying fallback method...", - automation: "Automation", - noChargesThreshold: "⌛ Waiting for charges to reach {threshold}. Currently {current}. Next in {time}...", - }, - ru: { - title: "WPlace Авто-Изображение", - scanColors: "Сканировать цвета", - uploadImage: "Загрузить изображение", - resizeImage: "Изменить размер изображения", - selectPosition: "Выбрать позицию", - startPainting: "Начать рисование", - stopPainting: "Остановить рисование", - checkingColors: "🔍 Проверка доступных цветов...", - noColorsFound: "❌ Откройте палитру цветов на сайте и попробуйте снова!", - colorsFound: "✅ Найдено доступных цветов: {count}. Готово к загрузке.", - loadingImage: "🖼️ Загрузка изображения...", - imageLoaded: "✅ Изображение загружено, валидных пикселей: {count}", - imageError: "❌ Ошибка при загрузке изображения", - selectPositionAlert: "Нарисуйте первый пиксель в месте, откуда начнётся рисунок!", - waitingPosition: "👆 Ожидание, пока вы нарисуете опорный пиксель...", - positionSet: "✅ Позиция успешно установлена!", - positionTimeout: "❌ Время ожидания выбора позиции истекло", - startPaintingMsg: "🎨 Начинаем рисование...", - paintingProgress: "🧱 Прогресс: {painted}/{total} пикселей...", - noCharges: "⌛ Нет зарядов. Ожидание {time}...", - paintingStopped: "⏹️ Рисование остановлено пользователем", - paintingComplete: "✅ Рисование завершено! Нарисовано пикселей: {count}.", - paintingError: "❌ Ошибка во время рисования", - missingRequirements: "❌ Сначала загрузите изображение и выберите позицию", - progress: "Прогресс", - pixels: "Пиксели", - charges: "Заряды", - estimatedTime: "Примерное время", - initMessage: "Нажмите 'Загрузить изображение', чтобы начать", - waitingInit: "Ожидание инициализации...", - initializingToken: "🔧 Инициализация генератора Turnstile токенов...", - tokenReady: "✅ Генератор токенов готов - можете начинать рисование!", - tokenRetryLater: "⚠️ Генератор токенов повторит попытку при необходимости", - resizeSuccess: "✅ Изображение изменено до {width}x{height}", - paintingPaused: "⏸️ Рисование приостановлено на позиции X: {x}, Y: {y}", - captchaNeeded: "❗ Генерация токена не удалась. Пожалуйста, попробуйте через некоторое время.", - saveData: "Сохранить прогресс", - loadData: "Загрузить прогресс", - saveToFile: "Сохранить в файл", - loadFromFile: "Загрузить из файла", - dataManager: "Менеджер данных", - autoSaved: "✅ Прогресс сохранён автоматически", - dataLoaded: "✅ Прогресс успешно загружен", - fileSaved: "✅ Прогресс успешно сохранён в файл", - fileLoaded: "✅ Прогресс успешно загружен из файла", - noSavedData: "❌ Сохранённый прогресс не найден", - savedDataFound: "✅ Найден сохранённый прогресс! Загрузить, чтобы продолжить?", - savedDate: "Сохранено: {date}", - clickLoadToContinue: "Нажмите 'Загрузить прогресс', чтобы продолжить.", - fileError: "❌ Ошибка при обработке файла", - invalidFileFormat: "❌ Неверный формат файла", - paintingSpeed: "Скорость рисования", - pixelsPerSecond: "пикселей/сек", - speedSetting: "Скорость: {speed} пикс./сек", - settings: "Настройки", - botSettings: "Настройки бота", - close: "Закрыть", - language: "Язык", - themeSettings: "Настройки темы", - themeSettingsDesc: "Выберите предпочтительную цветовую тему интерфейса.", - languageSelectDesc: "Выберите предпочтительный язык. Изменения вступят в силу немедленно.", - autoCaptcha: "Авто-решение CAPTCHA (Turnstile)", - autoCaptchaDesc: "Автоматически генерирует Turnstile токены используя встроенный генератор. Возвращается к автоматизации браузера при необходимости.", - applySettings: "Применить настройки", - settingsSaved: "✅ Настройки успешно сохранены!", - cooldownSettings: "Настройки перезарядки", - waitCharges: "Ждать до накопления зарядов", - captchaSolving: "🔑 Генерирую Turnstile токен...", - captchaFailed: "❌ Не удалось сгенерировать Turnstile токен. Пробую резервный метод...", - automation: "Автоматизация", - noChargesThreshold: "⌛ Ожидание зарядов до {threshold}. Сейчас {current}. Следующий через {time}...", - }, - pt: { - title: "WPlace Auto-Image", - scanColors: "Escanear Cores", - uploadImage: "Upload da Imagem", - resizeImage: "Redimensionar Imagem", - selectPosition: "Selecionar Posição", - startPainting: "Iniciar Pintura", - stopPainting: "Parar Pintura", - checkingColors: "🔍 Verificando cores disponíveis...", - noColorsFound: "❌ Abra a paleta de cores no site e tente novamente!", - colorsFound: "✅ {count} cores encontradas. Pronto para upload.", - loadingImage: "🖼️ Carregando imagem...", - imageLoaded: "✅ Imagem carregada com {count} pixels válidos", - imageError: "❌ Erro ao carregar imagem", - selectPositionAlert: "Pinte o primeiro pixel на localização onde deseja que a arte comece!", - waitingPosition: "👆 Aguardando você pintar o pixel de referência...", - positionSet: "✅ Posição definida com sucesso!", - positionTimeout: "❌ Tempo esgotado para selecionar posição", - startPaintingMsg: "🎨 Iniciando pintura...", - paintingProgress: "🧱 Progresso: {painted}/{total} pixels...", - noCharges: "⌛ Sem cargas. Aguardando {time}...", - paintingStopped: "⏹️ Pintura interromпида pelo usuário", - paintingComplete: "✅ Pintura concluída! {count} pixels pintados.", - paintingError: "❌ Erro durante a pintura", - missingRequirements: "❌ Carregue uma imagem e selecione uma posição primeiro", - progress: "Progresso", - pixels: "Pixels", - charges: "Cargas", - estimatedTime: "Tempo estimado", - initMessage: "Clique em 'Upload da Imagem' para começar", - waitingInit: "Aguardando inicialização...", - initializingToken: "🔧 Inicializando gerador de tokens Turnstile...", - tokenReady: "✅ Gerador de tokens pronto - você pode começar a pintar!", - tokenRetryLater: "⚠️ Gerador de tokens tentará novamente quando necessário", - resizeSuccess: "✅ Imagem redimensionada для {width}x{height}", - paintingPaused: "⏸️ Pintura pausada na posição X: {x}, Y: {y}", - captchaNeeded: "❗ Falha na geração de token. Tente novamente em alguns instantes.", - saveData: "Salvar Progresso", - loadData: "Carregar Progresso", - saveToFile: "Salvar em Arquivo", - loadFromFile: "Carregar de Arquivo", - dataManager: "Dados", - autoSaved: "✅ Progresso salvo automaticamente", - dataLoaded: "✅ Progresso carregado com sucesso", - fileSaved: "✅ Salvo em arquivo com sucesso", - fileLoaded: "✅ Carregado de arquivo com sucesso", - noSavedData: "❌ Nenhum progresso salvo encontrado", - savedDataFound: "✅ Progresso salvo encontrado! Carregar para continuar?", - savedDate: "Salvo em: {date}", - clickLoadToContinue: "Clique em 'Carregar Progresso' para continuar.", - fileError: "❌ Erro ao processar arquivo", - invalidFileFormat: "❌ Formato de arquivo inválido", - paintingSpeed: "Velocidade de Pintura", - pixelsPerSecond: "pixels/segundo", - speedSetting: "Velocidade: {speed} pixels/seg", - settings: "Configurações", - botSettings: "Configurações do Bot", - close: "Fechar", - language: "Idioma", - themeSettings: "Configurações de Tema", - themeSettingsDesc: "Escolha seu tema de cores preferido para a interface.", - languageSelectDesc: "Selecione seu idioma preferido. As alterações terão efeito imediatamente.", - autoCaptcha: "Resolvedor de CAPTCHA Automático", - autoCaptchaDesc: "Tenta resolver o CAPTCHA automaticamente simulando a colocação manual de um pixel quando o token expira.", - applySettings: "Aplicar Configurações", - settingsSaved: "✅ Configurações salvas com sucesso!", - cooldownSettings: "Configurações de Cooldown", - waitCharges: "Aguardar até as cargas atingirem", - captchaSolving: "🤖 Tentando resolver o CAPTCHA...", - captchaFailed: "❌ Falha ao resolver CAPTCHA. Pinte um pixel manualmente.", - automation: "Automação", - noChargesThreshold: "⌛ Aguardando cargas atingirem {threshold}. Atual: {current}. Próxima em {time}...", - }, - vi: { - title: "WPlace Auto-Image", - scanColors: "Quét màu", - uploadImage: "Tải lên hình ảnh", - resizeImage: "Thay đổi kích thước", - selectPosition: "Chọn vị trí", - startPainting: "Bắt đầu vẽ", - stopPainting: "Dừng vẽ", - checkingColors: "🔍 Đang kiểm tra màu sắc có sẵn...", - noColorsFound: "❌ Hãy mở bảng màu trên trang web và thử lại!", - colorsFound: "✅ Tìm thấy {count} màu. Sẵn sàng để tải lên.", - loadingImage: "🖼️ Đang tải hình ảnh...", - imageLoaded: "✅ Đã tải hình ảnh với {count} pixel hợp lệ", - imageError: "❌ Lỗi khi tải hình ảnh", - selectPositionAlert: "Vẽ pixel đầu tiên tại vị trí bạn muốn tác phẩm nghệ thuật bắt đầu!", - waitingPosition: "👆 Đang chờ bạn vẽ pixel tham chiếu...", - positionSet: "✅ Đã đặt vị trí thành công!", - positionTimeout: "❌ Hết thời gian chọn vị trí", - startPaintingMsg: "🎨 Bắt đầu vẽ...", - paintingProgress: "🧱 Tiến trình: {painted}/{total} pixel...", - noCharges: "⌛ Không có điện tích. Đang chờ {time}...", - paintingStopped: "⏹️ Người dùng đã dừng vẽ", - paintingComplete: "✅ Hoàn thành vẽ! Đã vẽ {count} pixel.", - paintingError: "❌ Lỗi trong quá trình vẽ", - missingRequirements: "❌ Hãy tải lên hình ảnh và chọn vị trí trước", - progress: "Tiến trình", - pixels: "Pixel", - charges: "Điện tích", - estimatedTime: "Thời gian ước tính", - initMessage: "Nhấp 'Tải lên hình ảnh' để bắt đầu", - waitingInit: "Đang chờ khởi tạo...", - initializingToken: "🔧 Đang khởi tạo bộ tạo token Turnstile...", - tokenReady: "✅ Bộ tạo token đã sẵn sàng - bạn có thể bắt đầu vẽ!", - tokenRetryLater: "⚠️ Bộ tạo token sẽ thử lại khi cần thiết", - resizeSuccess: "✅ Đã thay đổi kích thước hình ảnh thành {width}x{height}", - paintingPaused: "⏸️ Tạm dừng vẽ tại vị trí X: {x}, Y: {y}", - captchaNeeded: "❗ Tạo token thất bại. Vui lòng thử lại sau.", - saveData: "Lưu tiến trình", - loadData: "Tải tiến trình", - saveToFile: "Lưu vào tệp", - loadFromFile: "Tải từ tệp", - dataManager: "Dữ liệu", - autoSaved: "✅ Đã tự động lưu tiến trình", - dataLoaded: "✅ Đã tải tiến trình thành công", - fileSaved: "✅ Đã lưu vào tệp thành công", - fileLoaded: "✅ Đã tải từ tệp thành công", - noSavedData: "❌ Không tìm thấy tiến trình đã lưu", - savedDataFound: "✅ Tìm thấy tiến trình đã lưu! Tải để tiếp tục?", - savedDate: "Đã lưu vào: {date}", - clickLoadToContinue: "Nhấp 'Tải tiến trình' để tiếp tục.", - fileError: "❌ Lỗi khi xử lý tệp", - invalidFileFormat: "❌ Định dạng tệp không hợp lệ", - paintingSpeed: "Tốc độ vẽ", - pixelsPerSecond: "pixel/giây", - speedSetting: "Tốc độ: {speed} pixel/giây", - settings: "Cài đặt", - botSettings: "Cài đặt Bot", - close: "Đóng", - language: "Ngôn ngữ", - themeSettings: "Cài đặt Giao diện", - themeSettingsDesc: "Chọn chủ đề màu sắc yêu thích cho giao diện.", - languageSelectDesc: "Chọn ngôn ngữ ưa thích. Thay đổi sẽ có hiệu lực ngay lập tức.", - autoCaptcha: "Tự động giải CAPTCHA", - autoCaptchaDesc: "Tự động cố gắng giải CAPTCHA bằng cách mô phỏng việc đặt pixel thủ công khi token hết hạn.", - applySettings: "Áp dụng cài đặt", - settingsSaved: "✅ Đã lưu cài đặt thành công!", - cooldownSettings: "Cài đặt thời gian chờ", - waitCharges: "Chờ cho đến khi số lần sạc đạt", - captchaSolving: "🤖 Đang cố gắng giải CAPTCHA...", - captchaFailed: "❌ Giải CAPTCHA tự động thất bại. Vui lòng vẽ một pixel thủ công.", - automation: "Tự động hóa", - noChargesThreshold: "⌛ Đang chờ số lần sạc đạt {threshold}. Hiện tại {current}. Lần tiếp theo trong {time}...", - }, - fr: { - title: "WPlace Auto-Image", - scanColors: "Scanner les couleurs", - uploadImage: "Télécharger l'image", - resizeImage: "Redimensionner l'image", - selectPosition: "Sélectionner la position", - startPainting: "Commencer à peindre", - stopPainting: "Arrêter de peindre", - checkingColors: "🔍 Vérification des couleurs disponibles...", - noColorsFound: "❌ Ouvrez la palette de couleurs sur le site et réessayez!", - colorsFound: "✅ {count} couleurs trouvées. Prêt à télécharger.", - loadingImage: "🖼️ Chargement de l'image...", - imageLoaded: "✅ Image chargée avec {count} pixels valides", - imageError: "❌ Erreur lors du chargement de l'image", - selectPositionAlert: "Peignez le premier pixel à l'endroit où vous voulez que l'art commence!", - waitingPosition: "👆 En attente que vous peigniez le pixel de référence...", - positionSet: "✅ Position définie avec succès!", - positionTimeout: "❌ Délai d'attente pour la sélection de position", - startPaintingMsg: "🎨 Début de la peinture...", - paintingProgress: "🧱 Progrès: {painted}/{total} pixels...", - noCharges: "⌛ Aucune charge. En attente {time}...", - paintingStopped: "⏹️ Peinture arrêtée par l'utilisateur", - paintingComplete: "✅ Peinture terminée! {count} pixels peints.", - paintingError: "❌ Erreur pendant la peinture", - missingRequirements: "❌ Veuillez charger une image et sélectionner une position d'abord", - progress: "Progrès", - pixels: "Pixels", - charges: "Charges", - estimatedTime: "Temps estimé", - initMessage: "Cliquez sur 'Télécharger l'image' pour commencer", - waitingInit: "En attente d'initialisation...", - initializingToken: "🔧 Initialisation du générateur de tokens Turnstile...", - tokenReady: "✅ Générateur de tokens prêt - vous pouvez commencer à peindre!", - tokenRetryLater: "⚠️ Le générateur de tokens réessaiera si nécessaire", - resizeSuccess: "✅ Image redimensionnée en {width}x{height}", - paintingPaused: "⏸️ Peinture en pause à la position X: {x}, Y: {y}", - captchaNeeded: "❗ Échec de la génération de token. Veuillez réessayer dans un moment.", - saveData: "Sauvegarder le progrès", - loadData: "Charger le progrès", - saveToFile: "Sauvegarder dans un fichier", - loadFromFile: "Charger depuis un fichier", - dataManager: "Données", - autoSaved: "✅ Progrès sauvegardé automatiquement", - dataLoaded: "✅ Progrès chargé avec succès", - fileSaved: "✅ Sauvegardé dans un fichier avec succès", - fileLoaded: "✅ Chargé depuis un fichier avec succès", - noSavedData: "❌ Aucun progrès sauvegardé trouvé", - savedDataFound: "✅ Progrès sauvegardé trouvé! Charger pour continuer?", - savedDate: "Sauvegardé le: {date}", - clickLoadToContinue: "Cliquez sur 'Charger le progrès' pour continuer.", - fileError: "❌ Erreur lors du traitement du fichier", - invalidFileFormat: "❌ Format de fichier invalide", - paintingSpeed: "Vitesse de peinture", - pixelsPerSecond: "pixels/seconde", - speedSetting: "Vitesse: {speed} pixels/sec", - settings: "Paramètres", - botSettings: "Paramètres du Bot", - close: "Fermer", - language: "Langue", - themeSettings: "Paramètres de Thème", - themeSettingsDesc: "Choisissez votre thème de couleurs préféré pour l'interface.", - languageSelectDesc: "Sélectionnez votre langue préférée. Les changements prendront effet immédiatement.", - autoCaptcha: "Résolveur de CAPTCHA automatique", - autoCaptchaDesc: "Tente automatiquement de résoudre le CAPTCHA en simulant un placement manuel de pixel lorsque le jeton expire.", - applySettings: "Appliquer les paramètres", - settingsSaved: "✅ Paramètres enregistrés avec succès !", - cooldownSettings: "Paramètres de recharge", - waitCharges: "Attendre que les charges atteignent", - captchaSolving: "🤖 Tentative de résolution du CAPTCHA...", - captchaFailed: "❌ Échec de l'Auto-CAPTCHA. Peignez un pixel manuellement.", - automation: "Automatisation", - noChargesThreshold: "⌛ En attente que les charges atteignent {threshold}. Actuel: {current}. Prochaine dans {time}...", - }, - id: { - title: "WPlace Auto-Image", - scanColors: "Pindai Warna", - uploadImage: "Unggah Gambar", - resizeImage: "Ubah Ukuran Gambar", - selectPosition: "Pilih Posisi", - startPainting: "Mulai Melukis", - stopPainting: "Berhenti Melukis", - checkingColors: "🔍 Memeriksa warna yang tersedia...", - noColorsFound: "❌ Buka palet warna di situs dan coba lagi!", - colorsFound: "✅ {count} warna ditemukan. Siap untuk diunggah.", - loadingImage: "🖼️ Memuat gambar...", - imageLoaded: "✅ Gambar dimuat dengan {count} piksel valid", - imageError: "❌ Kesalahan saat memuat gambar", - selectPositionAlert: "Lukis piksel pertama di lokasi tempat karya seni akan dimulai!", - waitingPosition: "👆 Menunggu Anda melukis piksel referensi...", - positionSet: "✅ Posisi berhasil diatur!", - positionTimeout: "❌ Waktu habis untuk memilih posisi", - startPaintingMsg: "🎨 Mulai melukis...", - paintingProgress: "🧱 Progres: {painted}/{total} piksel...", - noCharges: "⌛ Tidak ada muatan. Menunggu {time}...", - paintingStopped: "⏹️ Melukis dihentikan oleh pengguna", - paintingComplete: "✅ Melukis selesai! {count} piksel telah dilukis.", - paintingError: "❌ Kesalahan selama melukis", - missingRequirements: "❌ Unggah gambar dan pilih posisi terlebih dahulu", - progress: "Progres", - pixels: "Piksel", - charges: "Muatan", - estimatedTime: "Perkiraan waktu", - initMessage: "Klik 'Unggah Gambar' untuk memulai", - waitingInit: "Menunggu inisialisasi...", - initializingToken: "🔧 Menginisialisasi generator token Turnstile...", - tokenReady: "✅ Generator token siap - Anda bisa mulai melukis!", - tokenRetryLater: "⚠️ Generator token akan mencoba lagi saat diperlukan", - resizeSuccess: "✅ Gambar berhasil diubah ukurannya menjadi {width}x{height}", - paintingPaused: "⏸️ Melukis dijeda di posisi X: {x}, Y: {y}", - captchaNeeded: "❗ Pembuatan token gagal. Silakan coba lagi sebentar lagi.", - saveData: "Simpan Progres", - loadData: "Muat Progres", - saveToFile: "Simpan ke File", - loadFromFile: "Muat dari File", - dataManager: "Data", - autoSaved: "✅ Progres disimpan secara otomatis", - dataLoaded: "✅ Progres berhasil dimuat", - fileSaved: "✅ Berhasil disimpan ke file", - fileLoaded: "✅ Berhasil dimuat dari file", - noSavedData: "❌ Tidak ditemukan progres yang disimpan", - savedDataFound: "✅ Progres yang disimpan ditemukan! Muat untuk melanjutkan?", - savedDate: "Disimpan pada: {date}", - clickLoadToContinue: "Klik 'Muat Progres' untuk melanjutkan.", - fileError: "❌ Kesalahan saat memproses file", - invalidFileFormat: "❌ Format file tidak valid", - paintingSpeed: "Kecepatan Melukis", - pixelsPerSecond: "piksel/detik", - speedSetting: "Kecepatan: {speed} piksel/detik", - settings: "Pengaturan", - botSettings: "Pengaturan Bot", - close: "Tutup", - language: "Bahasa", - themeSettings: "Pengaturan Tema", - themeSettingsDesc: "Pilih tema warna favorit Anda untuk antarmuka.", - languageSelectDesc: "Pilih bahasa yang Anda inginkan. Perubahan akan berlaku segera.", - autoCaptcha: "Penyelesai CAPTCHA Otomatis", - autoCaptchaDesc: "Mencoba menyelesaikan CAPTCHA secara otomatis dengan mensimulasikan penempatan piksel manual saat token kedaluwarsa.", - applySettings: "Terapkan Pengaturan", - settingsSaved: "✅ Pengaturan berhasil disimpan!", - cooldownSettings: "Pengaturan Cooldown", - waitCharges: "Tunggu hingga muatan mencapai", - captchaSolving: "🤖 Mencoba menyelesaikan CAPTCHA...", - captchaFailed: "❌ Gagal menyelesaikan CAPTCHA. Lukis satu piksel secara manual.", - automation: "Automasi", - noChargesThreshold: "⌛ Menunggu muatan mencapai {threshold}. Saat ini: {current}. Berikutnya dalam {time}...", - }, - tr: { - title: "WPlace Otomatik-Resim", - toggleOverlay: "Katmanı Aç/Kapat", - scanColors: "Renkleri Tara", - uploadImage: "Resim Yükle", - resizeImage: "Resmi Yeniden Boyutlandır", - selectPosition: "Konum Seç", - startPainting: "Boyamayı Başlat", - stopPainting: "Boyamayı Durdur", - checkingColors: "🔍 Uygun renkler kontrol ediliyor...", - noColorsFound: "❌ Sitede renk paletini açın ve tekrar deneyin!", - colorsFound: "✅ {count} uygun renk bulundu. Yüklemeye hazır.", - loadingImage: "🖼️ Resim yükleniyor...", - imageLoaded: "✅ Resim {count} geçerli piksel ile yüklendi", - imageError: "❌ Resim yüklenirken hata oluştu", - selectPositionAlert: "Sanatı başlatmak istediğiniz ilk pikseli boyayın!", - waitingPosition: "👆 Referans pikseli boyamanız bekleniyor...", - positionSet: "✅ Konum başarıyla ayarlandı!", - positionTimeout: "❌ Konum seçme süresi doldu", - startPaintingMsg: "🎨 Boyama başlatılıyor...", - paintingProgress: "🧱 İlerleme: {painted}/{total} piksel...", - noCharges: "⌛ Yeterli hak yok. Bekleniyor {time}...", - paintingStopped: "⏹️ Boyama kullanıcı tarafından durduruldu", - paintingComplete: "✅ Boyama tamamlandı! {count} piksel boyandı.", - paintingError: "❌ Boyama sırasında hata oluştu", - missingRequirements: "❌ Önce resim yükleyip konum seçmelisiniz", - progress: "İlerleme", - pixels: "Pikseller", - charges: "Haklar", - estimatedTime: "Tahmini süre", - initMessage: "Başlamak için 'Resim Yükle'ye tıklayın", - waitingInit: "Başlatma bekleniyor...", - resizeSuccess: "✅ Resim {width}x{height} boyutuna yeniden boyutlandırıldı", - paintingPaused: "⏸️ Boyama duraklatıldı, Konum X: {x}, Y: {y}", - captchaNeeded: "❗ CAPTCHA gerekli. Devam etmek için bir pikseli manuel olarak boyayın.", - saveData: "İlerlemeyi Kaydet", - loadData: "İlerlemeyi Yükle", - saveToFile: "Dosyaya Kaydet", - loadFromFile: "Dosyadan Yükle", - dataManager: "Veri Yöneticisi", - autoSaved: "✅ İlerleme otomatik olarak kaydedildi", - dataLoaded: "✅ İlerleme başarıyla yüklendi", - fileSaved: "✅ İlerleme dosyaya başarıyla kaydedildi", - fileLoaded: "✅ İlerleme dosyadan başarıyla yüklendi", - noSavedData: "❌ Kayıtlı ilerleme bulunamadı", - savedDataFound: "✅ Kayıtlı ilerleme bulundu! Devam etmek için yükleyin.", - savedDate: "Kaydedilme tarihi: {date}", - clickLoadToContinue: "Devam etmek için 'İlerlemeyi Yükle'ye tıklayın.", - fileError: "❌ Dosya işlenirken hata oluştu", - invalidFileFormat: "❌ Geçersiz dosya formatı", - paintingSpeed: "Boyama Hızı", - pixelsPerSecond: "piksel/saniye", - speedSetting: "Hız: {speed} piksel/sn", - settings: "Ayarlar", - botSettings: "Bot Ayarları", - close: "Kapat", - language: "Dil", - themeSettings: "Tema Ayarları", - themeSettingsDesc: "Arayüz için tercih ettiğiniz renk temasını seçin.", - languageSelectDesc: "Tercih ettiğiniz dili seçin. Değişiklikler hemen uygulanacaktır.", - autoCaptcha: "Oto-CAPTCHA Çözücü", - autoCaptchaDesc: "CAPTCHA süresi dolduğunda manuel piksel yerleştirmeyi taklit ederek otomatik çözmeyi dener.", - applySettings: "Ayarları Uygula", - settingsSaved: "✅ Ayarlar başarıyla kaydedildi!", - cooldownSettings: "Bekleme Süresi Ayarları", - waitCharges: "Haklar şu seviyeye ulaşana kadar bekle", - captchaSolving: "🤖 CAPTCHA çözülmeye çalışılıyor...", - captchaFailed: "❌ Oto-CAPTCHA başarısız oldu. Bir pikseli manuel boyayın.", - automation: "Otomasyon", - noChargesThreshold: "⌛ Hakların {threshold} seviyesine ulaşması bekleniyor. Şu anda {current}. Sonraki {time} içinde...", - }, - "zh-CN": { - title: "WPlace 自动图像", - toggleOverlay: "切换覆盖层", - scanColors: "扫描颜色", - uploadImage: "上传图像", - resizeImage: "调整大小", - selectPosition: "选择位置", - startPainting: "开始绘制", - stopPainting: "停止绘制", - checkingColors: "🔍 正在检查可用颜色...", - noColorsFound: "❌ 请在网站上打开调色板后再试!", - colorsFound: "✅ 找到 {count} 个可用颜色,准备上传。", - loadingImage: "🖼️ 正在加载图像...", - imageLoaded: "✅ 图像已加载,包含 {count} 个有效像素", - imageError: "❌ 加载图像时出错", - selectPositionAlert: "请在你想让作品开始的位置绘制第一个像素!", - waitingPosition: "👆 正在等待你绘制参考像素...", - positionSet: "✅ 位置设置成功!", - positionTimeout: "❌ 选择位置超时", - startPaintingMsg: "🎨 开始绘制...", - paintingProgress: "🧱 进度: {painted}/{total} 像素...", - noCharges: "⌛ 无可用次数,等待 {time}...", - paintingStopped: "⏹️ 已被用户停止", - paintingComplete: "✅ 绘制完成!共绘制 {count} 个像素。", - paintingError: "❌ 绘制过程中出错", - missingRequirements: "❌ 请先加载图像并选择位置", - progress: "进度", - pixels: "像素", - charges: "次数", - estimatedTime: "预计时间", - initMessage: "点击“上传图像”开始", - waitingInit: "正在等待初始化...", - initializingToken: "🔧 正在初始化 Turnstile 令牌生成器...", - tokenReady: "✅ 令牌生成器已就绪 - 可以开始绘制!", - tokenRetryLater: "⚠️ 令牌生成器稍后将重试", - resizeSuccess: "✅ 图像已调整为 {width}x{height}", - paintingPaused: "⏸️ 在位置 X: {x}, Y: {y} 暂停", - captchaNeeded: "❗ 令牌生成失败,请稍后再试。", - saveData: "保存进度", - loadData: "加载进度", - saveToFile: "保存到文件", - loadFromFile: "从文件加载", - dataManager: "数据管理", - autoSaved: "✅ 进度已自动保存", - dataLoaded: "✅ 进度加载成功", - fileSaved: "✅ 已成功保存到文件", - fileLoaded: "✅ 已成功从文件加载", - noSavedData: "❌ 未找到已保存进度", - savedDataFound: "✅ 找到已保存进度!是否加载继续?", - savedDate: "保存时间: {date}", - clickLoadToContinue: "点击“加载进度”继续。", - fileError: "❌ 处理文件时出错", - invalidFileFormat: "❌ 文件格式无效", - paintingSpeed: "绘制速度", - pixelsPerSecond: "像素/秒", - speedSetting: "速度: {speed} 像素/秒", - settings: "设置", - botSettings: "机器人设置", - close: "关闭", - language: "语言", - themeSettings: "主题设置", - themeSettingsDesc: "为界面选择你喜欢的配色主题。", - languageSelectDesc: "选择你偏好的语言,变更立即生效。", - autoCaptcha: "自动 CAPTCHA 解决", - autoCaptchaDesc: "使用集成的生成器自动生成 Turnstile 令牌,必要时回退到浏览器自动化。", - applySettings: "应用设置", - settingsSaved: "✅ 设置保存成功!", - speedOn: "开启", - speedOff: "关闭", - cooldownSettings: "冷却设置", - waitCharges: "等待次数达到", - captchaSolving: "🔑 正在生成 Turnstile 令牌...", - captchaFailed: "❌ 令牌生成失败。尝试回退方法...", - automation: "自动化", - noChargesThreshold: "⌛ 等待次数达到 {threshold}。当前 {current}。下次在 {time}...", - }, - "zh-TW": { - title: "WPlace 自動圖像", - toggleOverlay: "切換覆蓋層", - scanColors: "掃描顏色", - uploadImage: "上傳圖像", - resizeImage: "調整大小", - selectPosition: "選擇位置", - startPainting: "開始繪製", - stopPainting: "停止繪製", - checkingColors: "🔍 正在檢查可用顏色...", - noColorsFound: "❌ 請在網站上打開調色板後再試!", - colorsFound: "✅ 找到 {count} 個可用顏色,準備上傳。", - loadingImage: "🖼️ 正在載入圖像...", - imageLoaded: "✅ 圖像已載入,包含 {count} 個有效像素", - imageError: "❌ 載入圖像時出錯", - selectPositionAlert: "請在你想讓作品開始的位置繪製第一個像素!", - waitingPosition: "👆 正在等待你繪製參考像素...", - positionSet: "✅ 位置設定成功!", - positionTimeout: "❌ 選擇位置逾時", - startPaintingMsg: "🎨 開始繪製...", - paintingProgress: "🧱 進度: {painted}/{total} 像素...", - noCharges: "⌛ 無可用次數,等待 {time}...", - paintingStopped: "⏹️ 已被使用者停止", - paintingComplete: "✅ 繪製完成!共繪製 {count} 個像素。", - paintingError: "❌ 繪製過程中出錯", - missingRequirements: "❌ 請先載入圖像並選擇位置", - progress: "進度", - pixels: "像素", - charges: "次數", - estimatedTime: "預計時間", - initMessage: "點擊「上傳圖像」開始", - waitingInit: "正在等待初始化...", - initializingToken: "🔧 正在初始化 Turnstile 令牌產生器...", - tokenReady: "✅ 令牌產生器已就緒 - 可以開始繪製!", - tokenRetryLater: "⚠️ 令牌產生器稍後將重試", - resizeSuccess: "✅ 圖像已調整為 {width}x{height}", - paintingPaused: "⏸️ 在位置 X: {x}, Y: {y} 暫停", - captchaNeeded: "❗ 令牌產生失敗,請稍後再試。", - saveData: "儲存進度", - loadData: "載入進度", - saveToFile: "儲存至檔案", - loadFromFile: "從檔案載入", - dataManager: "資料管理", - autoSaved: "✅ 進度已自動儲存", - dataLoaded: "✅ 進度載入成功", - fileSaved: "✅ 已成功儲存至檔案", - fileLoaded: "✅ 已成功從檔案載入", - noSavedData: "❌ 未找到已儲存進度", - savedDataFound: "✅ 找到已儲存進度!是否載入以繼續?", - savedDate: "儲存時間: {date}", - clickLoadToContinue: "點擊「載入進度」繼續。", - fileError: "❌ 處理檔案時出錯", - invalidFileFormat: "❌ 檔案格式無效", - paintingSpeed: "繪製速度", - pixelsPerSecond: "像素/秒", - speedSetting: "速度: {speed} 像素/秒", - settings: "設定", - botSettings: "機器人設定", - close: "關閉", - language: "語言", - themeSettings: "主題設定", - themeSettingsDesc: "為介面選擇你喜歡的配色主題。", - languageSelectDesc: "選擇你偏好的語言,變更立即生效。", - autoCaptcha: "自動 CAPTCHA 解決", - autoCaptchaDesc: "使用整合的產生器自動產生 Turnstile 令牌,必要時回退到瀏覽器自動化。", - applySettings: "套用設定", - settingsSaved: "✅ 設定儲存成功!", - speedOn: "開啟", - speedOff: "關閉", - cooldownSettings: "冷卻設定", - waitCharges: "等待次數達到", - captchaSolving: "🔑 正在產生 Turnstile 令牌...", - captchaFailed: "❌ 令牌產生失敗。嘗試回退方法...", - automation: "自動化", - noChargesThreshold: "⌛ 等待次數達到 {threshold}。目前 {current}。下次在 {time}...", - }, - ja: { - title: "WPlace 自動画像", - toggleOverlay: "オーバーレイ切替", - scanColors: "色をスキャン", - uploadImage: "画像をアップロード", - resizeImage: "画像サイズ変更", - selectPosition: "位置を選択", - startPainting: "描画開始", - stopPainting: "描画停止", - checkingColors: "🔍 利用可能な色を確認中...", - noColorsFound: "❌ サイトでカラーパレットを開いて再試行してください!", - colorsFound: "✅ 利用可能な色 {count} 件を検出。アップロード可能。", - loadingImage: "🖼️ 画像を読み込み中...", - imageLoaded: "✅ 画像を読み込みました。有効なピクセル {count}", - imageError: "❌ 画像の読み込みエラー", - selectPositionAlert: "作品を開始したい位置に最初のピクセルを置いてください!", - waitingPosition: "👆 参照ピクセルの描画を待っています...", - positionSet: "✅ 位置を設定しました!", - positionTimeout: "❌ 位置選択のタイムアウト", - startPaintingMsg: "🎨 描画を開始...", - paintingProgress: "🧱 進捗: {painted}/{total} ピクセル...", - noCharges: "⌛ チャージなし。{time} 待機...", - paintingStopped: "⏹️ ユーザーにより停止されました", - paintingComplete: "✅ 描画完了! {count} ピクセル描画。", - paintingError: "❌ 描画中にエラー", - missingRequirements: "❌ 先に画像を読み込み位置を選択してください", - progress: "進捗", - pixels: "ピクセル", - charges: "チャージ", - estimatedTime: "推定時間", - initMessage: "「画像をアップロード」をクリックして開始", - waitingInit: "初期化待機中...", - initializingToken: "🔧 Turnstile トークン生成器を初期化中...", - tokenReady: "✅ トークン生成器準備完了 - 描画できます!", - tokenRetryLater: "⚠️ 必要に応じて再試行します", - resizeSuccess: "✅ 画像を {width}x{height} にリサイズ", - paintingPaused: "⏸️ X: {x}, Y: {y} で一時停止", - captchaNeeded: "❗ トークン生成に失敗。少ししてから再試行してください。", - saveData: "進捗を保存", - loadData: "進捗を読み込み", - saveToFile: "ファイルへ保存", - loadFromFile: "ファイルから読み込み", - dataManager: "データ管理", - autoSaved: "✅ 自動保存しました", - dataLoaded: "✅ 進捗を読み込みました", - fileSaved: "✅ ファイルに保存しました", - fileLoaded: "✅ ファイルから読み込みました", - noSavedData: "❌ 保存された進捗がありません", - savedDataFound: "✅ 保存された進捗が見つかりました。続行しますか?", - savedDate: "保存日時: {date}", - clickLoadToContinue: "「進捗を読み込み」をクリックして続行。", - fileError: "❌ ファイル処理エラー", - invalidFileFormat: "❌ 無効なファイル形式", - paintingSpeed: "描画速度", - pixelsPerSecond: "ピクセル/秒", - speedSetting: "速度: {speed} ピクセル/秒", - settings: "設定", - botSettings: "ボット設定", - close: "閉じる", - language: "言語", - themeSettings: "テーマ設定", - themeSettingsDesc: "インターフェースの好きなカラーテーマを選択。", - languageSelectDesc: "希望言語を選択。変更は即時反映されます。", - autoCaptcha: "自動 CAPTCHA ソルバー", - autoCaptchaDesc: "統合ジェネレーターで Turnstile トークンを自動生成し必要に応じてブラウザ自動化にフォールバック。", - applySettings: "設定を適用", - settingsSaved: "✅ 設定を保存しました!", - speedOn: "オン", - speedOff: "オフ", - cooldownSettings: "クールダウン設定", - waitCharges: "チャージ数が次に達するまで待機", - captchaSolving: "🔑 Turnstile トークン生成中...", - captchaFailed: "❌ トークン生成失敗。フォールバックを試行...", - automation: "自動化", - noChargesThreshold: "⌛ チャージ {threshold} を待機中。現在 {current}。次は {time} 後...", - }, - ko: { - title: "WPlace 자동 이미지", - toggleOverlay: "오버레이 전환", - scanColors: "색상 스캔", - uploadImage: "이미지 업로드", - resizeImage: "크기 조정", - selectPosition: "위치 선택", - startPainting: "그리기 시작", - stopPainting: "그리기 중지", - checkingColors: "🔍 사용 가능한 색상 확인 중...", - noColorsFound: "❌ 사이트에서 색상 팔레트를 연 후 다시 시도하세요!", - colorsFound: "✅ 사용 가능한 색상 {count}개 발견. 업로드 준비 완료.", - loadingImage: "🖼️ 이미지 불러오는 중...", - imageLoaded: "✅ 이미지 로드 완료. 유효 픽셀 {count}개", - imageError: "❌ 이미지 로드 오류", - selectPositionAlert: "작품을 시작할 위치에 첫 픽셀을 칠하세요!", - waitingPosition: "👆 기준 픽셀을 칠할 때까지 대기 중...", - positionSet: "✅ 위치 설정 완료!", - positionTimeout: "❌ 위치 선택 시간 초과", - startPaintingMsg: "🎨 그리기 시작...", - paintingProgress: "🧱 진행: {painted}/{total} 픽셀...", - noCharges: "⌛ 사용 가능 횟수 없음. {time} 대기...", - paintingStopped: "⏹️ 사용자에 의해 중지됨", - paintingComplete: "✅ 그리기 완료! {count} 픽셀 그렸습니다.", - paintingError: "❌ 그리는 중 오류", - missingRequirements: "❌ 먼저 이미지를 불러오고 위치를 선택하세요", - progress: "진행", - pixels: "픽셀", - charges: "횟수", - estimatedTime: "예상 시간", - initMessage: "'이미지 업로드'를 클릭하여 시작", - waitingInit: "초기화 대기 중...", - initializingToken: "🔧 Turnstile 토큰 생성기 초기화 중...", - tokenReady: "✅ 토큰 생성 준비 완료 - 그리기를 시작할 수 있습니다!", - tokenRetryLater: "⚠️ 필요 시 다시 시도합니다", - resizeSuccess: "✅ 이미지가 {width}x{height} 크기로 조정됨", - paintingPaused: "⏸️ 위치 X: {x}, Y: {y} 에서 일시 중지", - captchaNeeded: "❗ 토큰 생성 실패. 잠시 후 다시 시도하세요.", - saveData: "진행 저장", - loadData: "진행 불러오기", - saveToFile: "파일로 저장", - loadFromFile: "파일에서 불러오기", - dataManager: "데이터", - autoSaved: "✅ 진행 자동 저장됨", - dataLoaded: "✅ 진행 불러오기 성공", - fileSaved: "✅ 파일 저장 성공", - fileLoaded: "✅ 파일 불러오기 성공", - noSavedData: "❌ 저장된 진행 없음", - savedDataFound: "✅ 저장된 진행 발견! 계속하려면 불러오시겠습니까?", - savedDate: "저장 시각: {date}", - clickLoadToContinue: "'진행 불러오기'를 클릭하여 계속.", - fileError: "❌ 파일 처리 오류", - invalidFileFormat: "❌ 잘못된 파일 형식", - paintingSpeed: "그리기 속도", - pixelsPerSecond: "픽셀/초", - speedSetting: "속도: {speed} 픽셀/초", - settings: "설정", - botSettings: "봇 설정", - close: "닫기", - language: "언어", - themeSettings: "테마 설정", - themeSettingsDesc: "인터페이스용 선호 색상 테마를 선택하세요.", - languageSelectDesc: "선호 언어를 선택하세요. 변경 사항은 즉시 적용됩니다.", - autoCaptcha: "자동 CAPTCHA 해결", - autoCaptchaDesc: "통합 생성기를 사용해 Turnstile 토큰을 자동 생성하고 필요 시 브라우저 자동화로 폴백.", - applySettings: "설정 적용", - settingsSaved: "✅ 설정 저장 완료!", - speedOn: "켜짐", - speedOff: "꺼짐", - cooldownSettings: "쿨다운 설정", - waitCharges: "횟수가 다음 값에 도달할 때까지 대기", - captchaSolving: "🔑 Turnstile 토큰 생성 중...", - captchaFailed: "❌ 토큰 생성 실패. 폴백 시도...", - automation: "자동화", - noChargesThreshold: "⌛ 횟수가 {threshold} 에 도달할 때까지 대기 중. 현재 {current}. 다음 {time} 후...", - }, - uk: { - title: "WPlace Авто-Зображення", - toggleOverlay: "Перемкнути оверлей", - scanColors: "Сканувати кольори", - uploadImage: "Завантажити зображення", - resizeImage: "Змінити розмір зображення", - selectPosition: "Вибрати позицію", - startPainting: "Почати малювання", - stopPainting: "Зупинити малювання", - checkingColors: "🔍 Перевірка доступних кольорів...", - noColorsFound: "❌ Відкрий палітру кольорів на сайті та спробуй ще раз!", - colorsFound: "✅ Знайдено {count} доступних кольорів. Готово до завантаження.", - loadingImage: "🖼️ Завантаження зображення...", - imageLoaded: "✅ Зображення завантажено. Валідних пікселів: {count}", - imageError: "❌ Помилка завантаження зображення", - selectPositionAlert: "Намалюй перший піксель у місці, де має починатися арт!", - waitingPosition: "👆 Очікування на малювання референсного пікселя...", - positionSet: "✅ Позицію успішно встановлено!", - positionTimeout: "❌ Час вибору позиції вичерпано", - startPaintingMsg: "🎨 Початок малювання...", - paintingProgress: "🧱 Прогрес: {painted}/{total} пікселів...", - noCharges: "⌛ Немає зарядів. Очікування {time}...", - paintingStopped: "⏹️ Малювання зупинено користувачем", - paintingComplete: "✅ Малювання завершено! Намальовано {count} пікселів.", - paintingError: "❌ Помилка під час малювання", - missingRequirements: "❌ Спершу завантаж зображення та вибери позицію", - progress: "Прогрес", - pixels: "Пікселі", - charges: "Заряди", - estimatedTime: "Орієнтовний час", - initMessage: "Натисни 'Завантажити зображення', щоб почати", - waitingInit: "Очікування ініціалізації...", - initializingToken: "🔧 Ініціалізація генератора токенів Turnstile...", - tokenReady: "✅ Генератор токенів готовий – можна починати малювання!", - tokenRetryLater: "⚠️ Генератор токенів повторить спробу за потреби", - resizeSuccess: "✅ Зображення змінено до {width}x{height}", - paintingPaused: "⏸️ Малювання призупинено на позиції X: {x}, Y: {y}", - captchaNeeded: "❗ Не вдалося згенерувати токен. Спробуй трохи пізніше.", - saveData: "Зберегти прогрес", - loadData: "Завантажити прогрес", - saveToFile: "Зберегти у файл", - loadFromFile: "Завантажити з файлу", - dataManager: "Менеджер даних", - autoSaved: "✅ Прогрес збережено автоматично", - dataLoaded: "✅ Прогрес успішно завантажено", - fileSaved: "✅ Прогрес успішно збережено у файл", - fileLoaded: "✅ Прогрес успішно завантажено з файлу", - noSavedData: "❌ Не знайдено збереженого прогресу", - savedDataFound: "✅ Знайдено збережений прогрес! Завантажити, щоб продовжити?", - savedDate: "Збережено: {date}", - clickLoadToContinue: "Натисни 'Завантажити прогрес', щоб продовжити.", - fileError: "❌ Помилка обробки файлу", - invalidFileFormat: "❌ Невірний формат файлу", - paintingSpeed: "Швидкість малювання", - pixelsPerSecond: "пікселів/секунда", - speedSetting: "Швидкість: {speed} пікселів/сек", - settings: "Налаштування", - botSettings: "Налаштування бота", - close: "Закрити", - language: "Мова", - themeSettings: "Налаштування теми", - themeSettingsDesc: "Вибери бажану колірну тему для інтерфейсу.", - languageSelectDesc: "Вибери бажану мову. Зміни набудуть чинності одразу.", - autoCaptcha: "Авто-CAPTCHA (Turnstile)", - autoCaptchaDesc: "Автоматично генерує токени Turnstile за допомогою вбудованого генератора. Використовує автоматизацію браузера у разі потреби.", - applySettings: "Застосувати налаштування", - settingsSaved: "✅ Налаштування успішно збережено!", - cooldownSettings: "Налаштування відновлення", - waitCharges: "Очікувати, доки кількість зарядів досягне", - captchaSolving: "🔑 Генерація токена Turnstile...", - captchaFailed: "❌ Не вдалося згенерувати токен Turnstile. Використовую запасний метод...", - automation: "Автоматизація", - noChargesThreshold: "⌛ Очікування, доки заряди досягнуть {threshold}. Зараз {current}. Наступне через {time}...", - }, - } + loadingError: "Loading translations failed, using fallback" + } + }; // GLOBAL STATE const state = { @@ -2146,7 +1315,7 @@ }, t: async (key, params = {}) => { - // Try to get from ngrok first, with caching + // Try to get from cache first const cacheKey = `${state.language}_${key}`; if (translationCache.has(cacheKey)) { let text = translationCache.get(cacheKey); @@ -2156,32 +1325,57 @@ return text; } - // Try fetching from ngrok - try { - const response = await fetch(`TODO: /translations/${state.language}.json`, { - headers: { - "ngrok-skip-browser-warning": "69420" - } + // Try dynamically loaded translations + if (loadedTranslations[state.language]?.[key]) { + let text = loadedTranslations[state.language][key]; + // Cache for future use + translationCache.set(cacheKey, text); + Object.keys(params).forEach((param) => { + text = text.replace(`{${param}}`, params[param]); }); - - if (response.ok) { - const translations = await response.json(); - if (translations[key]) { - // Cache the successful translation - translationCache.set(cacheKey, translations[key]); + return text; + } + + // Try to load translations if not loaded yet + if (!loadedTranslations[state.language]) { + try { + const translations = await loadTranslations(state.language); + if (translations?.[key]) { let text = translations[key]; + translationCache.set(cacheKey, text); Object.keys(params).forEach((param) => { text = text.replace(`{${param}}`, params[param]); }); return text; } + } catch (error) { + console.warn(`Translation loading failed for ${state.language}:`, error); } - } catch (error) { - // Silently fail and use fallback } - // Fallback to local TEXT - let text = TEXT[state.language]?.[key] || TEXT.en[key] || key; + // Fallback to English if current language failed + if (state.language !== 'en') { + if (loadedTranslations['en']?.[key]) { + let text = loadedTranslations['en'][key]; + Object.keys(params).forEach((param) => { + text = text.replace(`{${param}}`, params[param]); + }); + return text; + } + + // Try to load English translations + const englishTranslations = await loadTranslations('en'); + if (englishTranslations?.[key]) { + let text = englishTranslations[key]; + Object.keys(params).forEach((param) => { + text = text.replace(`{${param}}`, params[param]); + }); + return text; + } + } + + // Final fallback to emergency fallback or key + let text = FALLBACK_TEXT[state.language]?.[key] || FALLBACK_TEXT.en?.[key] || key; Object.keys(params).forEach((param) => { text = text.replace(`{${param}}`, params[param]); }); @@ -3380,7 +2574,7 @@ if (existingResizeOverlay) existingResizeOverlay.remove() loadThemePreference() - loadLanguagePreference() + await loadLanguagePreference() const theme = getCurrentTheme() @@ -5698,11 +4892,14 @@ const languageSelect = settingsContainer.querySelector("#languageSelect") if (languageSelect) { - languageSelect.addEventListener("change", (e) => { + languageSelect.addEventListener("change", async (e) => { const newLanguage = e.target.value state.language = newLanguage localStorage.setItem('wplace_language', newLanguage) + // Load the new language translations + await loadTranslations(newLanguage) + setTimeout(() => { settingsContainer.style.display = "none" createUI()