diff --git a/.github/scripts/tests/test_command_descriptions.py b/.github/scripts/tests/test_command_descriptions.py index f8d48418..f86afd0c 100644 --- a/.github/scripts/tests/test_command_descriptions.py +++ b/.github/scripts/tests/test_command_descriptions.py @@ -90,18 +90,31 @@ class CommandDescriptionsTests(unittest.TestCase): path = root / lang / "common.json" path.parent.mkdir() shutil.copyfile(ROOT / f"AppImage/messages/{lang}/common.json", path) - # Model steady state after automation fills this intentional new key. - # Runtime missing-key fallback is tested separately in test_storage_messages.cjs. + # This steady-state fixture assumes generation already finished. + # Seed only the keys the two message PRs introduce; keep the + # recovered command arrays and the repo catalogs exact. Runtime + # missing-key fallback is covered by the JSX seam tests and by + # test_storage_messages.cjs. if lang != "en": - messages = json.loads(path.read_text()) - messages["storage"].setdefault("savedSmartData", catalog("en")["storage"]["savedSmartData"]) - report = messages["storage"]["smartReport"] + temporary = json.loads(path.read_text()) + for section, key in ( + ("archives", "emptyMessage"), + ("destinations", "backupsKeptMessage"), + ("destinations", "localAttachHelpMessage"), + ): + temporary["backup"][section].setdefault( + key, catalog("en")["backup"][section][key]) + temporary["storage"].setdefault( + "savedSmartData", catalog("en")["storage"]["savedSmartData"]) + report = temporary["storage"]["smartReport"] source_report = catalog("en")["storage"]["smartReport"] - for key in ("passedAssessment", "noReallocatedSectorsReported", "passedMeaningTitle", "passedMeaning"): + for key in ("passedAssessment", "noReallocatedSectorsReported", + "passedMeaningTitle", "passedMeaning"): report.setdefault(key, source_report[key]) for key in ("passedTitle", "passedText"): - report["recommendations"].setdefault(key, source_report["recommendations"][key]) - path.write_text(json.dumps(messages)) + report["recommendations"].setdefault( + key, source_report["recommendations"][key]) + path.write_text(json.dumps(temporary, ensure_ascii=False)) before = {p: p.read_bytes() for p in root.glob("*/common.json")} argv = [str(SCRIPT), "--source", str(root / "en/common.json"), "--messages-dir", str(root), "--languages", languages, "--sleep", "0"] diff --git a/AppImage/components/host-backup.tsx b/AppImage/components/host-backup.tsx index 2e455cd5..1a2f5e6c 100644 --- a/AppImage/components/host-backup.tsx +++ b/AppImage/components/host-backup.tsx @@ -1300,7 +1300,7 @@ export function HostBackup() { ) : unifiedArchives.length === 0 ? (
- {t("backup.archives.emptyBefore")} {t("backup.manual.run")} {t("backup.archives.emptyAfter")} + {t("backup.archives.emptyMessage")}
) : (
@@ -4089,7 +4089,7 @@ function CreateJobDialog({ className="font-mono" />

- {t("backup.destinations.localAttachHelpBefore")} /dump {t("backup.destinations.localAttachHelpMiddle")} /var/lib/vz/dump. + {t("backup.destinations.localAttachHelpMessage")}

) : ( @@ -5709,7 +5709,7 @@ function DestinationsSection({ )} {backups > 0 && (
- {t("backup.destinations.backupsKeptBefore", { count: backups })} {t("backup.destinations.kept")}. + {t("backup.destinations.backupsKeptMessage")}
)}
diff --git a/AppImage/messages/README.md b/AppImage/messages/README.md index 68e100f7..dd726e3d 100644 --- a/AppImage/messages/README.md +++ b/AppImage/messages/README.md @@ -7,4 +7,22 @@ The ProxMenux Monitor dashboard uses a small client-side i18n layer. To improve a translation, edit the values in your locale's `common.json` — keep placeholders such as `{uptime}`, `{vmid}` or `{count}` unchanged, and don't translate brand or product names (`ProxMenux Monitor`, `Proxmox Backup Server`, `Secure Gateway`, `Tailscale`, etc.). Missing keys fall back to English at runtime, so a partial refresh is always safe to merge. +## Backup message composition regressions + +From the repository root, run: + +```sh +node tests/test_backup_archives_empty.cjs +node tests/test_backup_destination_messages.cjs +``` + +Prerequisites: Node.js and the `typescript` development dependency from +`AppImage/package.json` must be installed/resolvable (normally in +`AppImage/node_modules`). These tests compile actual JSX message branches and +pure i18n lookup functions without importing the component or calling a backend. +They cover whole English messages, missing-key locale fallback, loading/error +branches, nonempty lists, the removal notice at zero/one/multiple archives, +local-attach help, and unchanged shared labels. They do not replace browser or +real-host acceptance. Keep literal paths in the local-attach help unchanged. + To add a new locale, see [§11 → Adding a new locale](../../CONTRIBUTING.md#adding-a-new-locale) in the Contributing Guide. diff --git a/AppImage/messages/de/common.json b/AppImage/messages/de/common.json index 84272238..bda71206 100644 --- a/AppImage/messages/de/common.json +++ b/AppImage/messages/de/common.json @@ -3845,7 +3845,8 @@ "restoreTitle": "Von diesem Backup wiederherstellen", "startingExport": "Download wird vorbereitet...", "title": "Backups", - "viewContentsTitle": "Sicherungsinhalte anzeigen" + "viewContentsTitle": "Sicherungsinhalte anzeigen", + "emptyMessage": "Keine Sicherungsarchive gefunden." }, "backends": { "borgDescription": "Borg-Repository für deduplizierte Host-Backups.", @@ -3961,7 +3962,9 @@ "sshUserHelpBefore": "Benutzer auf dem Remote-Host,", "title": "Reiseziele", "unmountTitle": "Hängen Sie dieses Ziel aus", - "whereIsBorgRepo": "Wo ist das Borg-Repository?" + "whereIsBorgRepo": "Wo ist das Borg-Repository?", + "backupsKeptMessage": "Vorhandene Backup-Archive werden nicht gelöscht.", + "localAttachHelpMessage": "Mounten Sie zuerst USB-Laufwerke.Lassen Sie das Feld leer, um den PVE-Speicherpfad + /dump zu verwenden (Fallback: /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Aus PVE-Speicher erkannt", diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index b578a021..904cb69e 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -3835,6 +3835,7 @@ "downloadTitle": "Download this backup", "emptyAfter": "backups yet.", "emptyBefore": "No", + "emptyMessage": "No backup archives found.", "encrypted": "Encrypted", "encryptedTitle": "This backup is encrypted", "filesInBackup": "Files in backup", @@ -3910,6 +3911,7 @@ "addPbsRepository": "Add PBS repository", "appendAuthorizedKeyBefore": "Append the generated public key to", "backupsKeptBefore": "Existing backups are kept in", + "backupsKeptMessage": "Existing backup archives are not deleted.", "borgEncryptionInherited": "Borg encryption is controlled by the repository.", "builtIn": "Built-in", "builtInDefault": "Built-in default", @@ -3930,6 +3932,7 @@ "leavePassphraseBlank": "Leave blank to keep the saved passphrase.", "leavePasswordBlank": "Leave blank to keep the saved password.", "localAttachHelpBefore": "Use a mounted local path. For USB drives, mount them first in", + "localAttachHelpMessage": "Mount USB drives first. Leave blank to use the PVE storage path + /dump (fallback: /var/lib/vz/dump).", "localAttachHelpMiddle": "USB drives", "localManualPickHelp": "Pick a local path for one-shot backup output.", "localPathHelpAfter": "or another mounted path.", diff --git a/AppImage/messages/es/common.json b/AppImage/messages/es/common.json index bd81573b..3d66d6c2 100644 --- a/AppImage/messages/es/common.json +++ b/AppImage/messages/es/common.json @@ -3845,7 +3845,8 @@ "restoreTitle": "Restaurar desde esta copia de seguridad", "startingExport": "Preparando descarga...", "title": "Copias de seguridad", - "viewContentsTitle": "Ver contenidos de la copia de seguridad" + "viewContentsTitle": "Ver contenidos de la copia de seguridad", + "emptyMessage": "No se encontraron archivos de respaldo." }, "backends": { "borgDescription": "Repositorio Borg para copias de seguridad de host deduplicadas.", @@ -3961,7 +3962,9 @@ "sshUserHelpBefore": "Usuario en el host remoto,", "title": "Destinos", "unmountTitle": "Desmontar este destino", - "whereIsBorgRepo": "¿Dónde está el repositorio Borg?" + "whereIsBorgRepo": "¿Dónde está el repositorio Borg?", + "backupsKeptMessage": "los archivos de copia de seguridad existentes no se eliminan.", + "localAttachHelpMessage": "Monte primero las unidades USB.Déjelo en blanco para usar la ruta de almacenamiento PVE + /dump (alternativa: /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Detectado desde el almacenamiento PVE", diff --git a/AppImage/messages/fr/common.json b/AppImage/messages/fr/common.json index b3169f51..c27ad791 100644 --- a/AppImage/messages/fr/common.json +++ b/AppImage/messages/fr/common.json @@ -3845,7 +3845,8 @@ "restoreTitle": "Restaurer à partir de cette sauvegarde", "startingExport": "Préparation du téléchargement...", "title": "Sauvegardes", - "viewContentsTitle": "Afficher le contenu de la sauvegarde" + "viewContentsTitle": "Afficher le contenu de la sauvegarde", + "emptyMessage": "Aucune archive de sauvegarde trouvée." }, "backends": { "borgDescription": "Référentiel Borg pour les sauvegardes d'hôtes dédupliquées.", @@ -3961,7 +3962,9 @@ "sshUserHelpBefore": "Utilisateur sur l'hôte distant,", "title": "Destinations", "unmountTitle": "Démonter cette destination", - "whereIsBorgRepo": "Où se trouve le référentiel Borg ?" + "whereIsBorgRepo": "Où se trouve le référentiel Borg ?", + "backupsKeptMessage": "Les archives de sauvegarde existantes ne sont pas supprimées.", + "localAttachHelpMessage": "Montez d’abord les clés USB.Laissez vide pour utiliser le chemin de stockage PVE + /dump (repli : /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Détecté depuis le stockage PVE", diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index eaab50b4..c812e8ca 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -3845,7 +3845,8 @@ "restoreTitle": "Ripristina da questo backup", "startingExport": "Preparazione download...", "title": "Backup", - "viewContentsTitle": "Visualizza i contenuti del backup" + "viewContentsTitle": "Visualizza i contenuti del backup", + "emptyMessage": "nessun archivio di backup trovato." }, "backends": { "borgDescription": "Repository Borg per backup host deduplicati.", @@ -3961,7 +3962,9 @@ "sshUserHelpBefore": "Utente sull'host remoto,", "title": "Destinazioni", "unmountTitle": "Smonta questa destinazione", - "whereIsBorgRepo": "Dov'è il deposito Borg?" + "whereIsBorgRepo": "Dov'è il deposito Borg?", + "backupsKeptMessage": "gli archivi di backup esistenti non vengono eliminati.", + "localAttachHelpMessage": "monta prima le unità USB.Lascia vuoto per utilizzare il percorso di archiviazione PVE + /dump (fallback: /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Rilevato dall'archivio PVE", diff --git a/AppImage/messages/pt/common.json b/AppImage/messages/pt/common.json index 13f1e7de..c54ed25c 100644 --- a/AppImage/messages/pt/common.json +++ b/AppImage/messages/pt/common.json @@ -3845,7 +3845,8 @@ "restoreTitle": "Restaurar deste backup", "startingExport": "Preparando download...", "title": "Cópias de segurança", - "viewContentsTitle": "Ver o conteúdo do backup" + "viewContentsTitle": "Ver o conteúdo do backup", + "emptyMessage": "Nenhum arquivo de backup encontrado." }, "backends": { "borgDescription": "Repositório Borg para backups de host desduplicados.", @@ -3961,7 +3962,9 @@ "sshUserHelpBefore": "Usuário no host remoto,", "title": "Destinos", "unmountTitle": "Desmontar este destino", - "whereIsBorgRepo": "Onde está o repositório Borg?" + "whereIsBorgRepo": "Onde está o repositório Borg?", + "backupsKeptMessage": "os arquivos de backup existentes não são excluídos.", + "localAttachHelpMessage": "Monte as unidades USB primeiro.Deixe em branco para usar o caminho de armazenamento PVE + /dump (fallback: /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Detectado no armazenamento PVE", diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 64ed0903..b241c561 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -3844,7 +3844,8 @@ "restoreTitle": "Obnoviť z tejto zálohy", "startingExport": "Pripravujem stiahnutie...", "title": "Zálohy", - "viewContentsTitle": "Zobraziť obsah zálohy" + "viewContentsTitle": "Zobraziť obsah zálohy", + "emptyMessage": "Nenašli sa žiadne zálohy." }, "backends": { "borgDescription": "Borg repozitár pre deduplikované zálohy servera.", @@ -3960,7 +3961,9 @@ "sshUserHelpBefore": "Používateľ na vzdialenom serveri,", "title": "Ciele", "unmountTitle": "Odpojiť tento cieľ", - "whereIsBorgRepo": "Kde je Borg repozitár?" + "whereIsBorgRepo": "Kde je Borg repozitár?", + "backupsKeptMessage": "Existujúce zálohy sa neodstránia.", + "localAttachHelpMessage": "Najprv pripojte jednotky USB.Ak chcete použiť cestu k úložisku PVE + /dump, nechajte pole prázdne (záložný: /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Zistené z PVE úložiska", @@ -6095,5 +6098,736 @@ "cancel": "Zrušiť" } }, - "runtime": {"notifications":{"templates":{"state_change":{"title":"{hostname}: {category} sa zmenilo na {current}{entity_suffix}","body":"Stav kategórie {category} sa zmenil z {previous} na {current}.\n{reason}","label":"Zmena stavu kontroly"},"new_error":{"title":"{hostname}: Nový {severity} - {category}{entity_suffix}","body":"{reason}","label":"Nový problém so stavom"},"error_resolved":{"title":"{hostname}: Vyriešené - {category}{entity_suffix}","body":"Problém v kategórii {category} bol vyriešený.\n{reason}\n🚦 Predchádzajúca závažnosť: {original_severity}\n⏱️ Trvanie: {duration}","label":"Problém bol vyriešený"},"error_escalated":{"title":"{hostname}: Zvýšené na {severity} - {category}{entity_suffix}","body":"{reason}","label":"Problém sa zhoršil"},"health_degraded":{"title":"{title_or_default}","body":"{reason}","label":"Zhoršený stav kontroly"},"lxc_updates_available":{"title":"{hostname}: Aktualizácie balíkov sú dostupné pre {count} LXC","body":"📊 Čakajúce aktualizácie balíkov má {count} LXC (📦 spolu {total_packages}, 🔒 bezpečnostné {security_count}):\n\n{ct_list}","label":"Dostupné aktualizácie LXC (experimentálne)"},"lxc_update_applied":{"title":"{hostname}: LXC {ct_name} ({vmid}) aktualizácia {result}","body":"{details}","label":"Aktualizácia LXC použitá"},"app_update_available":{"title":"{hostname}: Pre {app_name} je na CT {vmid} dostupná aktualizácia","body":"Aplikácia {app_name} na CT {vmid} ({ct_name}) má novú verziu:\n {installed} → {latest}","label":"K dispozícii je aktualizácia aplikácie"},"docker_stack_update_available":{"title":"{hostname}: Na CT {vmid} sú dostupné aktualizácie Docker","body":"Kontajner {ct_name} (CT {vmid}) má {count} aktualizácií Docker:\n{details}","label":"Dostupné aktualizácie Docker"},"vm_start":{"title":"{hostname}: VM {vmname} ({vmid}) spustený","body":"Virtuálny stroj {vmname} (ID: {vmid}) je spustený.","label":"VM bola spustená"},"vm_start_warning":{"title":"{hostname}: VM {vmname} ({vmid}) spustený s upozorneniami","body":"Virtuálny stroj {vmname} (ID: {vmid}) sa úspešne spustil, ale obsahuje upozornenia.\nUpozornenia: {reason}","label":"VM bola spustená s upozorneniami"},"vm_stop":{"title":"{hostname}: VM {vmname} ({vmid}) zastavený","body":"Virtuálny stroj {vmname} (ID: {vmid}) bol zastavený.","label":"VM bola zastavená"},"vm_shutdown":{"title":"{hostname}: VM {vmname} ({vmid}) vypnutý","body":"Virtuálny stroj {vmname} (ID: {vmid}) bol korektne vypnutý.","label":"VM bola vypnutá"},"vm_fail":{"title":"{hostname}: VM {vmname} ({vmid}) ZLYHAL","body":"Virtuálny stroj {vmname} (ID: {vmid}) zlyhal alebo sa ho nepodarilo spustiť.\nDôvod: {reason}","label":"Spustenie VM zlyhalo"},"vm_restart":{"title":"{hostname}: VM {vmname} ({vmid}) reštartovaný","body":"Virtuálny stroj {vmname} (ID: {vmid}) bol reštartovaný.","label":"VM bola reštartovaná"},"ct_start":{"title":"{hostname}: CT {vmname} ({vmid}) spustený","body":"Kontajner {vmname} (ID: {vmid}) je spustený.","label":"Kontajner bol spustený"},"ct_start_warning":{"title":"{hostname}: CT {vmname} ({vmid}) spustený s upozorneniami","body":"Kontajner {vmname} (ID: {vmid}) sa úspešne spustil, ale obsahuje upozornenia.\nUpozornenia: {reason}","label":"Kontajner bol spustený s upozorneniami"},"ct_stop":{"title":"{hostname}: CT {vmname} ({vmid}) zastavený","body":"Kontajner {vmname} (ID: {vmid}) bol zastavený.","label":"Kontajner bol zastavený"},"ct_shutdown":{"title":"{hostname}: CT {vmname} ({vmid}) vypnutý","body":"Kontajner {vmname} (ID: {vmid}) bol korektne vypnutý.","label":"Kontajner bol vypnutý"},"ct_restart":{"title":"{hostname}: CT {vmname} ({vmid}) reštartovaný","body":"Kontajner {vmname} (ID: {vmid}) bol reštartovaný.","label":"Kontajner bol reštartovaný"},"ct_fail":{"title":"{hostname}: CT {vmname} ({vmid}) ZLYHAL","body":"Kontajner {vmname} (ID: {vmid}) zlyhal alebo sa ho nepodarilo spustiť.\nDôvod: {reason}","label":"Spustenie kontajnera zlyhalo"},"migration_start":{"title":"{hostname}: Migrácia spustená — {vmname} ({vmid})","body":"Živá migrácia VM alebo LXC {vmname} (ID: {vmid}) na uzol {target_node} sa začala.","label":"Migrácia sa začala"},"migration_complete":{"title":"{hostname}: Migrácia dokončená — {vmname} ({vmid})","body":"{vmname} (ID: {vmid}) bol úspešne presunutý na uzol {target_node}.","label":"Migrácia bola dokončená"},"migration_warning":{"title":"{hostname}: Migrácia dokončená s upozorneniami — {vmname} ({vmid})","body":"VM alebo LXC {vmname} (ID: {vmid}) bol presunutý na uzol {target_node}, ale vyskytli sa upozornenia.\nUpozornenia: {reason}","label":"Migrácia skončila s upozorneniami"},"migration_fail":{"title":"{hostname}: Migrácia ZLYHALA — {vmname} ({vmid})","body":"Migrácia VM alebo LXC {vmname} (ID: {vmid}) na uzol {target_node} zlyhala.\nDôvod: {reason}","label":"Migrácia zlyhala"},"replication_fail":{"title":"{hostname}: Replikácia ZLYHALA — {vmname} ({vmid})","body":"Replikácia VM alebo LXC {vmname} (ID: {vmid}) zlyhala.\nDôvod: {reason}","label":"Replikácia zlyhala"},"replication_complete":{"title":"{hostname}: Replikácia dokončená — {vmname} ({vmid})","body":"Replikácia VM alebo LXC {vmname} (ID: {vmid}) bola úspešne dokončená.","label":"Replikácia bola dokončená"},"backup_start":{"title":"{hostname}: Záloha na úložisko {storage} spustená","body":"Úloha zálohovania na úložisko {storage} sa začala.\n{reason}","label":"Zálohovanie sa začalo"},"backup_complete":{"title":"{hostname} → {storage}: Záloha dokončená — {vmname} ({vmid})","body":"Záloha {vmname} (ID: {vmid}) na úložisku {storage} bola úspešne dokončená.\nVeľkosť: {size}","label":"Záloha bola dokončená"},"backup_warning":{"title":"{hostname} → {storage}: Záloha dokončená s upozorneniami — {vmname} ({vmid})","body":"Záloha {vmname} (ID: {vmid}) na úložisku {storage} bola dokončená, ale vyskytli sa upozornenia.\nUpozornenia: {reason}","label":"Záloha skončila s upozorneniami"},"backup_fail":{"title":"{hostname} → {storage}: Záloha ZLYHALA — {vmname} ({vmid})","body":"Záloha {vmname} (ID: {vmid}) na úložisku {storage} zlyhala.\nDôvod: {reason}","label":"Zálohovanie zlyhalo"},"host_backup_start":{"title":"{hostname}: Záloha servera spustená → {backend_label}","body":"Úloha: {job_id}\nBackend: {backend_label}\nCieľ: {destination}\nProfil: {profile_mode}","label":"Zálohovanie servera sa začalo"},"host_backup_complete":{"title":"{hostname}: Záloha servera dokončená → {backend_label}","body":"Úloha: {job_id}\nBackend: {backend_label}\nCieľ: {destination}\nVeľkosť dát: {data_size}\nVeľkosť archívu: {archive_size}\nTrvanie: {duration}","label":"Záloha servera bola dokončená"},"host_backup_fail":{"title":"{hostname}: Záloha servera ZLYHALA → {backend_label}","body":"Úloha: {job_id}\nBackend: {backend_label}\nCieľ: {destination}\nTrvanie pred zlyhaním: {duration}\nDôvod: {reason}\nZáznam: {log_file}","label":"Zálohovanie servera zlyhalo"},"snapshot_complete":{"title":"{hostname}: Snímka vytvorená — {vmname} ({vmid})","body":"Snímka „{snapshot_name}“ bola vytvorená pre {vmname} (ID: {vmid}).","label":"Snapshot bol vytvorený"},"snapshot_fail":{"title":"{hostname}: Vytvorenie snímky ZLYHALO — {vmname} ({vmid})","body":"Vytvorenie snímky pre {vmname} (ID: {vmid}) zlyhalo.\nDôvod: {reason}","label":"Vytvorenie snapshotu zlyhalo"},"cpu_high":{"title":"{hostname}: Vysoké využitie CPU — {value}%","body":"Využitie CPU dosiahlo {value}% na {cores} jadrách.\n{details}","label":"Vysoké využitie CPU"},"ram_high":{"title":"{hostname}: Vysoké využitie pamäte — {value}%","body":"Využitie pamäte: {used} / {total} ({value}%).\n{details}","label":"Vysoké využitie pamäte"},"temp_high":{"title":"{hostname}: Vysoká teplota CPU — {value}°C","body":"Teplota CPU dosiahla {value}°C (prahová hodnota: {threshold}°C).\n{details}","label":"Vysoká teplota"},"disk_space_low":{"title":"{hostname}: Málo miesta na disku {mount}","body":"Súborový systém {mount}: {used}% využité ({available} voľných).\nUvoľnite miesto na disku, aby ste predišli výpadku služieb.","label":"Málo miesta na disku"},"disk_io_error":{"title":"{hostname}: Zistené zlyhanie disku — {device}","body":"Na zariadení bola zistená chyba I/O alebo zlyhanie disku {device}.\n{reason}","label":"Chyba disku alebo I/O"},"storage_unavailable":{"title":"{hostname}: Úložisko nedostupné — {storage_name}","body":"Úložisko PVE „{storage_name}“ (typ: {storage_type}) nie je dostupné.\nDôvod: {reason}","label":"Úložisko nie je dostupné"},"smart_test_complete":{"title":"{hostname}: Test SMART dokončený — {device}","body":"Test SMART typu {test_type} na /dev/{device} bol dokončený.\nVýsledok: {result}\nTrvanie: {duration}","label":"SMART test bol dokončený"},"smart_test_failed":{"title":"{hostname}: Test SMART ZLYHAL — {device}","body":"Test SMART typu {test_type} na /dev/{device} zlyhal.\nVýsledok: {result}\nDôvod: {reason}","label":"SMART test zlyhal"},"gpu_mode_switch":{"title":"{hostname}: Režim GPU zmenený na {new_mode}","body":"Režim priameho priradenia GPU bol zmenený.\nGPU: {gpu_name} ({gpu_pci})\nPredchádzajúci režim: {old_mode}\nNový režim: {new_mode}\n{details}","label":"Režim GPU bol zmenený"},"gpu_passthrough_blocked":{"title":"{hostname}: {guest_type} {guest_id} zablokovaný pri spustení","body":"Ochrana priameho priradenia PCIe zabránila spusteniu {guest_type} {guest_id} ({guest_name}).\nDôvod: {reason}\n{details}","label":"GPU passthrough bol zablokovaný"},"pci_passthrough_conflict":{"title":"{hostname}: Zistený konflikt zariadenia PCIe — {device_pci}","body":"Zariadenie PCIe je priradené viacerým VM alebo LXC.\nZariadenie: {device_pci}\nKonfliktné VM a LXC: {guest_list}\nVyžaduje sa zásah: zastavte jednu z VM alebo LXC alebo zmeňte priradenie zariadenia.","label":"Konflikt PCIe zariadenia"},"load_high":{"title":"{hostname}: Vysoká záťaž systému — {value}","body":"Priemerná záťaž systému je {value} na {cores} jadrách.\n{details}","label":"Vysoká záťaž systému"},"network_down":{"title":"{hostname}: Sieťové pripojenie prerušené{entity_suffix}","body":"Uzol stratil sieťové pripojenie.\nDôvod: {reason}","label":"Sieťové pripojenie bolo prerušené"},"network_latency":{"title":"{hostname}: Vysoká latencia siete — {value}ms","body":"Latencia k bráne: {value}ms (prahová hodnota: {threshold}ms).\nMôže to znamenať preťaženie siete alebo problém s hardvérom.","label":"Vysoká odozva siete"},"auth_fail":{"title":"{hostname}: Zlyhanie overenia","body":"Bol zistený neúspešný pokus o prihlásenie.\nZdrojová IP: {source_ip}\nPoužívateľ: {username}\nSlužba: {service}","label":"Neúspešné prihlásenie"},"ip_block":{"title":"{hostname}: IP zablokovaná cez Fail2Ban","body":"IP adresa {source_ip} bola zablokovaná.\nJail: {jail}\nNeúspešné pokusy: {failures}","label":"Fail2Ban zablokoval IP adresu"},"firewall_issue":{"title":"{hostname}: Zistený problém firewallu{entity_suffix}","body":"Bol zistený problém v konfigurácii firewallu.\nDôvod: {reason}","label":"Zistený problém s firewallom"},"user_permission_change":{"title":"{hostname}: Zmenené oprávnenie používateľa","body":"Používateľ: {username}\nZmena: {change_details}","label":"Zmena oprávnení používateľa"},"split_brain":{"title":"{hostname}: Zistený SPLIT-BRAIN","body":"V klastri bol zistený stav split-brain. Kvórum môže byť stratené.\nStav kvóra: {quorum}","label":"Zistený split-brain"},"node_disconnect":{"title":"{hostname}: Uzol {node_name} odpojený","body":"Uzol {node_name} sa odpojil od klastra.","label":"Server sa odpojil"},"node_reconnect":{"title":"{hostname}: Uzol {node_name} znovu pripojený","body":"Uzol {node_name} sa úspešne znovu pripojil ku klastru.","label":"Server sa znova pripojil"},"system_startup":{"title":"{hostname}: {reason}","body":"{summary}","label":"Súhrn po spustení systému"},"system_shutdown":{"title":"{hostname}: Systém sa vypína","body":"Uzol sa vypína.\n{reason}","label":"Systém sa vypína"},"system_reboot":{"title":"{hostname}: Systém sa reštartuje","body":"Uzol sa reštartuje.\n{reason}","label":"Systém sa reštartuje"},"system_restore_completed":{"title":"{hostname}: Obnova servera dokončená","body":"Úlohy po obnove boli dokončené na pozadí.\n\nPoužité VM a LXC: {guests}\nZástupné priečinky bind mountov: {stubs}\nOdstránené zastarané priečinky uzlov: {stale_nodes}\nPreinštalované súčasti: {components}\nTrvanie: {duration}\n{warnings_block}\nUzol je teraz úplne pripravený na použitie.","label":"Obnova servera bola dokončená"},"system_problem":{"title":"{hostname}: Zistený systémový problém{entity_suffix}","body":"Bol zistený problém na úrovni systému.\nDôvod: {reason}","label":"Zistený problém so systémom"},"service_fail":{"title":"{hostname}: Služba zlyhala — {service_name}","body":"Systémová služba \"{service_name}\" zlyhala.\nDôvod: {reason}","label":"Služba zlyhala"},"oom_kill":{"title":"{hostname}: Ukončenie pri nedostatku pamäte — {process}","body":"Proces „{process}“ bol ukončený správcom pri nedostatku pamäte.\n{reason}","label":"Proces bol ukončený pre nedostatok pamäte"},"service_fail_batch":{"title":"{hostname}: {service_count} zlyhaných služieb{entity_suffix}","body":"{reason}","label":"Zlyhanie viacerých služieb"},"cron_output":{"title":"{hostname}: {pve_title}","body":"{reason}","label":"Výstup úlohy cron"},"system_mail":{"title":"{hostname}: {pve_title}","body":"{reason}","label":"Správy Smartd a systému"},"apt_listchanges":{"title":"{hostname}: {pve_title}","body":"Informácie o balíkoch od ich tvorcov preposlané systémom Proxmox VE cez apt-listchanges. Nasledujúci text pochádza od správcu balíka a nie je odporúčaním ProxMenux.\n\n{reason}","label":"Oznámenia o balíkoch apt-listchanges"},"webhook_test":{"title":"{hostname}: Prijatý test webhooku","body":"Test pripojenia webhooku PVE bol úspešný.\n{reason}","label":"Test webhooku"},"update_available":{"title":"{hostname}: Dostupné aktualizácie","body":"Aktualizácie spolu: {total_count}\nBezpečnostné: {security_count}\nProxmox: {pve_count}\nJadro: {kernel_count}\nDôležité balíky:\n{important_list}","label":"Dostupné aktualizácie (starší typ)"},"unknown_persistent":{"title":"{hostname}: Kontrola nedostupná - {category}{entity_suffix}","body":"Kontrola stavu pre kategóriu {category} je nedostupná aspoň 3 cykly.\n{reason}","label":"Kontrola nie je dostupná"},"health_persistent":{"title":"{hostname}: {count} aktívnych problémov so stavom{entity_suffix}","body":"Nasledujúce problémy so stavom systému zostávajú nevyriešené:\n{issue_list}\n\nTento súhrn sa odosiela raz za 24 hodín, kým problémy pretrvávajú.","label":"Aktívne problémy so stavom (denne)"},"health_issue_new":{"title":"{hostname}: Nový problém so stavom systému — {category}{entity_suffix}","body":"Nový {severity} problém zistený v kategórii: {category}\nPodrobnosti: {reason}","label":"Nový problém so stavom"},"health_issue_resolved":{"title":"{hostname}: Vyriešené - {category}{entity_suffix}","body":"Problém v kategórii {category} bol vyriešený.\n{reason}\nTrvanie: {duration}","label":"Problém bol vyriešený"},"update_summary":{"title":"{hostname}: Dostupné aktualizácie","body":"Aktualizácie spolu: {total_count}\nBezpečnostné aktualizácie: {security_count}\nAktualizácie súvisiace s Proxmoxom: {pve_count}\nAktualizácie jadra: {kernel_count}\nDôležité balíky:\n{important_list}","label":"Dostupné aktualizácie"},"pve_update":{"title":"{hostname}: Dostupný Proxmox VE {new_version}","body":"Je dostupná nová verzia Proxmox VE.\nAktuálna: {current_version}\nNová: {new_version}\n{details}","label":"Dostupná aktualizácia Proxmox VE"},"update_complete":{"title":"{hostname}: Aktualizácia systému dokončená","body":"Systémové balíky boli úspešne aktualizované.\n{details}","label":"Aktualizácia bola dokončená"},"ai_model_migrated":{"title":"{hostname}: Model AI aktualizovaný","body":"Model AI pre notifikácie bol automaticky aktualizovaný.\nPoskytovateľ: {provider}\nPredchádzajúci model: {old_model}\nNový model: {new_model}\n\n{message}","label":"AI model bol automaticky aktualizovaný"},"proxmenux_update":{"title":"{hostname}: Dostupný ProxMenux {new_version}","body":"Je dostupná nová verzia ProxMenux.\nAktuálna: {current_version}\nNová: {new_version}","label":"Dostupná aktualizácia ProxMenux"},"mount_stale":{"title":"{hostname}: nereagujúce vzdialené pripojenie {mount_target}","body":"Vzdialené pripojenie {mount_target} ({fstype}) zo zdroja {mount_source} nereaguje{lxc_scope}.\nKontrola stat vypršala alebo vrátila chybu: {error}\n\nAplikácie zapisujúce do tejto cesty môžu nepozorovane zapisovať na podkladový súborový systém a zaplniť disk. Pripojenie obnovte alebo čo najskôr opravte konektivitu.","label":"Vzdialené pripojenie neodpovedá"},"mount_readonly":{"title":"{hostname}: vzdialené pripojenie {mount_target} je iba na čítanie","body":"Vzdialené pripojenie {mount_target} ({fstype}) zo zdroja {mount_source} je pripojené iba na čítanie{lxc_scope}. Zápisy zlyhajú. Ak to nebolo zámerné, pripojte ho znova s voľbou rw.","label":"Vzdialené pripojenie je iba na čítanie"},"lxc_disk_low":{"title":"{hostname}: CT {vmid} koreňový systém na {usage_percent}%","body":"Koreňový súborový systém CT {vmid} ({name}) je zaplnený na {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nPlný rootfs LXC bráni korektnému spusteniu kontajnera. Zväčšite rootfs (pct resize {vmid} rootfs +1G) alebo uvoľnite miesto v kontajneri.","label":"Rootfs LXC je takmer plný"},"vm_disk_low":{"title":"{hostname}: VM {vmid} súborové systémy na {usage_percent}%","body":"Súborové systémy VM {vmid} ({name}) sú zaplnené na {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nÚdaje poskytol QEMU Guest Agent. Zahŕňajú každý trvalý súborový systém pripojený z blokového zariadenia vrátane virtuálnych diskov a diskov priamo priradených cez PCI. Uvoľnite miesto vo VM alebo zväčšite dotknuté úložisko skôr, než začnú zlyhávať zápisy.","label":"Súborové systémy VM sú takmer plné"},"lxc_mount_low":{"title":"{hostname}: Pripojenie {mount} v CT {vmid} je zaplnené na {usage_percent}%","body":"Pripojenie {mount} v CT {vmid} ({name}) je využité na {usage_percent}%.\nTyp súborového systému: {fstype}\n\nPlné pripojenie v kontajneri často nepozorovane zablokuje aplikáciu — zápisy zlyhajú alebo skončia na rootfs a vyvolajú ďalšie upozornenie. Uvoľnite miesto alebo pripojenie zväčšite.","label":"Pripojenie LXC je takmer plné"},"pve_storage_full":{"title":"{hostname}: Úložisko PVE {storage_name} je zaplnené na {usage_percent}%","body":"Úložisko Proxmox „{storage_name}“ (typ: {storage_type}) je využité na {usage_percent}%.\n\nPo zaplnení nebude možné vytvárať nové VM/CT a existujúcim VM a LXC môžu zlyhať zápisy. Presuňte alebo odstráňte nepoužívané zväzky, prípadne zväčšite podkladový pool/LV/RBD obraz.","label":"Úložisko PVE je takmer plné"},"zfs_pool_full":{"title":"{hostname}: Fond ZFS {pool_name} je zaplnený na {usage_percent}%","body":"Fond ZFS „{pool_name}“ je zaplnený na {usage_percent}%.\n\nVýkon a spoľahlivosť zápisu ZFS sa nad približne 80 % výrazne zhoršujú, pretože CoW potrebuje voľné miesto pre nové bloky. Uvoľnite snímky, odstráňte staré datasety alebo pridajte do fondu ďalšie vdevy.","label":"ZFS pool je takmer plný"},"post_install_update":{"title":"{hostname}: {count} ProxMenux dostupných aktualizácií optimalizácií","body":"Na tomto serveri je dostupných {count} aktualizácií optimalizácií ProxMenux.\n\n🛠️ Dostupné verzie:\n{tool_list}\n\n💡 Použiť cez:\n • ProxMenux Monitor → Nastavenia → Optimalizácie ProxMenux\n • Alebo spustite ponuku po inštalácii (možnosť 2) → „Použiť dostupné aktualizácie“","label":"Dostupné aktualizácie optimalizácií ProxMenux"},"secure_gateway_update_available":{"title":"{hostname}: {app_name} dostupná aktualizácia{update_title_suffix}","body":"Aplikácia {app_name} (spravovaná cez ProxMenux) má vo svojom kontajneri 📦 {package_count} čakajúcich aktualizácií balíkov.\n{version_line}\n\n💡 Otvorte ProxMenux Monitor > Nastavenia > Secure Gateway a kliknite na „Aktualizovať“.\n\n🗂️ Balíky:\n{package_list}","label":"K dispozícii je aktualizácia Secure Gateway"},"nvidia_driver_update_available":{"title":"{hostname}: NVIDIA dostupná aktualizácia ovládača — v{latest_version}","body":"Pre nainštalovaný rad ovládača NVIDIA je dostupná novšia údržbová verzia.\n🔹 Aktuálne nainštalovaná: v{current_version}\n🟢 Najnovšia dostupná: v{latest_version}\n\n{upgrade_reason}\n\n💡 Preinštalovanie:\n • V ponuke ProxMenux po inštalácii: {menu_label}\n\nPreinštalovanie zostaví modul DKMS pre aktuálne jadro a načítanie nového ovládača vyžaduje reštart.","label":"Dostupná aktualizácia ovládača NVIDIA"},"coral_driver_update_available":{"title":"{hostname}: Coral TPU dostupná aktualizácia ovládača — {latest_version}","body":"Je dostupná novšia verzia {variant_label}.\n🔹 Aktuálne nainštalovaná: {current_version}\n🟢 Najnovšia dostupná: {latest_version}\n\n{upgrade_reason}\n\n💡 Preinštalovanie:\n • V ponuke ProxMenux po inštalácii: {menu_label}\n\n{reboot_note}","label":"Dostupná aktualizácia ovládača Coral TPU"},"burst_auth_fail":{"title":"{hostname}: Ďalšie zlyhania overenia za {window}: +{count}","body":"Za {window} bolo zistených ďalších {count} zlyhaní overenia (spolu {total_count}).\nZdroje: {entity_list}","label":"Veľa neúspešných prihlásení"},"burst_ip_block":{"title":"{hostname}: Fail2Ban zablokoval za {window} ďalšie IP: +{count}","body":"Fail2Ban zablokoval za {window} ďalších {count} IP (spolu {total_count}).\nIP adresy: {entity_list}","label":"Veľa zablokovaných IP adries"},"burst_disk_io":{"title":"{hostname}: +{count} ďalších chýb I/O disku na {entity_list}","body":"Za {window} bolo zistených ďalších {count} chýb I/O (spolu {total_count}).\nZariadenia: {entity_list}","label":"Viacero diskových I/O chýb"},"burst_cluster":{"title":"{hostname}: Zistené kolísanie stavu klastra (+{count} ďalších zmien)","body":"Stav klastra sa za {window} zmenil ešte {count}-krát (spolu {total_count}).\nUzly: {entity_list}","label":"Nestabilné spojenie clustra"},"burst_service_fail":{"title":"{hostname}: Za {window} zlyhali ďalšie služby: +{count}","body":"Za {window} bolo zistených ďalších {count} zlyhaní služieb (spolu {total_count}).\nZvyčajne to znamená reštart uzla alebo služby PVE.\n\nĎalšie zlyhania:\n{details}","label":"Zlyhanie viacerých služieb"},"burst_system":{"title":"{hostname}: Ďalšie systémové problémy za {window}: +{count}","body":"Za {window} bolo zistených ďalších {count} systémových problémov (spolu {total_count}).\n\nĎalšie problémy:\n{details}","label":"Viacero systémových problémov"},"burst_generic":{"title":"{hostname}: Ďalšie udalosti {event_type} za {window}: +{count}","body":"Za {window} bolo zaznamenaných ďalších {count} udalostí typu {event_type} (spolu {total_count}).\n\nĎalšie udalosti:\n{details}","label":"Viacero súvisiacich udalostí"},"kernel_warning":{"title":"{hostname}: Zaznamenaná diagnostická udalosť jadra","body":"Jadro zaznamenalo diagnostickú udalosť: {kernel_details}","label":"Upozornenia jadra a diagnostické záznamy"}},"lxcUpdate":{"status":{"success":"úspešne dokončená","failure":"zlyhala","partial":"dokončená čiastočne","deferred":"odložená","skipped":"preskočená"},"source":{"scheduled":"Plánovane","manual":"Manuálne"},"sourceLabel":"Zdroj","targets":"Ciele","osPending":"Čakajúce aktualizácie OS: {before} → {after}","osUnverified":"Balíky OS: príkaz na aktualizáciu bol spustený, výsledok sa nepodarilo overiť","applications":"Aplikácie: {items}","applicationsUnverified":"Aplikácie: aktualizátor bol spustený, nezistila sa však zmena sledovanej verzie","dockerEngineChange":"Docker Engine: {before} → {after}","dockerEngineVerified":"Docker Engine: overený vo verzii {version}","dockerEngineUnverified":"Docker Engine: príkaz na aktualizáciu bol spustený, verziu sa nepodarilo overiť","dockerImagesPending":"Čakajúce aktualizácie Docker obrazov: {before} → {after}","dockerImagesChanged":"Zmenené Docker obrazy: {images}","deferredTargets":"Odložené ciele: {targets}","reason":"Dôvod: {reason}","restartRequired":"Vyžaduje sa reštart: {value}","yes":"áno","no":"nie","verificationPending":"Overenie bude pokračovať po spustení kontajnera","verificationWarning":"Upozornenie pri overení: {error}","duration":"Trvanie: {duration}"},"fallback":{"unknownTitle":"{hostname}: {event_type}","healthCheckDegraded":"Zhoršený stav kontroly","none":"žiadne"},"fields":{"vmid":"VM/CT","name":"Názov","device":"Zariadenie","sourceIp":"Zdrojová IP","node":"Uzol","category":"Kategória","service":"Služba","jail":"Jail","user":"Používateľ","count":"Počet","window":"Obdobie","affected":"Ovplyvnené"},"vzdump":{"size":"Veľkosť: {value}","duration":"Trvanie: {value}","file":"Súbor","backups":"Počet záloh: {count}","failed":"Zlyhalo: {count}","total":"Spolu: {value}","time":"Čas: {value}"},"startup":{"issuesTitle":"{hostname}: Spustenie systému – zistených problémov: {count}","completeTitle":"{hostname}: Spustenie systému dokončené","operational":"Všetky systémy sú funkčné.","vmCountOne":"{count} VM","vmCountMany":"Počet VM: {count}","ctCountOne":"{count} CT","ctCountMany":"Počet CT: {count}","started":"✅ Spustené: {counts}","vmFailed":"❌ VM {name} zlyhal – {reason}","ctFailed":"❌ CT {name} zlyhal – {reason}","unknownError":"neznáma chyba","storageUnavailable":"⚠️ Nedostupné úložiská: {count} ({names})","servicesFailed":"⚠️ Zlyhané služby: {count} ({names})","duration":"⏱️ Spustenie dokončené za {minutes} min"},"appUpdates":{"singleTitle":"{hostname}: Pre {app_name} je na CT {vmid} dostupná aktualizácia","singleBody":"Aplikácia {app_name} na CT {vmid} ({ct_name}) má novú verziu:\n {installed} → {latest}","emptyTitle":"{hostname}: Dostupné aktualizácie aplikácií","emptyBody":"Sú dostupné aktualizácie aplikácií.","batchTitle":"{hostname}: Dostupné aktualizácie aplikácií: {count}","batchLeadOneContainer":"Aplikácie s dostupnou novšou verziou: {count} v {container_count} kontajneri LXC:","batchLeadManyContainers":"Aplikácie s dostupnou novšou verziou: {count} v {container_count} kontajneroch LXC:","additional":"… ďalšie aplikácie: {count}","app":"aplikácia","unknown":"neznáme"},"healthDegraded":{"categories":{"cpu":"Využitie CPU a teplota","memory":"Pamäť a swap","storage":"Pripojené úložiská a miesto","disks":"I/O disku a chyby","network":"Sieťové rozhrania","vms":"VM a kontajnery","services":"Služby PVE","logs":"Systémové záznamy","updates":"Systémové aktualizácie","security":"Bezpečnosť"},"severity":{"critical":"Kritický","warning":"Upozornenie"},"singleTitle":"{hostname}: {severity} stav – {category}","multipleTitle":"{hostname}: Zhoršené kontroly stavu: {count}","multipleLine":"• {severity}: {category}\n {reason}","reasons":{"cpuSustained":"CPU je nad {threshold} % už {duration} s."}},"digest":{"title":"{hostname}: Súhrn za 24 hodín ({timestamp})","readFailed":"načítanie súhrnu zlyhalo: {error}","empty":"Pre toto obdobie súhrnu nie sú uložené žiadne udalosti INFO.","lead":"{count} udalostí INFO zoskupených podľa kategórie:\n","more":" • … a ďalšie: {count}","footer":"(Kritické udalosti a upozornenia boli doručené ihneď, nie v tomto súhrne.)","quietTitle":"{hostname}: Udalosti uložené počas tichých hodín: {count}","groups":{"vm_ct":"VM a CT","backup":"Zálohy","resources":"Zdroje","storage":"Úložisko","network":"Sieť","security":"Bezpečnosť","cluster":"Klaster","services":"Služby","health":"Stav systému","updates":"Aktualizácie","hardware":"Hardvér","system":"Systém","other":"Ostatné"}},"test":{"title":"Test ProxMenux","welcome":"Vitajte v ProxMenux Monitor!","verify":"Toto je testovacia správa na overenie správneho fungovania notifikačného kanála.","configuration":"Nastavenie kanála:","iconsEnabled":"Ikony: zapnuté","iconsDisabled":"Ikony: vypnuté","aiEnabled":"AI: zapnutá ({info})","aiDisabled":"AI: vypnutá","alerts":"Budete dostávať upozornenia na systémové udalosti, zmeny stavu a bezpečnostné incidenty.","photoCaption":"Tento obrázok môžete použiť ako profilovú fotografiu svojho notifikačného bota."},"channels":{"discord":{"category":"Kategória","host":"Server","severity":"Závažnosť"},"email":{"report":"Prehľad: {group}","details":"Podrobnosti","host":"Server","footer":"Notifikačná služba ProxMenux","severity":{"critical":"Kritické","warning":"Upozornenie","info":"Informácia","ok":"Vyriešené","default":"Oznámenie"},"groups":{"vm_ct":"Virtuálny stroj / kontajner","backup":"Záloha a snímka","resources":"Systémové zdroje","storage":"Úložisko","network":"Sieť","security":"Bezpečnosť","cluster":"Klaster","services":"Systémové služby","health":"Stav systému","updates":"Systémové aktualizácie","system":"Systém","hardware":"Hardvér","other":"Systémová notifikácia"},"fields":{"vmCtId":"ID VM/CT","name":"Názov","action":"Akcia","targetNode":"Cieľový uzol","reason":"Dôvod","storage":"Úložisko","status":"Stav","size":"Veľkosť","duration":"Trvanie","snapshot":"Snímka","metric":"Metrika","currentValue":"Aktuálna hodnota","threshold":"Prahová hodnota","cpuCores":"Jadrá CPU","memory":"Pamäť","temperature":"Teplota","mountPoint":"Bod pripojenia","usage":"Využitie","available":"Voľné","device":"Zariadenie","severity":"Závažnosť","storageName":"Názov úložiska","type":"Typ","interface":"Rozhranie","latency":"Latencia","event":"Udalosť","sourceIp":"Zdrojová IP","username":"Používateľ","service":"Služba","jail":"Jail","failures":"Zlyhania","change":"Zmena","node":"Uzol","quorum":"Kvórum","nodesAffected":"Ovplyvnené uzly","process":"Proces","category":"Kategória","previousSeverity":"Predchádzajúca závažnosť","activeIssues":"Aktívne problémy","totalUpdates":"Aktualizácie spolu","securityUpdates":"Bezpečnostné aktualizácie","proxmoxUpdates":"Aktualizácie Proxmox","kernelUpdates":"Aktualizácie jadra","importantPackages":"Dôležité balíky","currentVersion":"Aktuálna verzia","newVersion":"Nová verzia"},"status":{"failed":"Zlyhalo","completed":"Dokončené","started":"Spustené"}}}}} + "runtime": { + "notifications": { + "templates": { + "state_change": { + "title": "{hostname}: {category} sa zmenilo na {current}{entity_suffix}", + "body": "Stav kategórie {category} sa zmenil z {previous} na {current}.\n{reason}", + "label": "Zmena stavu kontroly" + }, + "new_error": { + "title": "{hostname}: Nový {severity} - {category}{entity_suffix}", + "body": "{reason}", + "label": "Nový problém so stavom" + }, + "error_resolved": { + "title": "{hostname}: Vyriešené - {category}{entity_suffix}", + "body": "Problém v kategórii {category} bol vyriešený.\n{reason}\n🚦 Predchádzajúca závažnosť: {original_severity}\n⏱️ Trvanie: {duration}", + "label": "Problém bol vyriešený" + }, + "error_escalated": { + "title": "{hostname}: Zvýšené na {severity} - {category}{entity_suffix}", + "body": "{reason}", + "label": "Problém sa zhoršil" + }, + "health_degraded": { + "title": "{title_or_default}", + "body": "{reason}", + "label": "Zhoršený stav kontroly" + }, + "lxc_updates_available": { + "title": "{hostname}: Aktualizácie balíkov sú dostupné pre {count} LXC", + "body": "📊 Čakajúce aktualizácie balíkov má {count} LXC (📦 spolu {total_packages}, 🔒 bezpečnostné {security_count}):\n\n{ct_list}", + "label": "Dostupné aktualizácie LXC (experimentálne)" + }, + "lxc_update_applied": { + "title": "{hostname}: LXC {ct_name} ({vmid}) aktualizácia {result}", + "body": "{details}", + "label": "Aktualizácia LXC použitá" + }, + "app_update_available": { + "title": "{hostname}: Pre {app_name} je na CT {vmid} dostupná aktualizácia", + "body": "Aplikácia {app_name} na CT {vmid} ({ct_name}) má novú verziu:\n {installed} → {latest}", + "label": "K dispozícii je aktualizácia aplikácie" + }, + "docker_stack_update_available": { + "title": "{hostname}: Na CT {vmid} sú dostupné aktualizácie Docker", + "body": "Kontajner {ct_name} (CT {vmid}) má {count} aktualizácií Docker:\n{details}", + "label": "Dostupné aktualizácie Docker" + }, + "vm_start": { + "title": "{hostname}: VM {vmname} ({vmid}) spustený", + "body": "Virtuálny stroj {vmname} (ID: {vmid}) je spustený.", + "label": "VM bola spustená" + }, + "vm_start_warning": { + "title": "{hostname}: VM {vmname} ({vmid}) spustený s upozorneniami", + "body": "Virtuálny stroj {vmname} (ID: {vmid}) sa úspešne spustil, ale obsahuje upozornenia.\nUpozornenia: {reason}", + "label": "VM bola spustená s upozorneniami" + }, + "vm_stop": { + "title": "{hostname}: VM {vmname} ({vmid}) zastavený", + "body": "Virtuálny stroj {vmname} (ID: {vmid}) bol zastavený.", + "label": "VM bola zastavená" + }, + "vm_shutdown": { + "title": "{hostname}: VM {vmname} ({vmid}) vypnutý", + "body": "Virtuálny stroj {vmname} (ID: {vmid}) bol korektne vypnutý.", + "label": "VM bola vypnutá" + }, + "vm_fail": { + "title": "{hostname}: VM {vmname} ({vmid}) ZLYHAL", + "body": "Virtuálny stroj {vmname} (ID: {vmid}) zlyhal alebo sa ho nepodarilo spustiť.\nDôvod: {reason}", + "label": "Spustenie VM zlyhalo" + }, + "vm_restart": { + "title": "{hostname}: VM {vmname} ({vmid}) reštartovaný", + "body": "Virtuálny stroj {vmname} (ID: {vmid}) bol reštartovaný.", + "label": "VM bola reštartovaná" + }, + "ct_start": { + "title": "{hostname}: CT {vmname} ({vmid}) spustený", + "body": "Kontajner {vmname} (ID: {vmid}) je spustený.", + "label": "Kontajner bol spustený" + }, + "ct_start_warning": { + "title": "{hostname}: CT {vmname} ({vmid}) spustený s upozorneniami", + "body": "Kontajner {vmname} (ID: {vmid}) sa úspešne spustil, ale obsahuje upozornenia.\nUpozornenia: {reason}", + "label": "Kontajner bol spustený s upozorneniami" + }, + "ct_stop": { + "title": "{hostname}: CT {vmname} ({vmid}) zastavený", + "body": "Kontajner {vmname} (ID: {vmid}) bol zastavený.", + "label": "Kontajner bol zastavený" + }, + "ct_shutdown": { + "title": "{hostname}: CT {vmname} ({vmid}) vypnutý", + "body": "Kontajner {vmname} (ID: {vmid}) bol korektne vypnutý.", + "label": "Kontajner bol vypnutý" + }, + "ct_restart": { + "title": "{hostname}: CT {vmname} ({vmid}) reštartovaný", + "body": "Kontajner {vmname} (ID: {vmid}) bol reštartovaný.", + "label": "Kontajner bol reštartovaný" + }, + "ct_fail": { + "title": "{hostname}: CT {vmname} ({vmid}) ZLYHAL", + "body": "Kontajner {vmname} (ID: {vmid}) zlyhal alebo sa ho nepodarilo spustiť.\nDôvod: {reason}", + "label": "Spustenie kontajnera zlyhalo" + }, + "migration_start": { + "title": "{hostname}: Migrácia spustená — {vmname} ({vmid})", + "body": "Živá migrácia VM alebo LXC {vmname} (ID: {vmid}) na uzol {target_node} sa začala.", + "label": "Migrácia sa začala" + }, + "migration_complete": { + "title": "{hostname}: Migrácia dokončená — {vmname} ({vmid})", + "body": "{vmname} (ID: {vmid}) bol úspešne presunutý na uzol {target_node}.", + "label": "Migrácia bola dokončená" + }, + "migration_warning": { + "title": "{hostname}: Migrácia dokončená s upozorneniami — {vmname} ({vmid})", + "body": "VM alebo LXC {vmname} (ID: {vmid}) bol presunutý na uzol {target_node}, ale vyskytli sa upozornenia.\nUpozornenia: {reason}", + "label": "Migrácia skončila s upozorneniami" + }, + "migration_fail": { + "title": "{hostname}: Migrácia ZLYHALA — {vmname} ({vmid})", + "body": "Migrácia VM alebo LXC {vmname} (ID: {vmid}) na uzol {target_node} zlyhala.\nDôvod: {reason}", + "label": "Migrácia zlyhala" + }, + "replication_fail": { + "title": "{hostname}: Replikácia ZLYHALA — {vmname} ({vmid})", + "body": "Replikácia VM alebo LXC {vmname} (ID: {vmid}) zlyhala.\nDôvod: {reason}", + "label": "Replikácia zlyhala" + }, + "replication_complete": { + "title": "{hostname}: Replikácia dokončená — {vmname} ({vmid})", + "body": "Replikácia VM alebo LXC {vmname} (ID: {vmid}) bola úspešne dokončená.", + "label": "Replikácia bola dokončená" + }, + "backup_start": { + "title": "{hostname}: Záloha na úložisko {storage} spustená", + "body": "Úloha zálohovania na úložisko {storage} sa začala.\n{reason}", + "label": "Zálohovanie sa začalo" + }, + "backup_complete": { + "title": "{hostname} → {storage}: Záloha dokončená — {vmname} ({vmid})", + "body": "Záloha {vmname} (ID: {vmid}) na úložisku {storage} bola úspešne dokončená.\nVeľkosť: {size}", + "label": "Záloha bola dokončená" + }, + "backup_warning": { + "title": "{hostname} → {storage}: Záloha dokončená s upozorneniami — {vmname} ({vmid})", + "body": "Záloha {vmname} (ID: {vmid}) na úložisku {storage} bola dokončená, ale vyskytli sa upozornenia.\nUpozornenia: {reason}", + "label": "Záloha skončila s upozorneniami" + }, + "backup_fail": { + "title": "{hostname} → {storage}: Záloha ZLYHALA — {vmname} ({vmid})", + "body": "Záloha {vmname} (ID: {vmid}) na úložisku {storage} zlyhala.\nDôvod: {reason}", + "label": "Zálohovanie zlyhalo" + }, + "host_backup_start": { + "title": "{hostname}: Záloha servera spustená → {backend_label}", + "body": "Úloha: {job_id}\nBackend: {backend_label}\nCieľ: {destination}\nProfil: {profile_mode}", + "label": "Zálohovanie servera sa začalo" + }, + "host_backup_complete": { + "title": "{hostname}: Záloha servera dokončená → {backend_label}", + "body": "Úloha: {job_id}\nBackend: {backend_label}\nCieľ: {destination}\nVeľkosť dát: {data_size}\nVeľkosť archívu: {archive_size}\nTrvanie: {duration}", + "label": "Záloha servera bola dokončená" + }, + "host_backup_fail": { + "title": "{hostname}: Záloha servera ZLYHALA → {backend_label}", + "body": "Úloha: {job_id}\nBackend: {backend_label}\nCieľ: {destination}\nTrvanie pred zlyhaním: {duration}\nDôvod: {reason}\nZáznam: {log_file}", + "label": "Zálohovanie servera zlyhalo" + }, + "snapshot_complete": { + "title": "{hostname}: Snímka vytvorená — {vmname} ({vmid})", + "body": "Snímka „{snapshot_name}“ bola vytvorená pre {vmname} (ID: {vmid}).", + "label": "Snapshot bol vytvorený" + }, + "snapshot_fail": { + "title": "{hostname}: Vytvorenie snímky ZLYHALO — {vmname} ({vmid})", + "body": "Vytvorenie snímky pre {vmname} (ID: {vmid}) zlyhalo.\nDôvod: {reason}", + "label": "Vytvorenie snapshotu zlyhalo" + }, + "cpu_high": { + "title": "{hostname}: Vysoké využitie CPU — {value}%", + "body": "Využitie CPU dosiahlo {value}% na {cores} jadrách.\n{details}", + "label": "Vysoké využitie CPU" + }, + "ram_high": { + "title": "{hostname}: Vysoké využitie pamäte — {value}%", + "body": "Využitie pamäte: {used} / {total} ({value}%).\n{details}", + "label": "Vysoké využitie pamäte" + }, + "temp_high": { + "title": "{hostname}: Vysoká teplota CPU — {value}°C", + "body": "Teplota CPU dosiahla {value}°C (prahová hodnota: {threshold}°C).\n{details}", + "label": "Vysoká teplota" + }, + "disk_space_low": { + "title": "{hostname}: Málo miesta na disku {mount}", + "body": "Súborový systém {mount}: {used}% využité ({available} voľných).\nUvoľnite miesto na disku, aby ste predišli výpadku služieb.", + "label": "Málo miesta na disku" + }, + "disk_io_error": { + "title": "{hostname}: Zistené zlyhanie disku — {device}", + "body": "Na zariadení bola zistená chyba I/O alebo zlyhanie disku {device}.\n{reason}", + "label": "Chyba disku alebo I/O" + }, + "storage_unavailable": { + "title": "{hostname}: Úložisko nedostupné — {storage_name}", + "body": "Úložisko PVE „{storage_name}“ (typ: {storage_type}) nie je dostupné.\nDôvod: {reason}", + "label": "Úložisko nie je dostupné" + }, + "smart_test_complete": { + "title": "{hostname}: Test SMART dokončený — {device}", + "body": "Test SMART typu {test_type} na /dev/{device} bol dokončený.\nVýsledok: {result}\nTrvanie: {duration}", + "label": "SMART test bol dokončený" + }, + "smart_test_failed": { + "title": "{hostname}: Test SMART ZLYHAL — {device}", + "body": "Test SMART typu {test_type} na /dev/{device} zlyhal.\nVýsledok: {result}\nDôvod: {reason}", + "label": "SMART test zlyhal" + }, + "gpu_mode_switch": { + "title": "{hostname}: Režim GPU zmenený na {new_mode}", + "body": "Režim priameho priradenia GPU bol zmenený.\nGPU: {gpu_name} ({gpu_pci})\nPredchádzajúci režim: {old_mode}\nNový režim: {new_mode}\n{details}", + "label": "Režim GPU bol zmenený" + }, + "gpu_passthrough_blocked": { + "title": "{hostname}: {guest_type} {guest_id} zablokovaný pri spustení", + "body": "Ochrana priameho priradenia PCIe zabránila spusteniu {guest_type} {guest_id} ({guest_name}).\nDôvod: {reason}\n{details}", + "label": "GPU passthrough bol zablokovaný" + }, + "pci_passthrough_conflict": { + "title": "{hostname}: Zistený konflikt zariadenia PCIe — {device_pci}", + "body": "Zariadenie PCIe je priradené viacerým VM alebo LXC.\nZariadenie: {device_pci}\nKonfliktné VM a LXC: {guest_list}\nVyžaduje sa zásah: zastavte jednu z VM alebo LXC alebo zmeňte priradenie zariadenia.", + "label": "Konflikt PCIe zariadenia" + }, + "load_high": { + "title": "{hostname}: Vysoká záťaž systému — {value}", + "body": "Priemerná záťaž systému je {value} na {cores} jadrách.\n{details}", + "label": "Vysoká záťaž systému" + }, + "network_down": { + "title": "{hostname}: Sieťové pripojenie prerušené{entity_suffix}", + "body": "Uzol stratil sieťové pripojenie.\nDôvod: {reason}", + "label": "Sieťové pripojenie bolo prerušené" + }, + "network_latency": { + "title": "{hostname}: Vysoká latencia siete — {value}ms", + "body": "Latencia k bráne: {value}ms (prahová hodnota: {threshold}ms).\nMôže to znamenať preťaženie siete alebo problém s hardvérom.", + "label": "Vysoká odozva siete" + }, + "auth_fail": { + "title": "{hostname}: Zlyhanie overenia", + "body": "Bol zistený neúspešný pokus o prihlásenie.\nZdrojová IP: {source_ip}\nPoužívateľ: {username}\nSlužba: {service}", + "label": "Neúspešné prihlásenie" + }, + "ip_block": { + "title": "{hostname}: IP zablokovaná cez Fail2Ban", + "body": "IP adresa {source_ip} bola zablokovaná.\nJail: {jail}\nNeúspešné pokusy: {failures}", + "label": "Fail2Ban zablokoval IP adresu" + }, + "firewall_issue": { + "title": "{hostname}: Zistený problém firewallu{entity_suffix}", + "body": "Bol zistený problém v konfigurácii firewallu.\nDôvod: {reason}", + "label": "Zistený problém s firewallom" + }, + "user_permission_change": { + "title": "{hostname}: Zmenené oprávnenie používateľa", + "body": "Používateľ: {username}\nZmena: {change_details}", + "label": "Zmena oprávnení používateľa" + }, + "split_brain": { + "title": "{hostname}: Zistený SPLIT-BRAIN", + "body": "V klastri bol zistený stav split-brain. Kvórum môže byť stratené.\nStav kvóra: {quorum}", + "label": "Zistený split-brain" + }, + "node_disconnect": { + "title": "{hostname}: Uzol {node_name} odpojený", + "body": "Uzol {node_name} sa odpojil od klastra.", + "label": "Server sa odpojil" + }, + "node_reconnect": { + "title": "{hostname}: Uzol {node_name} znovu pripojený", + "body": "Uzol {node_name} sa úspešne znovu pripojil ku klastru.", + "label": "Server sa znova pripojil" + }, + "system_startup": { + "title": "{hostname}: {reason}", + "body": "{summary}", + "label": "Súhrn po spustení systému" + }, + "system_shutdown": { + "title": "{hostname}: Systém sa vypína", + "body": "Uzol sa vypína.\n{reason}", + "label": "Systém sa vypína" + }, + "system_reboot": { + "title": "{hostname}: Systém sa reštartuje", + "body": "Uzol sa reštartuje.\n{reason}", + "label": "Systém sa reštartuje" + }, + "system_restore_completed": { + "title": "{hostname}: Obnova servera dokončená", + "body": "Úlohy po obnove boli dokončené na pozadí.\n\nPoužité VM a LXC: {guests}\nZástupné priečinky bind mountov: {stubs}\nOdstránené zastarané priečinky uzlov: {stale_nodes}\nPreinštalované súčasti: {components}\nTrvanie: {duration}\n{warnings_block}\nUzol je teraz úplne pripravený na použitie.", + "label": "Obnova servera bola dokončená" + }, + "system_problem": { + "title": "{hostname}: Zistený systémový problém{entity_suffix}", + "body": "Bol zistený problém na úrovni systému.\nDôvod: {reason}", + "label": "Zistený problém so systémom" + }, + "service_fail": { + "title": "{hostname}: Služba zlyhala — {service_name}", + "body": "Systémová služba \"{service_name}\" zlyhala.\nDôvod: {reason}", + "label": "Služba zlyhala" + }, + "oom_kill": { + "title": "{hostname}: Ukončenie pri nedostatku pamäte — {process}", + "body": "Proces „{process}“ bol ukončený správcom pri nedostatku pamäte.\n{reason}", + "label": "Proces bol ukončený pre nedostatok pamäte" + }, + "service_fail_batch": { + "title": "{hostname}: {service_count} zlyhaných služieb{entity_suffix}", + "body": "{reason}", + "label": "Zlyhanie viacerých služieb" + }, + "cron_output": { + "title": "{hostname}: {pve_title}", + "body": "{reason}", + "label": "Výstup úlohy cron" + }, + "system_mail": { + "title": "{hostname}: {pve_title}", + "body": "{reason}", + "label": "Správy Smartd a systému" + }, + "apt_listchanges": { + "title": "{hostname}: {pve_title}", + "body": "Informácie o balíkoch od ich tvorcov preposlané systémom Proxmox VE cez apt-listchanges. Nasledujúci text pochádza od správcu balíka a nie je odporúčaním ProxMenux.\n\n{reason}", + "label": "Oznámenia o balíkoch apt-listchanges" + }, + "webhook_test": { + "title": "{hostname}: Prijatý test webhooku", + "body": "Test pripojenia webhooku PVE bol úspešný.\n{reason}", + "label": "Test webhooku" + }, + "update_available": { + "title": "{hostname}: Dostupné aktualizácie", + "body": "Aktualizácie spolu: {total_count}\nBezpečnostné: {security_count}\nProxmox: {pve_count}\nJadro: {kernel_count}\nDôležité balíky:\n{important_list}", + "label": "Dostupné aktualizácie (starší typ)" + }, + "unknown_persistent": { + "title": "{hostname}: Kontrola nedostupná - {category}{entity_suffix}", + "body": "Kontrola stavu pre kategóriu {category} je nedostupná aspoň 3 cykly.\n{reason}", + "label": "Kontrola nie je dostupná" + }, + "health_persistent": { + "title": "{hostname}: {count} aktívnych problémov so stavom{entity_suffix}", + "body": "Nasledujúce problémy so stavom systému zostávajú nevyriešené:\n{issue_list}\n\nTento súhrn sa odosiela raz za 24 hodín, kým problémy pretrvávajú.", + "label": "Aktívne problémy so stavom (denne)" + }, + "health_issue_new": { + "title": "{hostname}: Nový problém so stavom systému — {category}{entity_suffix}", + "body": "Nový {severity} problém zistený v kategórii: {category}\nPodrobnosti: {reason}", + "label": "Nový problém so stavom" + }, + "health_issue_resolved": { + "title": "{hostname}: Vyriešené - {category}{entity_suffix}", + "body": "Problém v kategórii {category} bol vyriešený.\n{reason}\nTrvanie: {duration}", + "label": "Problém bol vyriešený" + }, + "update_summary": { + "title": "{hostname}: Dostupné aktualizácie", + "body": "Aktualizácie spolu: {total_count}\nBezpečnostné aktualizácie: {security_count}\nAktualizácie súvisiace s Proxmoxom: {pve_count}\nAktualizácie jadra: {kernel_count}\nDôležité balíky:\n{important_list}", + "label": "Dostupné aktualizácie" + }, + "pve_update": { + "title": "{hostname}: Dostupný Proxmox VE {new_version}", + "body": "Je dostupná nová verzia Proxmox VE.\nAktuálna: {current_version}\nNová: {new_version}\n{details}", + "label": "Dostupná aktualizácia Proxmox VE" + }, + "update_complete": { + "title": "{hostname}: Aktualizácia systému dokončená", + "body": "Systémové balíky boli úspešne aktualizované.\n{details}", + "label": "Aktualizácia bola dokončená" + }, + "ai_model_migrated": { + "title": "{hostname}: Model AI aktualizovaný", + "body": "Model AI pre notifikácie bol automaticky aktualizovaný.\nPoskytovateľ: {provider}\nPredchádzajúci model: {old_model}\nNový model: {new_model}\n\n{message}", + "label": "AI model bol automaticky aktualizovaný" + }, + "proxmenux_update": { + "title": "{hostname}: Dostupný ProxMenux {new_version}", + "body": "Je dostupná nová verzia ProxMenux.\nAktuálna: {current_version}\nNová: {new_version}", + "label": "Dostupná aktualizácia ProxMenux" + }, + "mount_stale": { + "title": "{hostname}: nereagujúce vzdialené pripojenie {mount_target}", + "body": "Vzdialené pripojenie {mount_target} ({fstype}) zo zdroja {mount_source} nereaguje{lxc_scope}.\nKontrola stat vypršala alebo vrátila chybu: {error}\n\nAplikácie zapisujúce do tejto cesty môžu nepozorovane zapisovať na podkladový súborový systém a zaplniť disk. Pripojenie obnovte alebo čo najskôr opravte konektivitu.", + "label": "Vzdialené pripojenie neodpovedá" + }, + "mount_readonly": { + "title": "{hostname}: vzdialené pripojenie {mount_target} je iba na čítanie", + "body": "Vzdialené pripojenie {mount_target} ({fstype}) zo zdroja {mount_source} je pripojené iba na čítanie{lxc_scope}. Zápisy zlyhajú. Ak to nebolo zámerné, pripojte ho znova s voľbou rw.", + "label": "Vzdialené pripojenie je iba na čítanie" + }, + "lxc_disk_low": { + "title": "{hostname}: CT {vmid} koreňový systém na {usage_percent}%", + "body": "Koreňový súborový systém CT {vmid} ({name}) je zaplnený na {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nPlný rootfs LXC bráni korektnému spusteniu kontajnera. Zväčšite rootfs (pct resize {vmid} rootfs +1G) alebo uvoľnite miesto v kontajneri.", + "label": "Rootfs LXC je takmer plný" + }, + "vm_disk_low": { + "title": "{hostname}: VM {vmid} súborové systémy na {usage_percent}%", + "body": "Súborové systémy VM {vmid} ({name}) sú zaplnené na {usage_percent}% ({disk_bytes_human} / {maxdisk_bytes_human}).\n\nÚdaje poskytol QEMU Guest Agent. Zahŕňajú každý trvalý súborový systém pripojený z blokového zariadenia vrátane virtuálnych diskov a diskov priamo priradených cez PCI. Uvoľnite miesto vo VM alebo zväčšite dotknuté úložisko skôr, než začnú zlyhávať zápisy.", + "label": "Súborové systémy VM sú takmer plné" + }, + "lxc_mount_low": { + "title": "{hostname}: Pripojenie {mount} v CT {vmid} je zaplnené na {usage_percent}%", + "body": "Pripojenie {mount} v CT {vmid} ({name}) je využité na {usage_percent}%.\nTyp súborového systému: {fstype}\n\nPlné pripojenie v kontajneri často nepozorovane zablokuje aplikáciu — zápisy zlyhajú alebo skončia na rootfs a vyvolajú ďalšie upozornenie. Uvoľnite miesto alebo pripojenie zväčšite.", + "label": "Pripojenie LXC je takmer plné" + }, + "pve_storage_full": { + "title": "{hostname}: Úložisko PVE {storage_name} je zaplnené na {usage_percent}%", + "body": "Úložisko Proxmox „{storage_name}“ (typ: {storage_type}) je využité na {usage_percent}%.\n\nPo zaplnení nebude možné vytvárať nové VM/CT a existujúcim VM a LXC môžu zlyhať zápisy. Presuňte alebo odstráňte nepoužívané zväzky, prípadne zväčšite podkladový pool/LV/RBD obraz.", + "label": "Úložisko PVE je takmer plné" + }, + "zfs_pool_full": { + "title": "{hostname}: Fond ZFS {pool_name} je zaplnený na {usage_percent}%", + "body": "Fond ZFS „{pool_name}“ je zaplnený na {usage_percent}%.\n\nVýkon a spoľahlivosť zápisu ZFS sa nad približne 80 % výrazne zhoršujú, pretože CoW potrebuje voľné miesto pre nové bloky. Uvoľnite snímky, odstráňte staré datasety alebo pridajte do fondu ďalšie vdevy.", + "label": "ZFS pool je takmer plný" + }, + "post_install_update": { + "title": "{hostname}: {count} ProxMenux dostupných aktualizácií optimalizácií", + "body": "Na tomto serveri je dostupných {count} aktualizácií optimalizácií ProxMenux.\n\n🛠️ Dostupné verzie:\n{tool_list}\n\n💡 Použiť cez:\n • ProxMenux Monitor → Nastavenia → Optimalizácie ProxMenux\n • Alebo spustite ponuku po inštalácii (možnosť 2) → „Použiť dostupné aktualizácie“", + "label": "Dostupné aktualizácie optimalizácií ProxMenux" + }, + "secure_gateway_update_available": { + "title": "{hostname}: {app_name} dostupná aktualizácia{update_title_suffix}", + "body": "Aplikácia {app_name} (spravovaná cez ProxMenux) má vo svojom kontajneri 📦 {package_count} čakajúcich aktualizácií balíkov.\n{version_line}\n\n💡 Otvorte ProxMenux Monitor > Nastavenia > Secure Gateway a kliknite na „Aktualizovať“.\n\n🗂️ Balíky:\n{package_list}", + "label": "K dispozícii je aktualizácia Secure Gateway" + }, + "nvidia_driver_update_available": { + "title": "{hostname}: NVIDIA dostupná aktualizácia ovládača — v{latest_version}", + "body": "Pre nainštalovaný rad ovládača NVIDIA je dostupná novšia údržbová verzia.\n🔹 Aktuálne nainštalovaná: v{current_version}\n🟢 Najnovšia dostupná: v{latest_version}\n\n{upgrade_reason}\n\n💡 Preinštalovanie:\n • V ponuke ProxMenux po inštalácii: {menu_label}\n\nPreinštalovanie zostaví modul DKMS pre aktuálne jadro a načítanie nového ovládača vyžaduje reštart.", + "label": "Dostupná aktualizácia ovládača NVIDIA" + }, + "coral_driver_update_available": { + "title": "{hostname}: Coral TPU dostupná aktualizácia ovládača — {latest_version}", + "body": "Je dostupná novšia verzia {variant_label}.\n🔹 Aktuálne nainštalovaná: {current_version}\n🟢 Najnovšia dostupná: {latest_version}\n\n{upgrade_reason}\n\n💡 Preinštalovanie:\n • V ponuke ProxMenux po inštalácii: {menu_label}\n\n{reboot_note}", + "label": "Dostupná aktualizácia ovládača Coral TPU" + }, + "burst_auth_fail": { + "title": "{hostname}: Ďalšie zlyhania overenia za {window}: +{count}", + "body": "Za {window} bolo zistených ďalších {count} zlyhaní overenia (spolu {total_count}).\nZdroje: {entity_list}", + "label": "Veľa neúspešných prihlásení" + }, + "burst_ip_block": { + "title": "{hostname}: Fail2Ban zablokoval za {window} ďalšie IP: +{count}", + "body": "Fail2Ban zablokoval za {window} ďalších {count} IP (spolu {total_count}).\nIP adresy: {entity_list}", + "label": "Veľa zablokovaných IP adries" + }, + "burst_disk_io": { + "title": "{hostname}: +{count} ďalších chýb I/O disku na {entity_list}", + "body": "Za {window} bolo zistených ďalších {count} chýb I/O (spolu {total_count}).\nZariadenia: {entity_list}", + "label": "Viacero diskových I/O chýb" + }, + "burst_cluster": { + "title": "{hostname}: Zistené kolísanie stavu klastra (+{count} ďalších zmien)", + "body": "Stav klastra sa za {window} zmenil ešte {count}-krát (spolu {total_count}).\nUzly: {entity_list}", + "label": "Nestabilné spojenie clustra" + }, + "burst_service_fail": { + "title": "{hostname}: Za {window} zlyhali ďalšie služby: +{count}", + "body": "Za {window} bolo zistených ďalších {count} zlyhaní služieb (spolu {total_count}).\nZvyčajne to znamená reštart uzla alebo služby PVE.\n\nĎalšie zlyhania:\n{details}", + "label": "Zlyhanie viacerých služieb" + }, + "burst_system": { + "title": "{hostname}: Ďalšie systémové problémy za {window}: +{count}", + "body": "Za {window} bolo zistených ďalších {count} systémových problémov (spolu {total_count}).\n\nĎalšie problémy:\n{details}", + "label": "Viacero systémových problémov" + }, + "burst_generic": { + "title": "{hostname}: Ďalšie udalosti {event_type} za {window}: +{count}", + "body": "Za {window} bolo zaznamenaných ďalších {count} udalostí typu {event_type} (spolu {total_count}).\n\nĎalšie udalosti:\n{details}", + "label": "Viacero súvisiacich udalostí" + }, + "kernel_warning": { + "title": "{hostname}: Zaznamenaná diagnostická udalosť jadra", + "body": "Jadro zaznamenalo diagnostickú udalosť: {kernel_details}", + "label": "Upozornenia jadra a diagnostické záznamy" + } + }, + "lxcUpdate": { + "status": { + "success": "úspešne dokončená", + "failure": "zlyhala", + "partial": "dokončená čiastočne", + "deferred": "odložená", + "skipped": "preskočená" + }, + "source": { + "scheduled": "Plánovane", + "manual": "Manuálne" + }, + "sourceLabel": "Zdroj", + "targets": "Ciele", + "osPending": "Čakajúce aktualizácie OS: {before} → {after}", + "osUnverified": "Balíky OS: príkaz na aktualizáciu bol spustený, výsledok sa nepodarilo overiť", + "applications": "Aplikácie: {items}", + "applicationsUnverified": "Aplikácie: aktualizátor bol spustený, nezistila sa však zmena sledovanej verzie", + "dockerEngineChange": "Docker Engine: {before} → {after}", + "dockerEngineVerified": "Docker Engine: overený vo verzii {version}", + "dockerEngineUnverified": "Docker Engine: príkaz na aktualizáciu bol spustený, verziu sa nepodarilo overiť", + "dockerImagesPending": "Čakajúce aktualizácie Docker obrazov: {before} → {after}", + "dockerImagesChanged": "Zmenené Docker obrazy: {images}", + "deferredTargets": "Odložené ciele: {targets}", + "reason": "Dôvod: {reason}", + "restartRequired": "Vyžaduje sa reštart: {value}", + "yes": "áno", + "no": "nie", + "verificationPending": "Overenie bude pokračovať po spustení kontajnera", + "verificationWarning": "Upozornenie pri overení: {error}", + "duration": "Trvanie: {duration}" + }, + "fallback": { + "unknownTitle": "{hostname}: {event_type}", + "healthCheckDegraded": "Zhoršený stav kontroly", + "none": "žiadne" + }, + "fields": { + "vmid": "VM/CT", + "name": "Názov", + "device": "Zariadenie", + "sourceIp": "Zdrojová IP", + "node": "Uzol", + "category": "Kategória", + "service": "Služba", + "jail": "Jail", + "user": "Používateľ", + "count": "Počet", + "window": "Obdobie", + "affected": "Ovplyvnené" + }, + "vzdump": { + "size": "Veľkosť: {value}", + "duration": "Trvanie: {value}", + "file": "Súbor", + "backups": "Počet záloh: {count}", + "failed": "Zlyhalo: {count}", + "total": "Spolu: {value}", + "time": "Čas: {value}" + }, + "startup": { + "issuesTitle": "{hostname}: Spustenie systému – zistených problémov: {count}", + "completeTitle": "{hostname}: Spustenie systému dokončené", + "operational": "Všetky systémy sú funkčné.", + "vmCountOne": "{count} VM", + "vmCountMany": "Počet VM: {count}", + "ctCountOne": "{count} CT", + "ctCountMany": "Počet CT: {count}", + "started": "✅ Spustené: {counts}", + "vmFailed": "❌ VM {name} zlyhal – {reason}", + "ctFailed": "❌ CT {name} zlyhal – {reason}", + "unknownError": "neznáma chyba", + "storageUnavailable": "⚠️ Nedostupné úložiská: {count} ({names})", + "servicesFailed": "⚠️ Zlyhané služby: {count} ({names})", + "duration": "⏱️ Spustenie dokončené za {minutes} min" + }, + "appUpdates": { + "singleTitle": "{hostname}: Pre {app_name} je na CT {vmid} dostupná aktualizácia", + "singleBody": "Aplikácia {app_name} na CT {vmid} ({ct_name}) má novú verziu:\n {installed} → {latest}", + "emptyTitle": "{hostname}: Dostupné aktualizácie aplikácií", + "emptyBody": "Sú dostupné aktualizácie aplikácií.", + "batchTitle": "{hostname}: Dostupné aktualizácie aplikácií: {count}", + "batchLeadOneContainer": "Aplikácie s dostupnou novšou verziou: {count} v {container_count} kontajneri LXC:", + "batchLeadManyContainers": "Aplikácie s dostupnou novšou verziou: {count} v {container_count} kontajneroch LXC:", + "additional": "… ďalšie aplikácie: {count}", + "app": "aplikácia", + "unknown": "neznáme" + }, + "healthDegraded": { + "categories": { + "cpu": "Využitie CPU a teplota", + "memory": "Pamäť a swap", + "storage": "Pripojené úložiská a miesto", + "disks": "I/O disku a chyby", + "network": "Sieťové rozhrania", + "vms": "VM a kontajnery", + "services": "Služby PVE", + "logs": "Systémové záznamy", + "updates": "Systémové aktualizácie", + "security": "Bezpečnosť" + }, + "severity": { + "critical": "Kritický", + "warning": "Upozornenie" + }, + "singleTitle": "{hostname}: {severity} stav – {category}", + "multipleTitle": "{hostname}: Zhoršené kontroly stavu: {count}", + "multipleLine": "• {severity}: {category}\n {reason}", + "reasons": { + "cpuSustained": "CPU je nad {threshold} % už {duration} s." + } + }, + "digest": { + "title": "{hostname}: Súhrn za 24 hodín ({timestamp})", + "readFailed": "načítanie súhrnu zlyhalo: {error}", + "empty": "Pre toto obdobie súhrnu nie sú uložené žiadne udalosti INFO.", + "lead": "{count} udalostí INFO zoskupených podľa kategórie:\n", + "more": " • … a ďalšie: {count}", + "footer": "(Kritické udalosti a upozornenia boli doručené ihneď, nie v tomto súhrne.)", + "quietTitle": "{hostname}: Udalosti uložené počas tichých hodín: {count}", + "groups": { + "vm_ct": "VM a CT", + "backup": "Zálohy", + "resources": "Zdroje", + "storage": "Úložisko", + "network": "Sieť", + "security": "Bezpečnosť", + "cluster": "Klaster", + "services": "Služby", + "health": "Stav systému", + "updates": "Aktualizácie", + "hardware": "Hardvér", + "system": "Systém", + "other": "Ostatné" + } + }, + "test": { + "title": "Test ProxMenux", + "welcome": "Vitajte v ProxMenux Monitor!", + "verify": "Toto je testovacia správa na overenie správneho fungovania notifikačného kanála.", + "configuration": "Nastavenie kanála:", + "iconsEnabled": "Ikony: zapnuté", + "iconsDisabled": "Ikony: vypnuté", + "aiEnabled": "AI: zapnutá ({info})", + "aiDisabled": "AI: vypnutá", + "alerts": "Budete dostávať upozornenia na systémové udalosti, zmeny stavu a bezpečnostné incidenty.", + "photoCaption": "Tento obrázok môžete použiť ako profilovú fotografiu svojho notifikačného bota." + }, + "channels": { + "discord": { + "category": "Kategória", + "host": "Server", + "severity": "Závažnosť" + }, + "email": { + "report": "Prehľad: {group}", + "details": "Podrobnosti", + "host": "Server", + "footer": "Notifikačná služba ProxMenux", + "severity": { + "critical": "Kritické", + "warning": "Upozornenie", + "info": "Informácia", + "ok": "Vyriešené", + "default": "Oznámenie" + }, + "groups": { + "vm_ct": "Virtuálny stroj / kontajner", + "backup": "Záloha a snímka", + "resources": "Systémové zdroje", + "storage": "Úložisko", + "network": "Sieť", + "security": "Bezpečnosť", + "cluster": "Klaster", + "services": "Systémové služby", + "health": "Stav systému", + "updates": "Systémové aktualizácie", + "system": "Systém", + "hardware": "Hardvér", + "other": "Systémová notifikácia" + }, + "fields": { + "vmCtId": "ID VM/CT", + "name": "Názov", + "action": "Akcia", + "targetNode": "Cieľový uzol", + "reason": "Dôvod", + "storage": "Úložisko", + "status": "Stav", + "size": "Veľkosť", + "duration": "Trvanie", + "snapshot": "Snímka", + "metric": "Metrika", + "currentValue": "Aktuálna hodnota", + "threshold": "Prahová hodnota", + "cpuCores": "Jadrá CPU", + "memory": "Pamäť", + "temperature": "Teplota", + "mountPoint": "Bod pripojenia", + "usage": "Využitie", + "available": "Voľné", + "device": "Zariadenie", + "severity": "Závažnosť", + "storageName": "Názov úložiska", + "type": "Typ", + "interface": "Rozhranie", + "latency": "Latencia", + "event": "Udalosť", + "sourceIp": "Zdrojová IP", + "username": "Používateľ", + "service": "Služba", + "jail": "Jail", + "failures": "Zlyhania", + "change": "Zmena", + "node": "Uzol", + "quorum": "Kvórum", + "nodesAffected": "Ovplyvnené uzly", + "process": "Proces", + "category": "Kategória", + "previousSeverity": "Predchádzajúca závažnosť", + "activeIssues": "Aktívne problémy", + "totalUpdates": "Aktualizácie spolu", + "securityUpdates": "Bezpečnostné aktualizácie", + "proxmoxUpdates": "Aktualizácie Proxmox", + "kernelUpdates": "Aktualizácie jadra", + "importantPackages": "Dôležité balíky", + "currentVersion": "Aktuálna verzia", + "newVersion": "Nová verzia" + }, + "status": { + "failed": "Zlyhalo", + "completed": "Dokončené", + "started": "Spustené" + } + } + } + } + } } diff --git a/AppImage/messages/sv/common.json b/AppImage/messages/sv/common.json index 849f4dd4..4d573709 100644 --- a/AppImage/messages/sv/common.json +++ b/AppImage/messages/sv/common.json @@ -3845,7 +3845,8 @@ "startingExport": "Förbereder nedladdning...", "title": "Säkerhetskopieringar", "viewContentsTitle": "Visa säkerhetskopieringsinnehåll", - "descriptionAfter": "" + "descriptionAfter": "", + "emptyMessage": "Inga backup-arkiv hittades." }, "backends": { "borgDescription": "Borg-arkiv för deduplicerade värdsäkerhetskopior.", @@ -3961,7 +3962,9 @@ "sshUserHelpBefore": "Användare på fjärrvärden,", "title": "Destinationer", "unmountTitle": "Avmontera denna destination", - "whereIsBorgRepo": "Var finns Borgarkivet?" + "whereIsBorgRepo": "Var finns Borgarkivet?", + "backupsKeptMessage": "Befintliga säkerhetskopieringsarkiv tas inte bort.", + "localAttachHelpMessage": "Montera USB-enheter först.Lämna tomt för att använda PVE-lagringssökvägen + /dump (fallback: /var/lib/vz/dump)." }, "encryption": { "autoDetectedFromPveStorage": "Upptäckt från PVE-lagring", diff --git a/lang/de.json b/lang/de.json index 2a7cb744..382217dc 100644 --- a/lang/de.json +++ b/lang/de.json @@ -644,6 +644,7 @@ "Completed Successfully!": "Erfolgreich abgeschlossen!", "Completed with errors —": "Mit Fehlern abgeschlossen –", "Completed.": "Vollendet.", + "Completed. Devices added to VM {vmid}: {count}.": "Abgeschlossen. Zur VM {vmid} hinzugefügte Geräte: {count}.", "Completed. Press Enter to return to menu...": "Vollendet. Drücken Sie die Eingabetaste, um zum Menü zurückzukehren...", "Completing pending package configurations...": "Ausstehende Paketkonfigurationen werden abgeschlossen...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "Konformitätsprüfung (PCI-DSS, HIPAA usw.)", @@ -1045,7 +1046,6 @@ "Current user": "Aktueller Benutzer", "Current user UID, GID and groups": "Aktuelle Benutzer-UID, GID und Gruppen", "Current version:": "Aktuelle Version:", - "Currently": "Momentan", "Currently Mounted:": "Derzeit montiert:", "Currently configured target:": "Derzeit konfiguriertes Ziel:", "Currently mounted:": "Derzeit montiert:", @@ -1068,6 +1068,7 @@ "Custom path": "Benutzerdefinierter Pfad", "Custom path...": "Benutzerdefinierter Pfad...", "Custom paths are included in BOTH default and custom backup profiles.": "Benutzerdefinierte Pfade sind SOWOHL in den Standard- als auch in den benutzerdefinierten Sicherungsprofilen enthalten.", + "Custom paths currently saved: {count}.": "Derzeit gespeicherte benutzerdefinierte Pfade: {count}.", "Custom restore by components": "Benutzerdefinierte Wiederherstellung nach Komponenten", "Custom restore by paths": "Benutzerdefinierte Wiederherstellung nach Pfaden", "Custom scripts and ProxMenux files": "Benutzerdefinierte Skripte und ProxMenux-Dateien", @@ -2220,7 +2221,6 @@ "Installed": "Installiert", "Installed at:": "Installiert unter:", "Installed components:": "Installierte Komponenten:", - "Installed:": "Installiert:", "Installer already downloaded and verified.": "Das Installationsprogramm wurde bereits heruntergeladen und überprüft.", "Installer copied to container.": "Installer in Container kopiert.", "Installer downloaded.": "Installer heruntergeladen.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Paketaktualisierung mit Warnungen abgeschlossen, Fortsetzung ...", "Package update had issues, checking details...": "Bei der Paketaktualisierung gab es Probleme, beim Überprüfen der Details ...", "Packages from backup to install:": "Pakete vom Backup zur Installation:", + "Packages installed: {count}.": "Installierte Pakete: {count}.", "Packages upgrade successfull": "Paketaktualisierung erfolgreich", "Packages upgraded": "Pakete aktualisiert", "Packages:": "Pakete:", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod fehlgeschlagen – wahrscheinlich unprivilegierter CT gegen Host-Bind-Mount. Zurückgreifen auf ACL.", "content:": "Inhalt:", "could not be compiled for kernel": "konnte nicht für den Kernel kompiliert werden", - "custom path(s) saved.": "Benutzerdefinierte(r) Pfad(e) gespeichert.", "default": "Standard", "delete the credentials file (if any)": "Löschen Sie die Anmeldeinformationsdatei (falls vorhanden).", "delete the matching line from /etc/fstab": "Löschen Sie die entsprechende Zeile aus /etc/fstab", "descriptor + VMDK files": "Deskriptor + VMDK-Dateien", - "device(s) added to VM": "Gerät(e) zur VM hinzugefügt", "devices": "Geräte", "did not become ready. Skipping.": "nicht fertig geworden. Überspringen.", "disk(s) added to CT": "Festplatte(n) zu CT hinzugefügt", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "Verwaiste Iface-Zeilen, keine Auswirkung auf die Wiederherstellung", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "andere .tar-Archive – keine ProxMenux-Host-Backups (z. B. PVE vzdump oder nicht verwandte Tarballs).", "packages (this may take a few minutes)...": "Pakete (dies kann einige Minuten dauern)...", - "packages.": "Pakete.", "parent PF:": "Eltern-PF:", "partition(s). Partition table preserved.": "Partition(en). Partitionstabelle bleibt erhalten.", "paths for next boot (/etc/pve, guests, drivers, ...)": "Pfade für den nächsten Start (/etc/pve, Gäste, Treiber, ...)", diff --git a/lang/es.json b/lang/es.json index 97cd2810..03a319d2 100644 --- a/lang/es.json +++ b/lang/es.json @@ -644,6 +644,7 @@ "Completed Successfully!": "¡Completado con éxito!", "Completed with errors —": "Completado con errores.", "Completed.": "Terminado.", + "Completed. Devices added to VM {vmid}: {count}.": "Completado. Dispositivos agregados a la VM {vmid}: {count}.", "Completed. Press Enter to return to menu...": "Terminado. Presione Enter para regresar al menú...", "Completing pending package configurations...": "Completando las configuraciones de paquetes pendientes...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "Comprobación de cumplimiento (PCI-DSS, HIPAA, etc.)", @@ -1045,7 +1046,6 @@ "Current user": "Usuario actual", "Current user UID, GID and groups": "UID, GID y grupos del usuario actual", "Current version:": "Versión actual:", - "Currently": "actualmente", "Currently Mounted:": "Actualmente montado:", "Currently configured target:": "Destino actualmente configurado:", "Currently mounted:": "Actualmente montado:", @@ -1068,6 +1068,7 @@ "Custom path": "Ruta personalizada", "Custom path...": "Ruta personalizada...", "Custom paths are included in BOTH default and custom backup profiles.": "Las rutas personalizadas se incluyen tanto en los perfiles de copia de seguridad predeterminados como en los personalizados.", + "Custom paths currently saved: {count}.": "Rutas personalizadas guardadas actualmente: {count}.", "Custom restore by components": "Restauración personalizada por componentes", "Custom restore by paths": "restauración personalizada por rutas", "Custom scripts and ProxMenux files": "Scripts personalizados y archivos ProxMenux", @@ -2220,7 +2221,6 @@ "Installed": "Instalado", "Installed at:": "Instalado en:", "Installed components:": "Componentes instalados:", - "Installed:": "Instalado:", "Installer already downloaded and verified.": "Instalador ya descargado y verificado.", "Installer copied to container.": "Instalador copiado al contenedor.", "Installer downloaded.": "Instalador descargado.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Actualización del paquete completada con advertencias, continúa...", "Package update had issues, checking details...": "La actualización del paquete tuvo problemas, comprobando detalles...", "Packages from backup to install:": "Paquetes de copia de seguridad para instalar:", + "Packages installed: {count}.": "Paquetes instalados: {count}.", "Packages upgrade successfull": "Actualización de paquetes exitosa", "Packages upgraded": "Paquetes actualizados", "Packages:": "Paquetes:", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod falló: probablemente CT sin privilegios contra el montaje de enlace del host. Regresando a ACL.", "content:": "contenido:", "could not be compiled for kernel": "no se pudo compilar para el kernel", - "custom path(s) saved.": "rutas personalizadas guardadas.", "default": "por defecto", "delete the credentials file (if any)": "eliminar el archivo de credenciales (si corresponde)", "delete the matching line from /etc/fstab": "elimine la línea coincidente de /etc/fstab", "descriptor + VMDK files": "descriptor + archivos VMDK", - "device(s) added to VM": "Dispositivo(s) agregado(s) a la VM", "devices": "dispositivos", "did not become ready. Skipping.": "no estuvo listo. Salto a la comba.", "disk(s) added to CT": "disco(s) agregado(s) a CT", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "líneas de iface huérfanas, sin impacto en la restauración", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "otros archivos .tar, no copias de seguridad del host ProxMenux (por ejemplo, PVE vzdump o archivos tar no relacionados).", "packages (this may take a few minutes)...": "paquetes (esto puede tardar unos minutos)...", - "packages.": "paquetes.", "parent PF:": "padre PF:", "partition(s). Partition table preserved.": "partición(es). Se conserva la tabla de particiones.", "paths for next boot (/etc/pve, guests, drivers, ...)": "rutas para el próximo arranque (/etc/pve, invitados, controladores, ...)", diff --git a/lang/fr.json b/lang/fr.json index f4a9f3cd..01cf3f3b 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -644,6 +644,7 @@ "Completed Successfully!": "Terminé avec succès !", "Completed with errors —": "Complété avec des erreurs —", "Completed.": "Complété.", + "Completed. Devices added to VM {vmid}: {count}.": "Terminé. Appareils ajoutés à la VM {vmid} : {count}.", "Completed. Press Enter to return to menu...": "Complété. Appuyez sur Entrée pour revenir au menu...", "Completing pending package configurations...": "Finalisation des configurations de packages en attente...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "Vérification de la conformité (PCI-DSS, HIPAA, etc.)", @@ -1045,7 +1046,6 @@ "Current user": "Utilisateur actuel", "Current user UID, GID and groups": "UID, GID et groupes de l'utilisateur actuel", "Current version:": "Version actuelle :", - "Currently": "Actuellement", "Currently Mounted:": "Actuellement monté :", "Currently configured target:": "cible actuellement configurée :", "Currently mounted:": "Actuellement monté :", @@ -1068,6 +1068,7 @@ "Custom path": "Chemin personnalisé", "Custom path...": "Chemin personnalisé...", "Custom paths are included in BOTH default and custom backup profiles.": "Les chemins personnalisés sont inclus dans les profils de sauvegarde par défaut et personnalisés.", + "Custom paths currently saved: {count}.": "Chemins personnalisés actuellement enregistrés : {count}.", "Custom restore by components": "Restauration personnalisée par composants", "Custom restore by paths": "Restauration personnalisée par chemins", "Custom scripts and ProxMenux files": "Scripts personnalisés et fichiers ProxMenux", @@ -2220,7 +2221,6 @@ "Installed": "Installé", "Installed at:": "Installé à :", "Installed components:": "Composants installés :", - "Installed:": "Installé:", "Installer already downloaded and verified.": "Installateur déjà téléchargé et vérifié.", "Installer copied to container.": "Programme d'installation copié dans le conteneur.", "Installer downloaded.": "Installateur téléchargé.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Mise à jour du package terminée avec des avertissements, suite...", "Package update had issues, checking details...": "La mise à jour du package a rencontré des problèmes, vérification des détails...", "Packages from backup to install:": "Packages de la sauvegarde à installer :", + "Packages installed: {count}.": "Packages installés : {count}.", "Packages upgrade successfull": "Mise à jour des packages réussie", "Packages upgraded": "Forfaits mis à niveau", "Packages:": "Forfaits :", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod a échoué - CT probablement non privilégié contre le montage de liaison de l'hôte. Revenir à ACL.", "content:": "contenu:", "could not be compiled for kernel": "n'a pas pu être compilé pour le noyau", - "custom path(s) saved.": "chemin(s) personnalisé(s) enregistré(s).", "default": "défaut", "delete the credentials file (if any)": "supprimer le fichier d'informations d'identification (le cas échéant)", "delete the matching line from /etc/fstab": "supprimez la ligne correspondante de /etc/fstab", "descriptor + VMDK files": "descripteur + fichiers VMDK", - "device(s) added to VM": "périphérique(s) ajouté(s) à la VM", "devices": "appareils", "did not become ready. Skipping.": "n'est pas devenu prêt. Saut.", "disk(s) added to CT": "disque(s) ajouté(s) à CT", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "lignes iface orphelines, aucun impact sur la restauration", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "autres archives .tar - pas les sauvegardes de l'hôte ProxMenux (par exemple, vzdump PVE ou archives tar sans rapport).", "packages (this may take a few minutes)...": "packages (cela peut prendre quelques minutes)...", - "packages.": "paquets.", "parent PF:": "PF parent :", "partition(s). Partition table preserved.": "partition(s). Table de partition conservée.", "paths for next boot (/etc/pve, guests, drivers, ...)": "chemins pour le prochain démarrage (/etc/pve, invités, pilotes, ...)", diff --git a/lang/it.json b/lang/it.json index cf276b33..eea682d5 100644 --- a/lang/it.json +++ b/lang/it.json @@ -644,6 +644,7 @@ "Completed Successfully!": "Completato con successo!", "Completed with errors —": "Completato con errori —", "Completed.": "Completato.", + "Completed. Devices added to VM {vmid}: {count}.": "completato. Dispositivi aggiunti alla VM {vmid}: {count}.", "Completed. Press Enter to return to menu...": "Completato. Premere Invio per tornare al menu...", "Completing pending package configurations...": "completamento delle configurazioni dei pacchetti in sospeso...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "Controllo della conformità (PCI-DSS, HIPAA, ecc.)", @@ -1045,7 +1046,6 @@ "Current user": "Utente attuale", "Current user UID, GID and groups": "UID, GID e gruppi dell'utente corrente", "Current version:": "Versione attuale:", - "Currently": "Attualmente", "Currently Mounted:": "Attualmente montato:", "Currently configured target:": "destinazione attualmente configurata:", "Currently mounted:": "Attualmente montato:", @@ -1068,6 +1068,7 @@ "Custom path": "Percorso personalizzato", "Custom path...": "Percorso personalizzato...", "Custom paths are included in BOTH default and custom backup profiles.": "I percorsi personalizzati sono inclusi SIA nei profili di backup predefiniti che in quelli personalizzati.", + "Custom paths currently saved: {count}.": "percorsi personalizzati attualmente salvati: {count}.", "Custom restore by components": "Ripristino personalizzato per componenti", "Custom restore by paths": "ripristino personalizzato per percorsi", "Custom scripts and ProxMenux files": "Script personalizzati e file ProxMenux", @@ -2220,7 +2221,6 @@ "Installed": "Installato", "Installed at:": "Installato presso:", "Installed components:": "Componenti installati:", - "Installed:": "Installato:", "Installer already downloaded and verified.": "Programma di installazione già scaricato e verificato.", "Installer copied to container.": "Programma di installazione copiato nel contenitore.", "Installer downloaded.": "Programma di installazione scaricato.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Aggiornamento del pacchetto completato con avvisi, continuando...", "Package update had issues, checking details...": "L'aggiornamento del pacchetto presentava problemi, verifica dei dettagli...", "Packages from backup to install:": "pacchetti dal backup all'installazione:", + "Packages installed: {count}.": "pacchetti installati: {count}.", "Packages upgrade successfull": "Aggiornamento dei pacchetti riuscito", "Packages upgraded": "Pacchetti aggiornati", "Packages:": "Pacchetti:", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod non è riuscito: probabilmente CT non privilegiato contro il montaggio del collegamento dell'host. Ritornando all'ACL.", "content:": "contenuto:", "could not be compiled for kernel": "non può essere compilato per il kernel", - "custom path(s) saved.": "percorsi personalizzati salvati.", "default": "predefinito", "delete the credentials file (if any)": "eliminare il file delle credenziali (se presente)", "delete the matching line from /etc/fstab": "elimina la riga corrispondente da /etc/fstab", "descriptor + VMDK files": "descrittore + file VMDK", - "device(s) added to VM": "dispositivi aggiunti alla VM", "devices": "dispositivi", "did not become ready. Skipping.": "non è diventato pronto. Saltare.", "disk(s) added to CT": "disco(i) aggiunto(i) a CT", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "Linee iface orfane, nessun impatto sul ripristino", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "altri archivi .tar — non backup dell'host ProxMenux (ad esempio PVE vzdump o tarball non correlati).", "packages (this may take a few minutes)...": "pacchetti (l'operazione potrebbe richiedere alcuni minuti)...", - "packages.": "pacchetti.", "parent PF:": "PF genitore:", "partition(s). Partition table preserved.": "partizione(i). Tabella delle partizioni conservata.", "paths for next boot (/etc/pve, guests, drivers, ...)": "percorsi per l'avvio successivo (/etc/pve, guest, driver, ...)", diff --git a/lang/pt.json b/lang/pt.json index c13957d6..c29e9276 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -644,6 +644,7 @@ "Completed Successfully!": "Concluído com sucesso!", "Completed with errors —": "Concluído com erros -", "Completed.": "Concluído.", + "Completed. Devices added to VM {vmid}: {count}.": "Concluído. Dispositivos adicionados à VM {vmid}: {count}.", "Completed. Press Enter to return to menu...": "Concluído. Pressione Enter para retornar ao menu...", "Completing pending package configurations...": "Concluindo configurações de pacotes pendentes...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "Verificação de conformidade (PCI-DSS, HIPAA, etc.)", @@ -1045,7 +1046,6 @@ "Current user": "Usuário atual", "Current user UID, GID and groups": "UID, GID e grupos do usuário atual", "Current version:": "Versão atual:", - "Currently": "Atualmente", "Currently Mounted:": "Atualmente montado:", "Currently configured target:": "destino atualmente configurado:", "Currently mounted:": "Atualmente montado:", @@ -1068,6 +1068,7 @@ "Custom path": "Caminho personalizado", "Custom path...": "Caminho personalizado...", "Custom paths are included in BOTH default and custom backup profiles.": "Os caminhos personalizados estão incluídos em AMBOS os perfis de backup padrão e personalizados.", + "Custom paths currently saved: {count}.": "caminhos personalizados salvos atualmente: {count}.", "Custom restore by components": "Restauração personalizada por componentes", "Custom restore by paths": "restauração personalizada por caminhos", "Custom scripts and ProxMenux files": "Scripts personalizados e arquivos ProxMenux", @@ -2220,7 +2221,6 @@ "Installed": "Instalado", "Installed at:": "Instalado em:", "Installed components:": "Componentes instalados:", - "Installed:": "Instalado:", "Installer already downloaded and verified.": "Instalador já baixado e verificado.", "Installer copied to container.": "Instalador copiado para contêiner.", "Installer downloaded.": "Instalador baixado.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Atualização do pacote concluída com avisos, continuando...", "Package update had issues, checking details...": "A atualização do pacote teve problemas, verificando detalhes...", "Packages from backup to install:": "Pacotes do backup para instalação:", + "Packages installed: {count}.": "Pacotes instalados: {count}.", "Packages upgrade successfull": "Atualização de pacotes bem-sucedida", "Packages upgraded": "Pacotes atualizados", "Packages:": "Pacotes:", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod falhou — provavelmente CT sem privilégios contra montagem de ligação do host. Voltando ao ACL.", "content:": "contente:", "could not be compiled for kernel": "não foi possível compilar para o kernel", - "custom path(s) saved.": "caminhos personalizados salvos.", "default": "padrão", "delete the credentials file (if any)": "exclua o arquivo de credenciais (se houver)", "delete the matching line from /etc/fstab": "exclua a linha correspondente de /etc/fstab", "descriptor + VMDK files": "descritor + arquivos VMDK", - "device(s) added to VM": "dispositivos adicionados à VM", "devices": "dispositivos", "did not become ready. Skipping.": "não ficou pronto. Pulando.", "disk(s) added to CT": "disco(s) adicionado(s) ao CT", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "linhas iface órfãs, sem impacto na restauração", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "outros arquivos .tar — não backups de host ProxMenux (por exemplo, PVE vzdump ou tarballs não relacionados).", "packages (this may take a few minutes)...": "pacotes (isso pode levar alguns minutos)...", - "packages.": "pacotes.", "parent PF:": "pai PF:", "partition(s). Partition table preserved.": "partição(ões). Tabela de partição preservada.", "paths for next boot (/etc/pve, guests, drivers, ...)": "caminhos para a próxima inicialização (/etc/pve, convidados, drivers, ...)", diff --git a/lang/sk.json b/lang/sk.json index 3b26488a..f19561d0 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -644,6 +644,7 @@ "Completed Successfully!": "Úspešne dokončené.", "Completed with errors —": "Dokončené s chybami -", "Completed.": "Dokončené.", + "Completed. Devices added to VM {vmid}: {count}.": "Dokončené. Zariadenia pridané do VM {vmid}: {count}.", "Completed. Press Enter to return to menu...": "Dokončené. Stlačte Enter pre návrat do menu...", "Completing pending package configurations...": "Dokončenie čakajúcich konfigurácií balíka...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "kontroly súladu (PCI-DSS, HIPAA atď.)", @@ -1045,7 +1046,6 @@ "Current user": "Aktuálny používateľ", "Current user UID, GID and groups": "UID, GID a skupiny aktuálneho používateľa", "Current version:": "Aktuálna verzia:", - "Currently": "Aktuálne", "Currently Mounted:": "Aktuálne pripojené:", "Currently configured target:": "Aktuálne nastavený cieľ:", "Currently mounted:": "Aktuálne pripojené:", @@ -1068,6 +1068,7 @@ "Custom path": "Vlastná cesta", "Custom path...": "Vlastná cesta...", "Custom paths are included in BOTH default and custom backup profiles.": "Vlastné cesty sú zahrnuté v predvolenom aj vlastnom profile zálohy.", + "Custom paths currently saved: {count}.": "Aktuálne uložené vlastné cesty: {count}.", "Custom restore by components": "Vlastná obnova podľa súčastí", "Custom restore by paths": "Vlastná obnova podľa ciest", "Custom scripts and ProxMenux files": "Vlastné skripty a súbory ProxMenux", @@ -2220,7 +2221,6 @@ "Installed": "Nainštalované", "Installed at:": "Nainštalované v:", "Installed components:": "Nainštalované súčasti:", - "Installed:": "Nainštalované:", "Installer already downloaded and verified.": "Inštalátor už je stiahnutý a overený.", "Installer copied to container.": "Inštalátor skopírovaný do kontajnera.", "Installer downloaded.": "Inštalátor bol stiahnutý.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Aktualizácia balíkov skončila s upozorneniami, pokračujem...", "Package update had issues, checking details...": "Pri aktualizácii balíkov nastali problémy, kontrolujem podrobnosti...", "Packages from backup to install:": "Balíky zo zálohy na inštaláciu:", + "Packages installed: {count}.": "Nainštalované balíky: {count}.", "Packages upgrade successfull": "Aktualizácia balíkov bola úspešná", "Packages upgraded": "Aktualizované balíky", "Packages:": "Balíky:", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod zlyhalo - pravdepodobne ide o neprivilegovaný CT nad bind mountom z hosta. Skúšam náhradné riešenie cez ACL.", "content:": "obsah:", "could not be compiled for kernel": "nepodarilo sa skompilovať pre jadro", - "custom path(s) saved.": "uložených vlastných ciest.", "default": "predvolené", "delete the credentials file (if any)": "vymazať súbor s prihlasovacími údajmi (ak existuje)", "delete the matching line from /etc/fstab": "odstrániť zodpovedajúci riadok z /etc/fstab", "descriptor + VMDK files": "popisovač + VMDK súbory", - "device(s) added to VM": "zariadení pridaných do VM", "devices": "zariadenia", "did not become ready. Skipping.": "nebol pripravený. Preskakujem.", "disk(s) added to CT": "diskov pridaných do CT", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "osirelé iface riadky, bez vplyvu na obnovu", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "iné .tar archívy - nie sú to ProxMenux zálohy hosta (napr. PVE vzdump alebo nesúvisiace tar archívy).", "packages (this may take a few minutes)...": "balíkov (môže to trvať pár minút)...", - "packages.": "balíkov.", "parent PF:": "rodičovská PF:", "partition(s). Partition table preserved.": "oddielov. Tabuľka oddielov zostala zachovaná.", "paths for next boot (/etc/pve, guests, drivers, ...)": "cesty na najbližší štart (/etc/pve, hostia, ovládače, ...)", diff --git a/lang/sv.json b/lang/sv.json index b7d80896..3480aee4 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -644,6 +644,7 @@ "Completed Successfully!": "Avslutad framgångsrikt!", "Completed with errors —": "Kompletterad med fel —", "Completed.": "Avslutad.", + "Completed. Devices added to VM {vmid}: {count}.": "Klar. Enheter som har lagts till VM {vmid}: {count}.", "Completed. Press Enter to return to menu...": "Avslutad. Tryck på Enter för att återgå till menyn...", "Completing pending package configurations...": "Slutför väntande paketkonfigurationer...", "Compliance checking (PCI-DSS, HIPAA, etc.)": "Överensstämmelsekontroll (PCI-DSS, HIPAA, etc.)", @@ -1045,7 +1046,6 @@ "Current user": "Nuvarande användare", "Current user UID, GID and groups": "Aktuell användar-UID, GID och grupper", "Current version:": "Aktuell version:", - "Currently": "För närvarande", "Currently Mounted:": "För närvarande monterad:", "Currently configured target:": "För närvarande konfigurerat mål:", "Currently mounted:": "För närvarande monterad:", @@ -1068,6 +1068,7 @@ "Custom path": "Anpassad sökväg", "Custom path...": "Anpassad sökväg...", "Custom paths are included in BOTH default and custom backup profiles.": "Anpassade sökvägar ingår i BÅDE standard- och anpassade säkerhetskopieringsprofiler.", + "Custom paths currently saved: {count}.": "Anpassade sökvägar sparade för närvarande: {count}.", "Custom restore by components": "Anpassad återställning av komponenter", "Custom restore by paths": "Anpassad återställning av sökvägar", "Custom scripts and ProxMenux files": "Anpassade skript och ProxMenux-filer", @@ -2220,7 +2221,6 @@ "Installed": "Installerad", "Installed at:": "Installerad på:", "Installed components:": "Installerade komponenter:", - "Installed:": "Installerat:", "Installer already downloaded and verified.": "Installationsprogrammet har redan laddats ner och verifierats.", "Installer copied to container.": "Installatören kopierad till behållaren.", "Installer downloaded.": "Installationsprogrammet nedladdat.", @@ -3178,6 +3178,7 @@ "Package update completed with warnings, continuing...": "Paketuppdateringen slutförd med varningar, fortsätter...", "Package update had issues, checking details...": "Paketuppdateringen hade problem, kontrollerade detaljer...", "Packages from backup to install:": "Paket från säkerhetskopia till installation:", + "Packages installed: {count}.": "Paket installerade: {count}.", "Packages upgrade successfull": "Paketuppgraderingen lyckades", "Packages upgraded": "Paketen har uppgraderats", "Packages:": "Paket:", @@ -5057,12 +5058,10 @@ "chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod misslyckades — sannolikt oprivilegierad CT mot värdbindningsmontering. Faller tillbaka till ACL.", "content:": "innehåll:", "could not be compiled for kernel": "kunde inte kompileras för kärnan", - "custom path(s) saved.": "anpassade sökvägar har sparats.", "default": "standard", "delete the credentials file (if any)": "radera autentiseringsfilen (om någon)", "delete the matching line from /etc/fstab": "ta bort den matchande raden från /etc/fstab", "descriptor + VMDK files": "descriptor + VMDK-filer", - "device(s) added to VM": "enhet(er) har lagts till i VM", "devices": "enheter", "did not become ready. Skipping.": "blev inte redo. Hoppa över.", "disk(s) added to CT": "disk(ar) har lagts till i CT", @@ -5238,7 +5237,6 @@ "orphan iface lines, no impact on restore": "föräldralösa iface-linjer, ingen inverkan på återställning", "other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "andra .tar-arkiv – inte ProxMenux-värdsäkerhetskopior (t.ex. PVE vzdump eller orelaterade tarballs).", "packages (this may take a few minutes)...": "paket (detta kan ta några minuter)...", - "packages.": "paket.", "parent PF:": "förälder PF:", "partition(s). Partition table preserved.": "partition(er). Partitionstabell bevarad.", "paths for next boot (/etc/pve, guests, drivers, ...)": "sökvägar för nästa start (/etc/pve, gäster, drivrutin, ...)", diff --git a/scripts/backup_restore/backup_host.sh b/scripts/backup_restore/backup_host.sh index f86783ad..35b82d60 100755 --- a/scripts/backup_restore/backup_host.sh +++ b/scripts/backup_restore/backup_host.sh @@ -919,7 +919,11 @@ _bk_manage_extra_paths() { if (( count == 0 )); then preview="$(hb_translate "You haven't added any custom paths yet.")" else - preview="$(hb_translate "Currently"): \Zb\Z4${count}\Zn $(hb_translate "custom path(s) saved.")" + preview="$(hb_translate "Custom paths currently saved: {count}.")" + if [[ "$preview" != *'{count}'* ]]; then + preview="Custom paths currently saved: {count}." + fi + preview="${preview//\{count\}/"\Zb\Z4${count}\Zn"}" fi preview+=$'\n\n'"$(hb_translate "Custom paths are included in BOTH default and custom backup profiles.")" @@ -3878,7 +3882,13 @@ _rs_run_complete_extras() { "${installable[@]}" >>"$apt_log" 2>&1 local apt_rc=$? if (( apt_rc == 0 )); then - msg_ok "$(translate "Installed:") ${#installable[@]} $(translate "packages.")" + local installed_message + installed_message="$(translate "Packages installed: {count}.")" + if [[ "$installed_message" != *'{count}'* ]]; then + installed_message="Packages installed: {count}." + fi + installed_message="${installed_message//\{count\}/${#installable[@]}}" + msg_ok "$installed_message" else msg_warn "$(translate "apt-get exited") ${apt_rc} — $(translate "see log:") $apt_log" fi diff --git a/scripts/storage/add_controller_nvme_vm.sh b/scripts/storage/add_controller_nvme_vm.sh index d249bd17..b8e8186f 100644 --- a/scripts/storage/add_controller_nvme_vm.sh +++ b/scripts/storage/add_controller_nvme_vm.sh @@ -694,7 +694,14 @@ apply_assignment() { echo -e "${TAB}${BL}Log: ${LOG_FILE}${CL}" if [[ "$assigned_count" -gt 0 ]]; then - msg_ok "$(translate "Completed.") $assigned_count $(translate "device(s) added to VM") ${SELECTED_VMID}." + local completed_message + completed_message="$(translate "Completed. Devices added to VM {vmid}: {count}.")" + if [[ "$completed_message" != *'{vmid}'* || "$completed_message" != *'{count}'* ]]; then + completed_message="Completed. Devices added to VM {vmid}: {count}." + fi + completed_message="${completed_message//\{vmid\}/$SELECTED_VMID}" + completed_message="${completed_message//\{count\}/$assigned_count}" + msg_ok "$completed_message" else msg_warn "$(translate "No new Controller/NVMe entries were added.")" fi diff --git a/tests/test_backup_archives_empty.cjs b/tests/test_backup_archives_empty.cjs new file mode 100644 index 00000000..c78c5797 --- /dev/null +++ b/tests/test_backup_archives_empty.cjs @@ -0,0 +1,91 @@ +// Offline JSX seam, following the TypeScript compiler harness in adjacent tests. +// No component imports, React effects, API calls or host-management execution. +// Run from the repository root: node tests/test_backup_archives_empty.cjs +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { createRequire } = require('node:module'); +const app = path.resolve(__dirname, '../AppImage'); +const ts = createRequire(path.join(app, 'package.json'))('typescript'); +const read = file => fs.readFileSync(path.join(app, file), 'utf8'); +const parse = file => ts.createSourceFile(file, read(file), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); +const compile = text => ts.transpileModule(text, { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, jsx: ts.JsxEmit.React }, +}).outputText; +const printer = ts.createPrinter(); +const provider = parse('lib/i18n/provider.tsx'); +const pure = provider.statements.filter(node => ts.isFunctionDeclaration(node) && + ['getMessage', 'interpolate'].includes(node.name?.text)); +assert.equal(pure.length, 2); +let lookup; +function visitLookup(node) { + if (ts.isVariableDeclaration(node) && node.name.getText(provider) === 't' && + node.initializer && ts.isCallExpression(node.initializer) && + node.initializer.expression.getText(provider) === 'useCallback') lookup = node.initializer.arguments[0]; + ts.forEachChild(node, visitLookup); +} +visitLookup(provider); +assert.ok(lookup, 'real provider lookup callback'); +const lookupCode = pure.map(node => printer.printNode(ts.EmitHint.Unspecified, node, provider)).join('\n') + + '\nconst t = ' + printer.printNode(ts.EmitHint.Expression, lookup, provider) + ';'; +const catalogs = Object.fromEntries(fs.readdirSync(path.join(app, 'messages')).filter(locale => + fs.existsSync(path.join(app, 'messages', locale, 'common.json'))).map(locale => + [locale, JSON.parse(read(`messages/${locale}/common.json`))])); +// Compile only trusted, checked-out source; no catalog/input strings enter code. +const translator = (language, messages = catalogs) => + new Function('MESSAGE_CATALOG', 'language', compile(lookupCode) + '\nreturn t;')(messages, language); + +const source = parse('components/host-backup.tsx'); +let branch; +function visitBranch(node) { + if (ts.isConditionalExpression(node) && node.condition.getText(source) === 'archivesErr && remoteArchivesErr') branch = node; + ts.forEachChild(node, visitBranch); +} +visitBranch(source); +assert.ok(branch, 'real archives loading/error/empty decision'); +// Keep the actual guards and JSX messages. Only replace the nonempty archive +// list with a sentinel: rendering its controls is outside this message test. +const transformed = ts.transform(branch, [context => root => { + const visit = node => { + if (ts.isConditionalExpression(node) && node.condition.getText(source) === 'unifiedArchives.length === 0') { + return ts.factory.updateConditionalExpression(node, node.condition, node.questionToken, + node.whenTrue, node.colonToken, ts.factory.createStringLiteral('NONEMPTY_ARCHIVES')); + } + return ts.visitEachChild(node, visit, context); + }; + return ts.visitNode(root, visit); +}]); +const expression = printer.printNode(ts.EmitHint.Expression, transformed.transformed[0], source); +transformed.dispose(); +const render = new Function('React', 'Loader2', 't', 'archivesErr', 'remoteArchivesErr', + 'archivesResp', 'remoteArchivesResp', 'unifiedArchives', compile(`const result = ${expression};`) + '\nreturn result;'); +// Text-only JSX sink: TypeScript performs the real JSX whitespace conversion. +const React = { createElement: (tag, props, ...children) => children.flat(Infinity).filter(x => x != null && x !== false).join('') }; +const text = (locale = 'en', overrides = {}, messages = catalogs) => { + const state = { archivesErr: null, remoteArchivesErr: null, archivesResp: { archives: [] }, + remoteArchivesResp: { snapshots: [] }, unifiedArchives: [], ...overrides }; + return render(React, 'spinner', translator(locale, messages), state.archivesErr, state.remoteArchivesErr, + state.archivesResp, state.remoteArchivesResp, state.unifiedArchives); +}; + +assert.equal(text(), 'No backup archives found.'); +assert.equal(text('it'), 'No backup archives found.', 'Italian catalog uses English fallback'); +// Keep fallback coverage even after all shipped locales gain the new key. +assert.equal(text('missing', {}, { ...catalogs, missing: {} }), 'No backup archives found.'); +for (const locale of Object.keys(catalogs)) { + // Existing catalogs intentionally have no new key; actual provider falls back. + if (catalogs[locale].backup?.archives?.emptyMessage === undefined) { + assert.equal(text(locale), 'No backup archives found.', `${locale}: missing-key fallback`); + } +} +for (const count of [1, 3]) assert.equal(text('en', { unifiedArchives: Array(count).fill({}) }), 'NONEMPTY_ARCHIVES'); +assert.equal(text('en', { archivesResp: undefined, remoteArchivesResp: undefined }), translator('en')('backup.common.loading')); +assert.equal(text('en', { archivesErr: true, remoteArchivesErr: true }), translator('en')('backup.archives.loadFailed')); +// Preserve existing partial-source semantics; no claim that every source was queried. +assert.equal(text('en', { archivesErr: true, archivesResp: undefined }), 'No backup archives found.'); +assert.equal(text('en', { remoteArchivesErr: true, remoteArchivesResp: undefined }), 'No backup archives found.'); +assert.equal(translator('en')('backup.manual.run'), 'Run'); +assert.ok(read('components/host-backup.tsx').includes('{t("backup.manual.run")}'), 'manual action remains'); +console.log('PASS: actual archives JSX seam, zero/one/multiple, loading/errors, locale fallback, Run unchanged'); +// Export only test seams for separate, unapplied linguistic proposal checks. +module.exports = { text, translator, catalogs }; diff --git a/tests/test_backup_destination_messages.cjs b/tests/test_backup_destination_messages.cjs new file mode 100644 index 00000000..c1cc8e92 --- /dev/null +++ b/tests/test_backup_destination_messages.cjs @@ -0,0 +1,56 @@ +// Offline actual JSX/provider seams; no component/backend imports or effects. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const { createRequire } = require('node:module'); +const { translator, catalogs } = require('./test_backup_archives_empty.cjs'); +const app = path.resolve(__dirname, '../AppImage'); +const ts = createRequire(path.join(app, 'package.json'))('typescript'); +const source = ts.createSourceFile('host-backup.tsx', fs.readFileSync(path.join(app, 'components/host-backup.tsx'), 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); +const printer = ts.createPrinter(); +const React = { createElement: (tag, props, ...children) => children.flat(Infinity).filter(x => x != null && x !== false).join('') }; +function find(predicate) { + const found = []; + function visit(node) { if (predicate(node)) found.push(node); ts.forEachChild(node, visit); } + visit(source); + assert.equal(found.length, 1, 'unique actual message seam'); + return found[0]; +} +function render(node, locale, backups = 1, messages = catalogs) { + const code = ts.transpileModule(`const result = ${printer.printNode(ts.EmitHint.Expression, node, source)};`, { + compilerOptions: { target: ts.ScriptTarget.ES2020, jsx: ts.JsxEmit.React }, + }).outputText; + // Only trusted checked-out AST enters executable code, never catalog values. + return new Function('React', 't', 'backups', code + '\nreturn result;')(React, translator(locale, messages), backups); +} +const kept = find(node => ts.isBinaryExpression(node) && node.left.getText(source) === 'backups > 0'); +const keptMessage = 'Existing backup archives are not deleted.'; +for (const count of [1, 3]) assert.equal(render(kept, 'it', count), keptMessage, 'Italian catalog uses English fallback'); +assert.equal(render(kept, 'en', 0), false); +for (const locale of Object.keys(catalogs)) { + if (catalogs[locale].backup?.destinations?.backupsKeptMessage !== undefined && locale !== 'en') continue; + for (const count of [1, 3]) assert.equal(render(kept, locale, count), keptMessage, `${locale}: kept whole-message fallback`); +} +console.log('PASS: destination removal JSX guard 0/1/N and whole-message locale fallback'); +// Select the actual help paragraph, keeping its literal paths/JSX whitespace. +const help = find(node => ts.isJsxElement(node) && node.openingElement.tagName.getText(source) === 'p' && + /backup\.destinations\.localAttachHelp(?:Before|Message)/.test(node.getText(source))); +const helpMessage = 'Mount USB drives first. Leave blank to use the PVE storage path + /dump (fallback: /var/lib/vz/dump).'; +assert.equal(render(help, 'it'), helpMessage, 'Italian catalog uses English fallback'); +for (const locale of Object.keys(catalogs)) { + if (catalogs[locale].backup?.destinations?.localAttachHelpMessage !== undefined && locale !== 'en') continue; + assert.equal(render(help, locale), helpMessage, `${locale}: local attach help fallback`); +} +// Missing-key fallback must remain covered after shipped catalogs are complete. +const missing = { ...catalogs, missing: {} }; +assert.equal(render(kept, 'missing', 1, missing), keptMessage); +assert.equal(render(help, 'missing', 1, missing), helpMessage); +// Synthetic locale proves complete messages are translatable, not hardcoded. +const synthetic = { ...catalogs, fixture: { backup: { destinations: { + backupsKeptMessage: 'Fixture kept.', localAttachHelpMessage: 'Fixture help.', +} } } }; +assert.equal(render(kept, 'fixture', 1, synthetic), 'Fixture kept.'); +assert.equal(render(help, 'fixture', 1, synthetic), 'Fixture help.'); +assert.equal(catalogs.en.backup.destinations.kept, 'kept'); +assert.equal(catalogs.en.backup.destinations.localAttachHelpMiddle, 'USB drives'); +console.log('PASS: local attach JSX whole-message fallback and literal paths'); diff --git a/tests/test_tui_count_messages.py b/tests/test_tui_count_messages.py new file mode 100644 index 00000000..2335aaa6 --- /dev/null +++ b/tests/test_tui_count_messages.py @@ -0,0 +1,206 @@ +"""Offline message seams: no full administrative script is sourced or run. + +Run: python3 -m unittest discover -s tests -p test_tui_count_messages.py -v +Requires Bash and jq. Only extracted translation functions and bounded message +consumers execute. PATH contains jq only; VM/disk/backup commands are unavailable. +""" +import importlib.util +import json +from pathlib import Path +import re +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +VM = "scripts/storage/add_controller_nvme_vm.sh" +BACKUP = "scripts/backup_restore/backup_host.sh" + + +def between(path, start, end): + text = (ROOT / path).read_text() + assert text.count(start) == 1, (path, start) + tail = text.split(start, 1)[1] + assert end in tail, (path, end) + return start + tail.split(end, 1)[0] + + +def function(path, name): + # These two small functions have no unindented nested closing braces. + text = (ROOT / path).read_text() + match = re.search(r"^" + re.escape(name) + r"\(\) \{\n.*?^\}", text, re.M | re.S) + assert match, (path, name) + return match[0] + + +def lookup(catalog, text): + return (catalog or {}).get(text) or text + + +class CountMessages(unittest.TestCase): + def locales(self, key, translated): + return (("en", None), ("it", {}), ("zz", None), + ("it", json.loads((ROOT / "lang/it.json").read_text())), + ("xx", {key: translated}), + # Presence is the bounded contract: repeated tokens are replaced + # everywhere; unrelated braces remain literal, not a format DSL. + ("repeat", {key: translated + " / again " + key}), + ("extra", {key: translated + " / {unknown} $(literal) %s"}) + ) + self.malformed_locales(key) + + def malformed_locales(self, key): + required = ("{count}", "{vmid}") if "{vmid}" in key else ("{count}",) + # These cached values must fall back as a whole, not lose a value. + malformed = ["Translated without values."] + for token in required: + malformed.extend((key.replace(token, ""), + key.replace(token, "{renamed}"), + key.replace(token, token.upper()))) + return tuple(("bad", {key: text}) for text in malformed) + + def test_new_messages_are_discovered_by_cache_builder(self): + spec = importlib.util.spec_from_file_location( + "translation_cache", ROOT / ".github/scripts/build_translation_cache.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + keys = module.extract_translate_texts(ROOT / "scripts") + for key in ("Completed. Devices added to VM {vmid}: {count}.", + "Custom paths currently saved: {count}.", "Packages installed: {count}."): + self.assertIn(key, keys) + + def render(self, seam, setup, language="en", catalog=None): + jq = shutil.which("jq") + self.assertIsNotNone(jq, "jq is required for actual translation lookup") + with tempfile.TemporaryDirectory(prefix="pmx-count-messages-") as tmp: + tmp = Path(tmp) + (tmp / "bin").mkdir() + (tmp / "bin" / "jq").symlink_to(jq) + (tmp / "lang").mkdir() + if catalog is not None: + (tmp / "lang" / f"{language}.json").write_text(json.dumps(catalog)) + shell = function("scripts/utils.sh", "translate") + "\n" + shell += function("scripts/backup_restore/lib_host_backup_common.sh", "hb_translate") + shell += ''' +msg_ok() { printf 'OK:%s\\n' "$1"; } +msg_warn() { printf 'WARN:%s\\n' "$1"; } +consumer() { +''' + shell += setup + "\n" + seam + "\n}\nconsumer\n" + env = {"PATH": str(tmp / "bin"), "LANGUAGE": language, + "LANG_DIR": str(tmp / "lang"), "LC_ALL": "C"} + result = subprocess.run(["/bin/bash", "--noprofile", "--norc", "-c", shell], + env=env, text=True, capture_output=True, timeout=10) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stderr, "") + return result.stdout.rstrip("\n") + + def test_vm_devices_zero_one_many_and_translation(self): + seam = between(VM, ' if [[ "$assigned_count" -gt 0 ]]; then', + ' if [[ "${IOMMU_ALREADY_ACTIVE:-0}" == "1" ]]; then') + key = "Completed. Devices added to VM {vmid}: {count}." + translated = "Devices: {count} / VM {vmid} / done 100%." + for count in (0, 1, 3): + for vmid in (101, 987654): + for language, catalog in self.locales(key, translated): + with self.subTest(count=count, vmid=vmid, language=language): + actual = self.render(seam, f"assigned_count={count}; SELECTED_VMID={vmid}", + language, catalog) + if count == 0: + expected = "WARN:" + lookup(catalog, "No new Controller/NVMe entries were added.") + elif language == "xx": + expected = f"OK:Devices: {count} / VM {vmid} / done 100%." + elif language == "bad": + expected = f"OK:Completed. Devices added to VM {vmid}: {count}." + else: + expected = "OK:" + lookup(catalog, key).replace("{vmid}", str(vmid)).replace("{count}", str(count)) + self.assertEqual(actual, expected) + + def test_vm_summary_counts_successful_new_assignments_only(self): + seam = between(VM, ' local pci bdf assigned_count=0', + ' if [[ "${IOMMU_ALREADY_ACTIVE:-0}" == "1" ]]; then') + for devices, expected_count in (([], 0), (["ok1"], 1), + (["ok1", "ok2", "ok3"], 3), + (["existing", "failed"], 0), + (["existing", "ok1", "failed", "ok2"], 2)): + with self.subTest(devices=devices): + setup = 'SELECTED_CONTROLLER_PCIS=(' + ' '.join(devices) + ')\n' + setup += '''SELECTED_VMID=101; hostpci_idx=0; LOG_FILE="$LANG_DIR/vm.log" +msg_info() { :; } +msg_error() { printf 'ERROR:%s\\n' "$1"; } +_pci_function_assigned_to_vm() { [[ "$1" == existing ]]; } +_pci_storage_display_name() { printf '%s' "$1"; } +qm() { + [[ "$1 $2" == 'set 101' && "$3" == "--hostpci${hostpci_idx}" ]] || return 99 + [[ "$4" != 'failed,pcie=1' ]] +} +''' + actual = self.render(seam, setup).splitlines()[-1] + expected = (f"OK:Completed. Devices added to VM 101: {expected_count}." if expected_count + else "WARN:No new Controller/NVMe entries were added.") + self.assertEqual(actual, expected) + + def test_custom_paths_zero_one_many_and_translation(self): + seam = between(BACKUP, ' local -a paths=()', ' local choice') + seam += '\n printf "%s" "$preview"\n' + key = "Custom paths currently saved: {count}." + translated = "Saved 100%: {count} custom paths." + suffix = "Custom paths are included in BOTH default and custom backup profiles." + for count in (0, 1, 3): + setup = 'hb_load_extra_paths() {\n' + setup += ''.join(f"printf '%s\\n' '/fixture/path {i}'\n" for i in range(count)) + setup += ':;\n}\n' + for language, catalog in self.locales(key, translated): + with self.subTest(count=count, language=language): + actual = self.render(seam, setup, language, catalog) + colored_count = rf"\Zb\Z4{count}\Zn" + if count == 0: + expected = lookup(catalog, "You haven't added any custom paths yet.") + elif language == "xx": + expected = f"Saved 100%: {colored_count} custom paths." + elif language == "bad": + expected = f"Custom paths currently saved: {colored_count}." + else: + expected = lookup(catalog, key).replace("{count}", colored_count) + self.assertEqual(actual, expected + "\n\n" + lookup(catalog, suffix)) + + def test_installed_packages_zero_one_many_success_and_failure(self): + # Keep the actual nonempty guard and apt return-code consumer; omit + # unrelated preview/cache/log setup. apt-get is a function stub only. + guard = ' if (( ${#installable[@]} > 0 )); then\n' + self.assertEqual((ROOT / BACKUP).read_text().count(guard), 1) + seam = guard + between(BACKUP, ' DEBIAN_FRONTEND=noninteractive \\\n', + ' if (( ${#unknown[@]} > 0 )); then') + key = "Packages installed: {count}." + translated = "Done 100% / {count} installed packages." + for count in (0, 1, 3): + for status in (0, 100): + for language, catalog in self.locales(key, translated): + with self.subTest(count=count, status=status, language=language): + setup = f'installable=({" ".join("pkg" + str(i) for i in range(count))}); apt_status={status}\n' + setup += '''apt_log="$LANG_DIR/apt.log" +apt-get() { + [[ "$DEBIAN_FRONTEND" == noninteractive ]] || return 99 + [[ "$1 $2 $3 $4 $5 $6" == 'install -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' ]] || return 99 + shift 6 + [[ "$*" == "${installable[*]}" ]] || return 99 + printf 'CALLED\\n' > "$LANG_DIR/calls" + return "$apt_status" +} +''' + check_call = '\nif [[ -f "$LANG_DIR/calls" ]]; then printf "CALLED\\n"; fi\n' + actual = self.render(seam + check_call, setup, language, catalog) + if count == 0: + self.assertEqual(actual, "") + elif status: + prefix = "WARN:" + lookup(catalog, "apt-get exited") + " 100 — " + lookup(catalog, "see log:") + self.assertRegex(actual, "^" + re.escape(prefix) + r" .*/lang/apt.log\nCALLED$") + else: + message = (f"Done 100% / {count} installed packages." if language == "xx" + else f"Packages installed: {count}." if language == "bad" + else lookup(catalog, key).replace("{count}", str(count))) + self.assertEqual(actual, f"OK:{message}\nCALLED") + + +if __name__ == "__main__": + unittest.main()