This commit is contained in:
Staninna committed 2025-08-30 01:24:06 +02:00
1 parent 745ce578b9
commit ce0ac59ec2
1 file changed
+172 -43
+172 -43
View File
@@ -277,71 +277,159 @@ function applyTheme() {
// 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) => {
// Function to load translations from JSON file with retry mechanism
const loadTranslations = async (language, retryCount = 0) => {
if (loadedTranslations[language]) {
return loadedTranslations[language];
}
// Load translations from CDN
const url = `https://staninna.github.io/WPlace-AutoBOT/decoupled-translations/lang/${language}.json`;
const maxRetries = 3;
const baseDelay = 1000; // 1 second
try {
console.log(`🔄 Loading ${language} translations from CDN...`);
if (retryCount === 0) {
console.log(`🔄 Loading ${language} translations from CDN...`);
} else {
console.log(`🔄 Retrying ${language} translations (attempt ${retryCount + 1}/${maxRetries + 1})...`);
}
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;
// Validate that translations is an object with keys
if (typeof translations === 'object' && translations !== null && Object.keys(translations).length > 0) {
loadedTranslations[language] = translations;
console.log(`📚 Loaded ${language} translations successfully from CDN (${Object.keys(translations).length} keys)`);
return translations;
} else {
console.warn(`❌ Invalid translation format for ${language}`);
throw new Error('Invalid translation format');
}
} else {
console.warn(`❌ CDN returned HTTP ${response.status}: ${response.statusText} for ${language} translations`);
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
console.error(`❌ Failed to load ${language} translations from CDN:`, error);
console.error(`❌ Failed to load ${language} translations from CDN (attempt ${retryCount + 1}):`, error);
// Retry with exponential backoff
if (retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount);
console.log(`⏳ Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
return loadTranslations(language, retryCount + 1);
}
}
return null;
};
const loadLanguagePreference = async () => {
const savedLanguage = localStorage.getItem("wplace_language")
const savedLanguage = localStorage.getItem("wplace_language");
const browserLocale = navigator.language;
const browserLanguage = browserLocale.split("-")[0];
const browserLocale = navigator.language
const browserLanguage = browserLocale.split("-")[0]
let selectedLanguage = "en"; // Default fallback
// Check if we have the saved language available
if (savedLanguage && AVAILABLE_LANGUAGES.includes(savedLanguage)) {
state.language = savedLanguage
await loadTranslations(savedLanguage)
return
try {
// Check if we have the saved language available
if (savedLanguage && AVAILABLE_LANGUAGES.includes(savedLanguage)) {
selectedLanguage = savedLanguage;
console.log(`🔄 Using saved language preference: ${selectedLanguage}`);
}
// Try full locale match (e.g. "zh-CN", "zh-TW" etc)
else if (AVAILABLE_LANGUAGES.includes(browserLocale)) {
selectedLanguage = browserLocale;
localStorage.setItem("wplace_language", browserLocale);
console.log(`🔄 Using browser locale: ${selectedLanguage}`);
}
// Try base language match (e.g. "en" for "en-US" or "en-GB" etc)
else if (AVAILABLE_LANGUAGES.includes(browserLanguage)) {
selectedLanguage = browserLanguage;
localStorage.setItem("wplace_language", browserLanguage);
console.log(`🔄 Using browser language: ${selectedLanguage}`);
}
// Use English as fallback
else {
console.log(`🔄 No matching language found, using English fallback`);
}
// Set the language in state first
state.language = selectedLanguage;
// Only load translations if not already loaded and not English (which should already be loaded)
if (selectedLanguage !== "en" && !loadedTranslations[selectedLanguage]) {
const loaded = await loadTranslations(selectedLanguage);
if (!loaded) {
console.warn(`⚠️ Failed to load ${selectedLanguage} translations, falling back to English`);
state.language = "en";
localStorage.setItem("wplace_language", "en");
}
}
} catch (error) {
console.error(`❌ Error in loadLanguagePreference:`, error);
state.language = "en"; // Always ensure we have a valid language
}
// Try full locale match (e.g. "zh-CN", "zh-TW" etc)
if (AVAILABLE_LANGUAGES.includes(browserLocale)) {
state.language = browserLocale
localStorage.setItem("wplace_language", browserLocale)
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")
}
};
// Always ensure English is loaded as fallback
if (!loadedTranslations["en"]) {
await loadTranslations("en")
}
// Simple user notification function for critical issues
const showTranslationWarning = (message) => {
try {
// Create a simple temporary notification banner
const warning = document.createElement('div');
warning.style.cssText = `
position: fixed; top: 10px; right: 10px; z-index: 10001;
background: rgba(255, 193, 7, 0.95); color: #212529; padding: 12px 16px;
border-radius: 8px; font-size: 14px; font-weight: 500;
box-shadow: 0 4px 12px rgba(0,0,0,0.3); border: 1px solid rgba(255, 193, 7, 0.8);
max-width: 300px; word-wrap: break-word;
`;
warning.textContent = message;
document.body.appendChild(warning);
// Auto-remove after 8 seconds
setTimeout(() => {
if (warning.parentNode) {
warning.remove();
}
}, 8000);
} catch (e) {
// If DOM manipulation fails, just log
console.warn("Failed to show translation warning UI:", e);
}
};
// Initialize translations function
const initializeTranslations = async () => {
try {
console.log("🌐 Initializing translation system...");
// Always ensure English is loaded as fallback first
if (!loadedTranslations["en"]) {
const englishLoaded = await loadTranslations("en");
if (!englishLoaded) {
console.warn("⚠️ Failed to load English translations from CDN, using fallback");
showTranslationWarning("⚠️ Translation loading failed, using basic fallbacks");
}
}
// Then load user's language preference
await loadLanguagePreference();
console.log(`✅ Translation system initialized. Active language: ${state.language}`);
} catch (error) {
console.error("❌ Translation initialization failed:", error);
// Ensure state has a valid language even if loading fails
if (!state.language) {
state.language = "en";
}
console.warn("⚠️ Using fallback translations due to initialization failure");
showTranslationWarning("⚠️ Translation system error, using basic English");
}
};
// Emergency fallback TEXT (minimal)
const FALLBACK_TEXT = {
@@ -350,10 +438,45 @@ function applyTheme() {
settings: "Settings",
close: "Close",
language: "Language",
loadingError: "Loading translations failed, using fallback"
loadingError: "Loading translations failed, using fallback",
scanColors: "Scan Colors",
uploadImage: "Upload Image",
startPainting: "Start Painting",
stopPainting: "Stop Painting",
progress: "Progress",
pixels: "Pixels",
charges: "Charges",
initMessage: "Click 'Upload Image' to begin"
}
};
// Safe translation function with multiple fallback levels
const getText = (key, replacements = {}) => {
// Try current language first
let text = loadedTranslations[state.language]?.[key];
// Fallback to English translations
if (!text && state.language !== 'en') {
text = loadedTranslations['en']?.[key];
}
// Fallback to hardcoded English
if (!text) {
text = FALLBACK_TEXT['en']?.[key];
}
// Last resort - return the key itself
if (!text) {
console.warn(`⚠️ Missing translation for key: ${key}`);
return key;
}
// Handle string replacements like {count}, {time}, etc.
return Object.entries(replacements).reduce((result, [placeholder, value]) => {
return result.replace(new RegExp(`\\{${placeholder}\\}`, 'g'), value);
}, text);
};
// GLOBAL STATE
const state = {
running: false,
@@ -1401,8 +1524,14 @@ function applyTheme() {
// 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]);
text = text.replace(new RegExp(`\\{${param}\\}`, 'g'), params[param]);
});
// Log missing translations for debugging
if (text === key && key !== 'undefined') {
console.warn(`⚠️ Missing translation for key: ${key} (language: ${state.language})`);
}
return text;
},
@@ -2580,7 +2709,7 @@ function applyTheme() {
if (existingResizeOverlay) existingResizeOverlay.remove()
loadThemePreference()
await loadLanguagePreference()
await initializeTranslations()
const theme = getCurrentTheme()
applyTheme() // <- new: set CSS vars and theme class before building UI