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

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