Localize HeliosLauncher UI using lang files (#301)

* First step to use Language .json file in ejs

* i18n for landing.ejs

* i18n for login.ejs

* i18n for loginOptions.ejs

* i18n for overlay.ejs

* i18n for settings.ejs

* i18n for waiting.ejs

* i18n for welcome.ejs

* langloader.js placeholder support

* i18n for landing.js

* i18n for login.js

* i18n for overlay.js

* i18n for settings.js

* i18n for uibinder.js

* i18n for uicore.js

* remove html language replacement

* use toml for i18n

* Fix mojang/microsoft status icon is undefined

* cascadable langloader

* separate lang file for customization

* move some placeholder text to _placeholder.toml

* Update

* Reduce package lock diff.

* Remove another placeholder.

* Checkbox does not require translation.

* Icons don't need translation.

* Leave placeholders inline.

* Fix translation for news pages.

* Remove more unneeded translations.

---------

Co-authored-by: Daniel Scalzi <d_scalzi@yahoo.com>
This commit is contained in:
Kamesuta
2023-10-06 04:26:32 +09:00
committed by GitHub
parent 92f2aab2a1
commit 9d80d3b1d5
23 changed files with 580 additions and 302 deletions

View File

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

View File

@@ -23,7 +23,7 @@ DistroAPI['commonDir'] = ConfigManager.getCommonDirectory()
DistroAPI['instanceDir'] = ConfigManager.getInstanceDirectory()
// Load Strings
LangLoader.loadLanguage('en_US')
LangLoader.setupLanguage()
/**
*

View File

@@ -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 = '&#8226; ' + (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 = '&#8226; ' + 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<statuses.length; i++){
const service = statuses[i]
const tooltipHTML = `<div class="mojangStatusContainer">
<span class="mojangStatusIcon" style="color: ${MojangRestAPI.statusToHex(service.status)};">&#8226;</span>
<span class="mojangStatusName">${service.name}</span>
</div>`
if(service.essential){
tooltipEssentialHTML += `<div class="mojangStatusContainer">
<span class="mojangStatusIcon" style="color: ${MojangRestAPI.statusToHex(service.status)};">&#8226;</span>
<span class="mojangStatusName">${service.name}</span>
</div>`
tooltipEssentialHTML += tooltipHTML
} else {
tooltipNonEssentialHTML += `<div class="mojangStatusContainer">
<span class="mojangStatusIcon" style="color: ${MojangRestAPI.statusToHex(service.status)};">&#8226;</span>
<span class="mojangStatusName">${service.name}</span>
</div>`
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<br>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<br>to Launch',
`A valid x64 installation of Java ${effectiveJavaOptions.suggestedMajor} is required to launch.<br><br>Please refer to our <a href="https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java">Java Management Guide</a> 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.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href="https://github.com/dscalzi/HeliosLauncher/issues">submit an issue</a> 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)
}

View File

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

View File

@@ -289,7 +289,7 @@ async function populateServerListings(){
<path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/>
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
</svg>
<span class="serverListingStarTooltip">Main Server</span>
<span class="serverListingStarTooltip">${Lang.queryJS('settings.serverListing.mainServer')}</span>
</div>` : ''}
</div>
</div>

View File

@@ -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; i<selectBtns.length; i++){
if(selectBtns[i].hasAttribute('selected')){
selectBtns[i].removeAttribute('selected')
selectBtns[i].innerHTML = 'Select Account'
selectBtns[i].innerHTML = Lang.queryJS('settings.authAccountSelect.selectButton')
}
}
val.setAttribute('selected', '')
val.innerHTML = 'Selected Account &#10004;'
val.innerHTML = Lang.queryJS('settings.authAccountSelect.selectedButton')
setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid'))
}
})
@@ -483,10 +480,10 @@ function bindAuthAccountLogOut(){
if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
isLastAccount = true
setOverlayContent(
'Warning<br>This 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.<br><br>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 &#10004;'
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(){
<div class="settingsAuthAccountRight">
<div class="settingsAuthAccountDetails">
<div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">Username</div>
<div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.username')}</div>
<div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
</div>
<div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">UUID</div>
<div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.uuid')}</div>
<div class="settingsAuthAccountDetailValue">${acc.uuid}</div>
</div>
</div>
<div class="settingsAuthAccountActions">
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>Selected Account &#10004;' : '>Select Account'}</button>
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>' + Lang.queryJS('settings.authAccountPopulate.selectedAccount') : '>' + Lang.queryJS('settings.authAccountPopulate.selectAccount')}</button>
<div class="settingsAuthAccountWrapper">
<button class="settingsAuthAccountLogOut">Log Out</button>
<button class="settingsAuthAccountLogOut">${Lang.queryJS('settings.authAccountPopulate.logout')}</button>
</div>
</div>
</div>
@@ -873,7 +870,7 @@ async function resolveDropinModsForUI(){
<div class="settingsModDetails">
<span class="settingsModName">${dropin.name}</span>
<div class="settingsDropinRemoveWrapper">
<button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">Remove</button>
<button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">${Lang.queryJS('settings.dropinMods.removeButton')}</button>
</div>
</div>
</div>
@@ -901,9 +898,9 @@ function bindDropinModsRemoveButton(){
document.getElementById(fullName).remove()
} else {
setOverlayContent(
`Failed to Delete<br>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<br>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(){
<path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/>
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
</svg>
<span class="serverListingStarTooltip">Main Server</span>
<span class="serverListingStarTooltip">${Lang.queryJS('settings.serverListing.mainServer')}</span>
</div>` : ''}
</div>
</div>
@@ -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 GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Close the launcher and run the dmg to update.</span>', 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)
}
})
}

View File

@@ -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. <br><br>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 <strong>${selectedAcc.displayName}</strong>. 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(() => {

View File

@@ -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(() => {