Added internationalization support (i18n) (#74)

* - Implemented i18n.
- Updated UI elements to use localized strings for various messages and confirmations.
- Added language selection functionality in settings with appropriate event handling.
- Created English localization file with translations for all new strings.
- Updated backend to save and load user-selected language preferences.

* Add Spanish localization for the GUI

* Add Portuguese (Brazil) localization for the GUI

* update main branch to release/v2.0.2b (#86)

* add more linux pkgs, create auto-release and pre-release feature for Github Actions

* removed package-lock from gitignore

* update .gitignore for local build

* add package-lock.json to maintain stability development

* update version to 2.0.2b also add deps for rpm and arch

* update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap;

* update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap

* fix release build naming

* Prepare release v2.0.2b

* Update localization for game repair and GPU settings

Added new localization entries for game repair and GPU preferences.

* Update spanish localization for game repair and GPU settings

* Update portuguese (brazil) for game repair and GPU settings

* Update localization for system logs in English, Spanish, and Portuguese

---------

Co-authored-by: Fazri Gading <fazrigading@gmail.com>
This commit is contained in:
xSamiVS
2026-01-21 14:41:12 +01:00
committed by GitHub
parent 4ac12e0e24
commit 9ef05e8322
17 changed files with 1195 additions and 213 deletions

89
GUI/js/i18n.js Normal file
View File

@@ -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;

View File

@@ -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);

View File

@@ -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';
}
}

View File

@@ -113,10 +113,15 @@ function displayInstalledMods(mods) {
modsContainer.innerHTML = `
<div class=\"empty-installed-mods\">
<i class=\"fas fa-box-open\"></i>
<h4>No Mods Installed</h4>
<p>Add mods from CurseForge or import local files</p>
<h4 data-i18n="mods.noModsInstalled">No Mods Installed</h4>
<p data-i18n="mods.noModsInstalledDesc">Add mods from CurseForge or import local files</p>
</div>
`;
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) {
<h4 class="installed-mod-name">${mod.name}</h4>
<span class="installed-mod-version">v${mod.version}</span>
</div>
<p class="installed-mod-description">${mod.description || 'No description available'}</p>
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
</div>
<div class="installed-mod-actions">
@@ -163,7 +168,7 @@ function createInstalledModCard(mod) {
${statusText}
</div>
<div class="installed-mod-buttons">
<button id="delete-installed-${mod.id}" class="installed-mod-btn-icon" title="Delete mod">
<button id="delete-installed-${mod.id}" class="installed-mod-btn-icon" title="${window.i18n ? window.i18n.t('mods.delete') : 'Delete mod'}">
<i class="fas fa-trash"></i>
</button>
<button id="toggle-installed-${mod.id}" class="installed-mod-btn-toggle ${toggleBtnClass}">
@@ -180,7 +185,7 @@ async function loadBrowseMods() {
const browseContainer = document.getElementById('browseModsList');
if (!browseContainer) return;
browseContainer.innerHTML = '<div class=\"loading-mods\"><div class=\"loading-spinner\"></div><span>Loading mods from CurseForge...</span></div>';
browseContainer.innerHTML = `<div class="loading-mods"><div class="loading-spinner"></div><span>${window.i18n ? window.i18n.t('mods.loadingMods') : 'Loading mods from CurseForge...'}</span></div>`;
try {
if (!API_KEY || API_KEY.length < 10) {
@@ -264,10 +269,15 @@ function displayBrowseMods(mods) {
browseContainer.innerHTML = `
<div class=\"empty-browse-mods\">
<i class=\"fas fa-search\"></i>
<h4>No Mods Found</h4>
<p>Try adjusting your search</p>
<h4 data-i18n="mods.noModsFound">No Mods Found</h4>
<p data-i18n="mods.noModsFoundDesc">Try adjusting your search</p>
</div>
`;
if (window.i18n) {
const container = browseContainer.querySelector('.empty-browse-mods');
container.querySelector('h4').textContent = window.i18n.t('mods.noModsFound');
container.querySelector('p').textContent = window.i18n.t('mods.noModsFoundDesc');
}
return;
}
@@ -324,16 +334,16 @@ function createBrowseModCard(mod) {
<div class=\"mod-actions\">
<button id=\"view-${mod.id}\" class=\"mod-btn-toggle bg-blue-600 text-white hover:bg-blue-700\" onclick=\"window.modsManager.viewModPage(${mod.id})\">
<i class=\"fas fa-external-link-alt\"></i>
VIEW
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
</button>
${!isInstalled ?
`<button id=\"install-${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
<i class=\"fas fa-download\"></i>
INSTALL
`<button id="install-${mod.id}" class="mod-btn-toggle bg-primary text-black hover:bg-primary/80">
<i class="fas fa-download"></i>
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
</button>` :
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
<i class=\"fas fa-check\"></i>
INSTALLED
`<button class="mod-btn-toggle bg-white/10 text-white" disabled>
<i class="fas fa-check"></i>
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
</button>`
}
</div>
@@ -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) {
<div style="padding: 24px; border-bottom: 1px solid rgba(255,255,255,0.1);">
<div style="display: flex; align-items: center; gap: 12px; color: #ef4444;">
<i class="fas fa-exclamation-triangle" style="font-size: 24px;"></i>
<h3 style="margin: 0; font-size: 1.2rem; font-weight: 600;">Confirm Deletion</h3>
<h3 style="margin: 0; font-size: 1.2rem; font-weight: 600;">${window.i18n ? window.i18n.t('mods.confirmDeletion') : 'Confirm Deletion'}</h3>
</div>
</div>
<div style="padding: 24px; color: #e5e7eb;">
@@ -587,7 +609,7 @@ function showConfirmModal(message, onConfirm, onCancel = null) {
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
">Cancel</button>
">${window.i18n ? window.i18n.t('common.cancel') : 'Cancel'}</button>
<button class="mod-confirm-delete" style="
background: #ef4444;
color: white;
@@ -597,7 +619,7 @@ function showConfirmModal(message, onConfirm, onCancel = null) {
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
">Delete</button>
">${window.i18n ? window.i18n.t('common.delete') : 'Delete'}</button>
</div>
`;
@@ -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');
}
}

View File

@@ -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();
};

View File

@@ -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');
}
}

View File

@@ -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');
}
}