Sprache Deutsch/Englisch
Umschalter in den Einstellungen und auf der Anmeldeseite; Meldungen des Launchers (Lang.cs) und der Website (Accept-Language) in der gewaehlten Sprache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use strict";
|
||||
// BSO Launcher - Oberflaeche. Spricht ueber window.chrome.webview mit dem Launcher (C#), nie direkt mit dem Netz.
|
||||
// Texte auf Deutsch im Code, t() uebersetzt (i18n.js).
|
||||
|
||||
// ---------------------------------------------------------------- Verbindung zum Launcher
|
||||
|
||||
@@ -46,9 +47,9 @@ function h(tag, attrs, ...children) {
|
||||
}
|
||||
|
||||
function toast(text, error) {
|
||||
const t = h("div", { class: "toast" + (error ? " error" : "") }, text);
|
||||
$("toasts").append(t);
|
||||
setTimeout(() => t.remove(), error ? 7000 : 4000);
|
||||
const el = h("div", { class: "toast" + (error ? " error" : "") }, text);
|
||||
$("toasts").append(el);
|
||||
setTimeout(() => el.remove(), error ? 7000 : 4000);
|
||||
}
|
||||
|
||||
/** Knopf waehrend einer Aktion sperren; Fehler als Meldung. */
|
||||
@@ -59,7 +60,19 @@ async function busy(button, action) {
|
||||
finally { button.disabled = false; }
|
||||
}
|
||||
|
||||
const date = (iso) => new Date(iso).toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric" });
|
||||
const date = (iso) => new Date(iso).toLocaleDateString(lang === "en" ? "en-GB" : "de-DE", { day: "2-digit", month: "2-digit", year: "numeric" });
|
||||
|
||||
// ---------------------------------------------------------------- Sprache
|
||||
|
||||
/** Sprache wechseln: im Launcher speichern (auch fuer seine eigenen Meldungen), alles neu beschriften. */
|
||||
async function setLang(l) {
|
||||
lang = l;
|
||||
await call("setSettings", { language: l });
|
||||
translateStatic();
|
||||
renderLangSwitch(setLang);
|
||||
setAuthMode(registerMode);
|
||||
if (!$("app").classList.contains("hidden")) showPage(page);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Fenster
|
||||
|
||||
@@ -78,7 +91,7 @@ function setAuthMode(register) {
|
||||
$("tabRegister").classList.toggle("on", register);
|
||||
$("emailRow").classList.toggle("hidden", !register);
|
||||
$("password2Row").classList.toggle("hidden", !register);
|
||||
$("authButton").textContent = register ? "Konto erstellen" : "Anmelden";
|
||||
$("authButton").textContent = register ? t("Konto erstellen") : t("Anmelden");
|
||||
$("password").autocomplete = register ? "new-password" : "current-password";
|
||||
$("authError").classList.add("hidden");
|
||||
}
|
||||
@@ -91,7 +104,7 @@ $("authForm").addEventListener("submit", async (e) => {
|
||||
const error = $("authError");
|
||||
error.classList.add("hidden");
|
||||
if (registerMode && $("password").value !== $("password2").value) {
|
||||
error.textContent = "Die Passwoerter sind nicht gleich";
|
||||
error.textContent = t("Die Passwoerter sind nicht gleich");
|
||||
error.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +114,7 @@ $("authForm").addEventListener("submit", async (e) => {
|
||||
{ username: $("username").value, password: $("password").value, email: $("email").value });
|
||||
$("password").value = $("password2").value = "";
|
||||
showApp(r.username);
|
||||
if (registerMode) toast("Willkommen bei BSO, " + r.username + "!");
|
||||
if (registerMode) toast(t("Willkommen bei BSO, {0}!", r.username));
|
||||
} catch (err) {
|
||||
error.textContent = err.message;
|
||||
error.classList.remove("hidden");
|
||||
@@ -139,14 +152,14 @@ function showPage(name) {
|
||||
page = name;
|
||||
document.querySelectorAll("#nav [data-page]").forEach((b) => b.classList.toggle("on", b.dataset.page === name));
|
||||
const root = $("page");
|
||||
root.replaceChildren(h("p", { class: "muted" }, "Laden …"));
|
||||
root.replaceChildren(h("p", { class: "muted" }, t("Laden …")));
|
||||
({ home: renderHome, friends: renderFriends, news: renderNews, settings: renderSettings })[name](root);
|
||||
}
|
||||
|
||||
// Anmeldung abgelaufen (z.B. beim Holen des Spiel-Tickets)
|
||||
on("loggedOut", () => showAuth());
|
||||
|
||||
// Fortschritt (Downloads)
|
||||
// Fortschritt (Downloads) - der Text kommt schon uebersetzt vom Launcher
|
||||
let progressBox = null;
|
||||
on("progress", (m) => {
|
||||
if (!progressBox) return;
|
||||
@@ -162,31 +175,31 @@ async function renderHome(root) {
|
||||
if (page !== "home") return;
|
||||
|
||||
progressBox = h("div", { class: "progress hidden" }, h("span", { class: "small muted" }), h("div", { class: "bar" }, h("div")));
|
||||
const play = h("button", { class: "btn primary play", type: "button" }, s.game.running ? "LAEUFT" : "SPIELEN");
|
||||
const play = h("button", { class: "btn primary play", type: "button" }, s.game.running ? t("LAEUFT") : t("SPIELEN"));
|
||||
play.disabled = s.game.running;
|
||||
play.addEventListener("click", () => busy(play, async () => {
|
||||
await call("launch");
|
||||
toast("Bus-Simulator 2012 startet …");
|
||||
toast(t("Bus-Simulator 2012 startet …"));
|
||||
}));
|
||||
|
||||
const ready = s.game.found && s.dotnet && (s.mod.installed || s.mod.latest);
|
||||
const line = s.mod.installed ? `BSO ${s.mod.installed} installiert · Server ${s.settings.server}` : "BSO wird beim ersten Start installiert";
|
||||
const line = s.mod.installed ? t("BSO {0} installiert · Server {1}", s.mod.installed, s.settings.server) : t("BSO wird beim ersten Start installiert");
|
||||
|
||||
const children = [
|
||||
h("div", { class: "hero" },
|
||||
h("div", {}, h("h1", {}, "Bus Simulator Online"), h("p", {}, "Multiplayer fuer Bus-Simulator 2012")),
|
||||
h("div", {}, h("h1", {}, "Bus Simulator Online"), h("p", {}, t("Multiplayer fuer Bus-Simulator 2012"))),
|
||||
h("img", { src: "logo.svg", alt: "" }),
|
||||
h("div", { class: "hero-actions" }, play, h("span", { class: "muted small" }, ready ? line : "Vor dem Start fehlt noch etwas - siehe unten"))),
|
||||
h("div", { class: "hero-actions" }, play, h("span", { class: "muted small" }, ready ? line : t("Vor dem Start fehlt noch etwas - siehe unten")))),
|
||||
progressBox
|
||||
];
|
||||
|
||||
if (s.launcherUpdate) {
|
||||
const b = h("button", { class: "btn primary small", type: "button" }, "Jetzt aktualisieren");
|
||||
b.addEventListener("click", () => busy(b, async () => { await call("installLauncherUpdate"); toast("Launcher startet neu …"); }));
|
||||
children.push(h("div", { class: "banner" }, h("span", {}, h("b", {}, "Launcher " + s.launcherUpdate), " ist da."), b));
|
||||
const b = h("button", { class: "btn primary small", type: "button" }, t("Jetzt aktualisieren"));
|
||||
b.addEventListener("click", () => busy(b, async () => { await call("installLauncherUpdate"); toast(t("Launcher startet neu …")); }));
|
||||
children.push(h("div", { class: "banner" }, h("span", {}, h("b", {}, t("Launcher {0}", s.launcherUpdate)), t(" ist da.")), b));
|
||||
}
|
||||
if (!s.masterReachable)
|
||||
children.push(h("div", { class: "banner warn" }, h("span", {}, "BSO-Server nicht erreichbar - Updates werden spaeter geprueft.")));
|
||||
children.push(h("div", { class: "banner warn" }, h("span", {}, t("BSO-Server nicht erreichbar - Updates werden spaeter geprueft."))));
|
||||
|
||||
children.push(h("div", { class: "cards" }, gameCard(s), modCard(s), dotnetCard(s), serverCard(s)));
|
||||
root.replaceChildren(...children);
|
||||
@@ -197,26 +210,26 @@ function card(title, state, value, ...extra) {
|
||||
}
|
||||
|
||||
function gameCard(s) {
|
||||
const choose = h("button", { class: "btn small", type: "button" }, s.game.found ? "Anderen Ordner waehlen" : "Ordner waehlen");
|
||||
const choose = h("button", { class: "btn small", type: "button" }, s.game.found ? t("Anderen Ordner waehlen") : t("Ordner waehlen"));
|
||||
choose.addEventListener("click", () => busy(choose, async () => { if ((await call("chooseGameFolder")).changed) showPage("home"); }));
|
||||
return card("Bus-Simulator 2012", s.game.found ? "ok" : "bad", s.game.found ? "Gefunden" : "Nicht gefunden",
|
||||
s.game.found ? h("span", { class: "mono muted" }, s.game.folder) : h("span", { class: "muted small" }, "Ueber Steam installiert? Sonst den Ordner von Hand waehlen."),
|
||||
return card("Bus-Simulator 2012", s.game.found ? "ok" : "bad", s.game.found ? t("Gefunden") : t("Nicht gefunden"),
|
||||
s.game.found ? h("span", { class: "mono muted" }, s.game.folder) : h("span", { class: "muted small" }, t("Ueber Steam installiert? Sonst den Ordner von Hand waehlen.")),
|
||||
h("div", { class: "row" }, choose));
|
||||
}
|
||||
|
||||
function modCard(s) {
|
||||
const m = s.mod;
|
||||
const state = m.installed && !m.updateAvailable ? "ok" : m.latest ? "warn" : "bad";
|
||||
const value = m.installed ? `Version ${m.installed}` : "Nicht installiert";
|
||||
const info = m.updateAvailable ? `Neu: ${m.latest}` : m.latest ? "Aktuell" : "Keine Version auf dem Server";
|
||||
const value = m.installed ? t("Version {0}", m.installed) : t("Nicht installiert");
|
||||
const info = m.updateAvailable ? t("Neu: {0}", m.latest) : m.latest ? t("Aktuell") : t("Keine Version auf dem Server");
|
||||
const extra = [h("span", { class: "muted small" }, info)];
|
||||
// Immer da: holt die neueste Mod von der Website (bei SPIELEN passiert das sonst automatisch, wenn eingeschaltet)
|
||||
if (m.latest && s.game.found) {
|
||||
const label = !m.installed ? "Mod herunterladen" : m.updateAvailable ? "Mod aktualisieren" : "Neu herunterladen";
|
||||
const label = !m.installed ? t("Mod herunterladen") : m.updateAvailable ? t("Mod aktualisieren") : t("Neu herunterladen");
|
||||
const b = h("button", { class: "btn small" + (!m.installed || m.updateAvailable ? " primary" : ""), type: "button" }, label);
|
||||
b.addEventListener("click", () => busy(b, async () => {
|
||||
const r = await call("installMod");
|
||||
toast("BSO " + r.version + " installiert");
|
||||
toast(t("BSO {0} installiert", r.version));
|
||||
showPage("home");
|
||||
}));
|
||||
extra.push(h("div", { class: "row" }, b));
|
||||
@@ -225,11 +238,11 @@ function modCard(s) {
|
||||
}
|
||||
|
||||
function dotnetCard(s) {
|
||||
if (s.dotnet) return card(".NET 10 Runtime", "ok", "Installiert", h("span", { class: "muted small" }, "Die Mod laeuft mit .NET 10 (x64)."));
|
||||
const b = h("button", { class: "btn small", type: "button" }, "Herunterladen");
|
||||
if (s.dotnet) return card(".NET 10 Runtime", "ok", t("Installiert"), h("span", { class: "muted small" }, t("Die Mod laeuft mit .NET 10 (x64).")));
|
||||
const b = h("button", { class: "btn small", type: "button" }, t("Herunterladen"));
|
||||
b.addEventListener("click", () => call("openDotnet"));
|
||||
return card(".NET 10 Runtime", "bad", "Fehlt",
|
||||
h("span", { class: "muted small" }, "BSO braucht die .NET 10 Runtime (x64) - \".NET Runtime\" fuer Windows x64 installieren."),
|
||||
return card(".NET 10 Runtime", "bad", t("Fehlt"),
|
||||
h("span", { class: "muted small" }, t("BSO braucht die .NET 10 Runtime (x64) - \".NET Runtime\" fuer Windows x64 installieren.")),
|
||||
h("div", { class: "row" }, b));
|
||||
}
|
||||
|
||||
@@ -237,12 +250,12 @@ function serverCard(s) {
|
||||
const input = h("input", { value: s.settings.server, placeholder: "host:7777", spellcheck: "false" });
|
||||
const auto = h("input", { type: "checkbox" });
|
||||
auto.checked = s.settings.autoConnect;
|
||||
const save = () => call("setSettings", { server: input.value.trim(), autoConnect: auto.checked }).then(() => toast("Gespeichert"));
|
||||
const save = () => call("setSettings", { server: input.value.trim(), autoConnect: auto.checked }).then(() => toast(t("Gespeichert")));
|
||||
input.addEventListener("change", save);
|
||||
auto.addEventListener("change", save);
|
||||
return card("Server", "ok", "Mitspielen",
|
||||
h("label", {}, h("span", {}, "Adresse"), input),
|
||||
h("label", { class: "switch" }, h("span", {}, "Beim Start verbinden", h("small", {}, "sonst im Spiel mit F8")), auto));
|
||||
return card("Server", "ok", t("Mitspielen"),
|
||||
h("label", {}, h("span", {}, t("Adresse")), input),
|
||||
h("label", { class: "switch" }, h("span", {}, t("Beim Start verbinden"), h("small", {}, t("sonst im Spiel mit F8"))), auto));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Freunde & Lobby
|
||||
@@ -258,8 +271,8 @@ async function loadSocial() {
|
||||
$("friendsBadge").textContent = n;
|
||||
$("friendsBadge").classList.toggle("hidden", n === 0);
|
||||
// Neue Einladungen/Anfragen melden (auch auf anderen Seiten)
|
||||
for (const i of lobby.invites) notice("l" + i.lobbyId, `${i.owner} lädt dich in die Lobby ein`);
|
||||
for (const r of friends.incoming) notice("f" + r.id, `${r.username} möchte dein Freund sein`);
|
||||
for (const i of lobby.invites) notice("l" + i.lobbyId, t("{0} lädt dich in die Lobby ein", i.owner));
|
||||
for (const r of friends.incoming) notice("f" + r.id, t("{0} möchte dein Freund sein", r.username));
|
||||
}
|
||||
|
||||
function notice(key, text) {
|
||||
@@ -277,7 +290,7 @@ setInterval(async () => {
|
||||
}, 3000);
|
||||
|
||||
async function renderFriends(root) {
|
||||
root.replaceChildren(h("h1", {}, "Freunde & Lobby"), h("div", { class: "social", id: "socialRoot" }, h("p", { class: "muted" }, "Laden …")));
|
||||
root.replaceChildren(h("h1", {}, t("Freunde & Lobby")), h("div", { class: "social", id: "socialRoot" }, h("p", { class: "muted" }, t("Laden …"))));
|
||||
try { await loadSocial(); }
|
||||
catch (e) { $("socialRoot").replaceChildren(h("p", { class: "error" }, e.message)); return; }
|
||||
if (page === "friends") drawSocial();
|
||||
@@ -305,48 +318,48 @@ function lobbyCard() {
|
||||
const { lobby, invites } = social.lobby;
|
||||
const card = h("div", { class: "card" });
|
||||
for (const i of invites)
|
||||
card.append(h("div", { class: "invite" }, h("span", {}, h("b", {}, i.owner), " lädt dich in die Lobby ein"),
|
||||
action("Beitreten", "primary", "lobbyJoin", { lobbyId: i.lobbyId }), action("Ablehnen", "", "lobbyDecline", { lobbyId: i.lobbyId })));
|
||||
card.append(h("div", { class: "invite" }, h("span", {}, h("b", {}, i.owner), t(" lädt dich in die Lobby ein")),
|
||||
action(t("Beitreten"), "primary", "lobbyJoin", { lobbyId: i.lobbyId }), action(t("Ablehnen"), "", "lobbyDecline", { lobbyId: i.lobbyId })));
|
||||
|
||||
if (!lobby) {
|
||||
card.append(h("h3", {}, "Lobby"),
|
||||
h("p", { class: "muted small" }, "Erstelle eine Lobby und lade Freunde ein. Der Server läuft auf dem BSO-Relay – niemand muss etwas hosten oder Ports freigeben."),
|
||||
h("div", { class: "row" }, action("Lobby erstellen", "primary", "lobbyCreate")));
|
||||
card.append(h("h3", {}, t("Lobby")),
|
||||
h("p", { class: "muted small" }, t("Erstelle eine Lobby und lade Freunde ein. Der Server läuft auf dem BSO-Relay – niemand muss etwas hosten oder Ports freigeben.")),
|
||||
h("div", { class: "row" }, action(t("Lobby erstellen"), "primary", "lobbyCreate")));
|
||||
return card;
|
||||
}
|
||||
|
||||
const play = h("button", { class: "btn primary play", type: "button" }, "SPIELEN");
|
||||
const play = h("button", { class: "btn primary play", type: "button" }, t("SPIELEN"));
|
||||
play.disabled = !lobby.serverOnline;
|
||||
play.addEventListener("click", () => busy(play, async () => { await call("playLobby"); toast("Bus-Simulator 2012 startet und verbindet mit der Lobby …"); }));
|
||||
play.addEventListener("click", () => busy(play, async () => { await call("playLobby"); toast(t("Bus-Simulator 2012 startet und verbindet mit der Lobby …")); }));
|
||||
|
||||
const state = h("div", { class: "server-state" },
|
||||
h("span", { class: "dot " + (lobby.serverOnline ? "ok" : "bad") }),
|
||||
h("span", { style: "flex:1" }, lobby.serverOnline ? "Server läuft" : lobby.isOwner ? "Server aus – starte ihn, dann können alle spielen" : `Server aus – warte, bis ${lobby.owner} ihn startet`),
|
||||
lobby.isOwner ? (lobby.serverOnline ? action("Server stoppen", "", "lobbyStop") : action("Server starten", "primary", "lobbyStart")) : null);
|
||||
h("span", { style: "flex:1" }, lobby.serverOnline ? t("Server läuft") : lobby.isOwner ? t("Server aus – starte ihn, dann können alle spielen") : t("Server aus – warte, bis {0} ihn startet", lobby.owner)),
|
||||
lobby.isOwner ? (lobby.serverOnline ? action(t("Server stoppen"), "", "lobbyStop") : action(t("Server starten"), "primary", "lobbyStart")) : null);
|
||||
|
||||
const members = h("div", { class: "people" }, ...lobby.members.map((m) => h("div", { class: "person" },
|
||||
h("span", { class: "dot " + (m.online ? "ok" : "") }),
|
||||
h("span", { class: "name" }, m.username),
|
||||
m.isOwner ? h("span", { class: "crown", title: "Ersteller" }, "★") : null,
|
||||
lobby.isOwner && !m.isOwner ? action("Entfernen", "ghost", "lobbyKick", { accountId: m.accountId }) : null)));
|
||||
m.isOwner ? h("span", { class: "crown", title: t("Ersteller") }, "★") : null,
|
||||
lobby.isOwner && !m.isOwner ? action(t("Entfernen"), "ghost", "lobbyKick", { accountId: m.accountId }) : null)));
|
||||
|
||||
card.append(h("h3", {}, lobby.isOwner ? "Deine Lobby" : `Lobby von ${lobby.owner}`), state,
|
||||
h("div", { class: "section-title" }, `Mitglieder (${lobby.members.length})`), members,
|
||||
lobby.invited.length ? h("p", { class: "muted small" }, `${lobby.invited.length} Einladung(en) offen`) : null,
|
||||
card.append(h("h3", {}, lobby.isOwner ? t("Deine Lobby") : t("Lobby von {0}", lobby.owner)), state,
|
||||
h("div", { class: "section-title" }, t("Mitglieder ({0})", lobby.members.length)), members,
|
||||
lobby.invited.length ? h("p", { class: "muted small" }, t("{0} Einladung(en) offen", lobby.invited.length)) : null,
|
||||
h("div", { class: "row", style: "justify-content:space-between;margin-top:6px" }, play,
|
||||
action(lobby.isOwner ? "Lobby auflösen" : "Lobby verlassen", "danger", "lobbyLeave")));
|
||||
action(lobby.isOwner ? t("Lobby auflösen") : t("Lobby verlassen"), "danger", "lobbyLeave")));
|
||||
return card;
|
||||
}
|
||||
|
||||
function friendsCard() {
|
||||
const { friends, incoming, outgoing } = social.friends;
|
||||
const lobby = social.lobby.lobby;
|
||||
const name = h("input", { placeholder: "Benutzername", maxlength: "32", spellcheck: "false" });
|
||||
const add = h("button", { class: "btn small primary", type: "button" }, "Hinzufügen");
|
||||
const name = h("input", { placeholder: t("Benutzername"), maxlength: "32", spellcheck: "false" });
|
||||
const add = h("button", { class: "btn small primary", type: "button" }, t("Hinzufügen"));
|
||||
const send = () => busy(add, async () => {
|
||||
if (!name.value.trim()) return;
|
||||
await call("friendRequest", { username: name.value.trim() });
|
||||
toast("Anfrage an " + name.value.trim() + " geschickt");
|
||||
toast(t("Anfrage an {0} geschickt", name.value.trim()));
|
||||
name.value = "";
|
||||
await loadSocial();
|
||||
drawSocial();
|
||||
@@ -354,41 +367,44 @@ function friendsCard() {
|
||||
add.addEventListener("click", send);
|
||||
name.addEventListener("keydown", (e) => { if (e.key === "Enter") send(); });
|
||||
|
||||
const card = h("div", { class: "card" }, h("h3", {}, "Freunde"), h("div", { class: "row" }, name, add));
|
||||
const card = h("div", { class: "card" }, h("h3", {}, t("Freunde")), h("div", { class: "row" }, name, add));
|
||||
if (incoming.length)
|
||||
card.append(h("div", { class: "section-title" }, "Anfragen"), h("div", { class: "people" }, ...incoming.map((r) => h("div", { class: "person" },
|
||||
h("span", { class: "name" }, r.username), action("Annehmen", "primary", "friendAccept", { id: r.id }), action("Ablehnen", "", "friendRemove", { id: r.id })))));
|
||||
card.append(h("div", { class: "section-title" }, t("Anfragen")), h("div", { class: "people" }, ...incoming.map((r) => h("div", { class: "person" },
|
||||
h("span", { class: "name" }, r.username), action(t("Annehmen"), "primary", "friendAccept", { id: r.id }), action(t("Ablehnen"), "", "friendRemove", { id: r.id })))));
|
||||
|
||||
card.append(h("div", { class: "section-title" }, `Freunde (${friends.filter((f) => f.online).length} online)`));
|
||||
if (!friends.length) card.append(h("p", { class: "muted small" }, "Noch keine Freunde – füge jemanden über seinen BSO-Benutzernamen hinzu."));
|
||||
card.append(h("div", { class: "section-title" }, t("Freunde ({0} online)", friends.filter((f) => f.online).length)));
|
||||
if (!friends.length) card.append(h("p", { class: "muted small" }, t("Noch keine Freunde – füge jemanden über seinen BSO-Benutzernamen hinzu.")));
|
||||
card.append(h("div", { class: "people" }, ...friends.map((f) => {
|
||||
const inLobby = lobby && lobby.members.some((m) => m.accountId === f.accountId);
|
||||
const invited = lobby && lobby.invited.includes(f.accountId);
|
||||
const remove = h("button", { class: "x", type: "button", title: "Freund entfernen" }, "");
|
||||
remove.addEventListener("click", () => { if (confirm(`${f.username} als Freund entfernen?`)) busy(remove, async () => { await call("friendRemove", { id: f.id }); await loadSocial(); drawSocial(); }); });
|
||||
const remove = h("button", { class: "x", type: "button", title: t("Freund entfernen") }, "");
|
||||
remove.addEventListener("click", () => { if (confirm(t("{0} als Freund entfernen?", f.username))) busy(remove, async () => { await call("friendRemove", { id: f.id }); await loadSocial(); drawSocial(); }); });
|
||||
return h("div", { class: "person" },
|
||||
h("span", { class: "dot " + (f.online ? "ok" : "") }), h("span", { class: "name" }, f.username),
|
||||
lobby && lobby.isOwner && !inLobby ? (invited ? h("span", { class: "muted small" }, "eingeladen") : action("Einladen", "", "lobbyInvite", { accountId: f.accountId })) : null,
|
||||
inLobby ? h("span", { class: "muted small" }, "in der Lobby") : null,
|
||||
lobby && lobby.isOwner && !inLobby ? (invited ? h("span", { class: "muted small" }, t("eingeladen")) : action(t("Einladen"), "", "lobbyInvite", { accountId: f.accountId })) : null,
|
||||
inLobby ? h("span", { class: "muted small" }, t("in der Lobby")) : null,
|
||||
remove);
|
||||
})));
|
||||
|
||||
if (outgoing.length)
|
||||
card.append(h("div", { class: "section-title" }, "Gesendet"), h("div", { class: "people" }, ...outgoing.map((r) => h("div", { class: "person" },
|
||||
h("span", { class: "name" }, r.username), h("span", { class: "muted small" }, "wartet …"), action("Zurückziehen", "ghost", "friendRemove", { id: r.id })))));
|
||||
card.append(h("div", { class: "section-title" }, t("Gesendet")), h("div", { class: "people" }, ...outgoing.map((r) => h("div", { class: "person" },
|
||||
h("span", { class: "name" }, r.username), h("span", { class: "muted small" }, t("wartet …")), action(t("Zurückziehen"), "ghost", "friendRemove", { id: r.id })))));
|
||||
return card;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Neuigkeiten, Einstellungen
|
||||
|
||||
async function renderNews(root) {
|
||||
let list;
|
||||
try { list = await call("releases"); }
|
||||
catch (e) { root.replaceChildren(h("h1", {}, "Neuigkeiten"), h("p", { class: "error" }, e.message)); return; }
|
||||
catch (e) { root.replaceChildren(h("h1", {}, t("Neuigkeiten")), h("p", { class: "error" }, e.message)); return; }
|
||||
if (page !== "news") return;
|
||||
const names = { mod: "Mod", launcher: "Launcher", server: "Server" };
|
||||
root.replaceChildren(h("h1", {}, "Neuigkeiten"),
|
||||
list = list.filter((r) => r.product !== "server"); // Server laufen jetzt im BSO-Relay
|
||||
root.replaceChildren(h("h1", {}, t("Neuigkeiten")),
|
||||
...(list.length ? list.map((r) => h("article", { class: "release" },
|
||||
h("header", {}, h("span", { class: "tag" }, names[r.product] || r.product), h("h2", {}, r.version), h("span", { class: "muted small" }, date(r.date))),
|
||||
h("pre", {}, r.changelog || "Keine Beschreibung"))) : [h("p", { class: "muted" }, "Noch keine Versionen veroeffentlicht.")]));
|
||||
h("pre", {}, r.changelog || t("Keine Beschreibung")))) : [h("p", { class: "muted" }, t("Noch keine Versionen veroeffentlicht."))]));
|
||||
}
|
||||
|
||||
async function renderSettings(root) {
|
||||
@@ -397,34 +413,41 @@ async function renderSettings(root) {
|
||||
const toggle = (label, hint, key, value) => {
|
||||
const box = h("input", { type: "checkbox" });
|
||||
box.checked = value;
|
||||
box.addEventListener("change", () => call("setSettings", { [key]: box.checked }).then(() => toast("Gespeichert")));
|
||||
box.addEventListener("change", () => call("setSettings", { [key]: box.checked }).then(() => toast(t("Gespeichert"))));
|
||||
return h("label", { class: "switch" }, h("span", {}, label, h("small", {}, hint)), box);
|
||||
};
|
||||
const folder = h("button", { class: "btn small", type: "button" }, "Spielordner waehlen");
|
||||
folder.addEventListener("click", () => busy(folder, async () => { if ((await call("chooseGameFolder")).changed) { toast("Spielordner gespeichert"); showPage("settings"); } }));
|
||||
const log = h("button", { class: "btn small", type: "button" }, "Protokoll-Ordner oeffnen");
|
||||
const folder = h("button", { class: "btn small", type: "button" }, t("Spielordner waehlen"));
|
||||
folder.addEventListener("click", () => busy(folder, async () => { if ((await call("chooseGameFolder")).changed) { toast(t("Spielordner gespeichert")); showPage("settings"); } }));
|
||||
const log = h("button", { class: "btn small", type: "button" }, t("Protokoll-Ordner oeffnen"));
|
||||
log.addEventListener("click", () => call("openLog"));
|
||||
const uninstall = h("button", { class: "btn small danger", type: "button" }, "BSO deinstallieren");
|
||||
const uninstall = h("button", { class: "btn small danger", type: "button" }, t("BSO deinstallieren"));
|
||||
uninstall.addEventListener("click", () => busy(uninstall, () => call("uninstall")));
|
||||
const langBox = h("div", { class: "lang-switch", "data-lang-switch": true });
|
||||
|
||||
root.replaceChildren(h("h1", {}, "Einstellungen"), h("div", { class: "settings" },
|
||||
root.replaceChildren(h("h1", {}, t("Einstellungen")), h("div", { class: "settings" },
|
||||
h("div", { class: "card" },
|
||||
toggle("BSO automatisch aktualisieren", "Vor jedem Start die neueste Mod-Version installieren", "autoUpdateMod", s?.settings.autoUpdateMod ?? true),
|
||||
toggle("Launcher offen lassen", "Sonst schliesst er sich nach dem Spielstart", "keepOpen", s?.settings.keepOpen ?? false)),
|
||||
toggle(t("BSO automatisch aktualisieren"), t("Vor jedem Start die neueste Mod-Version installieren"), "autoUpdateMod", s?.settings.autoUpdateMod ?? true),
|
||||
toggle(t("Launcher offen lassen"), t("Sonst schliesst er sich nach dem Spielstart"), "keepOpen", s?.settings.keepOpen ?? false),
|
||||
h("div", { class: "switch" }, h("span", {}, t("Sprache")), langBox)),
|
||||
h("div", { class: "card" },
|
||||
h("h3", {}, "Spiel"),
|
||||
h("span", { class: "mono muted" }, s?.game.folder || "Nicht gefunden"),
|
||||
h("h3", {}, t("Spiel")),
|
||||
h("span", { class: "mono muted" }, s?.game.folder || t("Nicht gefunden")),
|
||||
h("div", { class: "row" }, folder)),
|
||||
h("div", { class: "card" },
|
||||
h("h3", {}, "Launcher"),
|
||||
h("span", { class: "muted small" }, `Version ${init.version} · Server ${init.master}`),
|
||||
h("span", { class: "muted small" }, t("Version {0} · Server {1}", init.version, init.master)),
|
||||
h("div", { class: "row" }, log, uninstall))));
|
||||
renderLangSwitch(setLang);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Start
|
||||
|
||||
(async () => {
|
||||
const init = await call("init");
|
||||
lang = init.language === "en" ? "en" : "de";
|
||||
translateStatic();
|
||||
renderLangSwitch(setLang);
|
||||
setAuthMode(false);
|
||||
$("meVersion").textContent = "Launcher " + init.version;
|
||||
if (init.dev) {
|
||||
// -dev: gegen die lokale Test-Website - gut sichtbar, damit niemand das verwechselt
|
||||
@@ -433,9 +456,9 @@ async function renderSettings(root) {
|
||||
$("devBadge").classList.remove("hidden");
|
||||
$("meVersion").textContent += " · DEV";
|
||||
}
|
||||
$("authServer").textContent = "Server: " + init.master.replace(/^https?:\/\//, "");
|
||||
$("authServer").textContent = t("Server: {0}", init.master.replace(/^https?:\/\//, ""));
|
||||
if (!init.loggedIn) { showAuth(); return; }
|
||||
const me = await call("me");
|
||||
if (me.loggedIn) showApp(me.username);
|
||||
else showAuth();
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user