Merge pull request #9 from Grasscutters/multilanguage

Multilanguage support
This commit is contained in:
SpikeHD 2022-04-22 22:29:34 -07:00 committed by GitHub
commit 29053e453c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 39 deletions

39
languages/en.json Normal file
View File

@ -0,0 +1,39 @@
{
"fullLangName": "English",
"appName": "GrassClipper",
"playOfficial": "Play Official",
"playPrivate": "Play Private",
"launchLocalServer": "Launch Local Server",
"genshinFolderSet": "Set \"Genshin Impact Game\" folder",
"grasscutterFileSet": "Set \"GrassCutter\" .jar file",
"folderNotSet": "Not set",
"ipPlaceholder": "IP Address",
"noFavorites": "No favorites set",
"settingsTitle": "Settings",
"scriptsSectionTitle": "Scripts",
"killswitchOption": "Kill Switch",
"killswitchSubtitle": "Only for those very paranoid about bans. Kills the game process *and your internet* if something happens to the proxy.",
"proxyOption": "Proxy",
"proxySubtitle": "Install the proxy server via the install script",
"updateOption": "Update",
"updateSubtitle": "Auto updating is temporarily disabled. Check GitHub for the newest releease.",
"languageOption": "Language",
"languageSubtitle": "Select your language!",
"enableServerLauncherOption": "Enable Server Launcher",
"enableServerLauncherSubtitle": "Enable to server launcher tile for launcher a local Grasscutter instance.",
"introSen1": "Looks like this is your first time opening GrassClipper!",
"introSen2": "First of all, welcome, happy to see you here! :)",
"introSen3": "Would you like to run the proxy installer?",
"introSen4": "(required to connect to private servers)",
"proxyInstallBtn": "Install",
"proxyInstallDeny": "No thanks",
"genshinFolderDialog": "Select Genshin Impact Game folder",
"grasscutterFileDialog": "Select GrassCutter server jar file"
}

View File

@ -1,5 +1,5 @@
{ {
"applicationId": "js.grassclipper.app", "applicationId": "js.grassclipper.app",
"version": "0.5.0", "version": "0.5.1",
"resourcesURL": "https://github.com/Grasscutters/GrassClipper/releases/latest/download/resources.neu" "resourcesURL": "https://github.com/Grasscutters/GrassClipper/releases/latest/download/resources.neu"
} }

View File

@ -1,6 +1,6 @@
{ {
"applicationId": "js.grassclipper.app", "applicationId": "js.grassclipper.app",
"version": "0.5.0", "version": "0.5.1",
"defaultMode": "window", "defaultMode": "window",
"port": 0, "port": 0,
"documentRoot": "/resources/", "documentRoot": "/resources/",

View File

@ -1,6 +1,6 @@
{ {
"name": "GrassClipper", "name": "GrassClipper",
"version": "0.5.0", "version": "0.5.1",
"repository": "https://github.com/Grasscutters/GrassClipper.git", "repository": "https://github.com/Grasscutters/GrassClipper.git",
"author": "SpikeHD <spikegdofficial@gmail.com>", "author": "SpikeHD <spikegdofficial@gmail.com>",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -8,7 +8,7 @@
"dev": "neu run", "dev": "neu run",
"build": "npm run clean_dist && neu build && npm run move_files && npm run move_bgs && npm run rename_exe", "build": "npm run clean_dist && neu build && npm run move_files && npm run move_bgs && npm run rename_exe",
"move_files": "cp -r ./proxy ./dist/GrassClipper && cp -r ./scripts ./dist/GrassClipper", "move_files": "cp -r ./proxy ./dist/GrassClipper && cp -r ./scripts ./dist/GrassClipper",
"move_bgs": "mkdir dist\\GrassClipper\\resources\\bg\\private && cp -r ./resources/bg/private ./dist/GrassClipper/resources/bg", "move_bgs": "mkdir dist\\GrassClipper\\resources\\bg\\private && cp -r ./resources/bg/private ./dist/GrassClipper/resources/bg && cp -r ./resources/bg/server ./dist/GrassClipper/resources/bg",
"rename_exe": "mv ./dist/GrassClipper/GrassClipper-win_x64.exe ./dist/GrassClipper/GrassClipper.exe", "rename_exe": "mv ./dist/GrassClipper/GrassClipper-win_x64.exe ./dist/GrassClipper/GrassClipper.exe",
"clean_dist": "rm -rf ./dist" "clean_dist": "rm -rf ./dist"
} }

View File

@ -6,50 +6,49 @@
<script src="js/windowDrag.js"></script> <script src="js/windowDrag.js"></script>
<script src="js/hoverEvt.js"></script> <script src="js/hoverEvt.js"></script>
<script src="js/index.js"></script> <script src="js/index.js"></script>
<script src="js/translation.js"></script>
</head> </head>
<body> <body>
<div id="firstTimeNotice" style="display: none"> <div id="firstTimeNotice" style="display: none">
<span> <span>
<span class="boldTitle">Looks like this is your first time opening GrassClipper!<br/></span>
First of all, welcome, happy to see you here! :)<br/><br/>
Would you like to run the proxy installer?<br/>
(required to connect to private servers)
</span> </span>
<div id="firstTimeBtns"> <div id="firstTimeBtns">
<button class="playBtn" id="firstTimeBtn" onclick="runInstallScript()">Install</button> <button class="playBtn" id="firstTimeInstallBtn" onclick="runInstallScript()">Install</button>
<button class="altBtn" id="firstTimeBtn" onclick="closeFirstTimePopup()">No thanks</button> <button class="altBtn" id="firstTimeDenyBtn" onclick="closeFirstTimePopup()">No thanks</button>
</div> </div>
</div> </div>
<div id="settingsPanel" style="display: none;"> <div id="settingsPanel" style="display: none;">
<div id="settingsTitleBar">
<span id="fullSettingsTitle">Settings</span> <span id="fullSettingsTitle">Settings</span>
<div id="settingsClose"> <div id="settingsClose">
<img src="icons/close.svg" onclick="closeSettings()" /> <img src="icons/close.svg" onclick="closeSettings()" />
</div> </div>
</div>
<div id="settingsPanelInner"> <div id="settingsPanelInner">
<div class="settingTitle"> <div class="settingTitle", id="scriptsTitle">
<span>Scripts</span> <span>Scripts</span>
</div> </div>
<div class="settingsRow"> <div class="settingsRow">
<div class="settingSection"> <div class="settingSection">
<span class="settingLabel">Kill Switch</span> <span class="settingLabel" id="killswitchTitle">Kill Switch</span>
<input type="checkbox" id="killswitchOption" onchange="toggleKillSwitch()" /> <input type="checkbox" id="killswitchOption" onchange="toggleKillSwitch()" />
</div> </div>
<span class="settingSubtitle"> <span class="settingSubtitle" id="killswitchSubtitle">
Only for those very paranoid about bans. Kills the game process *and your internet* if something happens to the proxy. Only for those very paranoid about bans. Kills the game process *and your internet* if something happens to the proxy.
</span> </span>
</div> </div>
<div class="settingsRow"> <div class="settingsRow">
<div class="settingSection"> <div class="settingSection">
<span class="settingLabel">Install Proxy Server</span> <span class="settingLabel" id="proxyTitle">Install Proxy Server</span>
<button class="smolBtn" onclick="runInstallScript()">Install</button> <button class="smolBtn" onclick="runInstallScript()" id="proxyInstall">Install</button>
</div> </div>
<span class="settingSubtitle"> <span class="settingSubtitle" id="proxySubtitle">
Install the proxy server via the install script. Install the proxy server via the install script.
</span> </span>
</div> </div>
<div class="settingsRow"> <div class="settingsRow">
<div class="settingSection"> <div class="settingSection">
<span class="settingLabel">Update</span> <span class="settingLabel" id="updateTitle">Update</span>
<button class="smolBtn disabled" onclick="updateResources()" id="updateBtn" disabled>Update</button> <button class="smolBtn disabled" onclick="updateResources()" id="updateBtn" disabled>Update</button>
</div> </div>
<span class="settingSubtitle" id="updateSubtitle"> <span class="settingSubtitle" id="updateSubtitle">
@ -58,13 +57,23 @@
</div> </div>
<div class="settingsRow"> <div class="settingsRow">
<div class="settingSection"> <div class="settingSection">
<span class="settingLabel">Enable Server Launcher</span> <span class="settingLabel", id="serverLaunchTitle">Enable Server Launcher</span>
<input type="checkbox" id="serverLaunchOption" onchange="toggleServerLaunchSection()" /> <input type="checkbox" id="serverLaunchOption" onchange="toggleServerLaunchSection()" />
</div> </div>
<span class="settingSubtitle" id="serverSubtitle"> <span class="settingSubtitle" id="serverSubtitle">
Enable to server launcher tile for launcher a local Grasscutter instance. Enable to server launcher tile for launcher a local Grasscutter instance.
</span> </span>
</div> </div>
<div class="settingsRow">
<div class="settingSection">
<span class="settingLabel", id="languageTitle">Language</span>
<select id="languageSelect" onchange="handleLanguageChange(this)">
</select>
</div>
<span class="settingSubtitle" id="languageSubtitle">
Select your language!
</span>
</div>
</div> </div>
</div> </div>
<div id="controlBar"> <div id="controlBar">
@ -105,11 +114,11 @@
<div id="bottomBar"> <div id="bottomBar">
<div class="bottomSection"> <div class="bottomSection">
<div> <div>
<button class="smolBtn" onclick="setGenshinImpactFolder()">Set "Genshin Impact Game" folder</button> <button class="smolBtn" onclick="setGenshinImpactFolder()" id="genshinFolderSet">Set "Genshin Impact Game" folder</button>
<span id="genshinPath" style="margin-top: 4px;"></span> <span id="genshinPath" style="margin-top: 4px;"></span>
</div> </div>
<div style="display: none;"> <div style="display: none;">
<button class="smolBtn" onclick="setGrassCutterFolder()">Set "GrassCutter" .jar file</button> <button class="smolBtn" onclick="setGrassCutterFolder()" id="grasscutterFileSet">Set "GrassCutter" .jar file</button>
<span id="serverPath" style="margin-top: 4px;"></span> <span id="serverPath" style="margin-top: 4px;"></span>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
Neutralino.init(); Neutralino.init();
let localeObj;
const filesystem = Neutralino.filesystem const filesystem = Neutralino.filesystem
/** /**
@ -17,14 +18,6 @@ document.addEventListener('DOMContentLoaded', async () => {
const config = await getCfg() const config = await getCfg()
const ipArr = await getFavIps() const ipArr = await getFavIps()
if (!config.genshinImpactFolder) {
handleGenshinFolderNotSet()
}
if (!config.serverFolder) {
handleServerNotSet()
}
if (config.serverLaunchPanel) { if (config.serverLaunchPanel) {
displayServerLaunchSection() displayServerLaunchSection()
} }
@ -75,6 +68,17 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
} }
}); });
// Ensure we do the translation at the very end, after everything else has loaded
await doTranslation()
if (!config.genshinImpactFolder) {
handleGenshinFolderNotSet()
}
if (!config.serverFolder) {
handleServerNotSet()
}
}) })
/** /**
@ -104,7 +108,8 @@ async function getCfg() {
serverFolder: '', serverFolder: '',
lastConnect: '', lastConnect: '',
enableKillswitch: false, enableKillswitch: false,
serverLaunchPanel: false serverLaunchPanel: false,
language: 'en'
} }
const cfgStr = await Neutralino.storage.getData('config').catch(e => { const cfgStr = await Neutralino.storage.getData('config').catch(e => {
// The data isn't set, so this is our first time opening // The data isn't set, so this is our first time opening
@ -151,7 +156,7 @@ async function enableButtons() {
*/ */
async function handleGenshinFolderNotSet() { async function handleGenshinFolderNotSet() {
// Set buttons to greyed out and disable // Set buttons to greyed out and disable
document.querySelector('#genshinPath').innerHTML = 'Not set' document.querySelector('#genshinPath').innerHTML = localeObj.folderNotSet
// Set official server background to default // Set official server background to default
document.querySelector('#firstPanel').style.backgroundImage = `url("../bg/private/default.png")` document.querySelector('#firstPanel').style.backgroundImage = `url("../bg/private/default.png")`
@ -170,7 +175,7 @@ async function handleGenshinFolderNotSet() {
async function handleServerNotSet() { async function handleServerNotSet() {
// Set buttons to greyed out and disable // Set buttons to greyed out and disable
document.querySelector('#serverPath').innerHTML = 'Not set' document.querySelector('#serverPath').innerHTML = localeObj.folderNotSet
// Set official server background to default // Set official server background to default
// document.querySelector('#firstPanel').style.backgroundImage = `url("../bg/private/default.png")` // document.querySelector('#firstPanel').style.backgroundImage = `url("../bg/private/default.png")`
@ -339,7 +344,7 @@ async function handleFavoriteList() {
document.createElement('li') document.createElement('li')
) )
listItem.innerHTML = 'No favorites set' listItem.innerHTML = localeObj.noFavorites
} }
for (const ip of ipArr) { for (const ip of ipArr) {
@ -405,6 +410,9 @@ async function openSettings() {
killSwitch.checked = config.enableKillswitch killSwitch.checked = config.enableKillswitch
serverLaunch.checked = config.serverLaunchPanel serverLaunch.checked = config.serverLaunchPanel
// Load languages
getLanguages()
// Check for updates // Check for updates
//checkForUpdatesAndShow() //checkForUpdatesAndShow()
} }
@ -487,11 +495,46 @@ async function toggleServerLaunchSection() {
Neutralino.storage.setData('config', JSON.stringify(config)) Neutralino.storage.setData('config', JSON.stringify(config))
} }
async function getLanguages() {
const languageFiles = (await filesystem.readDirectory(`${NL_CWD}/languages`)).filter(file => file.entry.endsWith('.json'))
const config = await getCfg()
// Load all languages as options
for (const file of languageFiles) {
const fullLanguageName = JSON.parse(await filesystem.readFile(`${NL_CWD}/languages/${file.entry}`)).fullLangName
const lang = file.entry.split('.json')[0]
const option = document.createElement('option')
option.value = lang
option.innerHTML = fullLanguageName
// Set language selected to config language
if (lang === config.language) {
option.selected = true
}
document.querySelector('#languageSelect').appendChild(option)
}
}
async function handleLanguageChange(elm) {
const list = elm
const config = await getCfg()
// Set language in config
config.language = list.value
Neutralino.storage.setData('config', JSON.stringify(config))
// Force refresh of application, no need for restart!
window.location.reload()
}
/** /**
* Set the game folder by opening a folder picker * Set the game folder by opening a folder picker
*/ */
async function setGenshinImpactFolder() { async function setGenshinImpactFolder() {
const folder = await Neutralino.os.showFolderDialog('Select Genshin Impact Game folder') const folder = await Neutralino.os.showFolderDialog(localeObj.genshinFolderDialog)
// Set the folder in our configuration // Set the folder in our configuration
const config = await getCfg() const config = await getCfg()
@ -516,7 +559,7 @@ async function setGenshinImpactFolder() {
} }
async function setGrassCutterFolder() { async function setGrassCutterFolder() {
const folder = await Neutralino.os.showOpenDialog('Select GrassCutter server jar', { const folder = await Neutralino.os.showOpenDialog(localeObj.grasscutterFileDialog, {
filters: [ filters: [
{ name: 'Jar files', extensions: ['jar'] } { name: 'Jar files', extensions: ['jar'] }
] ]

View File

@ -0,0 +1,67 @@
async function doTranslation() {
const config = await getCfg()
// See if the localization file exists
const localizations = await filesystem.readDirectory(`${NL_CWD}/languages`)
// Use english if the selected file does not exist
const selectedLanguage = localizations.find(f => f.entry === `${config.language}.json`)
// Use english if the selected file does not exist
if (!selectedLanguage) {
config.language = 'en'
}
const localization = await filesystem.readFile(`${NL_CWD}/languages/${config.language}.json`)
localeObj = JSON.parse(localization)
const set = (id, localeString) => document.getElementById(id).innerHTML = localeString || 'UNKNOWN'
// Begin filling in values
set('titleSection', localeObj.appName)
// Play buttons
set('playOfficial', localeObj.playOfficial)
set('playPrivate', localeObj.playPrivate)
set('serverLaunch', localeObj.launchLocalServer)
// File select buttons
set('genshinFolderSet', localeObj.genshinFolderSet)
set('grasscutterFileSet', localeObj.grasscutterFileSet)
// Private options
set('ip', localeObj.ipPlaceholder)
// Settings
set('fullSettingsTitle', localeObj.settingsTitle)
set('scriptsTitle', localeObj.scriptsSectionTitle)
set('killswitchTitle', localeObj.killswitchOption)
set('killswitchSubtitle', localeObj.killswitchSubtitle)
set('proxyTitle', localeObj.proxyOption)
set('proxyInstall', localeObj.proxyInstallBtn)
set('proxySubtitle', localeObj.proxySubtitle)
set('updateBtn', localeObj.updateOption)
set('updateTitle', localeObj.updateOption)
set('updateSubtitle', localeObj.updateSubtitle)
set('languageTitle', localeObj.languageOption)
set('languageSubtitle', localeObj.languageSubtitle)
set('serverLaunchTitle', localeObj.enableServerLauncherOption)
set('serverSubtitle', localeObj.enableServerLauncherSubtitle)
// Intro popup
const popup = document.getElementById('firstTimeNotice')
const introSpan = popup.querySelector('span')
const boldIntroSpan = document.createElement('span')
boldIntroSpan.innerHTML = localeObj.introSen1 + '\n'
boldIntroSpan.classList.add('boldTitle')
introSpan.appendChild(boldIntroSpan)
introSpan.innerHTML += localeObj.introSen2 + '<br>'
introSpan.innerHTML += localeObj.introSen3 + '<br>'
introSpan.innerHTML += localeObj.introSen4 + '<br>'
set('firstTimeInstallBtn', localeObj.proxyInstallBtn)
set('firstTimeDenyBtn', localeObj.proxyInstallDeny)
}

View File

@ -51,7 +51,7 @@ body {
#settingsPanel { #settingsPanel {
width: 35%; width: 35%;
height: 60%; height: 70%;
} }
#fullSettingsTitle { #fullSettingsTitle {
@ -102,9 +102,15 @@ body {
height: 30px; height: 30px;
} }
#settingsTitleBar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
#settingsClose { #settingsClose {
display: inline-block; display: inline-block;
margin-left: 70%;
transition: filter 0.1s ease-in-out; transition: filter 0.1s ease-in-out;
} }