diff --git a/app/app.ejs b/app/app.ejs index e829fa1..75b5ea2 100644 --- a/app/app.ejs +++ b/app/app.ejs @@ -1,7 +1,7 @@ - Helios Launcher + <%= lang('app.title') %> @@ -45,11 +45,5 @@ - \ No newline at end of file diff --git a/app/assets/js/langloader.js b/app/assets/js/langloader.js index 24ab84a..b1f13c9 100644 --- a/app/assets/js/langloader.js +++ b/app/assets/js/langloader.js @@ -1,21 +1,43 @@ const fs = require('fs-extra') const path = require('path') +const toml = require('toml') +const merge = require('lodash.merge') let lang exports.loadLanguage = function(id){ - lang = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.json`))) || {} + lang = merge(lang || {}, toml.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.toml`))) || {}) } -exports.query = function(id){ +exports.query = function(id, placeHolders){ let query = id.split('.') let res = lang for(let q of query){ res = res[q] } - return res === lang ? {} : res + let text = res === lang ? '' : res + if (placeHolders) { + Object.entries(placeHolders).forEach(([key, value]) => { + text = text.replace(`{${key}}`, value) + }) + } + return text } -exports.queryJS = function(id){ - return exports.query(`js.${id}`) +exports.queryJS = function(id, placeHolders){ + return exports.query(`js.${id}`, placeHolders) +} + +exports.queryEJS = function(id, placeHolders){ + return exports.query(`ejs.${id}`, placeHolders) +} + +exports.setupLanguage = function(){ + // Load Language Files + exports.loadLanguage('en_US') + // Uncomment this when translations are ready + //exports.loadLanguage('xx_XX') + + // Load Custom Language File for Launcher Customizer + exports.loadLanguage('_custom') } \ No newline at end of file diff --git a/app/assets/js/preloader.js b/app/assets/js/preloader.js index ae01bb8..95ead73 100644 --- a/app/assets/js/preloader.js +++ b/app/assets/js/preloader.js @@ -23,7 +23,7 @@ DistroAPI['commonDir'] = ConfigManager.getCommonDirectory() DistroAPI['instanceDir'] = ConfigManager.getInstanceDirectory() // Load Strings -LangLoader.loadLanguage('en_US') +LangLoader.setupLanguage() /** * diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 94d311a..7db2765 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -125,7 +125,7 @@ document.getElementById('launch_button').addEventListener('click', async e => { } } catch(err) { loggerLanding.error('Unhandled error in during launch process.', err) - showLaunchFailure('Error During Launch', 'See console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.launch.failureTitle'), Lang.queryJS('landing.launch.failureText')) } }) @@ -145,7 +145,7 @@ document.getElementById('avatarOverlay').onclick = async e => { // Bind selected account function updateSelectedAccount(authUser){ - let username = 'No Account Selected' + let username = Lang.queryJS('landing.selectedAccount.noAccountSelected') if(authUser != null){ if(authUser.displayName != null){ username = authUser.displayName @@ -165,14 +165,14 @@ function updateSelectedServer(serv){ } ConfigManager.setSelectedServer(serv != null ? serv.rawServer.id : null) ConfigManager.save() - server_selection_button.innerHTML = '\u2022 ' + (serv != null ? serv.rawServer.name : 'No Server Selected') + server_selection_button.innerHTML = '• ' + (serv != null ? serv.rawServer.name : Lang.queryJS('landing.noSelection')) if(getCurrentView() === VIEWS.settings){ animateSettingsTabRefresh() } setLaunchEnabled(serv != null) } // Real text is set in uibinder.js on distributionIndexDone. -server_selection_button.innerHTML = '\u2022 Loading..' +server_selection_button.innerHTML = '• ' + Lang.queryJS('landing.selectedServer.loading') server_selection_button.onclick = async e => { e.target.blur() await toggleServerSelection(true) @@ -201,16 +201,14 @@ const refreshMojangStatuses = async function(){ for(let i=0; i + + ${service.name} + ` if(service.essential){ - tooltipEssentialHTML += `
- - ${service.name} -
` + tooltipEssentialHTML += tooltipHTML } else { - tooltipNonEssentialHTML += `
- - ${service.name} -
` + tooltipNonEssentialHTML += tooltipHTML } if(service.status === 'yellow' && status !== 'red'){ @@ -243,14 +241,14 @@ const refreshServerStatus = async (fade = false) => { loggerLanding.info('Refreshing Server Status') const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()) - let pLabel = 'SERVER' - let pVal = 'OFFLINE' + let pLabel = Lang.queryJS('landing.serverStatus.server') + let pVal = Lang.queryJS('landing.serverStatus.offline') try { const servStat = await getServerStatus(47, serv.hostname, serv.port) console.log(servStat) - pLabel = 'PLAYERS' + pLabel = Lang.queryJS('landing.serverStatus.players') pVal = servStat.players.online + '/' + servStat.players.max } catch (err) { @@ -288,7 +286,7 @@ function showLaunchFailure(title, desc){ setOverlayContent( title, desc, - 'Okay' + Lang.queryJS('landing.launch.okay') ) setOverlayHandler(null) toggleOverlay(true) @@ -304,7 +302,7 @@ function showLaunchFailure(title, desc){ */ async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){ - setLaunchDetails('Checking system info..') + setLaunchDetails(Lang.queryJS('landing.systemScan.checking')) toggleLaunchArea(true) setLaunchPercentage(0, 100) @@ -317,30 +315,30 @@ async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){ // If the result is null, no valid Java installation was found. // Show this information to the user. setOverlayContent( - 'No Compatible
Java Installation Found', - `In order to join WesterosCraft, you need a 64-bit installation of Java ${effectiveJavaOptions.suggestedMajor}. Would you like us to install a copy?`, - 'Install Java', - 'Install Manually' + Lang.queryJS('landing.systemScan.noCompatibleJava'), + Lang.queryJS('landing.systemScan.installJavaMessage', { 'major': effectiveJavaOptions.suggestedMajor }), + Lang.queryJS('landing.systemScan.installJava'), + Lang.queryJS('landing.systemScan.installJavaManually') ) setOverlayHandler(() => { - setLaunchDetails('Preparing Java Download..') + setLaunchDetails(Lang.queryJS('landing.systemScan.javaDownloadPrepare')) toggleOverlay(false) try { downloadJava(effectiveJavaOptions, launchAfter) } catch(err) { loggerLanding.error('Unhandled error in Java Download', err) - showLaunchFailure('Error During Java Download', 'See console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.systemScan.javaDownloadFailureTitle'), Lang.queryJS('landing.systemScan.javaDownloadFailureText')) } }) setDismissHandler(() => { $('#overlayContent').fadeOut(250, () => { //$('#overlayDismiss').toggle(false) setOverlayContent( - 'Java is Required
to Launch', - `A valid x64 installation of Java ${effectiveJavaOptions.suggestedMajor} is required to launch.

Please refer to our Java Management Guide for instructions on how to manually install Java.`, - 'I Understand', - 'Go Back' + Lang.queryJS('landing.systemScan.javaRequired', { 'major': effectiveJavaOptions.suggestedMajor }), + Lang.queryJS('landing.systemScan.javaRequiredMessage', { 'major': effectiveJavaOptions.suggestedMajor }), + Lang.queryJS('landing.systemScan.javaRequiredDismiss'), + Lang.queryJS('landing.systemScan.javaRequiredCancel') ) setOverlayHandler(() => { toggleLaunchArea(false) @@ -385,7 +383,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { effectiveJavaOptions.distribution) if(asset == null) { - throw new Error('Failed to find OpenJDK distribution.') + throw new Error(Lang.queryJS('landing.downloadJava.findJdkFailure')) } let received = 0 @@ -400,7 +398,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { if(!await validateLocalFile(asset.path, asset.algo, asset.hash)) { log.error(`Hashes do not match, ${asset.id} may be corrupted.`) // Don't know how this could happen, but report it. - throw new Error('Downloaded JDK has bad hash, file may be corrupted.') + throw new Error(Lang.queryJS('landing.downloadJava.javaDownloadCorruptedError')) } } @@ -409,7 +407,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { remote.getCurrentWindow().setProgressBar(2) // Wait for extration to complete. - const eLStr = 'Extracting Java' + const eLStr = Lang.queryJS('landing.downloadJava.extractingJava') let dotStr = '' setLaunchDetails(eLStr) const extractListener = setInterval(() => { @@ -431,7 +429,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { ConfigManager.save() clearInterval(extractListener) - setLaunchDetails('Java Installed!') + setLaunchDetails(Lang.queryJS('landing.downloadJava.javaInstalled')) // TODO Callback hell // Refactor the launch functions @@ -456,7 +454,7 @@ async function dlAsync(login = true) { const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') - setLaunchDetails('Loading server information..') + setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) let distro @@ -465,7 +463,7 @@ async function dlAsync(login = true) { onDistroRefresh(distro) } catch(err) { loggerLaunchSuite.error('Unable to refresh distribution index.', err) - showLaunchFailure('Fatal Error', 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) return } @@ -478,7 +476,7 @@ async function dlAsync(login = true) { } } - setLaunchDetails('Please wait..') + setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')) toggleLaunchArea(true) setLaunchPercentage(0, 100) @@ -494,17 +492,17 @@ async function dlAsync(login = true) { fullRepairModule.childProcess.on('error', (err) => { loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure('Error During Launch', err.message || 'See console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')) }) fullRepairModule.childProcess.on('close', (code, _signal) => { if(code !== 0){ loggerLaunchSuite.error(`Full Repair Module exited with code ${code}, assuming error.`) - showLaunchFailure('Error During Launch', 'See console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) } }) loggerLaunchSuite.info('Validating files.') - setLaunchDetails('Validating file integrity..') + setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) let invalidFileCount = 0 try { invalidFileCount = await fullRepairModule.verifyFiles(percent => { @@ -513,14 +511,14 @@ async function dlAsync(login = true) { setLaunchPercentage(100) } catch (err) { loggerLaunchSuite.error('Error during file validation.') - showLaunchFailure('Error During File Verification', err.displayable || 'See console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileVerificationTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) return } if(invalidFileCount > 0) { loggerLaunchSuite.info('Downloading files.') - setLaunchDetails('Downloading files..') + setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles')) setLaunchPercentage(0) try { await fullRepairModule.download(percent => { @@ -529,7 +527,7 @@ async function dlAsync(login = true) { setDownloadPercentage(100) } catch(err) { loggerLaunchSuite.error('Error during file download.') - showLaunchFailure('Error During File Download', err.displayable || 'See console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) return } } else { @@ -541,7 +539,7 @@ async function dlAsync(login = true) { fullRepairModule.destroyReceiver() - setLaunchDetails('Preparing to launch..') + setLaunchDetails(Lang.queryJS('landing.dlAsync.preparingToLaunch')) const mojangIndexProcessor = new MojangIndexProcessor( ConfigManager.getCommonDirectory(), @@ -559,7 +557,7 @@ async function dlAsync(login = true) { const authUser = ConfigManager.getSelectedAccount() loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) let pb = new ProcessBuilder(serv, versionData, forgeData, authUser, remote.app.getVersion()) - setLaunchDetails('Launching game..') + setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame')) // const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/ const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`) @@ -604,7 +602,7 @@ async function dlAsync(login = true) { data = data.trim() if(data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1){ loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.') - showLaunchFailure('Error During Launch', 'The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.

To fix this issue, temporarily turn off your antivirus software and launch the game again.

If you have time, please submit an issue and let us know what antivirus software you use. We\'ll contact them and try to straighten things out.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.launchWrapperNotDownloaded')) } } @@ -616,7 +614,7 @@ async function dlAsync(login = true) { proc.stdout.on('data', tempListener) proc.stderr.on('data', gameErrorListener) - setLaunchDetails('Done. Enjoy the server!') + setLaunchDetails(Lang.queryJS('landing.dlAsync.doneEnjoyServer')) // Init Discord Hook if(distro.rawDistribution.discord != null && serv.rawServerdiscord != null){ @@ -633,7 +631,7 @@ async function dlAsync(login = true) { } catch(err) { loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure('Error During Launch', 'Please check the console (CTRL + Shift + i) for more details.') + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.checkConsoleForDetails')) } } @@ -740,7 +738,7 @@ let newsLoadingListener = null */ function setNewsLoading(val){ if(val){ - const nLStr = 'Checking for News' + const nLStr = Lang.queryJS('landing.news.checking') let dotStr = '..' nELoadSpan.innerHTML = nLStr + dotStr newsLoadingListener = setInterval(() => { @@ -955,7 +953,7 @@ function displayArticle(articleObject, index){ text.style.display = text.style.display === 'block' ? 'none' : 'block' } }) - newsNavigationStatus.innerHTML = index + ' of ' + newsArr.length + newsNavigationStatus.innerHTML = Lang.query('ejs.landing.newsNavigationStatus', {currentPage: index, totalPages: newsArr.length}) newsContent.setAttribute('article', index-1) } diff --git a/app/assets/js/scripts/login.js b/app/assets/js/scripts/login.js index 96ec923..30ff893 100644 --- a/app/assets/js/scripts/login.js +++ b/app/assets/js/scripts/login.js @@ -220,10 +220,7 @@ loginButton.addEventListener('click', () => { } else { // Uh oh. msftLoginLogger.error('Unhandled error during login.', displayableError) - actualDisplayableError = { - title: 'Unknown Error During Login', - desc: 'An unknown error has occurred. Please see the console for details.' - } + actualDisplayableError = Lang.queryJS('login.error.unknown') } setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain')) diff --git a/app/assets/js/scripts/overlay.js b/app/assets/js/scripts/overlay.js index f85a28b..b7acfa2 100644 --- a/app/assets/js/scripts/overlay.js +++ b/app/assets/js/scripts/overlay.js @@ -289,7 +289,7 @@ async function populateServerListings(){ - Main Server + ${Lang.queryJS('settings.serverListing.mainServer')} ` : ''} diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 0b5f322..1d85005 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -59,8 +59,8 @@ function bindFileSelectors(){ if(isJavaExecSel && process.platform === 'win32') { options.filters = [ - { name: 'Executables', extensions: ['exe'] }, - { name: 'All Files', extensions: ['*'] } + { name: Lang.queryJS('settings.fileSelectors.executables'), extensions: ['exe'] }, + { name: Lang.queryJS('settings.fileSelectors.allFiles'), extensions: ['*'] } ] } @@ -374,9 +374,9 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => { // Unexpected error. setOverlayContent( - 'Something Went Wrong', - 'Microsoft authentication failed. Please try again.', - 'OK' + Lang.queryJS('settings.msftLogin.errorTitle'), + Lang.queryJS('settings.msftLogin.errorMessage'), + Lang.queryJS('settings.msftLogin.okButton') ) setOverlayHandler(() => { toggleOverlay(false) @@ -401,7 +401,7 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => { setOverlayContent( error, errorDesc, - 'OK' + Lang.queryJS('settings.msftLogin.okButton') ) setOverlayHandler(() => { toggleOverlay(false) @@ -429,10 +429,7 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => { } else { // Uh oh. msftLoginLogger.error('Unhandled error during login.', displayableError) - actualDisplayableError = { - title: 'Unknown Error During Login', - desc: 'An unknown error has occurred. Please see the console for details.' - } + actualDisplayableError = Lang.queryJS('login.error.unknown') } switchView(getCurrentView(), viewOnClose, 500, 500, () => { @@ -461,11 +458,11 @@ function bindAuthAccountSelect(){ for(let i=0; iThis is Your Last Account', - 'In order to use the launcher you must be logged into at least one account. You will need to login again after.

Are you sure you want to log out?', - 'I\'m Sure', - 'Cancel' + Lang.queryJS('settings.authAccountLogout.lastAccountWarningTitle'), + Lang.queryJS('settings.authAccountLogout.lastAccountWarningMessage'), + Lang.queryJS('settings.authAccountLogout.confirmButton'), + Lang.queryJS('settings.authAccountLogout.cancelButton') ) setOverlayHandler(() => { processLogOut(val, isLastAccount) @@ -555,9 +552,9 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGOUT, (_, ...arguments_) => { // Unexpected error. setOverlayContent( - 'Something Went Wrong', - 'Microsoft logout failed. Please try again.', - 'OK' + Lang.queryJS('settings.msftLogout.errorTitle'), + Lang.queryJS('settings.msftLogout.errorMessage'), + Lang.queryJS('settings.msftLogout.okButton') ) setOverlayHandler(() => { toggleOverlay(false) @@ -611,12 +608,12 @@ function refreshAuthAccountSelected(uuid){ const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0] if(uuid === val.getAttribute('uuid')){ selBtn.setAttribute('selected', '') - selBtn.innerHTML = 'Selected Account ✔' + selBtn.innerHTML = Lang.queryJS('settings.authAccountSelect.selectedButton') } else { if(selBtn.hasAttribute('selected')){ selBtn.removeAttribute('selected') } - selBtn.innerHTML = 'Select Account' + selBtn.innerHTML = Lang.queryJS('settings.authAccountSelect.selectButton') } }) } @@ -648,18 +645,18 @@ function populateAuthAccounts(){
-
Username
+
${Lang.queryJS('settings.authAccountPopulate.username')}
${acc.displayName}
-
UUID
+
${Lang.queryJS('settings.authAccountPopulate.uuid')}
${acc.uuid}
- +
- +
@@ -873,7 +870,7 @@ async function resolveDropinModsForUI(){
${dropin.name}
- +
@@ -901,9 +898,9 @@ function bindDropinModsRemoveButton(){ document.getElementById(fullName).remove() } else { setOverlayContent( - `Failed to Delete
Drop-in Mod ${fullName}`, - 'Make sure the file is not in use and try again.', - 'Okay' + Lang.queryJS('settings.dropinMods.deleteFailedTitle', { fullName }), + Lang.queryJS('settings.dropinMods.deleteFailedMessage'), + Lang.queryJS('settings.okButton') ) setOverlayHandler(null) toggleOverlay(true) @@ -956,9 +953,9 @@ function saveDropinModConfiguration(){ DropinModUtil.toggleDropinMod(CACHE_SETTINGS_MODS_DIR, dropin.fullName, dropinUIEnabled).catch(err => { if(!isOverlayVisible()){ setOverlayContent( - 'Failed to Toggle
One or More Drop-in Mods', + Lang.queryJS('settings.dropinMods.failedToggleTitle'), err.message, - 'Okay' + Lang.queryJS('settings.okButton') ) setOverlayHandler(null) toggleOverlay(true) @@ -1093,7 +1090,7 @@ async function loadSelectedServerOnModsTab(){ - Main Server + ${Lang.queryJS('settings.serverListing.mainServer')} ` : ''} @@ -1344,19 +1341,19 @@ async function populateJavaExecDetails(execPath){ const details = await validateSelectedJvm(ensureJavaDirIsRoot(execPath), server.effectiveJavaOptions.supported) if(details != null) { - settingsJavaExecDetails.innerHTML = `Selected: Java ${details.semverStr} (${details.vendor})` + settingsJavaExecDetails.innerHTML = Lang.queryJS('settings.java.selectedJava', { version: details.semverStr, vendor: details.vendor }) } else { - settingsJavaExecDetails.innerHTML = 'Invalid Selection' + settingsJavaExecDetails.innerHTML = Lang.queryJS('settings.java.invalidSelection') } } function populateJavaReqDesc(server) { - settingsJavaReqDesc.innerHTML = `Requires Java ${server.effectiveJavaOptions.suggestedMajor} x64.` + settingsJavaReqDesc.innerHTML = Lang.queryJS('settings.java.requiresJava', { major: server.effectiveJavaOptions.suggestedMajor }) } function populateJvmOptsLink(server) { const major = server.effectiveJavaOptions.suggestedMajor - settingsJvmOptsLink.innerHTML = `Available Options for Java ${major} (HotSpot VM)` + settingsJvmOptsLink.innerHTML = Lang.queryJS('settings.java.availableOptions', { major: major }) if(major >= 12) { settingsJvmOptsLink.href = `https://docs.oracle.com/en/java/javase/${major}/docs/specs/man/java.html#extra-options-for-java` } @@ -1433,11 +1430,11 @@ function isPrerelease(version){ function populateVersionInformation(version, valueElement, titleElement, checkElement){ valueElement.innerHTML = version if(isPrerelease(version)){ - titleElement.innerHTML = 'Pre-release' + titleElement.innerHTML = Lang.queryJS('settings.about.preReleaseTitle') titleElement.style.color = '#ff886d' checkElement.style.background = '#ff886d' } else { - titleElement.innerHTML = 'Stable Release' + titleElement.innerHTML = Lang.queryJS('settings.about.stableReleaseTitle') titleElement.style.color = null checkElement.style.background = null } @@ -1476,7 +1473,7 @@ function populateReleaseNotes(){ }, timeout: 2500 }).catch(err => { - settingsAboutChangelogText.innerHTML = 'Failed to load release notes.' + settingsAboutChangelogText.innerHTML = Lang.queryJS('settings.about.releaseNotesFailed') }) } @@ -1524,27 +1521,27 @@ function settingsUpdateButtonStatus(text, disabled = false, handler = null){ */ function populateSettingsUpdateInformation(data){ if(data != null){ - settingsUpdateTitle.innerHTML = `New ${isPrerelease(data.version) ? 'Pre-release' : 'Release'} Available` + settingsUpdateTitle.innerHTML = isPrerelease(data.version) ? Lang.queryJS('settings.updates.newPreReleaseTitle') : Lang.queryJS('settings.updates.newReleaseTitle') settingsUpdateChangelogCont.style.display = null settingsUpdateChangelogTitle.innerHTML = data.releaseName settingsUpdateChangelogText.innerHTML = data.releaseNotes populateVersionInformation(data.version, settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck) if(process.platform === 'darwin'){ - settingsUpdateButtonStatus('Download from GitHubClose the launcher and run the dmg to update.', false, () => { + settingsUpdateButtonStatus(Lang.queryJS('settings.updates.downloadButton'), false, () => { shell.openExternal(data.darwindownload) }) } else { - settingsUpdateButtonStatus('Downloading..', true) + settingsUpdateButtonStatus(Lang.queryJS('settings.updates.downloadingButton'), true) } } else { - settingsUpdateTitle.innerHTML = 'You Are Running the Latest Version' + settingsUpdateTitle.innerHTML = Lang.queryJS('settings.updates.latestVersionTitle') settingsUpdateChangelogCont.style.display = 'none' populateVersionInformation(remote.app.getVersion(), settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck) - settingsUpdateButtonStatus('Check for Updates', false, () => { + settingsUpdateButtonStatus(Lang.queryJS('settings.updates.checkForUpdatesButton'), false, () => { if(!isDev){ ipcRenderer.send('autoUpdateAction', 'checkForUpdate') - settingsUpdateButtonStatus('Checking for Updates..', true) + settingsUpdateButtonStatus(Lang.queryJS('settings.updates.checkingForUpdatesButton'), true) } }) } diff --git a/app/assets/js/scripts/uibinder.js b/app/assets/js/scripts/uibinder.js index a1cf7eb..469f49f 100644 --- a/app/assets/js/scripts/uibinder.js +++ b/app/assets/js/scripts/uibinder.js @@ -9,7 +9,6 @@ const { Type } = require('helios-distribution-types') const AuthManager = require('./assets/js/authmanager') const ConfigManager = require('./assets/js/configmanager') const { DistroAPI } = require('./assets/js/distromanager') -const Lang = require('./assets/js/langloader') let rscShouldLoad = false let fatalStartupError = false @@ -115,9 +114,9 @@ function showFatalStartupError(){ $('#loadingContainer').fadeOut(250, () => { document.getElementById('overlayContainer').style.background = 'none' setOverlayContent( - 'Fatal Error: Unable to Load Distribution Index', - 'A connection could not be established to our servers to download the distribution index. No local copies were available to load.

The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application.', - 'Close' + Lang.queryJS('uibinder.startup.fatalErrorTitle'), + Lang.queryJS('uibinder.startup.fatalErrorMessage'), + Lang.queryJS('uibinder.startup.closeButton') ) setOverlayHandler(() => { const window = remote.getCurrentWindow() @@ -333,10 +332,12 @@ async function validateSelectedAccount(){ ConfigManager.save() const accLen = Object.keys(ConfigManager.getAuthAccounts()).length setOverlayContent( - 'Failed to Refresh Login', - `We were unable to refresh the login for ${selectedAcc.displayName}. Please ${accLen > 0 ? 'select another account or ' : ''} login again.`, - 'Login', - 'Select Another Account' + Lang.queryJS('uibinder.validateAccount.failedMessageTitle'), + accLen > 0 + ? Lang.queryJS('uibinder.validateAccount.failedMessage', { 'account': selectedAcc.displayName }) + : Lang.queryJS('uibinder.validateAccount.failedMessageSelectAnotherAccount', { 'account': selectedAcc.displayName }), + Lang.queryJS('uibinder.validateAccount.loginButton'), + Lang.queryJS('uibinder.validateAccount.selectAnotherAccountButton') ) setOverlayHandler(() => { diff --git a/app/assets/js/scripts/uicore.js b/app/assets/js/scripts/uicore.js index c35771a..02e9b5d 100644 --- a/app/assets/js/scripts/uicore.js +++ b/app/assets/js/scripts/uicore.js @@ -10,6 +10,7 @@ const {ipcRenderer, shell, webFrame} = require('electron') const remote = require('@electron/remote') const isDev = require('./assets/js/isdev') const { LoggerUtil } = require('helios-core') +const Lang = require('./assets/js/langloader') const loggerUICore = LoggerUtil.getLogger('UICore') const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater') @@ -42,7 +43,7 @@ if(!isDev){ switch(arg){ case 'checking-for-update': loggerAutoUpdater.info('Checking for update..') - settingsUpdateButtonStatus('Checking for Updates..', true) + settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.checkingForUpdateButton'), true) break case 'update-available': loggerAutoUpdater.info('New update available', info.version) @@ -56,7 +57,7 @@ if(!isDev){ break case 'update-downloaded': loggerAutoUpdater.info('Update ' + info.version + ' ready to be installed.') - settingsUpdateButtonStatus('Install Now', false, () => { + settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.installNowButton'), false, () => { if(!isDev){ ipcRenderer.send('autoUpdateAction', 'installUpdateNow') } @@ -65,7 +66,7 @@ if(!isDev){ break case 'update-not-available': loggerAutoUpdater.info('No new update found.') - settingsUpdateButtonStatus('Check for Updates') + settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.checkForUpdatesButton')) break case 'ready': updateCheckListener = setInterval(() => { diff --git a/app/assets/lang/_custom.toml b/app/assets/lang/_custom.toml new file mode 100644 index 0000000..da17faf --- /dev/null +++ b/app/assets/lang/_custom.toml @@ -0,0 +1,20 @@ +# Custom Language File for Launcher Customizer + +[ejs.app] +title = "Helios Launcher" + +[ejs.landing] +mediaGitHubURL = "https://github.com/dscalzi/HeliosLauncher" +mediaTwitterURL = "#" +mediaInstagramURL = "#" +mediaYouTubeURL = "#" +mediaDiscordURL = "https://discord.gg/zNWUXdt" + +[ejs.settings] +sourceGithubLink = "https://github.com/dscalZi/HeliosLauncher" +supportLink = "https://github.com/dscalZi/HeliosLauncher/issues" + +[ejs.welcome] +welcomeHeader = "WELCOME TO WESTEROSCRAFT" +welcomeDescription = "Our mission is to recreate the universe imagined by author George RR Martin in his fantasy series, A Song of Ice and Fire. Through the collaborative effort of thousands of community members, we have sought to create Westeros as accurately and precisely as possible within Minecraft. The world we are creating is yours to explore. Journey from Dorne to Castle Black, and if you aren’t afraid, beyond the Wall itself, but best not delay. As the words of House Stark ominously warn: Winter is Coming." +welcomeDescCTA = "You are just a few clicks away from Westeros." diff --git a/app/assets/lang/en_US.json b/app/assets/lang/en_US.json deleted file mode 100644 index 25b34c2..0000000 --- a/app/assets/lang/en_US.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "html": { - "avatarOverlay": "Edit" - }, - "js": { - "login": { - "error": { - "invalidValue": "* Invalid Value", - "requiredValue": "* Required", - "userMigrated": { - "title": "Error During Login:
Invalid Credentials", - "desc": "You've attempted to login with a migrated account. Try again using the account email as the username." - }, - "invalidCredentials": { - "title": "Error During Login:
Invalid Credentials", - "desc": "The email or password you've entered is incorrect. Please try again." - }, - "rateLimit": { - "title": "Error During Login:
Too Many Attempts", - "desc": "There have been too many login attempts with this account recently. Please try again later." - }, - "noInternet": { - "title": "Error During Login:
No Internet Connection", - "desc": "You must be connected to the internet in order to login. Please connect and try again." - }, - "authDown": { - "title": "Error During Login:
Authentication Server Offline", - "desc": "Mojang's authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on Mojang's help portal." - }, - "notPaid": { - "title": "Error During Login:
Game Not Purchased", - "desc": "The account you are trying to login with has not purchased a copy of Minecraft.
You may purchase a copy on Minecraft.net" - }, - "unknown": { - "title": "Error During Login:
Unknown Error" - } - }, - "login": "LOGIN", - "loggingIn": "LOGGING IN", - "success": "SUCCESS", - "tryAgain": "Try Again" - }, - "landing": { - "launch": { - "pleaseWait": "Please wait.." - } - } - } -} \ No newline at end of file diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml new file mode 100644 index 0000000..dc9052c --- /dev/null +++ b/app/assets/lang/en_US.toml @@ -0,0 +1,281 @@ +[ejs.landing] +updateAvailableTooltip = "Update Available" +usernamePlaceholder = "Username" +usernameEditButton = "Edit" +settingsTooltip = "Settings" +serverStatus = "SERVER" +serverStatusPlaceholder = "OFFLINE" +mojangStatus = "MOJANG STATUS" +mojangStatusTooltipTitle = "Services" +mojangStatusNETitle = "Non Essential" +newsButton = "NEWS" +launchButton = "PLAY" +launchButtonPlaceholder = "• No Server Selected" +launchDetails = "Please wait.." +newsNavigationStatus = "{currentPage} of {totalPages}" +newsErrorLoadSpan = "Checking for News.." +newsErrorFailedSpan = "Failed to Load News" +newsErrorRetryButton = "Try Again" +newsErrorNoneSpan = "No News" + +[ejs.login] +loginCancelText = "Cancel" +loginSubheader = "MINECRAFT LOGIN" +loginEmailError = "* Invalid Value" +loginEmailPlaceholder = "EMAIL OR USERNAME" +loginPasswordError = "* Required" +loginPasswordPlaceholder = "PASSWORD" +loginForgotPasswordLink = "https://minecraft.net/password/forgot/" +loginForgotPasswordText = "forgot password?" +loginRememberMeText = "remember me?" +loginButtonText = "LOGIN" +loginNeedAccountLink = "https://minecraft.net/store/minecraft-java-edition/" +loginNeedAccountText = "Need an Account?" +loginPasswordDisclaimer1 = "Your password is sent directly to mojang and never stored." +loginPasswordDisclaimer2 = "{appName} is not affiliated with Mojang AB." + +[ejs.loginOptions] +loginOptionsTitle = "Login Options" +loginWithMicrosoft = "Login with Microsoft" +loginWithMojang = "Login with Mojang" +cancelButton = "Cancel" + +[ejs.overlay] +serverSelectHeader = "Available Servers" +serverSelectConfirm = "Select" +serverSelectCancel = "Cancel" +accountSelectHeader = "Select an Account" +accountSelectConfirm = "Select" +accountSelectCancel = "Cancel" + +[ejs.settings] +navHeaderText = "Settings" +navAccount = "Account" +navMinecraft = "Minecraft" +navMods = "Mods" +navJava = "Java" +navLauncher = "Launcher" +navAbout = "About" +navUpdates = "Updates" +navDone = "Done" +tabAccountHeaderText = "Account Settings" +tabAccountHeaderDesc = "Add new accounts or manage existing ones." +microsoftAccount = "Microsoft" +addMicrosoftAccount = "+ Add Microsoft Account" +mojangAccount = "Mojang" +addMojangAccount = "+ Add Mojang Account" +minecraftTabHeaderText = "Minecraft Settings" +minecraftTabHeaderDesc = "Options related to game launch." +gameResolutionTitle = "Game Resolution" +launchFullscreenTitle = "Launch in fullscreen." +autoConnectTitle = "Automatically connect to the server on launch." +launchDetachedTitle = "Launch game process detached from launcher." +launchDetachedDesc = "If the game is not detached, closing the launcher will also close the game." +tabModsHeaderText = "Mod Settings" +tabModsHeaderDesc = "Enable or disable mods." +switchServerButton = "Switch" +requiredMods = "Required Mods" +optionalMods = "Optional Mods" +dropinMods = "Drop-in Mods" +addMods = "Add Mods" +dropinRefreshNote = "(F5 to Refresh)" +shaderpacks = "Shaderpacks" +shaderpackDesc = "Enable or disable shaders. Please note, shaders will only run smoothly on powerful setups. You may add custom packs here." +selectShaderpack = "Select Shaderpack" +tabJavaHeaderText = "Java Settings" +tabJavaHeaderDesc = "Manage the Java configuration (advanced)." +memoryTitle = "Memory" +maxRAM = "Maximum RAM" +minRAM = "Minimum RAM" +memoryDesc = "The recommended minimum RAM is 3 gigabytes. Setting the minimum and maximum values to the same value may reduce lag." +memoryTotalTitle = "Total" +memoryAvailableTitle = "Available" +javaExecutableTitle = "Java Executable" +javaExecSelDialogTitle = "Select Java Executable" +javaExecSelButtonText = "Choose File" +javaExecDesc = "The Java executable is validated before game launch." +javaPathDesc = "The path should end with {pathSuffix}." +jvmOptsTitle = "Additional JVM Options" +jvmOptsDesc = "Options to be provided to the JVM at runtime. -Xms and -Xmx should not be included." +launcherTabHeaderText = "Launcher Settings" +launcherTabHeaderDesc = "Options related to the launcher itself." +allowPrereleaseTitle = "Allow Pre-Release Updates." +allowPrereleaseDesc = "Pre-Releases include new features which may have not been fully tested or integrated.
This will always be true if you are using a pre-release version." +dataDirectoryTitle = "Data Directory" +selectDataDirectory = "Select Data Directory" +chooseFolder = "Choose Folder" +dataDirectoryDesc = "All game files and local Java installations will be stored in the data directory.
Screenshots and world saves are stored in the instance folder for the corresponding server configuration." +aboutTabHeaderText = "About" +aboutTabHeaderDesc = "View information and release notes for the current version." +aboutTitle = "{appName}" +stableRelease = "Stable Release" +versionText = "Version " +sourceGithub = "Source (GitHub)" +support = "Support" +devToolsConsole = "DevTools Console" +releaseNotes = "Release Notes" +changelog = "Changelog" +noReleaseNotes = "No Release Notes" +viewReleaseNotes = "View Release Notes on GitHub" +launcherUpdatesHeaderText = "Launcher Updates" +launcherUpdatesHeaderDesc = "Download, install, and review updates for the launcher." +checkForUpdates = "Check for Updates" +whatsNew = "What's New" +updateReleaseNotes = "Update Release Notes" + +[ejs.waiting] +waitingText = "Waiting for Microsoft.." + +[ejs.welcome] +continueButton = "CONTINUE" + + +[js.login] +login = "LOGIN" +loggingIn = "LOGGING IN" +success = "SUCCESS" +tryAgain = "Try Again" + +[js.login.error] +invalidValue = "* Invalid Value" +requiredValue = "* Required" + +[js.login.error.unknown] +title = "Unknown Error During Login" +desc = "An unknown error has occurred. Please see the console for details." + +[js.landing.launch] +pleaseWait = "Please wait.." +failureTitle = "Error During Launch" +failureText = "See console (CTRL + Shift + i) for more details." +okay = "Okay" + +[js.landing.selectedAccount] +noAccountSelected = "No Account Selected" + +[js.landing.selectedServer] +noSelection = "No Server Selected" +loading = "Loading.." + +[js.landing.serverStatus] +server = "SERVER" +offline = "OFFLINE" +players = "PLAYERS" + +[js.landing.systemScan] +checking = "Checking system info.." +noCompatibleJava = "No Compatible
Java Installation Found" +installJavaMessage = "In order to launch Minecraft, you need a 64-bit installation of Java {major}. Would you like us to install a copy?" +installJava = "Install Java" +installJavaManually = "Install Manually" +javaDownloadPrepare = "Preparing Java Download.." +javaDownloadFailureTitle = "Error During Java Download" +javaDownloadFailureText = "See console (CTRL + Shift + i) for more details." +javaRequired = "Java is Required
to Launch" +javaRequiredMessage = 'A valid x64 installation of Java {major} is required to launch.

Please refer to our Java Management Guide for instructions on how to manually install Java.' +javaRequiredDismiss = "I Understand" +javaRequiredCancel = "Go Back" + +[js.landing.downloadJava] +findJdkFailure = "Failed to find OpenJDK distribution." +javaDownloadCorruptedError = "Downloaded JDK has a bad hash, the file may be corrupted." +extractingJava = "Extracting Java" +javaInstalled = "Java Installed!" + +[js.landing.dlAsync] +loadingServerInfo = "Loading server information.." +fatalError = "Fatal Error" +unableToLoadDistributionIndex = "Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details." +pleaseWait = "Please wait.." +errorDuringLaunchTitle = "Error During Launch" +seeConsoleForDetails = "See console (CTRL + Shift + i) for more details." +validatingFileIntegrity = "Validating file integrity.." +errorDuringFileVerificationTitle = "Error During File Verification" +downloadingFiles = "Downloading files.." +errorDuringFileDownloadTitle = "Error During File Download" +preparingToLaunch = "Preparing to launch.." +launchingGame = "Launching game.." +launchWrapperNotDownloaded = "The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.

To fix this issue, temporarily turn off your antivirus software and launch the game again.

If you have time, please submit an issue and let us know what antivirus software you use. We'll contact them and try to straighten things out." +doneEnjoyServer = "Done. Enjoy the server!" +checkConsoleForDetails = "Please check the console (CTRL + Shift + i) for more details." + +[js.landing.news] +checking = "Checking for News" + +[js.settings.fileSelectors] +executables = "Executables" +allFiles = "All Files" + +[js.settings.mstfLogin] +errorTitle = "Something Went Wrong" +errorMessage = "Microsoft authentication failed. Please try again." +okButton = "OK" + +[js.settings.mstfLogout] +errorTitle = "Something Went Wrong" +errorMessage = "Microsoft logout failed. Please try again." +okButton = "OK" + +[js.settings.authAccountSelect] +selectButton = "Select Account" +selectedButton = "Selected Account ✔" + +[js.settings.authAccountLogout] +lastAccountWarningTitle = "Warning
This is Your Last Account" +lastAccountWarningMessage = "In order to use the launcher you must be logged into at least one account. You will need to login again after.

Are you sure you want to log out?" +confirmButton = "I'm Sure" +cancelButton = "Cancel" + +[js.settings.authAccountPopulate] +username = "Username" +uuid = "UUID" +selectAccount = "Select Account" +selectedAccount = "Selected Account ✓" +logout = "Log Out" + +[js.settings.dropinMods] +removeButton = "Remove" +deleteFailedTitle = "Failed to Delete
Drop-in Mod {fullName}" +deleteFailedMessage = "Make sure the file is not in use and try again." +failedToggleTitle = "Failed to Toggle
One or More Drop-in Mods" +okButton = "Okay" + +[js.settings.serverListing] +mainServer = "Main Server" + +[js.settings.java] +selectedJava = "Selected: Java {version} ({vendor})" +invalidSelection = "Invalid Selection" +requiresJava = "Requires Java {major} x64." +availableOptions = "Available Options for Java {major} (HotSpot VM)" + +[js.settings.about] +preReleaseTitle = "Pre-release" +stableReleaseTitle = "Stable Release" +releaseNotesFailed = "Failed to load release notes." + +[js.settings.updates] +newReleaseTitle = "New Release Available" +newPreReleaseTitle = "New Pre-release Available" +downloadingButton = "Downloading.." +downloadButton = 'Download from GitHubClose the launcher and run the dmg to update.' +latestVersionTitle = "You Are Running the Latest Version" +checkForUpdatesButton = "Check for Updates" +checkingForUpdatesButton = "Checking for Updates.." + +[js.uibinder.startup] +fatalErrorTitle = "Fatal Error: Unable to Load Distribution Index" +fatalErrorMessage = "A connection could not be established to our servers to download the distribution index. No local copies were available to load.

The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application." +closeButton = "Close" + +[js.uibinder.validateAccount] +failedMessageTitle = "Failed to Refresh Login" +failedMessage = "We were unable to refresh the login for {account}. Please select another account or login again." +failedMessageSelectAnotherAccount = "We were unable to refresh the login for {account}. Please login again." +loginButton = "Login" +selectAnotherAccountButton = "Select Another Account" + +[uicore.autoUpdate] +checkingForUpdateButton = "Checking for Updates..." +installNowButton = "Install Now" +checkForUpdatesButton = "Check for Updates" diff --git a/app/frame.ejs b/app/frame.ejs index c2aaf33..1151f91 100644 --- a/app/frame.ejs +++ b/app/frame.ejs @@ -13,7 +13,7 @@ <% } else{ %>
- Helios Launcher + <%= lang('app.title') %>
+
@@ -23,14 +23,14 @@ -
Settings
+
<%- lang('landing.settingsTooltip') %>
- + @@ -96,21 +96,21 @@
- SERVER - OFFLINE + <%- lang('landing.serverStatus') %> + <%- lang('landing.serverStatusPlaceholder') %>
- MOJANG STATUS + <%- lang('landing.mojangStatus') %>
-
Services
+
<%- lang('landing.mojangStatusTooltipTitle') %>
-
Non Essential
+
<%- lang('landing.mojangStatusNETitle') %>
@@ -133,7 +133,7 @@ - NEWS + <%- lang('landing.newsButton') %>
@@ -141,9 +141,9 @@
- Checking for News.. + <%- lang('landing.newsErrorLoadSpan') %>
diff --git a/app/login.ejs b/app/login.ejs index 7ecc4a6..2da8072 100644 --- a/app/login.ejs +++ b/app/login.ejs @@ -2,21 +2,21 @@
- MINECRAFT LOGIN + <%- lang('login.loginSubheader') %>
- * Invalid Value - + <%- lang('login.loginEmailError') %> +
@@ -24,22 +24,22 @@ - * Required - + <%- lang('login.loginPasswordError') %> +
- forgot password? + <%- lang('login.loginForgotPasswordText') %>
- Need an Account? + <%- lang('login.loginNeedAccountText') %> -

Your password is sent directly to mojang and never stored.

-

Helios Launcher is not affiliated with Mojang AB.

+

<%- lang('login.loginPasswordDisclaimer1') %>

+

<%- lang('login.loginPasswordDisclaimer2', { appName: lang('app.title') }) %>

diff --git a/app/loginOptions.ejs b/app/loginOptions.ejs index 36af37e..20aa67c 100644 --- a/app/loginOptions.ejs +++ b/app/loginOptions.ejs @@ -1,7 +1,7 @@