diff --git a/AppImage/lib/api-config.ts b/AppImage/lib/api-config.ts
index 04af2cbf..543418d7 100644
--- a/AppImage/lib/api-config.ts
+++ b/AppImage/lib/api-config.ts
@@ -2,6 +2,12 @@
* API Configuration for ProxMenux Monitor
* Handles API URL generation with automatic proxy detection
*/
+// Resolve wording only for a generic transport failure. Normal responses and
+// backend error payloads keep the transport free of locale-loading work.
+async function apiFailure(...args: Parameters): Promise {
+ const messages = await import("./api-errors")
+ return messages.apiFailure(...args)
+}
/**
* API Server Port Configuration
@@ -114,7 +120,7 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom
hadToken = true
}
if (!hadToken) {
- throw new Error(`Unauthorized: ${endpoint}`)
+ throw new Error(await apiFailure("unauthorized", { endpoint }))
}
try {
localStorage.removeItem("proxmenux-auth-token")
@@ -131,7 +137,7 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom
window.location.reload()
}
}
- throw new Error(`Unauthorized: ${endpoint}`)
+ throw new Error(await apiFailure("unauthorized", { endpoint }))
}
// Try to surface the backend's JSON error payload instead of a
// bare `500 INTERNAL SERVER ERROR`. The Flask routes consistently
@@ -168,7 +174,7 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom
throw parseErr
}
}
- throw new Error(`API request failed: ${response.status} ${response.statusText}`)
+ throw new Error(await apiFailure("http", { status: response.status, statusText: response.statusText }))
}
// Check content type to ensure we're getting JSON
@@ -176,13 +182,13 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom
if (!contentType || !contentType.includes("application/json")) {
const text = await response.text()
console.error("fetchApi: Expected JSON but got:", contentType, "- Body preview:", text.substring(0, 200))
- throw new Error(`Expected JSON response but got ${contentType || "unknown content type"}`)
+ throw new Error(await apiFailure("unexpectedContentType", { contentType: contentType || await apiFailure("unknownContentType") }))
}
try {
return await response.json()
} catch (jsonError) {
console.error("fetchApi: JSON parse error for", endpoint, "-", jsonError)
- throw new Error(`Invalid JSON response from ${endpoint}`)
+ throw new Error(await apiFailure("invalidJson", { endpoint }))
}
}
diff --git a/AppImage/lib/api-errors.ts b/AppImage/lib/api-errors.ts
new file mode 100644
index 00000000..a80c9add
--- /dev/null
+++ b/AppImage/lib/api-errors.ts
@@ -0,0 +1,19 @@
+import { MESSAGE_CATALOG } from "./i18n/catalogs"
+import { detectBrowserLanguage, isSupportedLanguage, LANGUAGE_STORAGE_KEY } from "./i18n/languages"
+
+type Failure = keyof (typeof import("../messages/en/common.json"))["apiErrors"]
+
+// fetchApi also runs outside React hooks. Read the provider's current document
+// language, with its stored/browser preference as a fallback.
+export function apiFailure(key: Failure, params: Record = {}): string {
+ let preference = typeof document === "undefined" ? "" : document.documentElement?.lang
+ if (!isSupportedLanguage(preference) && typeof window !== "undefined") {
+ try { preference = window.localStorage.getItem(LANGUAGE_STORAGE_KEY) || "" } catch { /* private browsing */ }
+ }
+ const language = isSupportedLanguage(preference) ? preference : detectBrowserLanguage()
+ const messages = MESSAGE_CATALOG[language].apiErrors
+ const localized = messages && typeof messages === "object" ? (messages as Record)[key] : undefined
+ const fallback = (MESSAGE_CATALOG.en.apiErrors as Record)[key]
+ const template = typeof localized === "string" ? localized : fallback
+ return template.replace(/\{(\w+)\}/g, (match, name) => params[name] === undefined ? match : String(params[name]))
+}
diff --git a/AppImage/lib/i18n/catalogs.ts b/AppImage/lib/i18n/catalogs.ts
new file mode 100644
index 00000000..3547eb10
--- /dev/null
+++ b/AppImage/lib/i18n/catalogs.ts
@@ -0,0 +1,23 @@
+import enMessages from "../../messages/en/common.json"
+import deMessages from "../../messages/de/common.json"
+import esMessages from "../../messages/es/common.json"
+import frMessages from "../../messages/fr/common.json"
+import itMessages from "../../messages/it/common.json"
+import ptMessages from "../../messages/pt/common.json"
+import skMessages from "../../messages/sk/common.json"
+import svMessages from "../../messages/sv/common.json"
+import type { LanguageCode } from "./languages"
+
+type MessageTree = Record
+
+// Shared by React translations and error handling outside React hooks.
+export const MESSAGE_CATALOG: Record = {
+ en: enMessages as MessageTree,
+ de: deMessages as MessageTree,
+ es: esMessages as MessageTree,
+ fr: frMessages as MessageTree,
+ it: itMessages as MessageTree,
+ pt: ptMessages as MessageTree,
+ sk: skMessages as MessageTree,
+ sv: svMessages as MessageTree,
+}
diff --git a/AppImage/lib/i18n/provider.tsx b/AppImage/lib/i18n/provider.tsx
index 04d5b638..ebdc6097 100644
--- a/AppImage/lib/i18n/provider.tsx
+++ b/AppImage/lib/i18n/provider.tsx
@@ -1,14 +1,7 @@
"use client"
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"
-import enMessages from "../../messages/en/common.json"
-import deMessages from "../../messages/de/common.json"
-import esMessages from "../../messages/es/common.json"
-import frMessages from "../../messages/fr/common.json"
-import itMessages from "../../messages/it/common.json"
-import ptMessages from "../../messages/pt/common.json"
-import skMessages from "../../messages/sk/common.json"
-import svMessages from "../../messages/sv/common.json"
+import { MESSAGE_CATALOG } from "./catalogs"
import {
DEFAULT_LANGUAGE,
LANGUAGE_STORAGE_KEY,
@@ -21,17 +14,6 @@ import {
type MessageTree = Record
type TranslationParams = Record
-const MESSAGE_CATALOG: Record = {
- en: enMessages as MessageTree,
- de: deMessages as MessageTree,
- es: esMessages as MessageTree,
- fr: frMessages as MessageTree,
- it: itMessages as MessageTree,
- pt: ptMessages as MessageTree,
- sk: skMessages as MessageTree,
- sv: svMessages as MessageTree,
-}
-
interface I18nContextValue {
language: LanguageCode
setLanguage: (language: LanguageCode) => void
diff --git a/AppImage/lib/optimization-descriptions.ts b/AppImage/lib/optimization-descriptions.ts
new file mode 100644
index 00000000..a01fc3aa
--- /dev/null
+++ b/AppImage/lib/optimization-descriptions.ts
@@ -0,0 +1,18 @@
+import enMessages from "../messages/en/common.json"
+
+type Translate = (key: string, params?: Record) => string
+
+// Descriptions are live server metadata. Translate only exact known wording;
+// a newer script description must remain visible rather than become stale text.
+export function optimizationDescription(description: string, t: Translate): string {
+ const entries = Object.entries(enMessages.settings.optimizations.toolDescriptions)
+ const match = entries.find(([, english]) => english === description)
+ return match ? t(`settings.optimizations.toolDescriptions.${match[0]}`) : description
+}
+
+// Unknown tools retain the server's name until their key is translated.
+export function optimizationToolName(tool: string, fallback: string, t: Translate): string {
+ return Object.prototype.hasOwnProperty.call(enMessages.settings.optimizations.toolNames, tool)
+ ? t(`settings.optimizations.toolNames.${tool}`)
+ : fallback
+}
diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json
index 81c4caaf..65a42efe 100644
--- a/AppImage/messages/en/common.json
+++ b/AppImage/messages/en/common.json
@@ -2407,7 +2407,9 @@
"inactiveFooter": "Enable notifications to receive system, health and security alerts through the configured channels.",
"notificationLanguage": "Notification language",
"selectNotificationLanguage": "Select notification language",
- "notificationLanguageHint": "Used for deterministic runtime notifications even when AI is disabled."
+ "notificationLanguageHint": "Used for deterministic runtime notifications even when AI is disabled.",
+ "botTokenPlaceholder": "7595377878:AAGE6Fb2cy...",
+ "botTokenHelp": "The token can include the 'bot' prefix or be entered without it."
},
"ai": {
"title": "AI enhancement",
@@ -2585,7 +2587,73 @@
"availableUpdates": "Available updates",
"availableUpdatesDescription": "{count} optimizations can be updated to a newer version.",
"selectedCount": "{selected} of {total} selected",
- "updateSelected": "Update selected"
+ "updateSelected": "Update selected",
+ "toolDescriptions": {
+ "subscriptionAuto": "Patch the Proxmox web UI to suppress the subscription dialog and register a successful patch.",
+ "timeSync": "Detect timezone from public IP and enable systemd time sync (NTP).",
+ "aptLanguages": "Stop APT from downloading translation files to speed up updates.",
+ "journald": "Cap journald size, raise rate limit and force info-level logging so the log viewer and Fail2Ban work.",
+ "logrotate": "Replace logrotate.conf with a Log2RAM-friendly profile (daily rotation, copytruncate).",
+ "systemLimits": "Raise inotify watches, file descriptors, process keys and PID limits to enterprise levels.",
+ "memory": "Tune swappiness, dirty page ratios and compaction proactiveness for VM hosts without overriding the kernel's memory-overcommit policy.",
+ "kernelPanic": "Auto-reboot on kernel panic / oops / hardlockup; write crash dumps to /var/crash.",
+ "aptIpv4": "Force APT to use IPv4 to avoid stalls on hosts with broken IPv6 connectivity.",
+ "network": "Tune TCP buffers, somaxconn, IPv4 hardening and disable rp_filter on fw bridges (PVE 9 compatible).",
+ "bashrc": "Install and safely migrate the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.",
+ "log2ramAuto": "Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.",
+ "zfsAutotrim": "Enable ZFS autotrim on detected pools and record only pools changed by ProxMenux.",
+ "persistentNetwork": "Pin NIC names to MAC addresses via systemd .link files so kernel updates don't shuffle interface names.",
+ "kexec": "Install kexec-tools and add a Ctrl+Alt+K hotkey + systemd unit for fast reboots that skip BIOS/POST.",
+ "amd": "Detect AMD EPYC/Ryzen CPUs and apply microcode + IOMMU + KVM-specific kernel boot params.",
+ "openVSwitch": "Install OpenVSwitch for software-defined networking inside VMs and containers.",
+ "tcpBbr": "Enable TCP Fast Open (clients + server) and BBR congestion control for better latency under load.",
+ "ceph": "Install Ceph (client + server packages) for distributed RBD/CephFS storage; PVE 8/9 aware repo selection.",
+ "zfsArc": "Cap ZFS ARC max using Proxmox VE's 10%-of-RAM policy (16 GiB ceiling), safely reconcile conflicting module settings and report the pool-size guideline before applying it.",
+ "zfsSnapshots": "Install zfs-auto-snapshot with cron schedules for hourly/daily/weekly/monthly snapshots.",
+ "rpc": "Disable rpcbind service/socket while preserving their exact previous systemd state for rollback.",
+ "pigz": "Replace gzip with pigz (parallel implementation) for faster vzdump backup compression.",
+ "guestAgent": "Detect the host's hypervisor (qemu/vmware/hyperv/virtualbox) and install the matching guest agent.",
+ "vfio": "Enable IOMMU and load VFIO modules to allow GPU/PCI passthrough into VMs.",
+ "motd": "Add the ProxMenux MOTD banner while preserving the original file contents or absence for rollback.",
+ "subscriptionCustom": "Patch the Proxmox web UI to suppress the \"no valid subscription\" dialog (PVE 8 + 9 variants supported).",
+ "zram": "Enable an opt-in ZRAM swap profile sized to one tenth of RAM.",
+ "backupLimits": "Lift vzdump bandwidth/IO limits so backups run at the storage's real throughput.",
+ "ovh": "Detect OVH-rented hardware via whois lookup and install OVH Real-Time Monitoring (no-op on non-OVH).",
+ "ha": "Enable the Proxmox HA stack (pve-ha-lrm, pve-ha-crm, corosync) for cluster failover.",
+ "fastfetch": "Install Fastfetch system summary tool with the ProxMenux logo + status block as the SSH login banner.",
+ "figurine": "Install Figurine (ASCII-art hostname banner) and wire it into the SSH login flow.",
+ "log2ramCustom": "Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.",
+ "systemUtilities": "Install selected system utilities and track only packages that were newly added by ProxMenux."
+ },
+ "toolNames": {
+ "subscription_banner": "Subscription Banner Removal",
+ "time_sync": "Time Synchronization",
+ "apt_languages": "APT Language Skip",
+ "journald": "Journald Optimization",
+ "logrotate": "Logrotate Optimization",
+ "system_limits": "System Limits Increase",
+ "memory_settings": "Memory Settings Optimization",
+ "zram_swap": "ZRAM Swap Profile",
+ "kernel_panic": "Kernel Panic Configuration",
+ "apt_ipv4": "APT IPv4 Force",
+ "kexec": "kexec for quick reboots",
+ "rpc": "RPC / rpcbind Disable",
+ "motd": "Custom MOTD Banner",
+ "system_utils": "System Utilities",
+ "network_optimization": "Network Optimizations",
+ "bashrc_custom": "Bashrc Customization",
+ "figurine": "Figurine",
+ "fastfetch": "Fastfetch",
+ "log2ram": "Log2ram (SSD Protection)",
+ "zfs_autotrim": "ZFS Autotrim",
+ "amd_fixes": "AMD CPU (Ryzen/EPYC) fixes",
+ "persistent_network": "Setting persistent network interfaces",
+ "vfio_iommu": "VFIO/IOMMU Passthrough",
+ "lvm_repair": "LVM PV Headers Repair",
+ "repo_cleanup": "Repository Cleanup",
+ "proxmox_repos": "Proxmox APT Repositories",
+ "entropy": "Entropy Generation (haveged)"
+ }
},
"healthThresholds": {
"title": "Health Monitor Thresholds",
@@ -6959,5 +7027,12 @@
"notFound": "The node was not found. Reload the list and try again.",
"detectedRemove": "Only nodes added by hand can be removed. Detected nodes can be hidden."
}
+ },
+ "apiErrors": {
+ "http": "API request failed: {status} {statusText}",
+ "unexpectedContentType": "Expected JSON response but got {contentType}",
+ "unknownContentType": "unknown content type",
+ "invalidJson": "Invalid JSON response from {endpoint}",
+ "unauthorized": "Unauthorized: {endpoint}"
}
}
diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json
index d884687a..265187fa 100644
--- a/AppImage/messages/sk/common.json
+++ b/AppImage/messages/sk/common.json
@@ -820,7 +820,7 @@
"idleTitle": "Nečítaj, kým ho nič nepoužíva, aby sa to mohlo roztočiť. Jeho teplota sa znova zobrazí, akonáhle sa disk používa.",
"diskExcluded": "Vylúčené",
"diskExcludedTitle": "Vylúčené z periodických čítania v Nastaveniach.",
- "temperatureNoDisks": "No disks",
+ "temperatureNoDisks": "Žiadne disky",
"temperatureNormal": "Teploty normálne",
"temperatureHighCount": "Vysoké teploty: {count}",
"temperatureCriticalCount": "Kritické teploty: {count}",
@@ -2279,7 +2279,9 @@
"appriseSlackDocs": "Formáty URL a nastavenie Slack",
"appriseMatrixSteps": "Použite účet pre oznámenia, ktorý môže odosielať správy do miestnosti. Nahraďte vzorového používateľa, heslo, domovský server a úplné ID miestnosti; špeciálne znaky v prihlasovacích údajoch zakódujte pre URL. Schéma matrixs používa HTTPS. Podporu tokenov, SSO a šifrovaných miestností v nainštalovanej verzii Apprise nájdete v dokumentácii.",
"appriseMatrixDocs": "Formáty URL a nastavenie Matrix",
- "appriseServiceFinish": "Povoľte Apprise, zadajte svoju URL, vyberte kategórie oznámení alebo jednotlivé udalosti, uložte a potom použite Odoslať test. Tieto príklady nemenia uloženú URL. Tento kanál Apprise má jeden cieľ; pre viac služieb naraz použite API Apprise."
+ "appriseServiceFinish": "Povoľte Apprise, zadajte svoju URL, vyberte kategórie oznámení alebo jednotlivé udalosti, uložte a potom použite Odoslať test. Tieto príklady nemenia uloženú URL. Tento kanál Apprise má jeden cieľ; pre viac služieb naraz použite API Apprise.",
+ "botTokenPlaceholder": "7595377878:AAGE6Fb2cy...",
+ "botTokenHelp": "Token môže byť s prefixom „bot“ alebo bez neho."
},
"ai": {
"title": "AI vylepšenie",
@@ -2407,13 +2409,13 @@
"emptyTitle": "Zatiaľ nie sú nainštalované žiadne systémové optimalizácie",
"emptyDescription": "Spustite ProxMenux a nastavte systémové optimalizácie",
"installedTools": "Nainštalované nástroje",
- "activeCount": "{count} aktívne",
+ "activeCount": "Aktívne: {count}",
"viewUpdates": "Zobraziť dostupné aktualizácie",
- "updateCount": "{count} aktualizácií",
+ "updateCount": "Aktualizácie: {count}",
"updateOneTitle": "Aktualizovať: {name}",
- "updateManyTitle": "Aktualizovať {count} optimalizácií",
+ "updateManyTitle": "Aktualizácia optimalizácií ({count})",
"updateOneDescription": "Znova sa spustí {functionName} z toku {source}.",
- "updateManyDescription": "Postupne sa spustí {count} post-install funkcií.",
+ "updateManyDescription": "Počet funkcií, ktoré sa postupne spustia: {count}.",
"legacySourceTitle": "Staršia optimalizácia - kliknutím zobrazíte zdroj",
"sourceTitle": "Kliknutím zobrazíte zdrojový kód",
"sourceDialogTitle": "Zdrojový kód: {name}",
@@ -2455,9 +2457,75 @@
"legacy": "staršie",
"updateToolTitle": "Aktualizovať {name} na v{version}",
"availableUpdates": "Dostupné aktualizácie",
- "availableUpdatesDescription": "{count} optimalizácií sa dá aktualizovať na novšiu verziu.",
+ "availableUpdatesDescription": "Nástroje s dostupnou novšou verziou: {count}.",
"selectedCount": "Vybrané {selected} z {total}",
- "updateSelected": "Aktualizovať vybrané"
+ "updateSelected": "Aktualizovať vybrané",
+ "toolDescriptions": {
+ "subscriptionAuto": "Upraví web Proxmoxu tak, aby nezobrazoval upozornenie na predplatné, a zaznamená úspešnú úpravu.",
+ "timeSync": "Zistí časové pásmo podľa verejnej IP adresy a zapne synchronizáciu času cez systemd (NTP).",
+ "aptLanguages": "Vypne sťahovanie prekladových súborov APT a zrýchli aktualizácie.",
+ "journald": "Obmedzí veľkosť journald, zvýši limit správ a zapne úroveň info, aby fungoval prehliadač záznamov aj Fail2Ban.",
+ "logrotate": "Nahradí logrotate.conf profilom vhodným pre Log2RAM (denná rotácia, copytruncate).",
+ "systemLimits": "Zvýši limity sledovaní inotify, otvorených súborov, kľúčov procesov a PID pre náročnú prevádzku.",
+ "memory": "Upraví swappiness, podiel neuložených stránok a aktívne zhusťovanie pamäte pre VM servery bez zmeny pravidiel jadra pre nadmerné prideľovanie pamäte.",
+ "kernelPanic": "Zapne automatický reštart pri kernel panic, oops alebo hardlockup a uloží výpisy pádu do /var/crash.",
+ "aptIpv4": "Vynúti IPv4 pre APT, aby sa predišlo čakaniu pri nefunkčnom pripojení IPv6.",
+ "network": "Upraví TCP vyrovnávacie pamäte, somaxconn a zabezpečenie IPv4; vypne rp_filter na firewallových mostoch (kompatibilné s PVE 9).",
+ "bashrc": "Nainštaluje a bezpečne prenesie spravovaný Bash prompt a aliasy ProxMenuxu so zachovaním alebo výberom krátkej či úplnej cesty pracovného priečinka.",
+ "log2ramAuto": "Nainštaluje Log2RAM s veľkosťou podľa RAM servera (128M/256M/512M); pri rotačných diskoch vynechá detekciu SSD/M.2.",
+ "zfsAutotrim": "Zapne ZFS autotrim na nájdených pooloch a zaznamená iba pooly zmenené ProxMenuxom.",
+ "persistentNetwork": "Priradí názvy sieťových rozhraní k MAC adresám cez súbory systemd .link, aby ich aktualizácie jadra nemenili.",
+ "kexec": "Nainštaluje kexec-tools a pridá skratku Ctrl+Alt+K aj službu systemd na rýchly reštart s vynechaním BIOS/POST.",
+ "amd": "Rozpozná procesory AMD EPYC/Ryzen a nastaví mikrokód aj parametre jadra pre IOMMU a KVM.",
+ "openVSwitch": "Nainštaluje OpenVSwitch na softvérovo riadené siete vo VM a kontajneroch.",
+ "tcpBbr": "Zapne TCP Fast Open pre klienta aj server a riadenie zahltenia BBR na zníženie oneskorenia pri záťaži.",
+ "ceph": "Nainštaluje klientské aj serverové balíky Ceph pre úložisko RBD/CephFS; repozitár vyberie podľa PVE 8/9.",
+ "zfsArc": "Obmedzí ZFS ARC podľa pravidla Proxmox VE na 10 % RAM (najviac 16 GiB), bezpečne zosúladí konfliktné nastavenia modulu a pred použitím zobrazí odporúčanie podľa veľkosti poolu.",
+ "zfsSnapshots": "Nainštaluje zfs-auto-snapshot s hodinovými, dennými, týždennými a mesačnými plánmi v crone.",
+ "rpc": "Vypne službu aj socket rpcbind a zachová ich presný pôvodný stav systemd na prípadnú obnovu.",
+ "pigz": "Nahradí gzip paralelným pigz na rýchlejšiu kompresiu záloh vzdump.",
+ "guestAgent": "Rozpozná hypervízor servera (qemu/vmware/hyperv/virtualbox) a nainštaluje príslušného hosťovského agenta.",
+ "vfio": "Zapne IOMMU a načíta moduly VFIO na priame priradenie GPU/PCI do VM.",
+ "motd": "Pridá uvítaciu správu MOTD ProxMenuxu a na obnovu zachová pôvodný obsah súboru alebo jeho neprítomnosť.",
+ "subscriptionCustom": "Upraví web Proxmoxu tak, aby nezobrazoval upozornenie na neplatné predplatné (podporuje PVE 8 aj 9).",
+ "zram": "Zapne voliteľný profil swapu ZRAM s veľkosťou jednej desatiny RAM.",
+ "backupLimits": "Odstráni limity prenosu a I/O vzdump, aby zálohy využili skutočnú priepustnosť úložiska.",
+ "ovh": "Zistí hardvér prenajatý od OVH cez whois a nainštaluje OVH Real-Time Monitoring (mimo OVH nič nemení).",
+ "ha": "Zapne služby Proxmox HA (pve-ha-lrm, pve-ha-crm, corosync) na prevzatie prevádzky v klastri pri výpadku.",
+ "fastfetch": "Nainštaluje systémový prehľad Fastfetch s logom a stavom ProxMenuxu ako uvítanie pri prihlásení cez SSH.",
+ "figurine": "Nainštaluje Figurine na zobrazenie názvu servera pomocou ASCII grafiky pri prihlásení cez SSH.",
+ "log2ramCustom": "Nainštaluje Log2RAM s veľkosťou zvolenou používateľom; pred použitím sa opýta na veľkosť a zohľadní SSD/M.2.",
+ "systemUtilities": "Nainštaluje vybrané systémové nástroje a zaznamená iba balíky novo pridané ProxMenuxom."
+ },
+ "toolNames": {
+ "subscription_banner": "Odstránenie upozornenia na predplatné",
+ "time_sync": "Synchronizácia času",
+ "apt_languages": "Vynechanie jazykových balíkov APT",
+ "journald": "Optimalizácia journald",
+ "logrotate": "Optimalizácia logrotate",
+ "system_limits": "Zvýšenie systémových limitov",
+ "memory_settings": "Optimalizácia nastavení pamäte",
+ "zram_swap": "Profil swapu ZRAM",
+ "kernel_panic": "Nastavenie správania pri páde jadra",
+ "apt_ipv4": "Vynútenie IPv4 pre APT",
+ "kexec": "Rýchly reštart cez kexec",
+ "rpc": "Vypnutie RPC / rpcbind",
+ "motd": "Vlastná uvítacia správa MOTD",
+ "system_utils": "Systémové nástroje",
+ "network_optimization": "Optimalizácia siete",
+ "bashrc_custom": "Prispôsobenie bashrc",
+ "figurine": "Figurine",
+ "fastfetch": "Fastfetch",
+ "log2ram": "Log2ram (ochrana SSD)",
+ "zfs_autotrim": "ZFS Autotrim",
+ "amd_fixes": "Opravy pre procesory AMD (Ryzen/EPYC)",
+ "persistent_network": "Trvalé názvy sieťových rozhraní",
+ "vfio_iommu": "Priame priradenie zariadení (VFIO/IOMMU)",
+ "lvm_repair": "Oprava hlavičiek fyzických zväzkov LVM",
+ "repo_cleanup": "Vyčistenie repozitárov",
+ "proxmox_repos": "Repozitáre APT pre Proxmox",
+ "entropy": "Generovanie entropie (haveged)"
+ }
},
"healthThresholds": {
"title": "Hranice kontroly stavu",
@@ -7750,5 +7818,12 @@
"notFound": "Uzol sa nenašiel. Obnov zoznam a skús to znova.",
"detectedRemove": "Odstrániť môžeš iba ručne pridané uzly. Automaticky zistené uzly môžeš skryť."
}
+ },
+ "apiErrors": {
+ "http": "Požiadavka na API zlyhala: {status} {statusText}",
+ "unexpectedContentType": "Očakávala sa odpoveď JSON, ale prišiel typ {contentType}",
+ "unknownContentType": "neznámy typ obsahu",
+ "invalidJson": "Odpoveď z {endpoint} obsahuje neplatný JSON",
+ "unauthorized": "Chýba platné prihlásenie: {endpoint}"
}
}
diff --git a/lang/sk.json b/lang/sk.json
index 20c8ec1a..cf4da7f8 100644
--- a/lang/sk.json
+++ b/lang/sk.json
@@ -669,7 +669,7 @@
"Bookstack is a free and open source Wiki designed for creating beautiful documentation. Featuring a simple, but powerful WYSIWYG editor it allows for teams to create detailed and useful documentation with ease.": "Bookstack je bezplatný a open source Wiki navrhnutý pre vytvorenie krásnej dokumentácie. Vďaka jednoduchému, ale výkonnému WYSIWYG editoru umožňuje tímom jednoducho vytvárať podrobnú a užitočnú dokumentáciu.",
"Boot Disk": "Bootovací disk",
"Boot artifacts regenerated — reboot the host to activate the merged config.": "Boot súbory boli znovu vytvorené - reštartujte host, aby sa zlúčené nastavenie aktivovalo.",
- "Boot disk:": "Boot disk:",
+ "Boot disk:": "Zavádzací disk:",
"Boot order set to": "Poradie bootovania nastavené na",
"Boot partition UUIDs from backup not found on this host:": "UUID boot oddielov zo zálohy sa na tomto hostovi nenašli:",
"Boot type (grub/zfs):": "Typ bootovania (grub/zfs):",
@@ -3826,7 +3826,7 @@
"Mount Point": "Bod pripojenia",
"Mount Point ID": "ID mount pointu",
"Mount Point Removal Summary:": "Zhrnutie odstránenia mount pointu:",
- "Mount Point:": "Mount point:",
+ "Mount Point:": "Bod pripojenia:",
"Mount Samba Share": "Pripojiť Samba zdieľanie",
"Mount Samba Share on Host": "Pripojiť Samba zdieľanie na hostovi",
"Mount USB disk?": "Pripojiť USB disk?",
@@ -3852,7 +3852,7 @@
"Mount point not visible inside the container yet": "Mount point zatiaľ nie je vo vnútri kontajnera viditeľný",
"Mount point ready:": "Mount point je pripravený:",
"Mount point removed successfully": "Mount point bol úspešne odstránený",
- "Mount point:": "Mount point:",
+ "Mount point:": "Bod pripojenia:",
"Mount points added:": "Pridanie bodov pripojenia:",
"Mount read-only": "Pripojiť iba na čítanie",
"Mount shares on HOST first": "Najprv pripojiť zdieľania na HOSTE",
@@ -8365,5 +8365,117 @@
"⚠ Disk data will NOT be erased.": "⚠ Dáta na disku sa NEVYMAŽÚ.",
"⚠ Disk will be unmounted and removed from /etc/fstab.": "⚠ Disk sa odpojí a odstráni z /etc/fstab.",
"⚠ The /etc/fstab entry will be removed.": "⚠ Záznam v /etc/fstab bude odstránený.",
- "⚠ The disk will be unmounted.": "⚠ Disk bude odpojený."
+ "⚠ The disk will be unmounted.": "⚠ Disk bude odpojený.",
+ "Allocated Cores:": "Pridelené jadrá:",
+ "Allocated RAM:": "Pridelená RAM:",
+ "Allocating virtual disk for %s...": "Prideľuje sa virtuálny disk pre %s...",
+ "BRIDGE": "SIEŤOVÝ MOST (BRIDGE)",
+ "CORE COUNT": "POČET JADIER",
+ "Choose": "Vyber",
+ "Choose BIOS Type": "Vyber typ BIOS",
+ "Choose Type": "Vyber typ",
+ "Code": "Kód",
+ "Configured VM description": "Popis VM je nastavený",
+ "Configured controller/NVMe as hostpci%s: %s": "Radič/NVMe je nastavený ako hostpci%s: %s",
+ "Configured import disk %s as %s": "Importovaný disk %s je nastavený ako %s",
+ "Configured loader disk as ide0": "Disk zavádzača je nastavený ako ide0",
+ "Configured virtual disk as %s, %sGB on %s": "Virtuálny disk je nastavený ako %s, %sGB na %s",
+ "Configuring Serial Port:": "Nastavenie sériového portu:",
+ "Controller/NVMe already present in VM config (%s)": "Radič/NVMe už je v nastaveniach VM (%s)",
+ "Create a %s": "Vytvorený %s",
+ "Created with ProxMenuX": "Vytvorené pomocou ProxMenuX",
+ "Creating a %s using the above advanced settings": "Vytvára sa %s s uvedenými pokročilými nastaveniami",
+ "Creating a %s using the above default settings": "Vytvára sa %s s uvedenými predvolenými nastaveniami",
+ "Creating images directory": "Vytvára sa priečinok s obrazmi",
+ "Creating virtual disk (format=raw) for %s...": "Vytvára sa virtuálny disk (formát raw) pre %s...",
+ "Custom Loader Image (from %s)": "Vlastný obraz zavádzača (z %s)",
+ "Default": "Predvolené",
+ "Do-Over": "Začať znova",
+ "Docs": "Dokumentácia",
+ "Downloaded %s to %s": "Stiahnuté %s do %s",
+ "EFI Disk Storage": "Úložisko EFI disku",
+ "EFI disk created (raw) and configured on %s": "EFI disk (raw) bol vytvorený a nastavený na %s",
+ "EFI disk created and configured on %s": "EFI disk bol vytvorený a nastavený na %s",
+ "EFI storage selection failed or was cancelled.": "Výber úložiska pre EFI disk zlyhal alebo bol zrušený.",
+ "Edition:": "Edícia:",
+ "Enter temporary folder path (default: %s):": "Zadaj cestu dočasného priečinka (predvolené: %s):",
+ "Exit-Script": "Ukončiť skript",
+ "Failed to allocate virtual disk %s": "Pridelenie virtuálneho disku %s zlyhalo",
+ "Failed to assign disk %s (%s) on %s": "Priradenie disku %s (%s) na %s zlyhalo",
+ "Failed to assign loader disk": "Priradenie disku zavádzača zlyhalo",
+ "Failed to assign loader disk to ide0": "Priradenie disku zavádzača k ide0 zlyhalo",
+ "Failed to configure controller/NVMe: %s": "Nastavenie radiča/NVMe zlyhalo: %s",
+ "Failed to configure import disk %s as %s": "Nastavenie importovaného disku %s ako %s zlyhalo",
+ "Failed to configure virtual disk as %s": "Nastavenie virtuálneho disku ako %s zlyhalo",
+ "Failed to install unzip": "Inštalácia unzip zlyhala",
+ "Failed to install:": "Inštalácia zlyhala:",
+ "HOSTNAME": "NÁZOV SERVERA",
+ "ID %s is already in use": "ID %s sa už používa",
+ "Images directory created: %s": "Priečinok s obrazmi bol vytvorený: %s",
+ "SeaBIOS (Legacy)": "SeaBIOS (starší režim BIOS)",
+ "Installed unzip successfully.": "unzip bol úspešne nainštalovaný.",
+ "Installing dependencies:": "Inštalujú sa závislosti:",
+ "Installing unzip...": "Inštaluje sa unzip...",
+ "Language:": "Jazyk:",
+ "Loader": "Zavádzač",
+ "Loader configured as boot device.": "Zavádzač je nastavený ako zariadenie na zavedenie systému.",
+ "Loader import failed. No disk detected in config.": "Import zavádzača zlyhal. V nastaveniach sa nenašiel disk.",
+ "Loader import failed. No disk detected.": "Import zavádzača zlyhal. Nenašiel sa disk.",
+ "Loader imported successfully to %s": "Zavádzač bol úspešne importovaný do %s",
+ "Loader storage selection failed or was cancelled.": "Výber úložiska pre zavádzač zlyhal alebo bol zrušený.",
+ "Loader:": "Zavádzač:",
+ "MAC ADDRESS": "MAC ADRESA",
+ "MTU SIZE": "VEĽKOSŤ MTU",
+ "Machine i440fx": "Stroj i440fx",
+ "Machine q35": "Stroj q35",
+ "No compatible images found in %s\n\nSupported formats: .img, .iso, .qcow2, .vmdk\n\nPlease add some images and try again.": "V %s sa nenašli podporované obrazy.\n\nPodporované formáty: .img, .iso, .qcow2, .vmdk\n\nPridaj obrazy a skús to znova.",
+ "No custom image selected": "Nebol vybraný vlastný obraz",
+ "No disks/controllers configured.": "Nie sú nastavené žiadne disky ani radiče.",
+ "Ready to create a %s?": "Chceš vytvoriť %s?",
+ "Retrieving the URL for the %s loader": "Zisťuje sa URL zavádzača %s",
+ "SELECT CUSTOM IMAGE": "VÝBER VLASTNÉHO OBRAZU",
+ "SELECT LOADER": "VÝBER ZAVÁDZAČA",
+ "SETTINGS": "NASTAVENIA",
+ "STORAGE PLAN": "PLÁN ÚLOŽISKA",
+ "Selected Loader:": "Vybraný zavádzač:",
+ "Start VM when completed:": "Spustiť VM po dokončení:",
+ "Starting ISO conversion...": "Spúšťa sa vytváranie ISO...",
+ "Synology VM Creator": "Vytvorenie VM Synology",
+ "This will create a New %s. Proceed?": "Vytvorí sa nový %s. Pokračovať?",
+ "Unable to detect a valid storage location for EFI disk.": "Nenašlo sa použiteľné úložisko pre EFI disk.",
+ "Unable to detect a valid storage location.": "Nenašlo sa použiteľné úložisko.",
+ "Using Advanced Settings": "Použijú sa pokročilé nastavenia",
+ "Using BIOS Type:": "Typ BIOS:",
+ "Using Bridge:": "Sieťový most (bridge):",
+ "Using CPU Model:": "Model CPU:",
+ "Using Custom Image:": "Vlastný obraz:",
+ "Using Default Settings": "Použijú sa predvolené nastavenia",
+ "Using Hostname:": "Názov servera:",
+ "Using Interface MTU Size:": "Veľkosť MTU rozhrania:",
+ "Using MAC Address:": "MAC adresa:",
+ "Using Machine Type:": "Typ stroja:",
+ "Using VLAN:": "VLAN:",
+ "Using Virtual Machine ID:": "ID virtuálneho stroja:",
+ "Using Vlan:": "VLAN:",
+ "VIRTUAL DISK": "VIRTUÁLNY DISK",
+ "VIRTUAL MACHINE ID": "ID VIRTUÁLNEHO STROJA",
+ "VM created with errors. Check configuration.": "VM bola vytvorená s chybami. Skontroluj jej nastavenia.",
+ "Virtual Machine ID:": "ID virtuálneho stroja:",
+ "Command not found.": "Príkaz sa nenašiel.",
+ "Custom Image": "Vlastný obraz",
+ "Error generating UUP download list.": "Vytvorenie zoznamu súborov UUP na stiahnutie zlyhalo.",
+ "Importing loader disk:": "Importuje sa disk zavádzača:",
+ "genisoimage/mkisofs not found.": "Nenašiel sa genisoimage ani mkisofs.",
+ "%s of %s updates failed — see the messages above.": "Zlyhalo %s z %s aktualizácií — pozri hlášky vyššie.",
+ "%s of %s updates failed.": "Zlyhalo %s z %s aktualizácií.",
+ "All %s update(s) completed successfully.": "Všetky aktualizácie (%s) sa úspešne dokončili.",
+ "Function '%s' is not defined in the %s flow.": "Funkcia '%s' nie je definovaná v postupe %s.",
+ "Press Enter to close this terminal...": "Stlač Enter na zatvorenie terminálu...",
+ "Update for %s completed.": "Aktualizácia %s je dokončená.",
+ "Update for %s exited with status %s.": "Aktualizácia %s skončila s návratovým kódom %s.",
+ "Updating %s (running %s from %s flow)...": "Aktualizuje sa %s (spúšťa sa %s z postupu %s)...",
+ "function '%s' is not defined in the %s flow.": "funkcia '%s' nie je definovaná v postupe %s.",
+ "invalid source '%s' (must be 'auto', 'custom', or 'detected')": "neplatný zdroj '%s' (musí byť 'auto', 'custom' alebo 'detected')",
+ "provide either SOURCE_TYPE+FUNCTION_NAME (single mode) or FUNCTIONS_BATCH (batch mode).": "zadaj SOURCE_TYPE+FUNCTION_NAME pre jednu funkciu alebo FUNCTIONS_BATCH pre dávku.",
+ "update for %s exited with status %s.": "aktualizácia %s skončila s návratovým kódom %s."
}
diff --git a/scripts/post_install/update_post_install_function.sh b/scripts/post_install/update_post_install_function.sh
index 17690202..1119efe9 100644
--- a/scripts/post_install/update_post_install_function.sh
+++ b/scripts/post_install/update_post_install_function.sh
@@ -40,6 +40,11 @@ if command -v initialize_cache >/dev/null 2>&1; then
initialize_cache
fi
+# Keep standalone invocations usable when the shared utilities are unavailable.
+if ! command -v translate >/dev/null 2>&1; then
+ translate() { printf '%s\n' "$1"; }
+fi
+
if command -v show_proxmenux_logo >/dev/null 2>&1; then
show_proxmenux_logo
fi
@@ -60,7 +65,7 @@ if [[ -n "$FUNCTIONS_BATCH" ]]; then
elif [[ -n "$SOURCE_TYPE" && -n "$FUNCTION_NAME" ]]; then
BATCH+=("${SOURCE_TYPE}:${FUNCTION_NAME}:${TOOL_KEY}")
else
- echo "ERROR: provide either SOURCE_TYPE+FUNCTION_NAME (single mode) or FUNCTIONS_BATCH (batch mode)."
+ echo "ERROR: $(translate "provide either SOURCE_TYPE+FUNCTION_NAME (single mode) or FUNCTIONS_BATCH (batch mode).")"
exit 2
fi
@@ -99,7 +104,7 @@ ensure_flow_loaded() {
fi
;;
*)
- echo "ERROR: invalid source '$source_type' (must be 'auto', 'custom', or 'detected')"
+ echo "ERROR: $(printf "$(translate "invalid source '%s' (must be 'auto', 'custom', or 'detected')")" "$source_type")"
return 2
;;
esac
@@ -135,9 +140,9 @@ for entry in "${BATCH[@]}"; do
[[ -z "$tkey" ]] && tkey="$fn"
if command -v msg_info2 >/dev/null 2>&1; then
- msg_info2 "[$INDEX/$TOTAL] Updating ${tkey} (running ${fn} from ${src} flow)..."
+ msg_info2 "[$INDEX/$TOTAL] $(printf "$(translate "Updating %s (running %s from %s flow)...")" "$tkey" "$fn" "$src")"
else
- echo "[ProxMenux] [$INDEX/$TOTAL] Updating ${tkey} (running ${fn} from ${src} flow)..."
+ echo "[ProxMenux] [$INDEX/$TOTAL] $(printf "$(translate "Updating %s (running %s from %s flow)...")" "$tkey" "$fn" "$src")"
fi
if ! ensure_flow_loaded "$src"; then
@@ -147,9 +152,9 @@ for entry in "${BATCH[@]}"; do
if ! declare -F "$fn" >/dev/null 2>&1; then
if command -v msg_error >/dev/null 2>&1; then
- msg_error "Function '$fn' is not defined in the ${src} flow."
+ msg_error "$(printf "$(translate "Function '%s' is not defined in the %s flow.")" "$fn" "$src")"
else
- echo "ERROR: function '$fn' is not defined in the ${src} flow."
+ echo "ERROR: $(printf "$(translate "function '%s' is not defined in the %s flow.")" "$fn" "$src")"
fi
FAILED=$((FAILED + 1))
continue
@@ -162,15 +167,15 @@ for entry in "${BATCH[@]}"; do
if [[ $RC -eq 0 ]]; then
if command -v msg_ok >/dev/null 2>&1; then
- msg_ok "Update for ${tkey} completed."
+ msg_ok "$(printf "$(translate "Update for %s completed.")" "$tkey")"
else
- echo "[ProxMenux] Update for ${tkey} completed."
+ echo "[ProxMenux] $(printf "$(translate "Update for %s completed.")" "$tkey")"
fi
else
if command -v msg_error >/dev/null 2>&1; then
- msg_error "Update for ${tkey} exited with status $RC."
+ msg_error "$(printf "$(translate "Update for %s exited with status %s.")" "$tkey" "$RC")"
else
- echo "ERROR: update for ${tkey} exited with status $RC."
+ echo "ERROR: $(printf "$(translate "update for %s exited with status %s.")" "$tkey" "$RC")"
fi
FAILED=$((FAILED + 1))
fi
@@ -178,22 +183,22 @@ done
if [[ $FAILED -eq 0 ]]; then
if command -v msg_success >/dev/null 2>&1; then
- msg_success "All $TOTAL update(s) completed successfully."
+ msg_success "$(printf "$(translate "All %s update(s) completed successfully.")" "$TOTAL")"
else
- echo "[ProxMenux] All $TOTAL update(s) completed successfully."
+ echo "[ProxMenux] $(printf "$(translate "All %s update(s) completed successfully.")" "$TOTAL")"
fi
else
if command -v msg_warn >/dev/null 2>&1; then
- msg_warn "$FAILED of $TOTAL updates failed — see the messages above."
+ msg_warn "$(printf "$(translate "%s of %s updates failed — see the messages above.")" "$FAILED" "$TOTAL")"
else
- echo "WARNING: $FAILED of $TOTAL updates failed."
+ echo "WARNING: $(printf "$(translate "%s of %s updates failed.")" "$FAILED" "$TOTAL")"
fi
fi
if command -v msg_success >/dev/null 2>&1; then
- msg_success "Press Enter to close this terminal..."
+ msg_success "$(translate "Press Enter to close this terminal...")"
else
- echo "Press Enter to close this terminal..."
+ echo "$(translate "Press Enter to close this terminal...")"
fi
read -r 2>/dev/null || true
diff --git a/scripts/utilities/uup_dump_iso_creator.sh b/scripts/utilities/uup_dump_iso_creator.sh
index abcfc6c6..64e8fc2d 100644
--- a/scripts/utilities/uup_dump_iso_creator.sh
+++ b/scripts/utilities/uup_dump_iso_creator.sh
@@ -125,18 +125,18 @@ function run_uupdump_creator() {
clear
show_proxmenux_logo
echo -e
- msg_info "$(translate "Installing dependencies: ${MISSING[*]}")"
+ msg_info "$(translate "Installing dependencies:") ${MISSING[*]}"
apt-get update -qq >/dev/null 2>&1
msg_ok "$(translate "All dependencies installed and verified.")"
# Build dependencies land on the host, so they are recorded;
# building the ISO is an operation and is not.
if declare -F pmx_install_pkg >/dev/null 2>&1; then
if ! PMX_JOURNAL_SOURCE="uup_dump_iso_creator.sh" pmx_install_pkg "${MISSING[@]}"; then
- msg_error "$(translate "Failed to install: ${MISSING[*]}")"
+ msg_error "$(translate "Failed to install:") ${MISSING[*]}"
exit 1
fi
elif ! apt-get install -y "${MISSING[@]}" >/dev/null 2>&1; then
- msg_error "$(translate "Failed to install: ${MISSING[*]}")"
+ msg_error "$(translate "Failed to install:") ${MISSING[*]}"
exit 1
fi
fi
@@ -151,7 +151,7 @@ function run_uupdump_creator() {
#if [[ ${#FAILED[@]} -eq 0 ]]; then
# msg_ok "$(translate "All dependencies installed and verified.")"
#else
- # msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
+ # msg_error "$(translate "Missing commands after installation:") ${FAILED[*]}"
# exit 1
#fi
@@ -166,7 +166,7 @@ function run_uupdump_creator() {
######################################
DEFAULT_TMP="/root/uup-temp"
- USER_INPUT=$(dialog --backtitle "ProxMenux" --inputbox "Enter temporary folder path (default: $DEFAULT_TMP):" 10 60 "$DEFAULT_TMP" 3>&1 1>&2 2>&3)
+ USER_INPUT=$(dialog --backtitle "ProxMenux" --inputbox "$(printf "$(translate "Enter temporary folder path (default: %s):")" "$DEFAULT_TMP")" 10 60 "$DEFAULT_TMP" 3>&1 1>&2 2>&3)
######################################
@@ -237,10 +237,10 @@ function run_uupdump_creator() {
echo -e
echo -e "\n${BGN}=============== UUP Dump Creator ===============${CL}"
echo -e " ${BGN}🆔 ID:${CL} ${DGN}$BUILD_ID${CL}"
- echo -e " ${BGN}🌐 Language:${CL} ${DGN}$LANG${CL}"
- echo -e " ${BGN}💿 Edition:${CL} ${DGN}$EDITION${CL}"
- echo -e " ${BGN}🖥️ Architecture:${CL} ${DGN}$ARCH${CL}"
- echo -e " ${BGN}📁 Destination:${CL} ${DGN}$OUT_DIR${CL}"
+ echo -e " ${BGN}🌐 $(translate "Language:")${CL} ${DGN}$LANG${CL}"
+ echo -e " ${BGN}💿 $(translate "Edition:")${CL} ${DGN}$EDITION${CL}"
+ echo -e " ${BGN}🖥️ $(translate "Architecture:")${CL} ${DGN}$ARCH${CL}"
+ echo -e " ${BGN}📁 $(translate "Destination:")${CL} ${DGN}$OUT_DIR${CL}"
echo -e "${BGN}===============================================${CL}\n"
@@ -262,10 +262,10 @@ mkdir -p files
echo "https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz" > files/converter_multi
for prog in aria2c cabextract wimlib-imagex chntpw; do
- which \$prog &>/dev/null || { echo "\$prog not found."; exit 1; }
+ which \$prog &>/dev/null || { printf '%s\n' "\$prog: $(translate "Command not found.")"; exit 1; }
done
-which genisoimage &>/dev/null || which mkisofs &>/dev/null || { echo "genisoimage/mkisofs not found."; exit 1; }
+which genisoimage &>/dev/null || which mkisofs &>/dev/null || { echo "$(translate "genisoimage/mkisofs not found.")"; exit 1; }
destDir="UUPs"
tempScript="aria2_script.\$RANDOM.txt"
@@ -277,7 +277,7 @@ aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download
-o"\$tempScript" --allow-overwrite=true --auto-file-renaming=false \\
"https://uupdump.net/get.php?id=$BUILD_ID&pack=$LANG&edition=$EDITION&aria2=2" || exit 1
-grep '#UUPDUMP_ERROR:' "\$tempScript" && { echo "❌ Error generating UUP download list."; exit 1; }
+grep '#UUPDUMP_ERROR:' "\$tempScript" && { echo "❌ $(translate "Error generating UUP download list.")"; exit 1; }
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
-x16 -s16 -j5 -c -R -d"\$destDir" -i"\$tempScript" || exit 1
@@ -293,7 +293,7 @@ EOF
[[ -z "$UUP_FOLDER" ]] && msg_error "$(translate "No UUP folder found.")" && exit 1
echo -e "\n${GN}=======================================${CL}"
- echo -e " 💿 ${GN}Starting ISO conversion...${CL}"
+ echo -e " 💿 ${GN}$(translate "Starting ISO conversion...")${CL}"
echo -e "${GN}=======================================${CL}\n"
@@ -336,4 +336,4 @@ EOF
fi
}
-run_uupdump_creator
\ No newline at end of file
+run_uupdump_creator
diff --git a/scripts/vm/synology.sh b/scripts/vm/synology.sh
index f8478cb7..b75ff7cc 100644
--- a/scripts/vm/synology.sh
+++ b/scripts/vm/synology.sh
@@ -90,7 +90,7 @@ VM_STORAGE_IOMMU_PENDING_REBOOT=0
function exit_script() {
clear
- if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then
+ if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(printf "$(translate "This will create a New %s. Proceed?")" "$NAME")" 10 58; then
start_script
else
clear
@@ -104,7 +104,7 @@ function exit_script() {
function header_info() {
clear
show_proxmenux_logo
- msg_title "Synology VM Creator"
+ msg_title "$(translate "Synology VM Creator")"
}
# ==========================================================
@@ -211,13 +211,13 @@ function run_gpu_passthrough_wizard() {
# start Script
# ==========================================================
function start_script() {
- if (whiptail --backtitle "ProxMenuX" --title "SETTINGS" --yesno "$(translate "Use Default Settings?")" --no-button Advanced 10 58); then
+ if (whiptail --backtitle "ProxMenuX" --title "$(translate "SETTINGS")" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 58); then
header_info
- echo -e "${DEF}Using Default Settings${CL}"
+ echo -e "${DEF}$(translate "Using Default Settings")${CL}"
default_settings || return 1
else
header_info
- echo -e "${CUS}Using Advanced Settings${CL}"
+ echo -e "${CUS}$(translate "Using Advanced Settings")${CL}"
advanced_settings || return 1
fi
return 0
@@ -247,21 +247,21 @@ function default_settings() {
SERIAL_PORT="socket"
START_VM="no"
- echo -e " ${TAB}${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}"
- echo -e " ${TAB}${DGN}Using Machine Type: ${BGN}q35${CL}"
- echo -e " ${TAB}${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}"
- echo -e " ${TAB}${DGN}Using Hostname: ${BGN}${HN}${CL}"
- echo -e " ${TAB}${DGN}Using CPU Model: ${BGN}Host${CL}"
- echo -e " ${TAB}${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}"
- echo -e " ${TAB}${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}"
- echo -e " ${TAB}${DGN}Using Bridge: ${BGN}${BRG}${CL}"
- echo -e " ${TAB}${DGN}Using MAC Address: ${BGN}${MAC}${CL}"
- echo -e " ${TAB}${DGN}Using VLAN: ${BGN}Default${CL}"
- echo -e " ${TAB}${DGN}Using Interface MTU Size: ${BGN}Default${CL}"
- echo -e " ${TAB}${DGN}Configuring Serial Port: ${BGN}${SERIAL_PORT}${CL}"
- echo -e " ${TAB}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using Virtual Machine ID:") ${BGN}${VMID}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using Machine Type:") ${BGN}q35${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using BIOS Type:") ${BGN}OVMF (UEFI)${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using Hostname:") ${BGN}${HN}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using CPU Model:") ${BGN}Host${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Allocated Cores:") ${BGN}${CORE_COUNT}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Allocated RAM:") ${BGN}${RAM_SIZE}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using Bridge:") ${BGN}${BRG}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using MAC Address:") ${BGN}${MAC}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using VLAN:") ${BGN}$(translate "Default")${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Using Interface MTU Size:") ${BGN}$(translate "Default")${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Configuring Serial Port:") ${BGN}${SERIAL_PORT}${CL}"
+ echo -e " ${TAB}${DGN}$(translate "Start VM when completed:") ${BGN}${START_VM}${CL}"
echo -e
- echo -e "${DEF}Creating a $NAME using the above default settings${CL}"
+ echo -e "${DEF}$(printf "$(translate "Creating a %s using the above default settings")" "$NAME")${CL}"
sleep 1
select_disk_type || return 1
@@ -278,16 +278,16 @@ function default_settings() {
function advanced_settings() {
# VM ID Selection
while true; do
- if VMID=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Virtual Machine ID")" 8 58 $NEXTID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if VMID=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Virtual Machine ID")" 8 58 $NEXTID --title "$(translate "VIRTUAL MACHINE ID")" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z "$VMID" ]; then
VMID="$NEXTID"
fi
if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then
- echo -e "${CROSS}${RD} ID $VMID is already in use${CL}"
+ echo -e "${CROSS}${RD} $(printf "$(translate "ID %s is already in use")" "$VMID")${CL}"
sleep 1
continue
fi
- echo -e "${DGN}Virtual Machine ID: ${BGN}$VMID${CL}"
+ echo -e "${DGN}$(translate "Virtual Machine ID:") ${BGN}$VMID${CL}"
break
else
exit_script
@@ -295,16 +295,16 @@ function advanced_settings() {
done
# Machine Type Selection
- if MACH=$(whiptail --backtitle "ProxMenuX" --title "$(translate "MACHINE TYPE")" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \
- "q35" "Machine q35" ON \
- "i440fx" "Machine i440fx" OFF \
+ if MACH=$(whiptail --backtitle "ProxMenuX" --title "$(translate "MACHINE TYPE")" --radiolist --cancel-button "$(translate "Exit-Script")" "$(translate "Choose Type")" 10 58 2 \
+ "q35" "$(translate "Machine q35")" ON \
+ "i440fx" "$(translate "Machine i440fx")" OFF \
3>&1 1>&2 2>&3); then
if [ $MACH = q35 ]; then
- echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}"
+ echo -e "${DGN}$(translate "Using Machine Type:") ${BGN}$MACH${CL}"
FORMAT=""
MACHINE=" -machine q35"
else
- echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}"
+ echo -e "${DGN}$(translate "Using Machine Type:") ${BGN}$MACH${CL}"
FORMAT=",efitype=4m"
MACHINE=""
fi
@@ -313,15 +313,15 @@ function advanced_settings() {
fi
# BIOS Type Selection
- if BIOS=$(whiptail --backtitle "ProxMenuX" --title "$(translate "BIOS TYPE")" --radiolist --cancel-button Exit-Script "Choose BIOS Type" 10 58 2 \
+ if BIOS=$(whiptail --backtitle "ProxMenuX" --title "$(translate "BIOS TYPE")" --radiolist --cancel-button "$(translate "Exit-Script")" "$(translate "Choose BIOS Type")" 10 58 2 \
"ovmf" "UEFI (OVMF)" ON \
- "seabios" "SeaBIOS (Legacy)" OFF \
+ "seabios" "$(translate "SeaBIOS (Legacy)")" OFF \
3>&1 1>&2 2>&3); then
if [ "$BIOS" = "seabios" ]; then
- echo -e "${DGN}Using BIOS Type: ${BGN}SeaBIOS${CL}"
+ echo -e "${DGN}$(translate "Using BIOS Type:") ${BGN}SeaBIOS${CL}"
BIOS_TYPE=" -bios seabios"
else
- echo -e "${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}"
+ echo -e "${DGN}$(translate "Using BIOS Type:") ${BGN}OVMF (UEFI)${CL}"
BIOS_TYPE=" -bios ovmf"
fi
else
@@ -329,28 +329,28 @@ function advanced_settings() {
fi
# Hostname Selection
- if VM_NAME=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Hostname")" 8 58 Synology-DSM --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if VM_NAME=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Hostname")" 8 58 Synology-DSM --title "$(translate "HOSTNAME")" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $VM_NAME ]; then
HN="Synology-DSM"
- echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}"
+ echo -e "${DGN}$(translate "Using Hostname:") ${BGN}$HN${CL}"
else
HN=$(echo ${VM_NAME,,} | tr -d ' ')
- echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}"
+ echo -e "${DGN}$(translate "Using Hostname:") ${BGN}$HN${CL}"
fi
else
exit_script
fi
# CPU Type Selection
- if CPU_TYPE1=$(whiptail --backtitle "ProxMenuX" --title "$(translate "CPU MODEL")" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
+ if CPU_TYPE1=$(whiptail --backtitle "ProxMenuX" --title "$(translate "CPU MODEL")" --radiolist "$(translate "Choose")" --cancel-button "$(translate "Exit-Script")" 10 58 2 \
"1" "Host" ON \
"0" "KVM64" OFF \
3>&1 1>&2 2>&3); then
if [ $CPU_TYPE1 = "1" ]; then
- echo -e "${DGN}Using CPU Model: ${BGN}Host${CL}"
+ echo -e "${DGN}$(translate "Using CPU Model:") ${BGN}Host${CL}"
CPU_TYPE=" -cpu host"
else
- echo -e "${DGN}Using CPU Model: ${BGN}KVM64${CL}"
+ echo -e "${DGN}$(translate "Using CPU Model:") ${BGN}KVM64${CL}"
CPU_TYPE=""
fi
else
@@ -358,77 +358,77 @@ function advanced_settings() {
fi
# Core Count Selection
- if CORE_COUNT=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate CPU Cores")" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if CORE_COUNT=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate CPU Cores")" 8 58 2 --title "$(translate "CORE COUNT")" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $CORE_COUNT ]; then
CORE_COUNT="2"
- echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}"
+ echo -e "${DGN}$(translate "Allocated Cores:") ${BGN}$CORE_COUNT${CL}"
else
- echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}"
+ echo -e "${DGN}$(translate "Allocated Cores:") ${BGN}$CORE_COUNT${CL}"
fi
else
exit_script
fi
# RAM Size Selection
- if RAM_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate RAM in MiB")" 8 58 4096 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if RAM_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate RAM in MiB")" 8 58 4096 --title "RAM" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $RAM_SIZE ]; then
RAM_SIZE="4096"
- echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}"
+ echo -e "${DGN}$(translate "Allocated RAM:") ${BGN}$RAM_SIZE${CL}"
else
- echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}"
+ echo -e "${DGN}$(translate "Allocated RAM:") ${BGN}$RAM_SIZE${CL}"
fi
else
exit_script
fi
# Bridge Selection
- if BRG=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Bridge")" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if BRG=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Bridge")" 8 58 vmbr0 --title "$(translate "BRIDGE")" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $BRG ]; then
BRG="vmbr0"
- echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}"
+ echo -e "${DGN}$(translate "Using Bridge:") ${BGN}$BRG${CL}"
else
- echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}"
+ echo -e "${DGN}$(translate "Using Bridge:") ${BGN}$BRG${CL}"
fi
else
exit_script
fi
# MAC Address Selection
- if MAC1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a MAC Address")" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if MAC1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a MAC Address")" 8 58 $GEN_MAC --title "$(translate "MAC ADDRESS")" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $MAC1 ]; then
MAC="$GEN_MAC"
- echo -e "${DGN}Using MAC Address: ${BGN}$MAC${CL}"
+ echo -e "${DGN}$(translate "Using MAC Address:") ${BGN}$MAC${CL}"
else
MAC="$MAC1"
- echo -e "${DGN}Using MAC Address: ${BGN}$MAC1${CL}"
+ echo -e "${DGN}$(translate "Using MAC Address:") ${BGN}$MAC1${CL}"
fi
else
exit_script
fi
# VLAN Selection
- if VLAN1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Vlan(leave blank for default)")" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if VLAN1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Vlan(leave blank for default)")" 8 58 --title "VLAN" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $VLAN1 ]; then
- VLAN1="Default"
+ VLAN1="$(translate "Default")"
VLAN=""
- echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}"
+ echo -e "${DGN}$(translate "Using Vlan:") ${BGN}$VLAN1${CL}"
else
VLAN=",tag=$VLAN1"
- echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}"
+ echo -e "${DGN}$(translate "Using Vlan:") ${BGN}$VLAN1${CL}"
fi
else
exit_script
fi
# MTU Selection
- if MTU1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Interface MTU Size (leave blank for default)")" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if MTU1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Interface MTU Size (leave blank for default)")" 8 58 --title "$(translate "MTU SIZE")" --cancel-button "$(translate "Exit-Script")" 3>&1 1>&2 2>&3); then
if [ -z $MTU1 ]; then
- MTU1="Default"
+ MTU1="$(translate "Default")"
MTU=""
- echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}"
+ echo -e "${DGN}$(translate "Using Interface MTU Size:") ${BGN}$MTU1${CL}"
else
MTU=",mtu=$MTU1"
- echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}"
+ echo -e "${DGN}$(translate "Using Interface MTU Size:") ${BGN}$MTU1${CL}"
fi
else
exit_script
@@ -437,15 +437,15 @@ function advanced_settings() {
# Confirmation
- if (whiptail --backtitle "ProxMenuX" --title "$(translate "ADVANCED SETTINGS COMPLETE")" --yesno "Ready to create a $NAME?" --no-button Do-Over 10 58); then
+ if (whiptail --backtitle "ProxMenuX" --title "$(translate "ADVANCED SETTINGS COMPLETE")" --yesno "$(printf "$(translate "Ready to create a %s?")" "$NAME")" --no-button "$(translate "Do-Over")" 10 58); then
echo -e
- echo -e "${CUS}Creating a $NAME using the above advanced settings${CL}"
+ echo -e "${CUS}$(printf "$(translate "Creating a %s using the above advanced settings")" "$NAME")${CL}"
sleep 1
select_disk_type || return 1
else
header_info
sleep 1
- echo -e "${CUS}Using Advanced Settings${CL}"
+ echo -e "${CUS}$(translate "Using Advanced Settings")${CL}"
advanced_settings
fi
}
@@ -482,13 +482,13 @@ function select_disk_type() {
while true; do
local choice
- choice=$(whiptail --backtitle "ProxMenuX" --title "STORAGE PLAN" --menu "$(_build_storage_plan_summary)" 18 78 5 \
+ choice=$(whiptail --backtitle "ProxMenuX" --title "$(translate "STORAGE PLAN")" --menu "$(_build_storage_plan_summary)" 18 78 5 \
"a" "$(translate "Add virtual disk")" \
"b" "$(translate "Add import disk")" \
"c" "$(translate "Add Controller or NVMe (PCI passthrough)")" \
"r" "$(translate "Reset current storage selection")" \
"d" "$(translate "──── [ Finish and continue ] ────")" \
- --ok-button "Select" --cancel-button "Cancel" 3>&1 1>&2 2>&3) || {
+ --ok-button "$(translate "Select")" --cancel-button "$(translate "Cancel")" 3>&1 1>&2 2>&3) || {
msg_warn "$(translate "Storage plan selection cancelled.")"
return 1
}
@@ -560,7 +560,7 @@ function select_virtual_disk() {
local VALID
VALID=$(pvesm status -content images | awk 'NR>1')
if [[ -z "$VALID" ]]; then
- msg_error "Unable to detect a valid storage location."
+ msg_error "$(translate "Unable to detect a valid storage location.")"
return 1
fi
@@ -577,7 +577,7 @@ function select_virtual_disk() {
local DISK_SIZE
stop_spinner
- DISK_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "System Disk Size (GB)")" 8 58 32 --title "VIRTUAL DISK" --cancel-button Cancel 3>&1 1>&2 2>&3) || return 0
+ DISK_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "System Disk Size (GB)")" 8 58 32 --title "$(translate "VIRTUAL DISK")" --cancel-button "$(translate "Cancel")" 3>&1 1>&2 2>&3) || return 0
[[ -z "$DISK_SIZE" ]] && DISK_SIZE="32"
VIRTUAL_DISKS+=("${STORAGE}:${DISK_SIZE}")
@@ -592,7 +592,7 @@ function select_controller_nvme() {
fi
elif declare -F _pci_is_iommu_active >/dev/null 2>&1; then
if ! _pci_is_iommu_active; then
- whiptail --title "Controller + NVMe" --msgbox \
+ whiptail --title "$(translate "Controller + NVMe")" --msgbox \
"$(translate "IOMMU is not active on this host.")\n\n$(translate "Controller/NVMe passthrough requires IOMMU enabled in BIOS/UEFI and kernel.")\n\n$(translate "Enable IOMMU, reboot the host, and try again.")" \
14 90
return 1
@@ -675,12 +675,12 @@ function select_controller_nvme() {
msg+="$(translate "Hidden for safety"):\n${blocked_report}"
fi
fi
- whiptail --title "Controller + NVMe" --msgbox "$msg" 18 84
+ whiptail --title "$(translate "Controller + NVMe")" --msgbox "$msg" 18 84
return 1
fi
local selected
- selected=$(whiptail --title "Controller + NVMe" --checklist \
+ selected=$(whiptail --title "$(translate "Controller + NVMe")" --checklist \
"$(translate "Select available Controllers/NVMe to add:")" 20 96 10 \
"${menu_items[@]}" 3>&1 1>&2 2>&3) || return 1
@@ -692,7 +692,7 @@ function select_controller_nvme() {
if [[ ${#CONTROLLER_NVME_PCIS[@]} -gt 0 ]] && declare -F _vm_storage_confirm_controller_passthrough_risk >/dev/null 2>&1; then
local vm_name_for_notice="${HN:-$NAME}"
- if ! _vm_storage_confirm_controller_passthrough_risk "${VMID:-}" "$vm_name_for_notice" "Controller + NVMe"; then
+ if ! _vm_storage_confirm_controller_passthrough_risk "${VMID:-}" "$vm_name_for_notice" "$(translate "Controller + NVMe")"; then
CONTROLLER_NVME_PCIS=()
return 1
fi
@@ -745,18 +745,18 @@ function prompt_controller_conflict_policy() {
function select_loader() {
# Ensure the images directory exists
if [ ! -d "$IMAGES_DIR" ]; then
- msg_info "Creating images directory"
+ msg_info "$(translate "Creating images directory")"
mkdir -p "$IMAGES_DIR"
chmod 755 "$IMAGES_DIR"
- msg_ok "Images directory created: $IMAGES_DIR"
+ msg_ok "$(printf "$(translate "Images directory created: %s")" "$IMAGES_DIR")"
fi
# Create the loader selection menu
- LOADER_OPTION=$(whiptail --backtitle "ProxMenuX" --title "SELECT LOADER" --menu "$(translate "Choose a loader for Synology DSM:")" 15 70 4 \
+ LOADER_OPTION=$(whiptail --backtitle "ProxMenuX" --title "$(translate "SELECT LOADER")" --menu "$(translate "Choose a loader for Synology DSM:")" 15 70 4 \
"1" "AuxXxilium Arc Loader" \
"2" "RedPill Loader (RROrg - RR)" \
"3" "Alpine RedPill Loader (PeterSuh-Q3 M-shell)" \
- "4" "Custom Loader Image (from $IMAGES_DIR)" \
+ "4" "$(printf "$(translate "Custom Loader Image (from %s)")" "$IMAGES_DIR")" \
3>&1 1>&2 2>&3)
if [ -z "$LOADER_OPTION" ]; then
@@ -768,28 +768,28 @@ function select_loader() {
LOADER_TYPE="arc"
LOADER_NAME="AuxXxilium Arc"
LOADER_URL="https://github.com/AuxXxilium/arc/"
- echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}"
+ echo -e "${DGN}${TAB}$(translate "Selected Loader:") ${BGN}$LOADER_NAME${CL}"
download_loader
;;
2)
LOADER_TYPE="redpill"
LOADER_NAME="RedPill RR"
LOADER_URL="https://github.com/RROrg/rr/"
- echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}"
+ echo -e "${DGN}${TAB}$(translate "Selected Loader:") ${BGN}$LOADER_NAME${CL}"
download_loader
;;
3)
LOADER_TYPE="tinycore"
LOADER_NAME="Alpine RedPill M-shell"
LOADER_URL="https://github.com/PeterSuh-Q3/tinycore-redpill/"
- echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}"
+ echo -e "${DGN}${TAB}$(translate "Selected Loader:") ${BGN}$LOADER_NAME${CL}"
download_loader
;;
4)
LOADER_TYPE="custom"
- LOADER_NAME="Custom Image"
+ LOADER_NAME="$(translate "Custom Image")"
LOADER_URL="https://xpenology.com/forum/"
- echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}"
+ echo -e "${DGN}${TAB}$(translate "Selected Loader:") ${BGN}$LOADER_NAME${CL}"
select_custom_image
;;
esac
@@ -800,7 +800,7 @@ function select_custom_image() {
IMAGES=$(find "$IMAGES_DIR" -type f -name "*.img" -o -name "*.iso" -o -name "*.qcow2" -o -name "*.vmdk" | sort)
if [ -z "$IMAGES" ]; then
- whiptail --title "$(translate "No Images Found")" --msgbox "No compatible images found in $IMAGES_DIR\n\nSupported formats: .img, .iso, .qcow2, .vmdk\n\nPlease add some images and try again." 15 70
+ whiptail --title "$(translate "No Images Found")" --msgbox "$(printf "$(translate "No compatible images found in %s\n\nSupported formats: .img, .iso, .qcow2, .vmdk\n\nPlease add some images and try again.")" "$IMAGES_DIR")" 15 70
select_loader
fi
@@ -814,14 +814,14 @@ function select_custom_image() {
done <<< "$IMAGES"
# Let the user select an image
- LOADER_FILE=$(whiptail --backtitle "ProxMenuX" --title "SELECT CUSTOM IMAGE" --menu "$(translate "Choose a custom image:")" 20 70 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
+ LOADER_FILE=$(whiptail --backtitle "ProxMenuX" --title "$(translate "SELECT CUSTOM IMAGE")" --menu "$(translate "Choose a custom image:")" 20 70 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
if [ -z "$LOADER_FILE" ]; then
- msg_error "No custom image selected"
+ msg_error "$(translate "No custom image selected")"
exit_script
fi
- echo -e "${DGN}${TAB}Using Custom Image: ${BGN}$(basename "$LOADER_FILE")${CL}"
+ echo -e "${DGN}${TAB}$(translate "Using Custom Image:") ${BGN}$(basename "$LOADER_FILE")${CL}"
FILE=$(basename "$LOADER_FILE")
}
# ==========================================================
@@ -840,17 +840,17 @@ function download_loader() {
FILE=""
LOADER_FILE=""
- echo -e "${DGN}${TAB}Retrieving the URL for the ${BGN}$LOADER_NAME loader${CL}"
+ echo -e "${DGN}${TAB}$(printf "$(translate "Retrieving the URL for the %s loader")" "${BGN}$LOADER_NAME")${CL}"
if [[ "$LOADER_TYPE" == "arc" || "$LOADER_TYPE" == "redpill" ]] && ! command -v unzip &> /dev/null; then
- msg_info "Installing unzip..."
+ msg_info "$(translate "Installing unzip...")"
apt-get update -qq && apt-get install -y unzip -qq >/dev/null 2>&1
if ! command -v unzip &> /dev/null; then
- msg_error "Failed to install unzip"
+ msg_error "$(translate "Failed to install unzip")"
sleep 2
return 1
fi
- msg_ok "Installed unzip successfully."
+ msg_ok "$(translate "Installed unzip successfully.")"
fi
asset_pattern='^[^/]+[.]img[.]zip$'
@@ -927,7 +927,7 @@ function download_loader() {
rm -rf "$stage"
FILE="$image_name"
LOADER_FILE="$IMAGES_DIR/$FILE"
- msg_ok "Downloaded ${CL}${BL}${FILE}${CL} to ${IMAGES_DIR}"
+ msg_ok "$(printf "$(translate "Downloaded %s to %s")" "${CL}${BL}${FILE}${CL}" "${IMAGES_DIR}")"
}
# =======================================================
@@ -960,7 +960,7 @@ function select_efi_storage() {
VALID=$(pvesm status -content images | awk 'NR>1')
if [ -z "$VALID" ]; then
- msg_error "Unable to detect a valid storage location for EFI disk." >&2
+ msg_error "$(translate "Unable to detect a valid storage location for EFI disk.")" >&2
return 1
elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
@@ -969,7 +969,7 @@ function select_efi_storage() {
else
[[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" > /dev/null 2>&1
while [ -z "${STORAGE:+x}" ]; do
- STORAGE=$(whiptail --backtitle "ProxMenuX" --title "EFI Disk Storage" --radiolist \
+ STORAGE=$(whiptail --backtitle "ProxMenuX" --title "$(translate "EFI Disk Storage")" --radiolist \
"$(translate "Choose the storage for the EFI disk (4MB):")"$'\n\n'"$(translate "Use Spacebar to select.")" \
16 $(($MSG_MAX_LENGTH + 23)) 6 \
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || {
@@ -1015,7 +1015,7 @@ function select_storage_volume() {
VALID=$(pvesm status -content images | awk 'NR>1')
if [ -z "$VALID" ]; then
- msg_error "Unable to detect a valid storage location." >&2
+ msg_error "$(translate "Unable to detect a valid storage location.")" >&2
return 1
elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
STORAGE=${STORAGE_MENU[0]}
@@ -1048,19 +1048,19 @@ function create_vm() {
if ! qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1${BIOS_TYPE}${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \
-name $HN -tags proxmenux,nas -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci \
-serial0 socket; then
- msg_error "Failed to create base VM. Check VM ID and host configuration."
+ msg_error "$(translate "Failed to create base VM. Check VM ID and host configuration.")"
return 1
fi
- msg_ok "Create a $NAME"
+ msg_ok "$(printf "$(translate "Create a %s")" "$NAME")"
# Check if UEFI (OVMF) is being used ===================
if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then
- msg_info "Configuring EFI disk"
+ msg_info "$(translate "Configuring EFI disk")"
if ! EFI_STORAGE=$(select_efi_storage $VMID); then
- msg_error "EFI storage selection failed or was cancelled."
+ msg_error "$(translate "EFI storage selection failed or was cancelled.")"
return 1
fi
EFI_DISK_NAME="vm-${VMID}-disk-efivars"
@@ -1084,9 +1084,9 @@ function create_vm() {
if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then
if qm set "$VMID" -$EFI_DISK_ID "$EFI_STORAGE:4,efitype=4m,format=raw,pre-enrolled-keys=0" >/dev/null 2>&1; then
- msg_ok "EFI disk created (raw) and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}"
+ msg_ok "$(printf "$(translate "EFI disk created (raw) and configured on %s")" "${CL}${BL}$EFI_STORAGE${GN}${CL}")"
else
- msg_error "Failed to configure EFI disk"
+ msg_error "$(translate "Failed to configure EFI disk")"
ERROR_FLAG=true
fi
else
@@ -1097,13 +1097,13 @@ function create_vm() {
if pvesm alloc "$EFI_STORAGE" "$VMID" "$EFI_DISK_NAME" 4M >/dev/null 2>&1; then
if qm set "$VMID" -$EFI_DISK_ID "$EFI_STORAGE:${EFI_DISK_NAME},pre-enrolled-keys=0" >/dev/null 2>&1; then
- msg_ok "EFI disk created and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}"
+ msg_ok "$(printf "$(translate "EFI disk created and configured on %s")" "${CL}${BL}$EFI_STORAGE${GN}${CL}")"
else
- msg_error "Failed to configure EFI disk"
+ msg_error "$(translate "Failed to configure EFI disk")"
ERROR_FLAG=true
fi
else
- msg_error "Failed to create EFI disk"
+ msg_error "$(translate "Failed to create EFI disk")"
ERROR_FLAG=true
fi
fi
@@ -1116,7 +1116,7 @@ function create_vm() {
# Select storage volume for loader =======================
if ! LOADER_STORAGE=$(select_storage_volume $VMID "loader disk"); then
- msg_error "Loader storage selection failed or was cancelled."
+ msg_error "$(translate "Loader storage selection failed or was cancelled.")"
return 1
fi
@@ -1126,7 +1126,7 @@ function create_vm() {
import_pid=$!
# Show a simple progress indicator
- echo -n "Importing loader disk: "
+ echo -n "$(translate "Importing loader disk:") "
while kill -0 $import_pid 2>/dev/null; do
echo -n "."
sleep 2.5
@@ -1139,10 +1139,10 @@ function create_vm() {
# If the disk was not imported correctly, show an error message but continue
if [ -z "$IMPORTED_DISK" ]; then
- msg_error "Loader import failed. No disk detected."
+ msg_error "$(translate "Loader import failed. No disk detected.")"
ERROR_FLAG=true
else
- msg_ok "Loader imported successfully to ${CL}${BL}$LOADER_STORAGE${GN}${CL}"
+ msg_ok "$(printf "$(translate "Loader imported successfully to %s")" "${CL}${BL}$LOADER_STORAGE${GN}${CL}")"
fi
@@ -1159,23 +1159,23 @@ function create_vm() {
if [[ -n "$IMPORTED_REF" && -n "$IMPORTED_ID" ]]; then
if qm set "$VMID" -ide0 "$IMPORTED_REF" >/dev/null 2>&1; then
- msg_ok "Configured loader disk as ide0"
+ msg_ok "$(translate "Configured loader disk as ide0")"
qm set "$VMID" -delete "$IMPORTED_ID" >/dev/null 2>&1
else
- msg_error "Failed to assign loader disk to ide0"
+ msg_error "$(translate "Failed to assign loader disk to ide0")"
ERROR_FLAG=true
fi
else
- msg_error "Loader import failed. No disk detected in config."
+ msg_error "$(translate "Loader import failed. No disk detected in config.")"
ERROR_FLAG=true
fi
else
DISK_NAME="vm-${VMID}-disk-0"
if qm set "$VMID" -ide0 "$LOADER_STORAGE:${DISK_NAME}" >/dev/null 2>&1; then
- msg_ok "Configured loader disk as ide0"
+ msg_ok "$(translate "Configured loader disk as ide0")"
else
- msg_error "Failed to assign loader disk"
+ msg_error "$(translate "Failed to assign loader disk")"
ERROR_FLAG=true
fi
fi
@@ -1185,7 +1185,7 @@ function create_vm() {
result=$(qm set "$VMID" -boot order=ide0 2>&1)
if [[ $? -eq 0 ]]; then
- msg_ok "Loader configured as boot device."
+ msg_ok "$(translate "Loader configured as boot device.")"
else
ERROR_FLAG=true
fi
@@ -1217,27 +1217,27 @@ if [[ ${#VIRTUAL_DISKS[@]} -gt 0 ]]; then
SATA_ID="sata${DISK_SLOT_INDEX}"
if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then
- msg_info "Creating virtual disk (format=raw) for $STORAGE_TYPE..."
+ msg_info "$(printf "$(translate "Creating virtual disk (format=raw) for %s...")" "$STORAGE_TYPE")"
if ! qm set "$VMID" -$SATA_ID "$STORAGE:$SIZE,format=raw" >/dev/null 2>&1; then
- msg_error "Failed to assign disk $DISK_NUM ($SATA_ID) on $STORAGE"
+ msg_error "$(printf "$(translate "Failed to assign disk %s (%s) on %s")" "$DISK_NUM" "$SATA_ID" "$STORAGE")"
ERROR_FLAG=true
continue
fi
else
- msg_info "Allocating virtual disk for $STORAGE_TYPE..."
+ msg_info "$(printf "$(translate "Allocating virtual disk for %s...")" "$STORAGE_TYPE")"
if ! pvesm alloc "$STORAGE" "$VMID" "$DISK_NAME" "$SIZE"G >/dev/null 2>&1; then
- msg_error "Failed to allocate virtual disk $DISK_NUM"
+ msg_error "$(printf "$(translate "Failed to allocate virtual disk %s")" "$DISK_NUM")"
ERROR_FLAG=true
continue
fi
if ! qm set "$VMID" -$SATA_ID "$STORAGE:${DISK_REF}$DISK_NAME" >/dev/null 2>&1; then
- msg_error "Failed to configure virtual disk as $SATA_ID"
+ msg_error "$(printf "$(translate "Failed to configure virtual disk as %s")" "$SATA_ID")"
ERROR_FLAG=true
continue
fi
fi
- msg_ok "Configured virtual disk as $SATA_ID, ${SIZE}GB on ${CL}${BL}$STORAGE${CL} ${GN}"
+ msg_ok "$(printf "$(translate "Configured virtual disk as %s, %sGB on %s")" "$SATA_ID" "${SIZE}" "${CL}${BL}$STORAGE${CL} ${GN}")"
DISK_INFO="${DISK_INFO}
Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE}
"
CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE} ($SATA_ID)\n"
DISK_SLOT_INDEX=$((DISK_SLOT_INDEX + 1))
@@ -1259,12 +1259,12 @@ if [[ ${#EFFECTIVE_IMPORT_DISKS[@]} -gt 0 ]]; then
DISK_ID="sata${DISK_SLOT_INDEX}"
if qm set "$VMID" -${DISK_ID} ${DISK} >/dev/null 2>&1; then
- msg_ok "Configured import disk ${CL}${BL}($DISK_MODEL $DISK_SIZE)${CL}${GN} as $DISK_ID"
+ msg_ok "$(printf "$(translate "Configured import disk %s as %s")" "${CL}${BL}($DISK_MODEL $DISK_SIZE)${CL}${GN}" "$DISK_ID")"
DISK_INFO="${DISK_INFO}
Import Disk ${DISK_NUM}: $DISK ($DISK_MODEL $DISK_SIZE)
"
CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Import Disk ${DISK_NUM}: $DISK ($DISK_MODEL $DISK_SIZE) (${DISK_ID})\n"
DISK_SLOT_INDEX=$((DISK_SLOT_INDEX + 1))
else
- msg_error "Failed to configure import disk $DISK as $DISK_ID"
+ msg_error "$(printf "$(translate "Failed to configure import disk %s as %s")" "$DISK" "$DISK_ID")"
ERROR_FLAG=true
fi
done
@@ -1343,14 +1343,14 @@ if [[ ${#CONTROLLER_NVME_PCIS[@]} -gt 0 ]]; then
for PCI_DEV in "${CONTROLLER_NVME_PCIS[@]}"; do
if declare -F _pci_function_assigned_to_vm >/dev/null 2>&1; then
if _pci_function_assigned_to_vm "$PCI_DEV" "$VMID"; then
- msg_warn "Controller/NVMe already present in VM config (${PCI_DEV})"
+ msg_warn "$(printf "$(translate "Controller/NVMe already present in VM config (%s)")" "${PCI_DEV}")"
continue
fi
else
local PCI_BDF
PCI_BDF="${PCI_DEV#0000:}"
if qm config "$VMID" 2>/dev/null | grep -qE "^hostpci[0-9]+:.*(0000:)?${PCI_BDF}([,[:space:]]|$)"; then
- msg_warn "Controller/NVMe already present in VM config (${PCI_DEV})"
+ msg_warn "$(printf "$(translate "Controller/NVMe already present in VM config (%s)")" "${PCI_DEV}")"
continue
fi
fi
@@ -1397,12 +1397,12 @@ if [[ ${#CONTROLLER_NVME_PCIS[@]} -gt 0 ]]; then
fi
if qm set "$VMID" --hostpci${HOSTPCI_INDEX} "${PCI_DEV},pcie=1" >/dev/null 2>&1; then
- msg_ok "Configured controller/NVMe as hostpci${HOSTPCI_INDEX}: ${PCI_DEV}"
+ msg_ok "$(printf "$(translate "Configured controller/NVMe as hostpci%s: %s")" "${HOSTPCI_INDEX}" "${PCI_DEV}")"
DISK_INFO="${DISK_INFO}
Controller/NVMe: ${PCI_DEV}
"
CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Controller/NVMe: ${PCI_DEV} (hostpci${HOSTPCI_INDEX})\n"
HOSTPCI_INDEX=$((HOSTPCI_INDEX + 1))
else
- msg_error "Failed to configure controller/NVMe: ${PCI_DEV}"
+ msg_error "$(printf "$(translate "Failed to configure controller/NVMe: %s")" "${PCI_DEV}")"
ERROR_FLAG=true
fi
done
@@ -1411,7 +1411,7 @@ if [[ ${#CONTROLLER_NVME_PCIS[@]} -gt 0 ]]; then
fi
if [[ ${#VIRTUAL_DISKS[@]} -eq 0 && ${#EFFECTIVE_IMPORT_DISKS[@]} -eq 0 && ${#CONTROLLER_NVME_PCIS[@]} -eq 0 ]]; then
- msg_error "No disks/controllers configured."
+ msg_error "$(translate "No disks/controllers configured.")"
exit_script
fi
@@ -1424,8 +1424,8 @@ HTML_DESC="