feat: add German and English language switcher
This commit is contained in:
@@ -60,6 +60,7 @@ The GUI opens in your browser and lets you:
|
|||||||
- choose the terminal used for installing updates
|
- choose the terminal used for installing updates
|
||||||
- manage settings in a dedicated dialog with tabs for general behavior, notifications, installation, and ignored packages
|
- manage settings in a dedicated dialog with tabs for general behavior, notifications, installation, and ignored packages
|
||||||
- disable reminders, keep install terminals open or let them close, and control confirmation for selective installs
|
- disable reminders, keep install terminals open or let them close, and control confirmation for selective installs
|
||||||
|
- switch the interface language between German and English
|
||||||
|
|
||||||
Selective package installs use `pacman -S --needed` or the configured AUR helper with `-S --needed`. On Arch, full system updates are still the safer default because partial upgrades can cause dependency mismatches.
|
Selective package installs use `pacman -S --needed` or the configured AUR helper with `-S --needed`. On Arch, full system updates are still the safer default because partial upgrades can cause dependency mismatches.
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type Config struct {
|
|||||||
CheckAUR bool `json:"check_aur"`
|
CheckAUR bool `json:"check_aur"`
|
||||||
ReminderIntervalHours int `json:"reminder_interval_hours"`
|
ReminderIntervalHours int `json:"reminder_interval_hours"`
|
||||||
Terminal string `json:"terminal"`
|
Terminal string `json:"terminal"`
|
||||||
|
Language string `json:"language"`
|
||||||
AutoRefreshMinutes int `json:"auto_refresh_minutes"`
|
AutoRefreshMinutes int `json:"auto_refresh_minutes"`
|
||||||
ShowIgnored bool `json:"show_ignored"`
|
ShowIgnored bool `json:"show_ignored"`
|
||||||
NotificationsEnabled bool `json:"notifications_enabled"`
|
NotificationsEnabled bool `json:"notifications_enabled"`
|
||||||
@@ -24,6 +25,7 @@ func Default() Config {
|
|||||||
CheckAUR: true,
|
CheckAUR: true,
|
||||||
ReminderIntervalHours: 168,
|
ReminderIntervalHours: 168,
|
||||||
Terminal: "auto",
|
Terminal: "auto",
|
||||||
|
Language: "de",
|
||||||
AutoRefreshMinutes: 30,
|
AutoRefreshMinutes: 30,
|
||||||
ShowIgnored: false,
|
ShowIgnored: false,
|
||||||
NotificationsEnabled: true,
|
NotificationsEnabled: true,
|
||||||
@@ -88,6 +90,9 @@ func normalize(cfg Config) Config {
|
|||||||
if cfg.Terminal == "" {
|
if cfg.Terminal == "" {
|
||||||
cfg.Terminal = Default().Terminal
|
cfg.Terminal = Default().Terminal
|
||||||
}
|
}
|
||||||
|
if cfg.Language != "de" && cfg.Language != "en" {
|
||||||
|
cfg.Language = Default().Language
|
||||||
|
}
|
||||||
if cfg.AutoRefreshMinutes < 0 {
|
if cfg.AutoRefreshMinutes < 0 {
|
||||||
cfg.AutoRefreshMinutes = Default().AutoRefreshMinutes
|
cfg.AutoRefreshMinutes = Default().AutoRefreshMinutes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,24 +16,24 @@ const indexHTML = `<!doctype html>
|
|||||||
<p id="summary">Pruefe Updates...</p>
|
<p id="summary">Pruefe Updates...</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button id="settingsBtn" type="button">Einstellungen</button>
|
<button id="settingsBtn" type="button" data-i18n="settings">Einstellungen</button>
|
||||||
<button id="refreshBtn" type="button" title="Updates pruefen">Refresh</button>
|
<button id="refreshBtn" type="button" title="Updates pruefen" data-i18n="refresh">Refresh</button>
|
||||||
<button id="installSelectedBtn" type="button" disabled>Auswahl installieren</button>
|
<button id="installSelectedBtn" type="button" disabled data-i18n="installSelected">Auswahl installieren</button>
|
||||||
<button id="installBtn" type="button">Alle installieren</button>
|
<button id="installBtn" type="button" data-i18n="installAll">Alle installieren</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="stats">
|
<section class="stats">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<span>Aktive Updates</span>
|
<span data-i18n="activeUpdates">Aktive Updates</span>
|
||||||
<strong id="activeCount">0</strong>
|
<strong id="activeCount">0</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<span>Ausgeblendet</span>
|
<span data-i18n="ignored">Ausgeblendet</span>
|
||||||
<strong id="ignoredCount">0</strong>
|
<strong id="ignoredCount">0</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<span>Freier Speicher</span>
|
<span data-i18n="freeDisk">Freier Speicher</span>
|
||||||
<strong id="diskFree">-</strong>
|
<strong id="diskFree">-</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
@@ -47,13 +47,13 @@ const indexHTML = `<!doctype html>
|
|||||||
<div class="panel updates-panel">
|
<div class="panel updates-panel">
|
||||||
<div class="panel-head toolbar">
|
<div class="panel-head toolbar">
|
||||||
<div>
|
<div>
|
||||||
<h2>Updates</h2>
|
<h2 data-i18n="updates">Updates</h2>
|
||||||
<span id="lastCheck">Noch nicht geprueft</span>
|
<span id="lastCheck">Noch nicht geprueft</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
<input id="searchInput" type="search" placeholder="Pakete suchen">
|
<input id="searchInput" type="search" placeholder="Pakete suchen" data-i18n-placeholder="searchPackages">
|
||||||
<select id="sourceFilter">
|
<select id="sourceFilter">
|
||||||
<option value="all">Alle Quellen</option>
|
<option value="all" data-i18n="allSources">Alle Quellen</option>
|
||||||
<option value="pacman">Pacman</option>
|
<option value="pacman">Pacman</option>
|
||||||
<option value="aur">AUR</option>
|
<option value="aur">AUR</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -64,18 +64,18 @@ const indexHTML = `<!doctype html>
|
|||||||
<input id="selectAll" type="checkbox">
|
<input id="selectAll" type="checkbox">
|
||||||
<span id="selectionText">0 Updates ausgewaehlt</span>
|
<span id="selectionText">0 Updates ausgewaehlt</span>
|
||||||
</label>
|
</label>
|
||||||
<span>Selektive Updates koennen unter Arch riskant sein. Vollupdate bleibt empfohlen.</span>
|
<span data-i18n="selectiveWarning">Selektive Updates koennen unter Arch riskant sein. Vollupdate bleibt empfohlen.</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="warnings" class="warnings" hidden></div>
|
<div id="warnings" class="warnings" hidden></div>
|
||||||
<div id="emptyState" class="empty">Keine passenden Updates gefunden.</div>
|
<div id="emptyState" class="empty" data-i18n="noMatchingUpdates">Keine passenden Updates gefunden.</div>
|
||||||
<table id="updatesTable" hidden>
|
<table id="updatesTable" hidden>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Quelle</th>
|
<th data-i18n="source">Quelle</th>
|
||||||
<th>Paket</th>
|
<th data-i18n="package">Paket</th>
|
||||||
<th>Aktuell</th>
|
<th data-i18n="current">Aktuell</th>
|
||||||
<th>Verfuegbar</th>
|
<th data-i18n="available">Verfuegbar</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -85,7 +85,7 @@ const indexHTML = `<!doctype html>
|
|||||||
|
|
||||||
<div id="ignoredPanel" class="panel ignored-panel" hidden>
|
<div id="ignoredPanel" class="panel ignored-panel" hidden>
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h2>Ausgeblendete Updates</h2>
|
<h2 data-i18n="ignoredUpdates">Ausgeblendete Updates</h2>
|
||||||
<span id="ignoredHint"></span>
|
<span id="ignoredHint"></span>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
@@ -97,12 +97,12 @@ const indexHTML = `<!doctype html>
|
|||||||
<aside class="side-column">
|
<aside class="side-column">
|
||||||
<section class="panel system-panel">
|
<section class="panel system-panel">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h2>System</h2>
|
<h2 data-i18n="system">System</h2>
|
||||||
<span id="lockState"></span>
|
<span id="lockState"></span>
|
||||||
</div>
|
</div>
|
||||||
<dl class="system-list">
|
<dl class="system-list">
|
||||||
<div>
|
<div>
|
||||||
<dt>AUR Helper</dt>
|
<dt data-i18n="aurHelper">AUR Helper</dt>
|
||||||
<dd id="aurHelper">-</dd>
|
<dd id="aurHelper">-</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -110,7 +110,7 @@ const indexHTML = `<!doctype html>
|
|||||||
<dd id="terminalStatus">-</dd>
|
<dd id="terminalStatus">-</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt>Letzte erfolgreiche Pruefung</dt>
|
<dt data-i18n="lastSuccessfulCheck">Letzte erfolgreiche Pruefung</dt>
|
||||||
<dd id="lastSuccess">-</dd>
|
<dd id="lastSuccess">-</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
@@ -123,52 +123,59 @@ const indexHTML = `<!doctype html>
|
|||||||
<form id="settingsForm" method="dialog" class="settings-modal">
|
<form id="settingsForm" method="dialog" class="settings-modal">
|
||||||
<header class="modal-head">
|
<header class="modal-head">
|
||||||
<div>
|
<div>
|
||||||
<h2>Einstellungen</h2>
|
<h2 data-i18n="settings">Einstellungen</h2>
|
||||||
<p>Update-Verhalten, Installation und ausgeblendete Pakete</p>
|
<p data-i18n="settingsSubtitle">Update-Verhalten, Installation und ausgeblendete Pakete</p>
|
||||||
</div>
|
</div>
|
||||||
<button id="closeSettingsBtn" type="button" class="icon-btn">Schliessen</button>
|
<button id="closeSettingsBtn" type="button" class="icon-btn" data-i18n="close">Schliessen</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<nav class="tabs" aria-label="Einstellungen">
|
<nav class="tabs" aria-label="Einstellungen">
|
||||||
<button class="tab active" type="button" data-tab="general">Allgemein</button>
|
<button class="tab active" type="button" data-tab="general" data-i18n="general">Allgemein</button>
|
||||||
<button class="tab" type="button" data-tab="notifications">Benachrichtigungen</button>
|
<button class="tab" type="button" data-tab="notifications" data-i18n="notifications">Benachrichtigungen</button>
|
||||||
<button class="tab" type="button" data-tab="install">Installation</button>
|
<button class="tab" type="button" data-tab="install" data-i18n="installation">Installation</button>
|
||||||
<button class="tab" type="button" data-tab="ignored">Ausgeblendet</button>
|
<button class="tab" type="button" data-tab="ignored" data-i18n="ignored">Ausgeblendet</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<section class="tab-panel active" data-panel="general">
|
<section class="tab-panel active" data-panel="general">
|
||||||
|
<label>
|
||||||
|
<span data-i18n="language">Sprache</span>
|
||||||
|
<select id="language">
|
||||||
|
<option value="de">Deutsch</option>
|
||||||
|
<option value="en">English</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input id="checkAur" type="checkbox">
|
<input id="checkAur" type="checkbox">
|
||||||
<span>AUR Updates pruefen</span>
|
<span data-i18n="checkAurUpdates">AUR Updates pruefen</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input id="showIgnored" type="checkbox">
|
<input id="showIgnored" type="checkbox">
|
||||||
<span>Ausgeblendete Updates in der App anzeigen</span>
|
<span data-i18n="showIgnoredUpdates">Ausgeblendete Updates in der App anzeigen</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Auto-Refresh</span>
|
<span data-i18n="autoRefresh">Auto-Refresh</span>
|
||||||
<input id="autoRefreshMinutes" type="number" min="0" step="1">
|
<input id="autoRefreshMinutes" type="number" min="0" step="1">
|
||||||
<small>Minuten, 0 deaktiviert</small>
|
<small data-i18n="minutesDisabled">Minuten, 0 deaktiviert</small>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="tab-panel" data-panel="notifications">
|
<section class="tab-panel" data-panel="notifications">
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input id="notificationsEnabled" type="checkbox">
|
<input id="notificationsEnabled" type="checkbox">
|
||||||
<span>Woechentliche Erinnerungen senden</span>
|
<span data-i18n="sendWeeklyReminders">Woechentliche Erinnerungen senden</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Erinnerung alle</span>
|
<span data-i18n="reminderEvery">Erinnerung alle</span>
|
||||||
<input id="reminderHours" type="number" min="1" step="1">
|
<input id="reminderHours" type="number" min="1" step="1">
|
||||||
<small>Stunden</small>
|
<small data-i18n="hours">Stunden</small>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="tab-panel" data-panel="install">
|
<section class="tab-panel" data-panel="install">
|
||||||
<label>
|
<label>
|
||||||
<span>Terminal</span>
|
<span data-i18n="terminal">Terminal</span>
|
||||||
<select id="terminal">
|
<select id="terminal">
|
||||||
<option value="auto">Automatisch</option>
|
<option value="auto" data-i18n="automatic">Automatisch</option>
|
||||||
<option value="foot">foot</option>
|
<option value="foot">foot</option>
|
||||||
<option value="kitty">kitty</option>
|
<option value="kitty">kitty</option>
|
||||||
<option value="alacritty">alacritty</option>
|
<option value="alacritty">alacritty</option>
|
||||||
@@ -180,11 +187,11 @@ const indexHTML = `<!doctype html>
|
|||||||
</label>
|
</label>
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input id="keepTerminalOpen" type="checkbox">
|
<input id="keepTerminalOpen" type="checkbox">
|
||||||
<span>Terminal nach Installationen offen lassen</span>
|
<span data-i18n="keepTerminalOpen">Terminal nach Installationen offen lassen</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input id="confirmSelectedInstalls" type="checkbox">
|
<input id="confirmSelectedInstalls" type="checkbox">
|
||||||
<span>Selektive Installationen bestaetigen</span>
|
<span data-i18n="confirmSelectedInstalls">Selektive Installationen bestaetigen</span>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -194,7 +201,7 @@ const indexHTML = `<!doctype html>
|
|||||||
|
|
||||||
<footer class="modal-actions">
|
<footer class="modal-actions">
|
||||||
<span id="saveState"></span>
|
<span id="saveState"></span>
|
||||||
<button type="submit">Speichern</button>
|
<button type="submit" data-i18n="save">Speichern</button>
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
@@ -722,6 +729,7 @@ const showIgnored = document.querySelector("#showIgnored");
|
|||||||
const notificationsEnabled = document.querySelector("#notificationsEnabled");
|
const notificationsEnabled = document.querySelector("#notificationsEnabled");
|
||||||
const keepTerminalOpen = document.querySelector("#keepTerminalOpen");
|
const keepTerminalOpen = document.querySelector("#keepTerminalOpen");
|
||||||
const confirmSelectedInstalls = document.querySelector("#confirmSelectedInstalls");
|
const confirmSelectedInstalls = document.querySelector("#confirmSelectedInstalls");
|
||||||
|
const language = document.querySelector("#language");
|
||||||
const reminderHours = document.querySelector("#reminderHours");
|
const reminderHours = document.querySelector("#reminderHours");
|
||||||
const autoRefreshMinutes = document.querySelector("#autoRefreshMinutes");
|
const autoRefreshMinutes = document.querySelector("#autoRefreshMinutes");
|
||||||
const terminal = document.querySelector("#terminal");
|
const terminal = document.querySelector("#terminal");
|
||||||
@@ -734,6 +742,155 @@ let currentData = null;
|
|||||||
let refreshTimer = null;
|
let refreshTimer = null;
|
||||||
let selectedPackages = new Set();
|
let selectedPackages = new Set();
|
||||||
|
|
||||||
|
const translations = {
|
||||||
|
de: {
|
||||||
|
checkingUpdates: "Pruefe Updates...",
|
||||||
|
noUpdates: "Keine Updates verfuegbar.",
|
||||||
|
oneUpdate: "1 Update verfuegbar.",
|
||||||
|
manyUpdates: "{count} Updates verfuegbar.",
|
||||||
|
settings: "Einstellungen",
|
||||||
|
refresh: "Refresh",
|
||||||
|
installSelected: "Auswahl installieren",
|
||||||
|
installAll: "Alle installieren",
|
||||||
|
activeUpdates: "Aktive Updates",
|
||||||
|
ignored: "Ausgeblendet",
|
||||||
|
freeDisk: "Freier Speicher",
|
||||||
|
updates: "Updates",
|
||||||
|
searchPackages: "Pakete suchen",
|
||||||
|
allSources: "Alle Quellen",
|
||||||
|
selectiveWarning: "Selektive Updates koennen unter Arch riskant sein. Vollupdate bleibt empfohlen.",
|
||||||
|
noMatchingUpdates: "Keine passenden Updates gefunden.",
|
||||||
|
source: "Quelle",
|
||||||
|
package: "Paket",
|
||||||
|
current: "Aktuell",
|
||||||
|
available: "Verfuegbar",
|
||||||
|
ignoredUpdates: "Ausgeblendete Updates",
|
||||||
|
system: "System",
|
||||||
|
aurHelper: "AUR Helper",
|
||||||
|
lastSuccessfulCheck: "Letzte erfolgreiche Pruefung",
|
||||||
|
settingsSubtitle: "Update-Verhalten, Installation und ausgeblendete Pakete",
|
||||||
|
close: "Schliessen",
|
||||||
|
general: "Allgemein",
|
||||||
|
notifications: "Benachrichtigungen",
|
||||||
|
installation: "Installation",
|
||||||
|
language: "Sprache",
|
||||||
|
checkAurUpdates: "AUR Updates pruefen",
|
||||||
|
showIgnoredUpdates: "Ausgeblendete Updates in der App anzeigen",
|
||||||
|
autoRefresh: "Auto-Refresh",
|
||||||
|
minutesDisabled: "Minuten, 0 deaktiviert",
|
||||||
|
sendWeeklyReminders: "Woechentliche Erinnerungen senden",
|
||||||
|
reminderEvery: "Erinnerung alle",
|
||||||
|
hours: "Stunden",
|
||||||
|
terminal: "Terminal",
|
||||||
|
automatic: "Automatisch",
|
||||||
|
keepTerminalOpen: "Terminal nach Installationen offen lassen",
|
||||||
|
confirmSelectedInstalls: "Selektive Installationen bestaetigen",
|
||||||
|
save: "Speichern",
|
||||||
|
hide: "Ausblenden",
|
||||||
|
show: "Einblenden",
|
||||||
|
notFound: "nicht gefunden",
|
||||||
|
ready: "Bereit",
|
||||||
|
pacmanLocked: "Pacman gesperrt",
|
||||||
|
lastCheck: "Letzte Pruefung: {date}",
|
||||||
|
free: "{value} frei",
|
||||||
|
noIgnoredPackages: "Keine ausgeblendeten Pakete.",
|
||||||
|
onePackage: "1 Paket",
|
||||||
|
manyPackages: "{count} Pakete",
|
||||||
|
oneSelected: "1 Update ausgewaehlt",
|
||||||
|
manySelected: "{count} Updates ausgewaehlt",
|
||||||
|
installStarted: "Installation im Terminal gestartet.",
|
||||||
|
selectedInstallStarted: "Ausgewaehlte Installation im Terminal gestartet.",
|
||||||
|
confirmSelected: "Ausgewaehlte Pakete gezielt installieren? Unter Arch ist ein vollstaendiges Systemupdate meistens sicherer.",
|
||||||
|
saving: "Speichere...",
|
||||||
|
saved: "Gespeichert",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
checkingUpdates: "Checking updates...",
|
||||||
|
noUpdates: "No updates available.",
|
||||||
|
oneUpdate: "1 update available.",
|
||||||
|
manyUpdates: "{count} updates available.",
|
||||||
|
settings: "Settings",
|
||||||
|
refresh: "Refresh",
|
||||||
|
installSelected: "Install selected",
|
||||||
|
installAll: "Install all",
|
||||||
|
activeUpdates: "Active updates",
|
||||||
|
ignored: "Ignored",
|
||||||
|
freeDisk: "Free disk",
|
||||||
|
updates: "Updates",
|
||||||
|
searchPackages: "Search packages",
|
||||||
|
allSources: "All sources",
|
||||||
|
selectiveWarning: "Selective updates can be risky on Arch. A full update is recommended.",
|
||||||
|
noMatchingUpdates: "No matching updates found.",
|
||||||
|
source: "Source",
|
||||||
|
package: "Package",
|
||||||
|
current: "Current",
|
||||||
|
available: "Available",
|
||||||
|
ignoredUpdates: "Ignored updates",
|
||||||
|
system: "System",
|
||||||
|
aurHelper: "AUR helper",
|
||||||
|
lastSuccessfulCheck: "Last successful check",
|
||||||
|
settingsSubtitle: "Update behavior, installation, and ignored packages",
|
||||||
|
close: "Close",
|
||||||
|
general: "General",
|
||||||
|
notifications: "Notifications",
|
||||||
|
installation: "Installation",
|
||||||
|
language: "Language",
|
||||||
|
checkAurUpdates: "Check AUR updates",
|
||||||
|
showIgnoredUpdates: "Show ignored updates in the app",
|
||||||
|
autoRefresh: "Auto-refresh",
|
||||||
|
minutesDisabled: "Minutes, 0 disables it",
|
||||||
|
sendWeeklyReminders: "Send weekly reminders",
|
||||||
|
reminderEvery: "Remind every",
|
||||||
|
hours: "Hours",
|
||||||
|
terminal: "Terminal",
|
||||||
|
automatic: "Automatic",
|
||||||
|
keepTerminalOpen: "Keep terminal open after installs",
|
||||||
|
confirmSelectedInstalls: "Confirm selective installs",
|
||||||
|
save: "Save",
|
||||||
|
hide: "Hide",
|
||||||
|
show: "Show",
|
||||||
|
notFound: "not found",
|
||||||
|
ready: "Ready",
|
||||||
|
pacmanLocked: "Pacman locked",
|
||||||
|
lastCheck: "Last check: {date}",
|
||||||
|
free: "{value} free",
|
||||||
|
noIgnoredPackages: "No ignored packages.",
|
||||||
|
onePackage: "1 package",
|
||||||
|
manyPackages: "{count} packages",
|
||||||
|
oneSelected: "1 update selected",
|
||||||
|
manySelected: "{count} updates selected",
|
||||||
|
installStarted: "Installation started in terminal.",
|
||||||
|
selectedInstallStarted: "Selected installation started in terminal.",
|
||||||
|
confirmSelected: "Install selected packages only? On Arch, a full system update is usually safer.",
|
||||||
|
saving: "Saving...",
|
||||||
|
saved: "Saved",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function currentLanguage() {
|
||||||
|
return currentData?.settings.language || language.value || "de";
|
||||||
|
}
|
||||||
|
|
||||||
|
function t(key, params = {}) {
|
||||||
|
const dict = translations[currentLanguage()] || translations.de;
|
||||||
|
let value = dict[key] || translations.de[key] || key;
|
||||||
|
for (const [name, replacement] of Object.entries(params)) {
|
||||||
|
value = value.replaceAll("{" + name + "}", replacement);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTranslations() {
|
||||||
|
document.documentElement.lang = currentLanguage();
|
||||||
|
for (const element of document.querySelectorAll("[data-i18n]")) {
|
||||||
|
element.textContent = t(element.dataset.i18n);
|
||||||
|
}
|
||||||
|
for (const element of document.querySelectorAll("[data-i18n-placeholder]")) {
|
||||||
|
element.placeholder = t(element.dataset.i18nPlaceholder);
|
||||||
|
}
|
||||||
|
refreshBtn.title = t("updates");
|
||||||
|
}
|
||||||
|
|
||||||
async function request(path, options = {}) {
|
async function request(path, options = {}) {
|
||||||
const response = await fetch(path, {
|
const response = await fetch(path, {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -750,12 +907,23 @@ function formatDate(value) {
|
|||||||
if (!value || value.startsWith("0001-")) {
|
if (!value || value.startsWith("0001-")) {
|
||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
return new Intl.DateTimeFormat("de-DE", {
|
const locale = currentLanguage() === "en" ? "en-US" : "de-DE";
|
||||||
|
return new Intl.DateTimeFormat(locale, {
|
||||||
dateStyle: "short",
|
dateStyle: "short",
|
||||||
timeStyle: "short",
|
timeStyle: "short",
|
||||||
}).format(new Date(value));
|
}).format(new Date(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function summaryText(total) {
|
||||||
|
if (total === 0) {
|
||||||
|
return t("noUpdates");
|
||||||
|
}
|
||||||
|
if (total === 1) {
|
||||||
|
return t("oneUpdate");
|
||||||
|
}
|
||||||
|
return t("manyUpdates", { count: total });
|
||||||
|
}
|
||||||
|
|
||||||
function visiblePackages() {
|
function visiblePackages() {
|
||||||
if (!currentData) {
|
if (!currentData) {
|
||||||
return [];
|
return [];
|
||||||
@@ -806,7 +974,7 @@ function renderTable() {
|
|||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.className = "ghost";
|
button.className = "ghost";
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.textContent = "Ausblenden";
|
button.textContent = t("hide");
|
||||||
button.addEventListener("click", () => setIgnored(pkg.Name, true));
|
button.addEventListener("click", () => setIgnored(pkg.Name, true));
|
||||||
actionCell.append(button);
|
actionCell.append(button);
|
||||||
row.append(actionCell);
|
row.append(actionCell);
|
||||||
@@ -822,7 +990,7 @@ function renderIgnored() {
|
|||||||
const ignored = currentData.packages.filter((pkg) => pkg.Ignored);
|
const ignored = currentData.packages.filter((pkg) => pkg.Ignored);
|
||||||
ignoredBody.replaceChildren();
|
ignoredBody.replaceChildren();
|
||||||
ignoredPanel.hidden = ignored.length === 0 || !currentData.settings.show_ignored;
|
ignoredPanel.hidden = ignored.length === 0 || !currentData.settings.show_ignored;
|
||||||
ignoredHint.textContent = ignored.length === 1 ? "1 Paket" : ignored.length + " Pakete";
|
ignoredHint.textContent = ignored.length === 1 ? t("onePackage") : t("manyPackages", { count: ignored.length });
|
||||||
|
|
||||||
for (const pkg of ignored) {
|
for (const pkg of ignored) {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
@@ -832,7 +1000,7 @@ function renderIgnored() {
|
|||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.className = "ghost";
|
button.className = "ghost";
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.textContent = "Einblenden";
|
button.textContent = t("show");
|
||||||
button.addEventListener("click", () => setIgnored(pkg.Name, false));
|
button.addEventListener("click", () => setIgnored(pkg.Name, false));
|
||||||
actionCell.append(button);
|
actionCell.append(button);
|
||||||
row.append(actionCell);
|
row.append(actionCell);
|
||||||
@@ -848,19 +1016,21 @@ function cell(value) {
|
|||||||
|
|
||||||
function render(data) {
|
function render(data) {
|
||||||
currentData = data;
|
currentData = data;
|
||||||
|
language.value = data.settings.language || "de";
|
||||||
|
applyTranslations();
|
||||||
const availableNames = new Set(data.packages.filter((pkg) => !pkg.Ignored).map((pkg) => pkg.Name));
|
const availableNames = new Set(data.packages.filter((pkg) => !pkg.Ignored).map((pkg) => pkg.Name));
|
||||||
selectedPackages = new Set([...selectedPackages].filter((name) => availableNames.has(name)));
|
selectedPackages = new Set([...selectedPackages].filter((name) => availableNames.has(name)));
|
||||||
summary.textContent = data.summary;
|
summary.textContent = summaryText(data.total);
|
||||||
activeCount.textContent = data.total;
|
activeCount.textContent = data.total;
|
||||||
ignoredCount.textContent = data.ignored_total;
|
ignoredCount.textContent = data.ignored_total;
|
||||||
installBtn.disabled = data.total === 0 || data.system.pacman_locked;
|
installBtn.disabled = data.total === 0 || data.system.pacman_locked;
|
||||||
diskFree.textContent = data.system.disk_free + " frei";
|
diskFree.textContent = t("free", { value: data.system.disk_free });
|
||||||
kernel.textContent = data.system.kernel;
|
kernel.textContent = data.system.kernel;
|
||||||
aurHelper.textContent = data.system.aur_helper || "nicht gefunden";
|
aurHelper.textContent = data.system.aur_helper || t("notFound");
|
||||||
terminalStatus.textContent = data.system.terminal || "nicht gefunden";
|
terminalStatus.textContent = data.system.terminal || t("notFound");
|
||||||
lastCheck.textContent = "Letzte Pruefung: " + formatDate(data.state.last_check);
|
lastCheck.textContent = t("lastCheck", { date: formatDate(data.state.last_check) });
|
||||||
lastSuccess.textContent = formatDate(data.state.last_success);
|
lastSuccess.textContent = formatDate(data.state.last_success);
|
||||||
lockState.textContent = data.system.pacman_locked ? "Pacman gesperrt" : "Bereit";
|
lockState.textContent = data.system.pacman_locked ? t("pacmanLocked") : t("ready");
|
||||||
lockState.classList.toggle("locked", data.system.pacman_locked);
|
lockState.classList.toggle("locked", data.system.pacman_locked);
|
||||||
|
|
||||||
checkAur.checked = data.settings.check_aur;
|
checkAur.checked = data.settings.check_aur;
|
||||||
@@ -868,6 +1038,7 @@ function render(data) {
|
|||||||
notificationsEnabled.checked = data.settings.notifications_enabled;
|
notificationsEnabled.checked = data.settings.notifications_enabled;
|
||||||
keepTerminalOpen.checked = data.settings.keep_terminal_open;
|
keepTerminalOpen.checked = data.settings.keep_terminal_open;
|
||||||
confirmSelectedInstalls.checked = data.settings.confirm_selected_installs;
|
confirmSelectedInstalls.checked = data.settings.confirm_selected_installs;
|
||||||
|
language.value = data.settings.language || "de";
|
||||||
reminderHours.value = data.settings.reminder_interval_hours;
|
reminderHours.value = data.settings.reminder_interval_hours;
|
||||||
autoRefreshMinutes.value = data.settings.auto_refresh_minutes;
|
autoRefreshMinutes.value = data.settings.auto_refresh_minutes;
|
||||||
terminal.value = data.settings.terminal || "auto";
|
terminal.value = data.settings.terminal || "auto";
|
||||||
@@ -887,7 +1058,7 @@ function renderIgnoredSettings() {
|
|||||||
if (names.length === 0) {
|
if (names.length === 0) {
|
||||||
const empty = document.createElement("p");
|
const empty = document.createElement("p");
|
||||||
empty.className = "empty-inline";
|
empty.className = "empty-inline";
|
||||||
empty.textContent = "Keine ausgeblendeten Pakete.";
|
empty.textContent = t("noIgnoredPackages");
|
||||||
ignoredSettingsList.append(empty);
|
ignoredSettingsList.append(empty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -900,7 +1071,7 @@ function renderIgnoredSettings() {
|
|||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.className = "ghost";
|
button.className = "ghost";
|
||||||
button.textContent = "Einblenden";
|
button.textContent = t("show");
|
||||||
button.addEventListener("click", () => setIgnored(name, false));
|
button.addEventListener("click", () => setIgnored(name, false));
|
||||||
row.append(label, button);
|
row.append(label, button);
|
||||||
ignoredSettingsList.append(row);
|
ignoredSettingsList.append(row);
|
||||||
@@ -909,7 +1080,7 @@ function renderIgnoredSettings() {
|
|||||||
|
|
||||||
function updateSelectionUi(packages = visiblePackages()) {
|
function updateSelectionUi(packages = visiblePackages()) {
|
||||||
const selectedCount = selectedPackages.size;
|
const selectedCount = selectedPackages.size;
|
||||||
selectionText.textContent = selectedCount === 1 ? "1 Update ausgewaehlt" : selectedCount + " Updates ausgewaehlt";
|
selectionText.textContent = selectedCount === 1 ? t("oneSelected") : t("manySelected", { count: selectedCount });
|
||||||
installSelectedBtn.disabled = selectedCount === 0 || currentData?.system.pacman_locked;
|
installSelectedBtn.disabled = selectedCount === 0 || currentData?.system.pacman_locked;
|
||||||
selectAll.checked = packages.length > 0 && packages.every((pkg) => selectedPackages.has(pkg.Name));
|
selectAll.checked = packages.length > 0 && packages.every((pkg) => selectedPackages.has(pkg.Name));
|
||||||
selectAll.indeterminate = packages.some((pkg) => selectedPackages.has(pkg.Name)) && !selectAll.checked;
|
selectAll.indeterminate = packages.some((pkg) => selectedPackages.has(pkg.Name)) && !selectAll.checked;
|
||||||
@@ -917,7 +1088,7 @@ function updateSelectionUi(packages = visiblePackages()) {
|
|||||||
|
|
||||||
async function loadStatus() {
|
async function loadStatus() {
|
||||||
refreshBtn.disabled = true;
|
refreshBtn.disabled = true;
|
||||||
summary.textContent = "Pruefe Updates...";
|
summary.textContent = t("checkingUpdates");
|
||||||
try {
|
try {
|
||||||
render(await request("/api/status"));
|
render(await request("/api/status"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -949,6 +1120,24 @@ settingsBtn.addEventListener("click", () => settingsDialog.showModal());
|
|||||||
closeSettingsBtn.addEventListener("click", () => settingsDialog.close());
|
closeSettingsBtn.addEventListener("click", () => settingsDialog.close());
|
||||||
searchInput.addEventListener("input", renderTable);
|
searchInput.addEventListener("input", renderTable);
|
||||||
sourceFilter.addEventListener("change", renderTable);
|
sourceFilter.addEventListener("change", renderTable);
|
||||||
|
language.addEventListener("change", () => {
|
||||||
|
if (currentData) {
|
||||||
|
currentData.settings.language = language.value;
|
||||||
|
}
|
||||||
|
applyTranslations();
|
||||||
|
if (currentData) {
|
||||||
|
summary.textContent = summaryText(currentData.total);
|
||||||
|
renderTable();
|
||||||
|
renderIgnored();
|
||||||
|
renderIgnoredSettings();
|
||||||
|
updateSelectionUi();
|
||||||
|
diskFree.textContent = t("free", { value: currentData.system.disk_free });
|
||||||
|
aurHelper.textContent = currentData.system.aur_helper || t("notFound");
|
||||||
|
terminalStatus.textContent = currentData.system.terminal || t("notFound");
|
||||||
|
lastCheck.textContent = t("lastCheck", { date: formatDate(currentData.state.last_check) });
|
||||||
|
lockState.textContent = currentData.system.pacman_locked ? t("pacmanLocked") : t("ready");
|
||||||
|
}
|
||||||
|
});
|
||||||
selectAll.addEventListener("change", () => {
|
selectAll.addEventListener("change", () => {
|
||||||
const packages = visiblePackages();
|
const packages = visiblePackages();
|
||||||
for (const pkg of packages) {
|
for (const pkg of packages) {
|
||||||
@@ -965,7 +1154,7 @@ installBtn.addEventListener("click", async () => {
|
|||||||
installBtn.disabled = true;
|
installBtn.disabled = true;
|
||||||
try {
|
try {
|
||||||
await request("/api/install", { method: "POST", body: "{}" });
|
await request("/api/install", { method: "POST", body: "{}" });
|
||||||
summary.textContent = "Installation im Terminal gestartet.";
|
summary.textContent = t("installStarted");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
summary.textContent = error.message;
|
summary.textContent = error.message;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -979,7 +1168,7 @@ installSelectedBtn.addEventListener("click", async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentData?.settings.confirm_selected_installs) {
|
if (currentData?.settings.confirm_selected_installs) {
|
||||||
const ok = window.confirm("Ausgewaehlte Pakete gezielt installieren? Unter Arch ist ein vollstaendiges Systemupdate meistens sicherer.");
|
const ok = window.confirm(t("confirmSelected"));
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -991,7 +1180,7 @@ installSelectedBtn.addEventListener("click", async () => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ packages }),
|
body: JSON.stringify({ packages }),
|
||||||
});
|
});
|
||||||
summary.textContent = "Ausgewaehlte Installation im Terminal gestartet.";
|
summary.textContent = t("selectedInstallStarted");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
summary.textContent = error.message;
|
summary.textContent = error.message;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1001,7 +1190,7 @@ installSelectedBtn.addEventListener("click", async () => {
|
|||||||
|
|
||||||
form.addEventListener("submit", async (event) => {
|
form.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
saveState.textContent = "Speichere...";
|
saveState.textContent = t("saving");
|
||||||
try {
|
try {
|
||||||
await request("/api/settings", {
|
await request("/api/settings", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -1011,13 +1200,14 @@ form.addEventListener("submit", async (event) => {
|
|||||||
notifications_enabled: notificationsEnabled.checked,
|
notifications_enabled: notificationsEnabled.checked,
|
||||||
keep_terminal_open: keepTerminalOpen.checked,
|
keep_terminal_open: keepTerminalOpen.checked,
|
||||||
confirm_selected_installs: confirmSelectedInstalls.checked,
|
confirm_selected_installs: confirmSelectedInstalls.checked,
|
||||||
|
language: language.value,
|
||||||
reminder_interval_hours: Number(reminderHours.value),
|
reminder_interval_hours: Number(reminderHours.value),
|
||||||
auto_refresh_minutes: Number(autoRefreshMinutes.value),
|
auto_refresh_minutes: Number(autoRefreshMinutes.value),
|
||||||
terminal: terminal.value,
|
terminal: terminal.value,
|
||||||
ignored_packages: currentData?.settings.ignored_packages || [],
|
ignored_packages: currentData?.settings.ignored_packages || [],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
saveState.textContent = "Gespeichert";
|
saveState.textContent = t("saved");
|
||||||
await loadStatus();
|
await loadStatus();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
saveState.textContent = error.message;
|
saveState.textContent = error.message;
|
||||||
|
|||||||
Reference in New Issue
Block a user