diff --git a/GUI/index.html b/GUI/index.html index f2d54b4..eb7b8e7 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -33,27 +33,27 @@ @@ -105,7 +105,7 @@ HYTALE
- FREE TO PLAY + FREE TO PLAY
@@ -116,27 +116,27 @@

HYTALE

-

FREE TO PLAY LAUNCHER

+

FREE TO PLAY LAUNCHER

- - Player Name +
- +
-
@@ -161,14 +161,14 @@

- READY TO PLAY + READY TO PLAY

-

Launch Hytale and enter the adventure

+

Launch Hytale and enter the adventure

@@ -177,10 +177,10 @@

- LATEST NEWS + LATEST NEWS

@@ -191,12 +191,12 @@
- +
@@ -207,13 +207,13 @@
- Page 1 of 1 + Page 1 of 1
@@ -223,7 +223,7 @@

- ALL NEWS + ALL NEWS

@@ -234,16 +234,16 @@

- PLAYERS CHAT + PLAYERS CHAT

- 0 online + 0 online
@@ -256,7 +256,7 @@ @@ -278,26 +278,25 @@

- SETTINGS + SETTINGS

-

- Game Options + Game Options

- + + data-i18n-placeholder="settings.playerNamePlaceholder" maxlength="16" />

- This name will be used in-game (1-16 characters) + This name will be used in-game (1-16 characters)

@@ -308,8 +307,8 @@ onclick="openGameLocation()">
-
Open Game Location
-
Open the game installation folder
+
Open Game Location
+
Open the game installation folder
@@ -321,8 +320,8 @@ onclick="repairGame()">
-
Repair Game
-
Reinstall game files (preserves data) +
Repair Game
+
Reinstall game files (preserves data)
@@ -330,21 +329,18 @@
- +
- - - - - - + + + + + +

- Select your preferred GPU (Linux: affects DRI_PRIME) + Select your preferred GPU (Linux: affects DRI_PRIME)

@@ -354,15 +350,15 @@

- Player UUID Management + Player UUID Management

- +
+ readonly data-i18n-placeholder="settings.uuidPlaceholder" /> @@ -373,7 +369,7 @@

- Your unique player identifier for this username + Your unique player identifier for this username

@@ -383,9 +379,8 @@
@@ -395,7 +390,7 @@

- Discord Integration + Discord Integration

@@ -403,8 +398,8 @@
-
Enable Discord Rich Presence
-
Show your launcher activity on Discord +
Enable Discord Rich Presence
+
Show your launcher activity on Discord
@@ -414,7 +409,7 @@

- Java Runtime + Java Runtime

@@ -422,8 +417,8 @@
-
Use Custom Java Path
-
Override the bundled Java runtime with +
Use Custom Java Path
+
Override the bundled Java runtime with your own installation
@@ -431,23 +426,38 @@
+
+

+ + Language +

+ +
+
+ + +
+
+
@@ -455,8 +465,8 @@
-

Skins

-

Skin customization coming soon...

+

Skins

+

Skin customization coming soon...

@@ -465,22 +475,22 @@

- SYSTEM LOGS + SYSTEM LOGS

-
Loading logs...
+
Loading logs...
@@ -493,7 +503,7 @@

- MY MODS + MY MODS

@@ -561,7 +571,7 @@

- UUID Management + UUID Management

- Loading UUIDs... + Loading UUIDs...
-

Set Custom UUID

+

Set Custom UUID

- Warning: Setting a custom UUID will change your current player identity + Warning: Setting a custom UUID will change your current player identity

@@ -625,7 +635,7 @@

- Manage Profiles + Manage Profiles

-
@@ -674,10 +684,10 @@
- Join our Discord community! + Join our Discord community!
-

Choose a solid color:

+

Choose a solid color:

@@ -711,25 +721,26 @@
- +
-

Preview:

-
YourUsername
+

Preview:

+
YourUsername
+ - + \ No newline at end of file diff --git a/GUI/js/i18n.js b/GUI/js/i18n.js new file mode 100644 index 0000000..47199a3 --- /dev/null +++ b/GUI/js/i18n.js @@ -0,0 +1,89 @@ +// Minimal i18n system - optimized async loading +const i18n = (() => { + let currentLang = 'en'; + let translations = {}; + const availableLanguages = [ + { code: 'en', name: 'English' }, + { code: 'es', name: 'Español' }, + { code: 'pt-BR', name: 'Português (Brasil)' } + ]; + + // Load single language file + async function loadLanguage(lang) { + if (translations[lang]) return true; + + try { + const response = await fetch(`locales/${lang}.json`); + if (response.ok) { + translations[lang] = await response.json(); + return true; + } + } catch (e) { + console.warn(`Failed to load language: ${lang}`); + } + return false; + } + + // Get translation by key + function t(key) { + const keys = key.split('.'); + let value = translations[currentLang]; + + for (const k of keys) { + if (value && value[k] !== undefined) { + value = value[k]; + } else { + return key; + } + } + + return value; + } + + // Set language + async function setLanguage(lang) { + await loadLanguage(lang); + if (translations[lang]) { + currentLang = lang; + updateDOM(); + window.electronAPI?.saveLanguage(lang); + } + } + + // Update all elements with data-i18n attribute + function updateDOM() { + document.querySelectorAll('[data-i18n]').forEach(el => { + const key = el.getAttribute('data-i18n'); + el.textContent = t(key); + }); + + document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { + const key = el.getAttribute('data-i18n-placeholder'); + el.placeholder = t(key); + }); + + document.querySelectorAll('[data-i18n-title]').forEach(el => { + const key = el.getAttribute('data-i18n-title'); + el.title = t(key); + }); + } + + // Initialize - load saved language only + async function init(savedLang) { + const lang = savedLang || 'en'; + await loadLanguage(lang); + currentLang = lang; + updateDOM(); + } + + return { + init, + t, + setLanguage, + getAvailableLanguages: () => availableLanguages, + getCurrentLanguage: () => currentLang + }; +})(); + +// Make i18n globally available +window.i18n = i18n; diff --git a/GUI/js/install.js b/GUI/js/install.js index 79a51ef..36bb46f 100644 --- a/GUI/js/install.js +++ b/GUI/js/install.js @@ -51,7 +51,7 @@ export async function installGame() { isDownloading = true; if (installBtn) { installBtn.disabled = true; - installText.textContent = 'INSTALLING...'; + installText.textContent = window.i18n ? window.i18n.t('install.installing') : 'INSTALLING...'; } try { @@ -59,8 +59,9 @@ export async function installGame() { const result = await window.electronAPI.installGame(playerName, '', installPath); if (result.success) { + const successMsg = window.i18n ? window.i18n.t('progress.installationComplete') : 'Installation completed successfully!'; if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' }); + window.LauncherUI.updateProgress({ message: successMsg }); setTimeout(() => { window.LauncherUI.hideProgress(); window.LauncherUI.showLauncherOrInstall(true); @@ -76,8 +77,9 @@ export async function installGame() { simulateInstallation(playerName); } } catch (error) { + const errorMsg = window.i18n ? window.i18n.t('progress.installationFailed').replace('{error}', error.message) : `Installation failed: ${error.message}`; if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: `Installation failed: ${error.message}` }); + window.LauncherUI.updateProgress({ message: errorMsg }); setTimeout(() => { window.LauncherUI.hideProgress(); resetInstallButton(); @@ -92,10 +94,13 @@ function simulateInstallation(playerName) { progress += Math.random() * 3; if (progress > 100) progress = 100; + const installingMsg = window.i18n ? window.i18n.t('progress.installingGameFiles') : 'Installing game files...'; + const completeMsg = window.i18n ? window.i18n.t('progress.installComplete') : 'Installation complete!'; + if (window.LauncherUI) { window.LauncherUI.updateProgress({ percent: progress, - message: progress < 100 ? 'Installing game files...' : 'Installation complete!', + message: progress < 100 ? installingMsg : completeMsg, speed: 1024 * 1024 * (5 + Math.random() * 10), downloaded: progress * 1024 * 1024 * 20, total: 1024 * 1024 * 2000 @@ -104,9 +109,10 @@ function simulateInstallation(playerName) { if (progress >= 100) { clearInterval(interval); + const successMsg = window.i18n ? window.i18n.t('progress.installationComplete') : 'Installation completed successfully!'; setTimeout(() => { if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' }); + window.LauncherUI.updateProgress({ message: successMsg }); setTimeout(() => { window.LauncherUI.hideProgress(); window.LauncherUI.showLauncherOrInstall(true); diff --git a/GUI/js/launcher.js b/GUI/js/launcher.js index 047078d..7e33b72 100644 --- a/GUI/js/launcher.js +++ b/GUI/js/launcher.js @@ -160,7 +160,8 @@ window.deleteProfile = async (id) => { window.switchProfile = async (id) => { try { if (window.LauncherUI) window.LauncherUI.showProgress(); - if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Switching Profile...' }); + const switchingMsg = window.i18n ? window.i18n.t('progress.switchingProfile') : 'Switching Profile...'; + if (window.LauncherUI) window.LauncherUI.updateProgress({ message: switchingMsg }); await window.electronAPI.profile.activate(id); @@ -178,7 +179,8 @@ window.switchProfile = async (id) => { if (dropdown) dropdown.classList.remove('show'); if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: 'Profile Switched!' }); + const switchedMsg = window.i18n ? window.i18n.t('progress.profileSwitched') : 'Profile Switched!'; + window.LauncherUI.updateProgress({ message: switchedMsg }); setTimeout(() => window.LauncherUI.hideProgress(), 1000); } @@ -221,7 +223,8 @@ export async function launch() { } try { - if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Starting game...' }); + const startingMsg = window.i18n ? window.i18n.t('progress.startingGame') : 'Starting game...'; + if (window.LauncherUI) window.LauncherUI.updateProgress({ message: startingMsg }); if (window.electronAPI && window.electronAPI.launchGame) { const result = await window.electronAPI.launchGame(playerName, javaPath, '', gpuPreference); @@ -261,7 +264,12 @@ export async function launch() { } } -function showCustomConfirm(message, title = 'Confirm Action', onConfirm, onCancel = null, confirmText = 'Confirm', cancelText = 'Cancel') { +function showCustomConfirm(message, title, onConfirm, onCancel = null, confirmText, cancelText) { + // Apply defaults with i18n support + title = title || (window.i18n ? window.i18n.t('confirm.defaultTitle') : 'Confirm Action'); + confirmText = confirmText || (window.i18n ? window.i18n.t('common.confirm') : 'Confirm'); + cancelText = cancelText || (window.i18n ? window.i18n.t('common.cancel') : 'Cancel'); + const existingModal = document.querySelector('.custom-confirm-modal'); if (existingModal) { existingModal.remove(); @@ -383,22 +391,28 @@ function showCustomConfirm(message, title = 'Confirm Action', onConfirm, onCance } export async function uninstallGame() { + const message = window.i18n ? window.i18n.t('confirm.uninstallGameMessage') : 'Are you sure you want to uninstall Hytale? All game files will be deleted.'; + const title = window.i18n ? window.i18n.t('confirm.uninstallGameTitle') : 'Uninstall Game'; + const confirmBtn = window.i18n ? window.i18n.t('confirm.uninstallGameButton') : 'Uninstall'; + const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel'; + showCustomConfirm( - 'Are you sure you want to uninstall Hytale? All game files will be deleted.', - 'Uninstall Game', + message, + title, async () => { await performUninstall(); }, null, - 'Uninstall', - 'Cancel' + confirmBtn, + cancelBtn ); } async function performUninstall() { if (window.LauncherUI) window.LauncherUI.showProgress(); - if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Uninstalling game...' }); + const uninstallingMsg = window.i18n ? window.i18n.t('progress.uninstallingGame') : 'Uninstalling game...'; + if (window.LauncherUI) window.LauncherUI.updateProgress({ message: uninstallingMsg }); if (uninstallBtn) uninstallBtn.disabled = true; try { @@ -406,8 +420,9 @@ async function performUninstall() { const result = await window.electronAPI.uninstallGame(); if (result.success) { + const successMsg = window.i18n ? window.i18n.t('progress.gameUninstalled') : 'Game uninstalled successfully!'; if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' }); + window.LauncherUI.updateProgress({ message: successMsg }); setTimeout(() => { window.LauncherUI.hideProgress(); window.LauncherUI.showLauncherOrInstall(false); @@ -417,9 +432,10 @@ async function performUninstall() { throw new Error(result.error || 'Uninstall failed'); } } else { + const successMsg = window.i18n ? window.i18n.t('progress.gameUninstalled') : 'Game uninstalled successfully!'; setTimeout(() => { if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' }); + window.LauncherUI.updateProgress({ message: successMsg }); setTimeout(() => { window.LauncherUI.hideProgress(); window.LauncherUI.showLauncherOrInstall(false); @@ -428,8 +444,9 @@ async function performUninstall() { }, 2000); } } catch (error) { + const errorMsg = window.i18n ? window.i18n.t('progress.uninstallFailed').replace('{error}', error.message) : `Uninstall failed: ${error.message}`; if (window.LauncherUI) { - window.LauncherUI.updateProgress({ message: `Uninstall failed: ${error.message}` }); + window.LauncherUI.updateProgress({ message: errorMsg }); setTimeout(() => window.LauncherUI.hideProgress(), 3000); } } finally { @@ -484,7 +501,7 @@ function resetPlayButton() { isDownloading = false; if (playBtn) { playBtn.disabled = false; - playText.textContent = 'PLAY'; + playText.textContent = window.i18n ? window.i18n.t('play.play') : 'PLAY'; } } diff --git a/GUI/js/mods.js b/GUI/js/mods.js index 934dd4f..32f4ddd 100644 --- a/GUI/js/mods.js +++ b/GUI/js/mods.js @@ -113,10 +113,15 @@ function displayInstalledMods(mods) { modsContainer.innerHTML = `
-

No Mods Installed

-

Add mods from CurseForge or import local files

+

No Mods Installed

+

Add mods from CurseForge or import local files

`; + if (window.i18n) { + const container = modsContainer.querySelector('.empty-installed-mods'); + container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled'); + container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc'); + } return; } @@ -138,9 +143,9 @@ function displayInstalledMods(mods) { function createInstalledModCard(mod) { const statusClass = mod.enabled ? 'text-primary' : 'text-zinc-500'; - const statusText = mod.enabled ? 'ACTIVE' : 'DISABLED'; + const statusText = mod.enabled ? (window.i18n ? window.i18n.t('mods.active') : 'ACTIVE') : (window.i18n ? window.i18n.t('mods.disabled') : 'DISABLED'); const toggleBtnClass = mod.enabled ? 'btn-disable' : 'btn-enable'; - const toggleBtnText = mod.enabled ? 'DISABLE' : 'ENABLE'; + const toggleBtnText = mod.enabled ? (window.i18n ? window.i18n.t('mods.disable') : 'DISABLE') : (window.i18n ? window.i18n.t('mods.enable') : 'ENABLE'); const toggleIcon = mod.enabled ? 'fa-pause' : 'fa-play'; return ` @@ -154,7 +159,7 @@ function createInstalledModCard(mod) {

${mod.name}

v${mod.version} -

${mod.description || 'No description available'}

+

${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}

@@ -163,7 +168,7 @@ function createInstalledModCard(mod) { ${statusText}
- ${!isInstalled ? - `` : - `` }
@@ -343,7 +353,8 @@ function createBrowseModCard(mod) { async function downloadAndInstallMod(modInfo) { try { - window.LauncherUI?.showProgress(`Downloading ${modInfo.name}...`); + const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`; + window.LauncherUI?.showProgress(downloadMsg); const result = await window.electronAPI?.downloadMod(modInfo); @@ -367,20 +378,23 @@ async function downloadAndInstallMod(modInfo) { await loadInstalledMods(); await loadBrowseMods(); window.LauncherUI?.hideProgress(); - showNotification(`${modInfo.name} installed successfully! 🎉`, 'success'); + const successMsg = window.i18n ? window.i18n.t('notifications.modsInstalledSuccess').replace('{name}', modInfo.name) : `${modInfo.name} installed successfully! 🎉`; + showNotification(successMsg, 'success'); } else { throw new Error(result?.error || 'Failed to download mod'); } } catch (error) { console.error('Error downloading mod:', error); window.LauncherUI?.hideProgress(); - showNotification('Failed to download mod: ' + error.message, 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.modsDownloadFailed').replace('{error}', error.message) : 'Failed to download mod: ' + error.message; + showNotification(errorMsg, 'error'); } } async function toggleMod(modId) { try { - window.LauncherUI?.showProgress('Toggling mod...'); + const toggleMsg = window.i18n ? window.i18n.t('notifications.modsTogglingMod') : 'Toggling mod...'; + window.LauncherUI?.showProgress(toggleMsg); const modsPath = await window.electronAPI?.getModsPath(); const result = await window.electronAPI?.toggleMod(modId, modsPath); @@ -394,7 +408,8 @@ async function toggleMod(modId) { } catch (error) { console.error('Error toggling mod:', error); window.LauncherUI?.hideProgress(); - showNotification('Failed to toggle mod: ' + error.message, 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.modsToggleFailed').replace('{error}', error.message) : 'Failed to toggle mod: ' + error.message; + showNotification(errorMsg, 'error'); } } @@ -402,11 +417,16 @@ async function deleteMod(modId) { const mod = installedMods.find(m => m.id === modId); if (!mod) return; + const confirmMsg = window.i18n ? + window.i18n.t('mods.confirmDelete').replace('{name}', mod.name) + ' ' + window.i18n.t('mods.confirmDeleteDesc') : + `Are you sure you want to delete "${mod.name}"? This action cannot be undone.`; + showConfirmModal( - `Are you sure you want to delete "${mod.name}"? This action cannot be undone.`, + confirmMsg, async () => { try { - window.LauncherUI?.showProgress('Deleting mod...'); + const deleteMsg = window.i18n ? window.i18n.t('notifications.modsDeletingMod') : 'Deleting mod...'; + window.LauncherUI?.showProgress(deleteMsg); const modsPath = await window.electronAPI?.getModsPath(); const result = await window.electronAPI?.uninstallMod(modId, modsPath); @@ -415,14 +435,16 @@ async function deleteMod(modId) { await loadInstalledMods(); await loadBrowseMods(); window.LauncherUI?.hideProgress(); - showNotification(`"${mod.name}" deleted successfully`, 'success'); + const successMsg = window.i18n ? window.i18n.t('notifications.modsDeletedSuccess').replace('{name}', mod.name) : `"${mod.name}" deleted successfully`; + showNotification(successMsg, 'success'); } else { throw new Error(result?.error || 'Failed to delete mod'); } } catch (error) { console.error('Error deleting mod:', error); window.LauncherUI?.hideProgress(); - showNotification('Failed to delete mod: ' + error.message, 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.modsDeleteFailed').replace('{error}', error.message) : 'Failed to delete mod: ' + error.message; + showNotification(errorMsg, 'error'); } } ); @@ -571,7 +593,7 @@ function showConfirmModal(message, onConfirm, onCancel = null) {
-

Confirm Deletion

+

${window.i18n ? window.i18n.t('mods.confirmDeletion') : 'Confirm Deletion'}

@@ -587,7 +609,7 @@ function showConfirmModal(message, onConfirm, onCancel = null) { cursor: pointer; font-weight: 500; transition: all 0.2s; - ">Cancel + ">${window.i18n ? window.i18n.t('common.cancel') : 'Cancel'} + ">${window.i18n ? window.i18n.t('common.delete') : 'Delete'}
`; @@ -715,7 +737,8 @@ function viewModPage(modId) { } } else { console.error('Mod not found with ID:', modId); - showNotification('Mod information not found', 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.modsModNotFound') : 'Mod information not found'; + showNotification(errorMsg, 'error'); } } diff --git a/GUI/js/script.js b/GUI/js/script.js index 7741528..101b2f0 100644 --- a/GUI/js/script.js +++ b/GUI/js/script.js @@ -8,17 +8,55 @@ import './chat.js'; import './settings.js'; import './logs.js'; -window.closeDiscordNotification = function () { - const notification = document.getElementById('discordNotification'); - if (notification) { - notification.classList.add('hidden'); - setTimeout(() => { - notification.style.display = 'none'; - }, 300); +// Initialize i18n immediately (before DOMContentLoaded) +let i18nInitialized = false; +(async () => { + const savedLang = await window.electronAPI?.loadLanguage(); + await i18n.init(savedLang); + i18nInitialized = true; + + // Update language selector if DOM is already loaded + if (document.readyState === 'complete' || document.readyState === 'interactive') { + updateLanguageSelector(); } -}; +})(); + +function updateLanguageSelector() { + const langSelect = document.getElementById('languageSelect'); + if (langSelect) { + // Clear existing options + langSelect.innerHTML = ''; + + const languages = i18n.getAvailableLanguages(); + const currentLang = i18n.getCurrentLanguage(); + + languages.forEach(lang => { + const option = document.createElement('option'); + option.value = lang.code; + option.textContent = lang.name; + if (lang.code === currentLang) { + option.selected = true; + } + langSelect.appendChild(option); + }); + + // Handle language change (add listener only once) + if (!langSelect.hasAttribute('data-listener-added')) { + langSelect.addEventListener('change', async (e) => { + await i18n.setLanguage(e.target.value); + }); + langSelect.setAttribute('data-listener-added', 'true'); + } + } +} document.addEventListener('DOMContentLoaded', () => { + // Populate language selector (wait for i18n if needed) + if (i18nInitialized) { + updateLanguageSelector(); + } + + // Discord notification const notification = document.getElementById('discordNotification'); if (notification) { const dismissed = localStorage.getItem('discordNotificationDismissed'); @@ -32,8 +70,13 @@ document.addEventListener('DOMContentLoaded', () => { } }); -const originalClose = window.closeDiscordNotification; -window.closeDiscordNotification = function () { +window.closeDiscordNotification = function() { + const notification = document.getElementById('discordNotification'); + if (notification) { + notification.classList.add('hidden'); + setTimeout(() => { + notification.style.display = 'none'; + }, 300); + } localStorage.setItem('discordNotificationDismissed', 'true'); - originalClose(); }; \ No newline at end of file diff --git a/GUI/js/settings.js b/GUI/js/settings.js index b62979b..0ec95e6 100644 --- a/GUI/js/settings.js +++ b/GUI/js/settings.js @@ -22,7 +22,12 @@ let uuidList; let customUuidInput; let setCustomUuidBtn; -function showCustomConfirm(message, title = 'Confirm Action', onConfirm, onCancel = null, confirmText = 'Confirm', cancelText = 'Cancel') { +function showCustomConfirm(message, title, onConfirm, onCancel = null, confirmText, cancelText) { + // Apply defaults with i18n support + title = title || (window.i18n ? window.i18n.t('confirm.defaultTitle') : 'Confirm Action'); + confirmText = confirmText || (window.i18n ? window.i18n.t('common.confirm') : 'Confirm'); + cancelText = cancelText || (window.i18n ? window.i18n.t('common.cancel') : 'Cancel'); + const existingModal = document.querySelector('.custom-confirm-modal'); if (existingModal) { existingModal.remove(); @@ -313,9 +318,11 @@ async function saveDiscordRPC() { // Feedback visuel pour l'utilisateur if (enabled) { - showNotification('Discord Rich Presence enabled', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.discordEnabled') : 'Discord Rich Presence enabled'; + showNotification(msg, 'success'); } else { - showNotification('Discord Rich Presence disabled', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.discordDisabled') : 'Discord Rich Presence disabled'; + showNotification(msg, 'success'); } } else { throw new Error('Failed to save Discord RPC setting'); @@ -323,7 +330,8 @@ async function saveDiscordRPC() { } } catch (error) { console.error('Error saving Discord RPC setting:', error); - showNotification('Failed to save Discord setting', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.discordSaveFailed') : 'Failed to save Discord setting'; + showNotification(msg, 'error'); } } @@ -347,16 +355,19 @@ async function savePlayerName() { const playerName = settingsPlayerName.value.trim(); if (!playerName) { - showNotification('Please enter a valid player name', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.playerNameRequired') : 'Please enter a valid player name'; + showNotification(msg, 'error'); return; } await window.electronAPI.saveUsername(playerName); - showNotification('Player name saved successfully', 'success'); + const successMsg = window.i18n ? window.i18n.t('notifications.playerNameSaved') : 'Player name saved successfully'; + showNotification(successMsg, 'success'); } catch (error) { console.error('Error saving player name:', error); - showNotification('Failed to save player name', 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.playerNameSaveFailed') : 'Failed to save player name'; + showNotification(errorMsg, 'error'); } } @@ -507,34 +518,43 @@ async function copyCurrentUuid() { const uuid = currentUuidDisplay ? currentUuidDisplay.value : modalCurrentUuid?.value; if (uuid && navigator.clipboard) { await navigator.clipboard.writeText(uuid); - showNotification('UUID copied to clipboard!', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!'; + showNotification(msg, 'success'); } } catch (error) { console.error('Error copying UUID:', error); - showNotification('Failed to copy UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidCopyFailed') : 'Failed to copy UUID'; + showNotification(msg, 'error'); } } async function regenerateCurrentUuid() { try { if (window.electronAPI && window.electronAPI.resetCurrentUserUuid) { + const message = window.i18n ? window.i18n.t('confirm.regenerateUuidMessage') : 'Are you sure you want to generate a new UUID? This will change your player identity.'; + const title = window.i18n ? window.i18n.t('confirm.regenerateUuidTitle') : 'Generate New UUID'; + const confirmBtn = window.i18n ? window.i18n.t('confirm.regenerateUuidButton') : 'Generate'; + const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel'; + showCustomConfirm( - 'Are you sure you want to generate a new UUID? This will change your player identity.', - 'Generate New UUID', + message, + title, async () => { await performRegenerateUuid(); }, null, - 'Generate', - 'Cancel' + confirmBtn, + cancelBtn ); } else { console.error('electronAPI.resetCurrentUserUuid not available'); - showNotification('UUID regeneration not available', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidRegenNotAvailable') : 'UUID regeneration not available'; + showNotification(msg, 'error'); } } catch (error) { console.error('Error in regenerateCurrentUuid:', error); - showNotification('Failed to regenerate UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidRegenFailed') : 'Failed to regenerate UUID'; + showNotification(msg, 'error'); } } @@ -544,7 +564,8 @@ async function performRegenerateUuid() { if (result.success && result.uuid) { if (currentUuidDisplay) currentUuidDisplay.value = result.uuid; if (modalCurrentUuid) modalCurrentUuid.value = result.uuid; - showNotification('New UUID generated successfully!', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.uuidGenerated') : 'New UUID generated successfully!'; + showNotification(msg, 'success'); if (uuidModal && uuidModal.style.display !== 'none') { await loadAllUuids(); @@ -554,7 +575,8 @@ async function performRegenerateUuid() { } } catch (error) { console.error('Error regenerating UUID:', error); - showNotification(`Failed to regenerate UUID: ${error.message}`, 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidRegenFailed').replace('{error}', error.message) : `Failed to regenerate UUID: ${error.message}`; + showNotification(msg, 'error'); } } @@ -647,19 +669,22 @@ async function generateNewUuid() { const newUuid = await window.electronAPI.generateNewUuid(); if (newUuid) { if (customUuidInput) customUuidInput.value = newUuid; - showNotification('New UUID generated!', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.uuidGeneratedShort') : 'New UUID generated!'; + showNotification(msg, 'success'); } } } catch (error) { console.error('Error generating new UUID:', error); - showNotification('Failed to generate new UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidGenerateFailed') : 'Failed to generate new UUID'; + showNotification(msg, 'error'); } } async function setCustomUuid() { try { if (!customUuidInput || !customUuidInput.value.trim()) { - showNotification('Please enter a UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidRequired') : 'Please enter a UUID'; + showNotification(msg, 'error'); return; } @@ -667,23 +692,30 @@ async function setCustomUuid() { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (!uuidRegex.test(uuid)) { - showNotification('Invalid UUID format', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidInvalidFormat') : 'Invalid UUID format'; + showNotification(msg, 'error'); return; } + const message = window.i18n ? window.i18n.t('confirm.setCustomUuidMessage') : 'Are you sure you want to set this custom UUID? This will change your player identity.'; + const title = window.i18n ? window.i18n.t('confirm.setCustomUuidTitle') : 'Set Custom UUID'; + const confirmBtn = window.i18n ? window.i18n.t('confirm.setCustomUuidButton') : 'Set UUID'; + const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel'; + showCustomConfirm( - 'Are you sure you want to set this custom UUID? This will change your player identity.', - 'Set Custom UUID', + message, + title, async () => { await performSetCustomUuid(uuid); }, null, - 'Set UUID', - 'Cancel' + confirmBtn, + cancelBtn ); } catch (error) { console.error('Error in setCustomUuid:', error); - showNotification('Failed to set custom UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidSetFailed') : 'Failed to set custom UUID'; + showNotification(msg, 'error'); } } @@ -698,7 +730,8 @@ async function setCustomUuid() { if (modalCurrentUuid) modalCurrentUuid.value = uuid; if (customUuidInput) customUuidInput.value = ''; - showNotification('Custom UUID set successfully!', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.uuidSetSuccess') : 'Custom UUID set successfully!'; + showNotification(msg, 'success'); await loadAllUuids(); } else { @@ -707,7 +740,8 @@ async function setCustomUuid() { } } catch (error) { console.error('Error setting custom UUID:', error); - showNotification(`Failed to set custom UUID: ${error.message}`, 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidSetFailed').replace('{error}', error.message) : `Failed to set custom UUID: ${error.message}`; + showNotification(msg, 'error'); } } @@ -715,29 +749,37 @@ window.copyUuid = async function(uuid) { try { if (navigator.clipboard) { await navigator.clipboard.writeText(uuid); - showNotification('UUID copied to clipboard!', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.uuidCopied') : 'UUID copied to clipboard!'; + showNotification(msg, 'success'); } } catch (error) { console.error('Error copying UUID:', error); - showNotification('Failed to copy UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidCopyFailed') : 'Failed to copy UUID'; + showNotification(msg, 'error'); } }; window.deleteUuid = async function(username) { try { + const message = window.i18n ? window.i18n.t('confirm.deleteUuidMessage').replace('{username}', username) : `Are you sure you want to delete the UUID for "${username}"? This action cannot be undone.`; + const title = window.i18n ? window.i18n.t('confirm.deleteUuidTitle') : 'Delete UUID'; + const confirmBtn = window.i18n ? window.i18n.t('confirm.deleteUuidButton') : 'Delete'; + const cancelBtn = window.i18n ? window.i18n.t('common.cancel') : 'Cancel'; + showCustomConfirm( - `Are you sure you want to delete the UUID for "${username}"? This action cannot be undone.`, - 'Delete UUID', + message, + title, async () => { await performDeleteUuid(username); }, null, - 'Delete', - 'Cancel' + confirmBtn, + cancelBtn ); } catch (error) { console.error('Error in deleteUuid:', error); - showNotification('Failed to delete UUID', 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteFailed') : 'Failed to delete UUID'; + showNotification(msg, 'error'); } }; @@ -747,7 +789,8 @@ async function performDeleteUuid(username) { const result = await window.electronAPI.deleteUuidForUser(username); if (result.success) { - showNotification('UUID deleted successfully!', 'success'); + const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteSuccess') : 'UUID deleted successfully!'; + showNotification(msg, 'success'); await loadAllUuids(); } else { throw new Error(result.error || 'Failed to delete UUID'); @@ -755,7 +798,8 @@ async function performDeleteUuid(username) { } } catch (error) { console.error('Error deleting UUID:', error); - showNotification(`Failed to delete UUID: ${error.message}`, 'error'); + const msg = window.i18n ? window.i18n.t('notifications.uuidDeleteFailed').replace('{error}', error.message) : `Failed to delete UUID: ${error.message}`; + showNotification(msg, 'error'); } } diff --git a/GUI/js/ui.js b/GUI/js/ui.js index 7bf1e5d..f417c87 100644 --- a/GUI/js/ui.js +++ b/GUI/js/ui.js @@ -366,7 +366,7 @@ function lockPlayButton(locked) { if (!playButton.getAttribute('data-original-text')) { playButton.setAttribute('data-original-text', spanElement.textContent); } - spanElement.textContent = 'CHECKING...'; + spanElement.textContent = window.i18n ? window.i18n.t('play.checking') : 'CHECKING...'; } console.log('Play button locked'); @@ -377,9 +377,9 @@ function lockPlayButton(locked) { playButton.removeAttribute('data-locked'); const spanElement = playButton.querySelector('span'); - const originalText = playButton.getAttribute('data-original-text'); - if (spanElement && originalText) { - spanElement.textContent = originalText; + if (spanElement) { + // Use i18n to get the current translation instead of restoring saved text + spanElement.textContent = window.i18n ? window.i18n.t('play.playButton') : 'PLAY HYTALE'; playButton.removeAttribute('data-original-text'); } @@ -393,7 +393,8 @@ async function acceptFirstLaunchUpdate() { const existingGame = window.firstLaunchExistingGame; if (!existingGame) { - showNotification('Error: Game data not found', 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.gameDataNotFound') : 'Error: Game data not found'; + showNotification(errorMsg, 'error'); return; } @@ -410,7 +411,8 @@ async function acceptFirstLaunchUpdate() { try { showProgress(); - updateProgress({ message: 'Starting mandatory game update...', percent: 0 }); + const updateMsg = window.i18n ? window.i18n.t('progress.startingUpdate') : 'Starting mandatory game update...'; + updateProgress({ message: updateMsg, percent: 0 }); const result = await window.electronAPI.acceptFirstLaunchUpdate(existingGame); @@ -424,10 +426,12 @@ async function acceptFirstLaunchUpdate() { if (result.success) { hideProgress(); - showNotification('Game updated successfully! 🎉', 'success'); + const successMsg = window.i18n ? window.i18n.t('notifications.gameUpdatedSuccess') : 'Game updated successfully! 🎉'; + showNotification(successMsg, 'success'); } else { hideProgress(); - showNotification(`Update failed: ${result.error}`, 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.updateFailed').replace('{error}', result.error) : `Update failed: ${result.error}`; + showNotification(errorMsg, 'error'); } } catch (error) { if (modal) { @@ -435,7 +439,8 @@ async function acceptFirstLaunchUpdate() { } lockPlayButton(false); hideProgress(); - showNotification(`Update error: ${error.message}`, 'error'); + const errorMsg = window.i18n ? window.i18n.t('notifications.updateError').replace('{error}', error.message) : `Update error: ${error.message}`; + showNotification(errorMsg, 'error'); } } diff --git a/GUI/locales/en.json b/GUI/locales/en.json new file mode 100644 index 0000000..8e48030 --- /dev/null +++ b/GUI/locales/en.json @@ -0,0 +1,234 @@ +{ + "nav": { + "play": "Play", + "mods": "Mods", + "news": "News", + "chat": "Players Chat", + "settings": "Settings", + "skins": "Skins" + }, + "header": { + "playersLabel": "Players:", + "manageProfiles": "Manage Profiles", + "defaultProfile": "Default", + "f2p": "FREE TO PLAY" + }, + "install": { + "title": "FREE TO PLAY LAUNCHER", + "playerName": "Player Name", + "playerNamePlaceholder": "Enter your name", + "customInstallation": "Custom Installation", + "installationFolder": "Installation Folder", + "pathPlaceholder": "Default location", + "browse": "Browse", + "installButton": "INSTALL HYTALE", + "installing": "INSTALLING..." + }, + "play": { + "ready": "READY TO PLAY", + "subtitle": "Launch Hytale and enter the adventure", + "playButton": "PLAY HYTALE", + "latestNews": "LATEST NEWS", + "viewAll": "VIEW ALL", + "checking": "CHECKING...", + "play": "PLAY" + }, + "mods": { + "searchPlaceholder": "Search mods...", + "myMods": "MY MODS", + "previous": "PREVIOUS", + "next": "NEXT", + "page": "Page", + "of": "of", + "modalTitle": "MY MODS", + "noModsFound": "No Mods Found", + "noModsFoundDesc": "Try adjusting your search", + "noModsInstalled": "No Mods Installed", + "noModsInstalledDesc": "Add mods from CurseForge or import local files", + "view": "VIEW", + "install": "INSTALL", + "installed": "INSTALLED", + "enable": "ENABLE", + "disable": "DISABLE", + "active": "ACTIVE", + "disabled": "DISABLED", + "delete": "Delete mod", + "noDescription": "No description available", + "confirmDelete": "Are you sure you want to delete \"{name}\"?", + "confirmDeleteDesc": "This action cannot be undone.", + "confirmDeletion": "Confirm Deletion" + }, + "news": { + "title": "ALL NEWS", + "readMore": "Read More" + }, + "chat": { + "title": "PLAYERS CHAT", + "pickColor": "Color", + "inputPlaceholder": "Type your message...", + "send": "Send", + "online": "online", + "charCounter": "{current}/{max}", + "secureChat": "Secure chat - Links are censored", + "joinChat": "Join Chat", + "chooseUsername": "Choose a username to join the Players Chat", + "username": "Username", + "usernamePlaceholder": "Enter your username...", + "usernameHint": "3-20 characters, letters, numbers, - and _ only", + "joinButton": "Join Chat", + "colorModal": { + "title": "Customize Username Color", + "chooseSolid": "Choose a solid color:", + "customColor": "Custom color:", + "preview": "Preview:", + "previewUsername": "Username", + "apply": "Apply Color" + } + }, + "settings": { + "title": "SETTINGS", + "java": "Java Runtime", + "useCustomJava": "Use Custom Java Path", + "javaDescription": "Override the bundled Java runtime with your own installation", + "javaPath": "Java Executable Path", + "javaPathPlaceholder": "Select Java path...", + "javaBrowse": "Browse", + "javaHint": "Select the Java installation folder (supports Windows, Mac, Linux)", + "discord": "Discord Integration", + "enableRPC": "Enable Discord Rich Presence", + "discordDescription": "Show your launcher activity on Discord", + "game": "Game Options", + "playerName": "Player Name", + "playerNamePlaceholder": "Enter your player name", + "playerNameHint": "This name will be used in-game (1-16 characters)", + "openGameLocation": "Open Game Location", + "openGameLocationDesc": "Open the game installation folder", + "account": "Player UUID Management", + "currentUUID": "Current UUID", + "uuidPlaceholder": "Loading UUID...", + "copyUUID": "Copy UUID", + "regenerateUUID": "Regenerate UUID", + "uuidHint": "Your unique player identifier for this username", + "manageUUIDs": "Manage All UUIDs", + "manageUUIDsDesc": "View and manage all player UUIDs", + "language": "Language", + "selectLanguage": "Select Language", + "repairGame": "Repair Game", + "reinstallGame": "Reinstall game files (preserves data)", + "gpuPreference": "GPU Preference", + "gpuHint": "Select your preferred GPU (Linux: affects DRI_PRIME)", + "gpuAuto": "Auto", + "gpuIntegrated": "Integrated", + "gpuDedicated": "Dedicated", + "logs": "SYSTEM LOGS", + "logsCopy": "Copy", + "logsRefresh": "Refresh", + "logsFolder": "Open Folder", + "logsLoading": "Loading logs..." + }, + "uuid": { + "modalTitle": "UUID Management", + "currentUserUUID": "Current User UUID", + "allPlayerUUIDs": "All Player UUIDs", + "generateNew": "Generate New UUID", + "loadingUUIDs": "Loading UUIDs...", + "setCustomUUID": "Set Custom UUID", + "customPlaceholder": "Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", + "setUUID": "Set UUID", + "warning": "Warning: Setting a custom UUID will change your current player identity", + "copyTooltip": "Copy UUID", + "regenerateTooltip": "Generate New UUID" + }, + "profiles": { + "modalTitle": "Manage Profiles", + "newProfilePlaceholder": "New Profile Name", + "createProfile": "Create Profile" + }, + "discord": { + "notificationText": "Join our Discord community!", + "joinButton": "Join Discord" + }, + "skins": { + "title": "Skins", + "comingSoon": "Skin customization coming soon..." + }, + "common": { + "confirm": "Confirm", + "cancel": "Cancel", + "save": "Save", + "close": "Close", + "delete": "Delete", + "edit": "Edit", + "loading": "Loading...", + "apply": "Apply" + }, + "notifications": { + "gameDataNotFound": "Error: Game data not found", + "gameUpdatedSuccess": "Game updated successfully! 🎉", + "updateFailed": "Update failed: {error}", + "updateError": "Update error: {error}", + "discordEnabled": "Discord Rich Presence enabled", + "discordDisabled": "Discord Rich Presence disabled", + "discordSaveFailed": "Failed to save Discord setting", + "playerNameRequired": "Please enter a valid player name", + "playerNameSaved": "Player name saved successfully", + "playerNameSaveFailed": "Failed to save player name", + "uuidCopied": "UUID copied to clipboard!", + "uuidCopyFailed": "Failed to copy UUID", + "uuidRegenNotAvailable": "UUID regeneration not available", + "uuidRegenFailed": "Failed to regenerate UUID", + "uuidGenerated": "New UUID generated successfully!", + "uuidGeneratedShort": "New UUID generated!", + "uuidGenerateFailed": "Failed to generate new UUID", + "uuidRequired": "Please enter a UUID", + "uuidInvalidFormat": "Invalid UUID format", + "uuidSetFailed": "Failed to set custom UUID", + "uuidSetSuccess": "Custom UUID set successfully!", + "uuidDeleteFailed": "Failed to delete UUID", + "uuidDeleteSuccess": "UUID deleted successfully!", + "modsDownloading": "Downloading {name}...", + "modsTogglingMod": "Toggling mod...", + "modsDeletingMod": "Deleting mod...", + "modsLoadingMods": "Loading mods from CurseForge...", + "modsInstalledSuccess": "{name} installed successfully! 🎉", + "modsDeletedSuccess": "{name} deleted successfully", + "modsDownloadFailed": "Failed to download mod: {error}", + "modsToggleFailed": "Failed to toggle mod: {error}", + "modsDeleteFailed": "Failed to delete mod: {error}", + "modsModNotFound": "Mod information not found" + }, + "confirm": { + "defaultTitle": "Confirm action", + "regenerateUuidTitle": "Generate new UUID", + "regenerateUuidMessage": "Are you sure you want to generate a new UUID? This will change your player identity.", + "regenerateUuidButton": "Generate", + "setCustomUuidTitle": "Set custom UUID", + "setCustomUuidMessage": "Are you sure you want to set this custom UUID? This will change your player identity.", + "setCustomUuidButton": "Set UUID", + "deleteUuidTitle": "Delete UUID", + "deleteUuidMessage": "Are you sure you want to delete the UUID for \"{username}\"? This action cannot be undone.", + "deleteUuidButton": "Delete", + "uninstallGameTitle": "Uninstall game", + "uninstallGameMessage": "Are you sure you want to uninstall Hytale? All game files will be deleted.", + "uninstallGameButton": "Uninstall" + }, + "progress": { + "initializing": "Initializing...", + "downloading": "Downloading...", + "installing": "Installing...", + "extracting": "Extracting...", + "verifying": "Verifying...", + "switchingProfile": "Switching profile...", + "profileSwitched": "Profile switched!", + "startingGame": "Starting game...", + "launching": "LAUNCHING...", + "uninstallingGame": "Uninstalling game...", + "gameUninstalled": "Game uninstalled successfully!", + "uninstallFailed": "Uninstall failed: {error}", + "startingUpdate": "Starting mandatory game update...", + "installationComplete": "Installation completed successfully!", + "installationFailed": "Installation failed: {error}", + "installingGameFiles": "Installing game files...", + "installComplete": "Installation complete!" + } +} diff --git a/GUI/locales/es.json b/GUI/locales/es.json new file mode 100644 index 0000000..fc3f4c4 --- /dev/null +++ b/GUI/locales/es.json @@ -0,0 +1,234 @@ +{ + "nav": { + "play": "Jugar", + "mods": "Mods", + "news": "Noticias", + "chat": "Chat de Jugadores", + "settings": "Configuración", + "skins": "Aspectos" + }, + "header": { + "playersLabel": "Jugadores:", + "manageProfiles": "Gestionar Perfiles", + "defaultProfile": "Predeterminado", + "f2p": "FREE TO PLAY" + }, + "install": { + "title": "LAUNCHER GRATUITO", + "playerName": "Nombre del Jugador", + "playerNamePlaceholder": "Ingresa tu nombre", + "customInstallation": "Instalación Personalizada", + "installationFolder": "Carpeta de Instalación", + "pathPlaceholder": "Ubicación predeterminada", + "browse": "Examinar", + "installButton": "INSTALAR HYTALE", + "installing": "INSTALANDO..." + }, + "play": { + "ready": "LISTO PARA JUGAR", + "subtitle": "Inicia Hytale y entra en la aventura", + "playButton": "JUGAR HYTALE", + "latestNews": "ÚLTIMAS NOTICIAS", + "viewAll": "VER TODO", + "checking": "VERIFICANDO...", + "play": "JUGAR" + }, + "mods": { + "searchPlaceholder": "Buscar mods...", + "myMods": "MIS MODS", + "previous": "ANTERIOR", + "next": "SIGUIENTE", + "page": "Página", + "of": "de", + "modalTitle": "MIS MODS", + "noModsFound": "No se encontraron mods", + "noModsFoundDesc": "Intenta ajustar tu búsqueda", + "noModsInstalled": "No hay mods instalados", + "noModsInstalledDesc": "Añade mods desde CurseForge o importa archivos locales", + "view": "VER", + "install": "INSTALAR", + "installed": "INSTALADO", + "enable": "ACTIVAR", + "disable": "DESACTIVAR", + "active": "ACTIVO", + "disabled": "DESACTIVADO", + "delete": "Eliminar mod", + "noDescription": "Sin descripción disponible", + "confirmDelete": "¿Estás seguro de que quieres eliminar \"{name}\"?", + "confirmDeleteDesc": "Esta acción no se puede deshacer.", + "confirmDeletion": "Confirmar eliminación" + }, + "news": { + "title": "TODAS LAS NOTICIAS", + "readMore": "Leer más" + }, + "chat": { + "title": "CHAT DE JUGADORES", + "pickColor": "Color", + "inputPlaceholder": "Escribe tu mensaje...", + "send": "Enviar", + "online": "en línea", + "charCounter": "{current}/{max}", + "secureChat": "Chat seguro - Los enlaces están censurados", + "joinChat": "Unirse al chat", + "chooseUsername": "Elige un nombre de usuario para unirte al chat de jugadores", + "username": "Nombre de usuario", + "usernamePlaceholder": "Ingresa tu nombre de usuario...", + "usernameHint": "3-20 caracteres, letras, números, - y _ solamente", + "joinButton": "Unirse al Chat", + "colorModal": { + "title": "Personalizar color del nombre", + "chooseSolid": "Elige un color sólido:", + "customColor": "Color personalizado:", + "preview": "Vista previa:", + "previewUsername": "Nombre de usuario", + "apply": "Aplicar color" + } + }, + "settings": { + "title": "CONFIGURACIÓN", + "java": "Entorno Java", + "useCustomJava": "Usar ruta de Java personalizada", + "javaDescription": "Reemplaza el entorno Java incluido con tu propia instalación", + "javaPath": "Ruta del ejecutable Java", + "javaPathPlaceholder": "Selecciona la ruta de Java...", + "javaBrowse": "Examinar", + "javaHint": "Selecciona la carpeta de instalación de Java (compatible con Windows, Mac, Linux)", + "discord": "Integración con Discord", + "enableRPC": "Habilitar Discord Rich Presence", + "discordDescription": "Muestra tu actividad del launcher en Discord", + "game": "Opciones del juego", + "playerName": "Nombre del jugador", + "playerNamePlaceholder": "Ingresa tu nombre de jugador", + "playerNameHint": "Este nombre se usará en el juego (1-16 caracteres)", + "openGameLocation": "Abrir ubicación del juego", + "openGameLocationDesc": "Abre la carpeta de instalación del juego", + "account": "Gestión de UUID del jugador", + "currentUUID": "UUID actual", + "uuidPlaceholder": "Cargando UUID...", + "copyUUID": "Copiar UUID", + "regenerateUUID": "Regenerar UUID", + "uuidHint": "Tu identificador único de jugador para este nombre de usuario", + "manageUUIDs": "Gestionar todos los UUIDs", + "manageUUIDsDesc": "Ver y gestionar todos los UUIDs de jugadores", + "language": "Idioma", + "selectLanguage": "Seleccionar idioma", + "repairGame": "Reparar juego", + "reinstallGame": "Reinstalar archivos del juego (conserva los datos)", + "gpuPreference": "Preferencia de GPU", + "gpuHint": "Selecciona tu GPU preferida (Linux: afecta DRI_PRIME)", + "gpuAuto": "Automático", + "gpuIntegrated": "Integrada", + "gpuDedicated": "Dedicada", + "logs": "REGISTROS DEL SISTEMA", + "logsCopy": "Copiar", + "logsRefresh": "Actualizar", + "logsFolder": "Abrir Carpeta", + "logsLoading": "Cargando registros..." + }, + "uuid": { + "modalTitle": "Gestión de UUID", + "currentUserUUID": "UUID del usuario actual", + "allPlayerUUIDs": "Todos los UUIDs de jugadores", + "generateNew": "Generar nuevo UUID", + "loadingUUIDs": "Cargando UUIDs...", + "setCustomUUID": "Establecer UUID personalizado", + "customPlaceholder": "Ingresa un UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", + "setUUID": "Establecer UUID", + "warning": "Advertencia: Establecer un UUID personalizado cambiará tu identidad de jugador actual", + "copyTooltip": "Copiar UUID", + "regenerateTooltip": "Generar nuevo UUID" + }, + "profiles": { + "modalTitle": "Gestionar perfiles", + "newProfilePlaceholder": "Nombre del nuevo perfil", + "createProfile": "Crear perfil" + }, + "discord": { + "notificationText": "¡Únete a nuestra comunidad de Discord!", + "joinButton": "Unirse a Discord" + }, + "skins": { + "title": "Aspectos", + "comingSoon": "Personalización de aspectos próximamente..." + }, + "common": { + "confirm": "Confirmar", + "cancel": "Cancelar", + "save": "Guardar", + "close": "Cerrar", + "delete": "Eliminar", + "edit": "Editar", + "loading": "Cargando...", + "apply": "Aplicar" + }, + "notifications": { + "gameDataNotFound": "Error: No se encontraron datos del juego", + "gameUpdatedSuccess": "¡Juego actualizado con éxito! 🎉", + "updateFailed": "Actualización fallida: {error}", + "updateError": "Error de actualización: {error}", + "discordEnabled": "Discord Rich Presence habilitado", + "discordDisabled": "Discord Rich Presence deshabilitado", + "discordSaveFailed": "Error al guardar la configuración de Discord", + "playerNameRequired": "Por favor ingresa un nombre de jugador válido", + "playerNameSaved": "Nombre de jugador guardado con éxito", + "playerNameSaveFailed": "Error al guardar el nombre de jugador", + "uuidCopied": "¡UUID copiado al portapapeles!", + "uuidCopyFailed": "Error al copiar UUID", + "uuidRegenNotAvailable": "Regeneración de UUID no disponible", + "uuidRegenFailed": "Error al regenerar UUID", + "uuidGenerated": "¡Nuevo UUID generado con éxito!", + "uuidGeneratedShort": "¡Nuevo UUID generado!", + "uuidGenerateFailed": "Error al generar nuevo UUID", + "uuidRequired": "Por favor ingresa un UUID", + "uuidInvalidFormat": "Formato de UUID inválido", + "uuidSetFailed": "Error al establecer UUID personalizado", + "uuidSetSuccess": "¡UUID personalizado establecido con éxito!", + "uuidDeleteFailed": "Error al eliminar UUID", + "uuidDeleteSuccess": "¡UUID eliminado con éxito!", + "modsDownloading": "Descargando {name}...", + "modsTogglingMod": "Alternando mod...", + "modsDeletingMod": "Eliminando mod...", + "modsLoadingMods": "Cargando mods desde CurseForge...", + "modsInstalledSuccess": "¡{name} instalado con éxito! 🎉", + "modsDeletedSuccess": "{name} eliminado con éxito", + "modsDownloadFailed": "Error al descargar mod: {error}", + "modsToggleFailed": "Error al alternar mod: {error}", + "modsDeleteFailed": "Error al eliminar mod: {error}", + "modsModNotFound": "Información del mod no encontrada" + }, + "confirm": { + "defaultTitle": "Confirmar acción", + "regenerateUuidTitle": "Generar nuevo UUID", + "regenerateUuidMessage": "¿Estás seguro de que quieres generar un nuevo UUID? Esto cambiará tu identidad de jugador.", + "regenerateUuidButton": "Generar", + "setCustomUuidTitle": "Establecer UUID personalizado", + "setCustomUuidMessage": "¿Estás seguro de que quieres establecer este UUID personalizado? Esto cambiará tu identidad de jugador.", + "setCustomUuidButton": "Establecer UUID", + "deleteUuidTitle": "Eliminar UUID", + "deleteUuidMessage": "¿Estás seguro de que quieres eliminar el UUID de \"{username}\"? Esta acción no se puede deshacer.", + "deleteUuidButton": "Eliminar", + "uninstallGameTitle": "Desinstalar juego", + "uninstallGameMessage": "¿Estás seguro de que quieres desinstalar Hytale? Se eliminarán todos los archivos del juego.", + "uninstallGameButton": "Desinstalar" + }, + "progress": { + "initializing": "Inicializando...", + "downloading": "Descargando...", + "installing": "Instalando...", + "extracting": "Extrayendo...", + "verifying": "Verificando...", + "switchingProfile": "Cambiando perfil...", + "profileSwitched": "¡Perfil cambiado!", + "startingGame": "Iniciando juego...", + "launching": "INICIANDO...", + "uninstallingGame": "Desinstalando juego...", + "gameUninstalled": "¡Juego desinstalado con éxito!", + "uninstallFailed": "Desinstalación fallida: {error}", + "startingUpdate": "Iniciando actualización obligatoria del juego...", + "installationComplete": "¡Instalación completada con éxito!", + "installationFailed": "Instalación fallida: {error}", + "installingGameFiles": "Instalando archivos del juego...", + "installComplete": "¡Instalación completa!" + } +} diff --git a/GUI/locales/pt-BR.json b/GUI/locales/pt-BR.json new file mode 100644 index 0000000..02c1dcf --- /dev/null +++ b/GUI/locales/pt-BR.json @@ -0,0 +1,234 @@ +{ + "nav": { + "play": "Jogar", + "mods": "Mods", + "news": "Notícias", + "chat": "Chat de Jogadores", + "settings": "Configurações", + "skins": "Aparências" + }, + "header": { + "playersLabel": "Jogadores:", + "manageProfiles": "Gerenciar Perfis", + "defaultProfile": "Padrão", + "f2p": "FREE TO PLAY" + }, + "install": { + "title": "LANÇADOR JOGO GRATUITO", + "playerName": "Nome do Jogador", + "playerNamePlaceholder": "Digite seu nome", + "customInstallation": "Instalação Personalizada", + "installationFolder": "Pasta de Instalação", + "pathPlaceholder": "Local padrão", + "browse": "Procurar", + "installButton": "INSTALAR HYTALE", + "installing": "INSTALANDO..." + }, + "play": { + "ready": "PRONTO PARA JOGAR", + "subtitle": "Inicie Hytale e entre na aventura", + "playButton": "JOGAR HYTALE", + "latestNews": "ÚLTIMAS NOTÍCIAS", + "viewAll": "VER TUDO", + "checking": "VERIFICANDO...", + "play": "JOGAR" + }, + "mods": { + "searchPlaceholder": "Pesquisar mods...", + "myMods": "MEUS MODS", + "previous": "ANTERIOR", + "next": "PRÓXIMO", + "page": "Página", + "of": "de", + "modalTitle": "MEUS MODS", + "noModsFound": "Nenhum mod encontrado", + "noModsFoundDesc": "Tente ajustar sua pesquisa", + "noModsInstalled": "Nenhum mod instalado", + "noModsInstalledDesc": "Adicione mods do CurseForge ou importe arquivos locais", + "view": "VER", + "install": "INSTALAR", + "installed": "INSTALADO", + "enable": "ATIVAR", + "disable": "DESATIVAR", + "active": "ATIVO", + "disabled": "DESATIVADO", + "delete": "Excluir mod", + "noDescription": "Nenhuma descrição disponível", + "confirmDelete": "Tem certeza de que deseja excluir \"{name}\"?", + "confirmDeleteDesc": "Esta ação não pode ser desfeita.", + "confirmDeletion": "Confirmar exclusão" + }, + "news": { + "title": "TODAS AS NOTÍCIAS", + "readMore": "Leia mais" + }, + "chat": { + "title": "CHAT DE JOGADORES", + "pickColor": "Cor", + "inputPlaceholder": "Digite sua mensagem...", + "send": "Enviar", + "online": "online", + "charCounter": "{current}/{max}", + "secureChat": "Chat seguro - Links são censurados", + "joinChat": "Entrar no chat", + "chooseUsername": "Escolha um nome de usuário para entrar no chat de jogadores", + "username": "Nome de usuário", + "usernamePlaceholder": "Digite seu nome de usuário...", + "usernameHint": "3-20 caracteres, letras, números, - e _ apenas", + "joinButton": "Entrar no Chat", + "colorModal": { + "title": "Personalizar cor do nome de usuário", + "chooseSolid": "Escolha uma cor sólida:", + "customColor": "Cor personalizada:", + "preview": "Visualização:", + "previewUsername": "Nome de usuário", + "apply": "Aplicar cor" + } + }, + "settings": { + "title": "CONFIGURAÇÕES", + "java": "Tempo de execução Java", + "useCustomJava": "Usar caminho personalizado do Java", + "javaDescription": "Substitua o tempo de execução Java incluído pela sua própria instalação", + "javaPath": "Caminho do executável Java", + "javaPathPlaceholder": "Selecione o caminho do Java...", + "javaBrowse": "Procurar", + "javaHint": "Selecione a pasta de instalação do Java (suporta Windows, Mac, Linux)", + "discord": "Integração do Discord", + "enableRPC": "Ativar Discord Rich Presence", + "discordDescription": "Mostre sua atividade do lançador no Discord", + "game": "Opções do jogo", + "playerName": "Nome do jogador", + "playerNamePlaceholder": "Digite seu nome de jogador", + "playerNameHint": "Este nome será usado no jogo (1-16 caracteres)", + "openGameLocation": "Abrir local do jogo", + "openGameLocationDesc": "Abra a pasta de instalação do jogo", + "account": "Gerenciamento de UUID do jogador", + "currentUUID": "UUID atual", + "uuidPlaceholder": "Carregando UUID...", + "copyUUID": "Copiar UUID", + "regenerateUUID": "Regenerar UUID", + "uuidHint": "Seu identificador único de jogador para este nome de usuário", + "manageUUIDs": "Gerenciar todos os UUIDs", + "manageUUIDsDesc": "Ver e gerenciar todos os UUIDs de jogadores", + "language": "Idioma", + "selectLanguage": "Selecionar idioma", + "repairGame": "Reparar jogo", + "reinstallGame": "Reinstalar arquivos do jogo (mantém os dados)", + "gpuPreference": "Preferência de GPU", + "gpuHint": "Selecione sua GPU preferida (Linux: afeta o DRI_PRIME)", + "gpuAuto": "Automático", + "gpuIntegrated": "Integrada", + "gpuDedicated": "Dedicada", + "logs": "REGISTROS DO SISTEMA", + "logsCopy": "Copiar", + "logsRefresh": "Atualizar", + "logsFolder": "Abrir Pasta", + "logsLoading": "Carregando registros..." + }, + "uuid": { + "modalTitle": "Gerenciamento de UUID", + "currentUserUUID": "UUID do usuário atual", + "allPlayerUUIDs": "Todos os UUIDs de jogadores", + "generateNew": "Gerar novo UUID", + "loadingUUIDs": "Carregando UUIDs...", + "setCustomUUID": "Definir UUID personalizado", + "customPlaceholder": "Digite um UUID personalizado (formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)", + "setUUID": "Definir UUID", + "warning": "Aviso: Definir um UUID personalizado alterará sua identidade de jogador atual", + "copyTooltip": "Copiar UUID", + "regenerateTooltip": "Gerar novo UUID" + }, + "profiles": { + "modalTitle": "Gerenciar perfis", + "newProfilePlaceholder": "Nome do novo perfil", + "createProfile": "Criar perfil" + }, + "discord": { + "notificationText": "Junte-se à nossa comunidade do Discord!", + "joinButton": "Entrar no Discord" + }, + "skins": { + "title": "Aparências", + "comingSoon": "Personalização de aparências em breve..." + }, + "common": { + "confirm": "Confirmar", + "cancel": "Cancelar", + "save": "Salvar", + "close": "Fechar", + "delete": "Excluir", + "edit": "Editar", + "loading": "Carregando...", + "apply": "Aplicar" + }, + "notifications": { + "gameDataNotFound": "Erro: Dados do jogo não encontrados", + "gameUpdatedSuccess": "Jogo atualizado com sucesso! 🎉", + "updateFailed": "Falha na atualização: {error}", + "updateError": "Erro de atualização: {error}", + "discordEnabled": "Discord Rich Presence ativado", + "discordDisabled": "Discord Rich Presence desativado", + "discordSaveFailed": "Falha ao salvar configuração do Discord", + "playerNameRequired": "Por favor, digite um nome de jogador válido", + "playerNameSaved": "Nome do jogador salvo com sucesso", + "playerNameSaveFailed": "Falha ao salvar o nome do jogador", + "uuidCopied": "UUID copiado para a área de transferência!", + "uuidCopyFailed": "Falha ao copiar UUID", + "uuidRegenNotAvailable": "Regeneração de UUID não disponível", + "uuidRegenFailed": "Falha ao regenerar UUID", + "uuidGenerated": "Novo UUID gerado com sucesso!", + "uuidGeneratedShort": "Novo UUID gerado!", + "uuidGenerateFailed": "Falha ao gerar novo UUID", + "uuidRequired": "Por favor, digite um UUID", + "uuidInvalidFormat": "Formato de UUID inválido", + "uuidSetFailed": "Falha ao definir UUID personalizado", + "uuidSetSuccess": "UUID personalizado definido com sucesso!", + "uuidDeleteFailed": "Falha ao excluir UUID", + "uuidDeleteSuccess": "UUID excluído com sucesso!", + "modsDownloading": "Baixando {name}...", + "modsTogglingMod": "Alternando mod...", + "modsDeletingMod": "Excluindo mod...", + "modsLoadingMods": "Carregando mods do CurseForge...", + "modsInstalledSuccess": "{name} instalado com sucesso! 🎉", + "modsDeletedSuccess": "{name} excluído com sucesso", + "modsDownloadFailed": "Falha ao baixar mod: {error}", + "modsToggleFailed": "Falha ao alternar mod: {error}", + "modsDeleteFailed": "Falha ao excluir mod: {error}", + "modsModNotFound": "Informações do mod não encontradas" + }, + "confirm": { + "defaultTitle": "Confirmar ação", + "regenerateUuidTitle": "Gerar novo UUID", + "regenerateUuidMessage": "Tem certeza de que deseja gerar um novo UUID? Isso alterará sua identidade de jogador.", + "regenerateUuidButton": "Gerar", + "setCustomUuidTitle": "Definir UUID personalizado", + "setCustomUuidMessage": "Tem certeza de que deseja definir este UUID personalizado? Isso alterará sua identidade de jogador.", + "setCustomUuidButton": "Definir UUID", + "deleteUuidTitle": "Excluir UUID", + "deleteUuidMessage": "Tem certeza de que deseja excluir o UUID de \"{username}\"? Esta ação não pode ser desfeita.", + "deleteUuidButton": "Excluir", + "uninstallGameTitle": "Desinstalar jogo", + "uninstallGameMessage": "Tem certeza de que deseja desinstalar Hytale? Todos os arquivos do jogo serão excluídos.", + "uninstallGameButton": "Desinstalar" + }, + "progress": { + "initializing": "Inicializando...", + "downloading": "Baixando...", + "installing": "Instalando...", + "extracting": "Extraindo...", + "verifying": "Verificando...", + "switchingProfile": "Alternando perfil...", + "profileSwitched": "Perfil alternado!", + "startingGame": "Iniciando jogo...", + "launching": "INICIANDO...", + "uninstallingGame": "Desinstalando jogo...", + "gameUninstalled": "Jogo desinstalado com sucesso!", + "uninstallFailed": "Falha na desinstalação: {error}", + "startingUpdate": "Iniciando atualização obrigatória do jogo...", + "installationComplete": "Instalação concluída com sucesso!", + "installationFailed": "Falha na instalação: {error}", + "installingGameFiles": "Instalando arquivos do jogo...", + "installComplete": "Instalação concluída!" + } +} diff --git a/GUI/style.css b/GUI/style.css index b14342f..f1fc2c4 100644 --- a/GUI/style.css +++ b/GUI/style.css @@ -4033,6 +4033,17 @@ body { cursor: not-allowed; } +/* Language selector styling */ +select.settings-input { + cursor: pointer; +} + +select.settings-input option { + background: #1a1a1a; + color: white; + padding: 0.5rem; +} + .settings-button-group { margin-bottom: 1rem; } diff --git a/README.md b/README.md index 0ea188b..2f48819 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ See [BUILD.md](BUILD.md) for comprehensive build instructions. ## 📋 Changelog +### 🆕 v2.0.ba *(Minor Update: Performance & Utilities)* +[TODO] add features list here + ### 🆕 v2.0.2a *(Minor Update)* - 🧑‍🚀 **Profiles System** — Added proper profile management: create, switch, and delete profiles. Each profile now has its own **isolated mod list**. - 🔒 **Mod Isolation** — Fixed ModManager so mods are **strictly scoped to the active profile**. Browsing and installing now only affects the selected profile. diff --git a/backend/core/config.js b/backend/core/config.js index c5f2d1b..23332a8 100644 --- a/backend/core/config.js +++ b/backend/core/config.js @@ -147,6 +147,15 @@ function loadDiscordRPC() { return config.discordRPC !== undefined ? config.discordRPC : true; } +function saveLanguage(language) { + saveConfig({ language: language || 'en' }); +} + +function loadLanguage() { + const config = loadConfig(); + return config.language || 'en'; +} + function saveModsToConfig(mods) { try { const config = loadConfig(); @@ -302,6 +311,8 @@ module.exports = { loadInstallPath, saveDiscordRPC, loadDiscordRPC, + saveLanguage, + loadLanguage, saveModsToConfig, loadModsFromConfig, isFirstLaunch, diff --git a/backend/launcher.js b/backend/launcher.js index c6c2f38..cadee5e 100644 --- a/backend/launcher.js +++ b/backend/launcher.js @@ -15,6 +15,8 @@ const { loadInstallPath, saveDiscordRPC, loadDiscordRPC, + saveLanguage, + loadLanguage, saveModsToConfig, loadModsFromConfig, getUuidForUser, @@ -118,6 +120,10 @@ module.exports = { saveDiscordRPC, loadDiscordRPC, + // Language functions + saveLanguage, + loadLanguage, + // GPU Preference functions saveGpuPreference, loadGpuPreference, diff --git a/main.js b/main.js index b529f1d..b7e639a 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); const path = require('path'); const fs = require('fs'); -const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher'); +const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher'); const UpdateManager = require('./backend/updateManager'); const logger = require('./backend/logger'); const profileManager = require('./backend/managers/profileManager'); @@ -405,6 +405,15 @@ ipcMain.handle('load-discord-rpc', () => { return loadDiscordRPC(); }); +ipcMain.handle('save-language', (event, language) => { + saveLanguage(language); + return { success: true }; +}); + +ipcMain.handle('load-language', () => { + return loadLanguage(); +}); + ipcMain.handle('select-install-path', async () => { const result = await dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'], diff --git a/preload.js b/preload.js index 03c6947..acbdccc 100644 --- a/preload.js +++ b/preload.js @@ -17,6 +17,8 @@ contextBridge.exposeInMainWorld('electronAPI', { loadInstallPath: () => ipcRenderer.invoke('load-install-path'), saveDiscordRPC: (enabled) => ipcRenderer.invoke('save-discord-rpc', enabled), loadDiscordRPC: () => ipcRenderer.invoke('load-discord-rpc'), + saveLanguage: (language) => ipcRenderer.invoke('save-language', language), + loadLanguage: () => ipcRenderer.invoke('load-language'), selectInstallPath: () => ipcRenderer.invoke('select-install-path'), browseJavaPath: () => ipcRenderer.invoke('browse-java-path'), isGameInstalled: () => ipcRenderer.invoke('is-game-installed'),