Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localize HeliosLauncher UI using lang files #301

Merged
merged 29 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1695b51
First step to use Language .json file in ejs
Kamesuta Aug 5, 2023
08b472f
i18n for landing.ejs
Kamesuta Aug 30, 2023
62f7b68
i18n for login.ejs
Kamesuta Aug 30, 2023
a9ad071
i18n for loginOptions.ejs
Kamesuta Aug 30, 2023
b2ad901
i18n for overlay.ejs
Kamesuta Aug 30, 2023
f5317e9
i18n for settings.ejs
Kamesuta Aug 30, 2023
8960fb2
i18n for waiting.ejs
Kamesuta Aug 31, 2023
10a75b9
i18n for welcome.ejs
Kamesuta Aug 31, 2023
10742f7
langloader.js placeholder support
Kamesuta Sep 1, 2023
747af27
i18n for landing.js
Kamesuta Sep 2, 2023
7013021
i18n for login.js
Kamesuta Sep 2, 2023
5c01046
i18n for overlay.js
Kamesuta Sep 2, 2023
988d939
i18n for settings.js
Kamesuta Sep 2, 2023
9a956c4
i18n for uibinder.js
Kamesuta Sep 2, 2023
ba8a346
i18n for uicore.js
Kamesuta Sep 2, 2023
386fe01
remove html language replacement
Kamesuta Sep 2, 2023
53aa67c
use toml for i18n
Kamesuta Sep 2, 2023
19d52be
Fix mojang/microsoft status icon is undefined
Kamesuta Sep 26, 2023
66efac7
cascadable langloader
Kamesuta Sep 26, 2023
d2aba8c
separate lang file for customization
Kamesuta Sep 26, 2023
4901199
move some placeholder text to _placeholder.toml
Kamesuta Sep 26, 2023
9aeb38d
Update
dscalzi Oct 5, 2023
8941198
Reduce package lock diff.
dscalzi Oct 5, 2023
0158fc8
Remove another placeholder.
dscalzi Oct 5, 2023
d88a1f7
Checkbox does not require translation.
dscalzi Oct 5, 2023
cf6ffb3
Icons don't need translation.
dscalzi Oct 5, 2023
6b5d1cb
Leave placeholders inline.
dscalzi Oct 5, 2023
45a2431
Fix translation for news pages.
dscalzi Oct 5, 2023
a8996cc
Remove more unneeded translations.
dscalzi Oct 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions app/app.ejs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-In6B8teKZQll5heMl9bS7CESTbGvuAt3VVV86BUQBDk='"/>
<title>Helios Launcher</title>
<title><%= lang('app.title') %></title>
<script src="./assets/js/scripts/uicore.js"></script>
<script src="./assets/js/scripts/uibinder.js"></script>
<link type="text/css" rel="stylesheet" href="./assets/css/launcher.css">
Expand Down Expand Up @@ -45,11 +45,5 @@
</div>
</div>
</div>
<script>
// Load language
for(let key of Object.keys(Lang.query('html'))){
document.getElementById(key).innerHTML = Lang.query(`html.${key}`)
}
</script>
</body>
</html>
32 changes: 27 additions & 5 deletions app/assets/js/langloader.js
Original file line number Diff line number Diff line change
@@ -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, placeHolders){
return exports.query(`js.${id}`, placeHolders)
}

exports.queryJS = function(id){
return exports.query(`js.${id}`)
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')
}
2 changes: 1 addition & 1 deletion app/assets/js/preloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ DistroAPI['commonDir'] = ConfigManager.getCommonDirectory()
DistroAPI['instanceDir'] = ConfigManager.getInstanceDirectory()

// Load Strings
LangLoader.loadLanguage('en_US')
LangLoader.setupLanguage()

/**
*
Expand Down
92 changes: 45 additions & 47 deletions app/assets/js/scripts/landing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
}
})

Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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'){
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -288,7 +286,7 @@ function showLaunchFailure(title, desc){
setOverlayContent(
title,
desc,
'Okay'
Lang.queryJS('landing.launch.okay')
)
setOverlayHandler(null)
toggleOverlay(true)
Expand All @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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'))
}
}

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

Expand All @@ -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
}

Expand All @@ -478,7 +476,7 @@ async function dlAsync(login = true) {
}
}

setLaunchDetails('Please wait..')
setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait'))
toggleLaunchArea(true)
setLaunchPercentage(0, 100)

Expand All @@ -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 => {
Expand All @@ -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 => {
Expand All @@ -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 {
Expand All @@ -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(),
Expand All @@ -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`)
Expand Down Expand Up @@ -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'))
}
}

Expand All @@ -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){
Expand All @@ -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'))

}
}
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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)
}

Expand Down
Loading