Merge remote-tracking branch 'origin/develop' into beta/1.2.6.2

This commit is contained in:
MacRimi
2026-09-25 21:51:23 +02:00
28 changed files with 661 additions and 78 deletions
@@ -0,0 +1,145 @@
"""Offline, bounded real Bash consumers: no administrative entrypoints are sourced."""
import importlib.util
import json
from pathlib import Path
import re
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[3]
SCRIPTS = ROOT / 'scripts'
NETWORK = {
'share/iscsi_host.sh': 1,
'share/nfs_client.sh': 1,
'share/nfs_host.sh': 2,
'share/samba_client.sh': 1,
'share/samba_host.sh': 2,
}
MOUNT = 'Mounting existing filesystem ({filesystem})...'
RAID = 'If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.'
def function(text, name):
start = text.index(name + '() {')
return text[start:text.index('\n}', start) + 2] + '\n'
def network_blocks(path):
lines = path.read_text().splitlines()
blocks = []
for i, line in enumerate(lines):
if re.search(r'^\s*if (?:pct exec .* -- )?nc -z -w 2 ', line):
depth = len(line) - len(line.lstrip())
end = next(j for j in range(i + 1, len(lines))
if lines[j].strip() == 'fi' and len(lines[j]) - len(lines[j].lstrip()) == depth)
block = '\n'.join(lines[i:end + 1])
assert '$(translate "Open")' in block and '$(translate "Closed")' in block or '$(translate "Probe failed")' in block
blocks.append(block)
return blocks
def run(code, translations=None, statuses='1', variables='', dialog=False):
translations = translations or {}
with tempfile.TemporaryDirectory(prefix='tui-wording-') as directory:
root = Path(directory)
(root / 'xx.json').write_text(json.dumps(translations))
body = f'''LANGUAGE=xx LANG_DIR={root!s}
{function((SCRIPTS / 'utils.sh').read_text(), 'translate')}
GN='\\033[32m' RD='\\033[31m' CL='\\033[0m'
{variables}
statuses=({statuses})
nc() {{ local code=${{statuses[0]}}; statuses=("${{statuses[@]:1}}"); return "$code"; }}
pct() {{ [[ "$1" == exec && "$3" == -- && "$4" == nc ]] || return 98; shift 3; nc "$@"; }}
msg_info() {{ printf 'INFO:%s\\n' "$1"; }}
msg_error() {{ printf 'ERROR:%s\\n' "$1"; }}
msg_ok() {{ printf 'OK:%s\\n' "$1"; }}
msg_title() {{ :; }}
show_proxmenux_logo() {{ :; }}
pmx_journal_context() {{ :; }}
blkid() {{ [[ "$2" == TYPE ]] && printf 'ext4\\n' || printf 'UUID123\\n'; }}
mkdir() {{ :; }}
mount() {{ return 1; }}
dialog() {{ printf 'DIALOG:%s\\n' "$*"; }}
{code}
'''
result = subprocess.run(['/bin/bash', '--noprofile', '--norc', '-c', body],
env={'PATH': '/usr/bin:/bin', 'LC_ALL': 'C.UTF-8'},
capture_output=True, text=True, timeout=10)
if result.returncode or result.stderr:
raise AssertionError((result.returncode, result.stderr, result.stdout))
return result.stdout
class TuiNetworkStorageWording(unittest.TestCase):
def test_failed_tcp_probe_is_not_declared_closed_at_every_caller(self):
for path, count in NETWORK.items():
blocks = network_blocks(SCRIPTS / path)
self.assertEqual(len(blocks), count, path)
for index, block in enumerate(blocks):
cases = [('0 1', 'Open'), ('1 1', 'Probe failed'), ('124 124', 'Probe failed')]
if 'samba_' in path:
cases.append(('1 0', 'Open'))
for codes, expected in cases:
with self.subTest(path=path, index=index, codes=codes):
output = run(block, statuses=codes, translations={'Probe failed': 'SONDAGGIO FALLITO'})
self.assertIn('SONDAGGIO FALLITO' if expected == 'Probe failed' else 'Open', output)
self.assertNotIn('Closed', output)
if expected == 'Open':
self.assertNotIn('SONDAGGIO FALLITO', output)
else:
fallback = run(block, statuses=codes)
self.assertIn('Probe failed', fallback)
self.assertNotIn('Closed', fallback)
def test_mount_progress_is_whole_and_uses_real_lookup_with_fallback(self):
source = (SCRIPTS / 'share/disk_host.sh').read_text()
start = source.index('mount_existing_disk() {')
portion = source[start:source.index(' if ! mount "$disk"', start)] + ' return 0\n}\n'
for translation, expected in [({}, MOUNT.replace('{filesystem}', 'ext4')),
({MOUNT: 'SYNTHETIC {filesystem} in corso'}, 'SYNTHETIC ext4 in corso'),
({MOUNT: 'SYNTHETIC malformed'}, MOUNT.replace('{filesystem}', 'ext4'))]:
with self.subTest(translation=translation):
output = run(portion + '\nmount_existing_disk /dev/fixture /mnt/fixture', translations=translation)
self.assertIn('INFO:' + expected, output)
self.assertNotIn('{filesystem}', output)
def test_raid_warning_is_complete_message_with_disk_identifier_and_fallback(self):
source = (SCRIPTS / 'storage/disk-passthrough_ct.sh').read_text().splitlines()
invocation = '\n'.join(source[457:460]) # original dialog invocation, not the raid detection command
for translation, expected in [({}, RAID), ({RAID: 'SYNTHETIC RAID metadata remains'}, 'SYNTHETIC RAID metadata remains')]:
with self.subTest(translation=translation):
output = run(invocation, translations=translation,
variables="BACKTITLE=fixture DISK_INFO='/dev/fixture 2TiB' UI_RESULT_H=16 UI_RESULT_W=80")
self.assertIn(expected, output)
self.assertIn('/dev/fixture 2TiB', output)
self.assertNotIn('remove the RAID metadata or format it manually using external tools. RAID metadata', output)
def test_shipped_catalogs_use_english_fallback_for_new_keys(self):
catalogs = sorted((ROOT / 'lang').glob('*.json'))
self.assertTrue(catalogs)
block = network_blocks(SCRIPTS / 'share/iscsi_host.sh')[0]
source = (SCRIPTS / 'share/disk_host.sh').read_text()
start = source.index('mount_existing_disk() {')
mount = source[start:source.index(' if ! mount "$disk"', start)] + ' return 0\n}\nmount_existing_disk /dev/fixture /mnt/fixture'
raid = '\n'.join((SCRIPTS / 'storage/disk-passthrough_ct.sh').read_text().splitlines()[457:460])
for path in catalogs:
with self.subTest(locale=path.name):
catalog = json.loads(path.read_text())
self.assertIn(catalog.get('Probe failed') or 'Probe failed', run(block, statuses='1', translations=catalog))
self.assertIn((catalog.get(MOUNT) or MOUNT).replace('{filesystem}', 'ext4'), run(mount, translations=catalog))
self.assertIn(catalog.get(RAID) or RAID, run(raid, translations=catalog,
variables="BACKTITLE=fixture DISK_INFO='/dev/fixture' UI_RESULT_H=16 UI_RESULT_W=80"))
def test_extractor_discovers_whole_keys(self):
spec = importlib.util.spec_from_file_location('cache_builder', ROOT / '.github/scripts/build_translation_cache.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
keys = module.extract_translate_texts(SCRIPTS)
for key in (MOUNT, RAID, 'Probe failed'):
self.assertTrue(key in keys, key)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,23 @@
"""Run the inert Node TUI consumer fixture in the existing offline i18n lane."""
import os
from pathlib import Path
import subprocess
import unittest
ROOT = Path(__file__).resolve().parents[3]
class TuiUpgradeGpuWording(unittest.TestCase):
def test_source_extracted_node_consumer(self):
fixture = ROOT / 'tests/test_tui_upgrade_gpu_wording.cjs'
self.assertTrue(fixture.is_file(), 'Node fixture must be packaged with the candidate')
node = os.environ.get('NODE_BINARY') or 'node'
result = subprocess.run([node, str(fixture)], cwd=ROOT, capture_output=True,
text=True, timeout=60, env={**os.environ, 'PYTHONDONTWRITEBYTECODE': '1'})
self.assertEqual(result.returncode, 0, result.stdout + '\n' + result.stderr)
self.assertIn('PASS: extracted upgrade/GPU consumers', result.stdout)
if __name__ == '__main__':
unittest.main()
+16 -31
View File
@@ -27,6 +27,7 @@ import { AppUpdaterEditor, type AppUpdateMethod } from "./app-updater-editor"
import { formatStorage } from "../lib/utils"
import { formatNetworkTraffic, getNetworkUnit } from "../lib/format-network"
import { fetchApi } from "../lib/api-config"
import { matchesVmSearch, normalizeVmSearchValue } from "../lib/vm-search"
import DOMPurify from "dompurify"
import { marked } from "marked"
import { getCountFormKey, useI18n, useT } from "@/lib/i18n/provider"
@@ -234,6 +235,9 @@ interface VMData {
diskread?: number
diskwrite?: number
ip?: string
// All LXC addresses reported by lxc-info. `ip` remains the primary
// address for backwards compatibility and compact card rendering.
ips?: string[]
update_check?: LxcUpdateCheck
// Proxmox tags as a raw string ("prod;web;monitoring" — PVE
// separator is ';' but ',' is also accepted). Rendered as
@@ -251,36 +255,6 @@ interface VMData {
modal_cache_revision?: number
}
function normalizeVmSearchValue(value: unknown): string {
return String(value ?? "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLocaleLowerCase()
}
// Type synonyms come from the active locale so a user can search by the
// word they'd naturally use ("contenedor", "machine virtuelle", …) rather
// than only the internal type token. "lxc" / "qemu" already match through
// vm.type, so the catalog entries only carry the natural-language terms.
function matchesVmSearch(
vm: VMData,
terms: string[],
typeSynonyms: { lxc: string; qemu: string },
): boolean {
if (terms.length === 0) return true
const searchable = normalizeVmSearchValue([
vm.name,
vm.vmid,
vm.type,
vm.type === "lxc" ? typeSynonyms.lxc : typeSynonyms.qemu,
vm.tags,
vm.description,
vm.ip,
...(vm.app_watches || []).map((app) => app.name || ""),
].join(" "))
return terms.every((term) => searchable.includes(term))
}
function hasLxcPendingUpdates(vm: VMData): boolean {
if (vm.type !== "lxc") return false
const osUpdates = vm.update_check?.count ?? 0
@@ -3295,13 +3269,16 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
// immediately without waiting for the batch.
let lxcIP: string | null | undefined = null
if (vm.type === "lxc") {
lxcIP = vmConfigs[vm.vmid]
lxcIP = vm.ip || vmConfigs[vm.vmid]
if (!lxcIP) {
const cached = vmModalCacheRef.current.details.get(vm.vmid) as any
lxcIP = cached?.lxc_ip_info?.primary_ip
|| (cached?.config ? extractIPFromConfig(cached.config, cached.lxc_ip_info) : null)
}
}
const additionalIpCount = lxcIP
? (vm.ips || []).filter((ip) => ip !== lxcIP).length
: 0
return (
<div key={vm.vmid}>
@@ -3380,6 +3357,14 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
<span className="text-sm text-foreground flex items-center gap-1">
<Network className="h-3 w-3 text-green-500" />
{lxcIP}
{additionalIpCount > 0 && (
<span
className="text-xs text-muted-foreground"
title={(vm.ips || []).join(", ")}
>
+{additionalIpCount}
</span>
)}
</span>
)}
</div>
+42
View File
@@ -0,0 +1,42 @@
export interface VMSearchable {
vmid: number
name: string
type: string
tags?: string
description?: string
ip?: string
ips?: string[]
app_watches?: Array<{ name?: string | null }>
}
export interface VMTypeSynonyms {
lxc: string
qemu: string
}
export function normalizeVmSearchValue(value: unknown): string {
return String(value ?? "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLocaleLowerCase()
}
export function matchesVmSearch(
vm: VMSearchable,
terms: string[],
typeSynonyms: VMTypeSynonyms,
): boolean {
if (terms.length === 0) return true
const searchable = normalizeVmSearchValue([
vm.name,
vm.vmid,
vm.type,
vm.type === "lxc" ? typeSynonyms.lxc : typeSynonyms.qemu,
vm.tags,
vm.description,
vm.ip,
...(vm.ips || []),
...(vm.app_watches || []).map((app) => app.name || ""),
].join(" "))
return terms.every((term) => searchable.includes(term))
}
+57 -27
View File
@@ -611,13 +611,14 @@ def parse_lxc_hardware_config(vmid, node):
return hardware_info
def _get_lxc_primary_ip_cached(vmid):
"""Return the LXC's primary non-Docker IP with an indefinite
cache. First read per CT spawns one `lxc-info` subprocess;
subsequent reads are free until the CT's lifecycle event drops
the entry via `_invalidate_lxc_ip`. A running CT's IP doesn't
change on its own — the invalidation on start/stop/reboot is the
only path that requires re-probing.
def _get_lxc_ip_info_cached(vmid):
"""Return an LXC's complete IP classification from the lifecycle cache.
First read per CT spawns one `lxc-info` subprocess; subsequent reads are
free until the CT's lifecycle event drops the entry via
`_invalidate_lxc_ip`. Keeping the complete result (rather than only the
primary address) lets `/api/vms` expose every address without changing
the cache's lifecycle semantics.
"""
try:
vmid_int = int(vmid)
@@ -626,11 +627,39 @@ def _get_lxc_primary_ip_cached(vmid):
if vmid_int in _lxc_ip_cache:
return _lxc_ip_cache[vmid_int]
info = get_lxc_ip_from_lxc_info(vmid_int)
ip = None
if info:
ip = info.get('primary_ip') or (info.get('real_ips') or [None])[0]
_lxc_ip_cache[vmid_int] = ip
return ip
_lxc_ip_cache[vmid_int] = info
return info
def _get_lxc_primary_ip_cached(vmid):
"""Return the primary IP for legacy callers of the LXC IP cache."""
info = _get_lxc_ip_info_cached(vmid)
if not info:
return None
return info.get('primary_ip') or (info.get('real_ips') or [None])[0]
def _normalise_lxc_ips(ip_info):
"""Return a stable, JSON-safe ordered list of LXC addresses.
`lxc-info` already decides which addresses are real or Docker addresses;
this helper deliberately does not reclassify or filter them. It merely
protects the list endpoint from malformed cache data and removes exact
duplicates while retaining the original order.
"""
if not isinstance(ip_info, dict):
return []
raw_ips = ip_info.get('all_ips')
if not isinstance(raw_ips, (list, tuple)):
return []
ips = []
seen = set()
for raw_ip in raw_ips:
ip = str(raw_ip).strip() if raw_ip is not None else ''
if ip and ip not in seen:
seen.add(ip)
ips.append(ip)
return ips
def _invalidate_lxc_ip(vmid):
@@ -669,11 +698,7 @@ def _warmup_lxc_ip_cache() -> int:
continue
if parts[1].lower() != 'running':
continue
info = get_lxc_ip_from_lxc_info(vmid_int)
ip = None
if info:
ip = info.get('primary_ip') or (info.get('real_ips') or [None])[0]
_lxc_ip_cache[vmid_int] = ip
_lxc_ip_cache[vmid_int] = get_lxc_ip_from_lxc_info(vmid_int)
count += 1
return count
@@ -1752,7 +1777,7 @@ _vm_mounts_cache: dict = {} # vmid -> (ts, payload) — LXC only
_VM_LIST_SEARCH_NOTES_TTL = 30
_vm_list_search_notes_cache: dict = {"ts": 0.0, "signature": (), "notes": {}}
_vm_list_search_notes_lock = threading.Lock()
# LXC primary IP cache — populated on first read, held indefinitely.
# LXC IP classification cache — populated on first read, held indefinitely.
# A running CT's IP doesn't change; the cache is invalidated only when
# the CT's lifecycle event fires (start/stop/reboot), so no periodic
# polling is needed. See `_handle_guest_lifecycle`.
@@ -6749,6 +6774,20 @@ def get_proxmox_vms():
upd = lxc_updates_map.get(str(resource.get('vmid')))
if upd is not None:
vm_data['update_check'] = upd
# IP addresses are core LXC inventory, not an App
# Watch feature. Keep `ip` for callers that expect
# a primary address and add `ips` for search and
# detail-aware clients. The cache is still warmed
# at startup and invalidated by LXC lifecycle events.
vm_data['ips'] = []
if resource.get('status') == 'running':
lxc_ip_info = _get_lxc_ip_info_cached(resource.get('vmid'))
lxc_ips = _normalise_lxc_ips(lxc_ip_info)
vm_data['ips'] = lxc_ips
if lxc_ip_info:
primary_ip = lxc_ip_info.get('primary_ip')
if isinstance(primary_ip, str) and primary_ip.strip():
vm_data['ip'] = primary_ip.strip()
# App Watch (Phase 2c) — list of registered
# apps per CT (0..N). Populates header badge,
# Updates modal connected row, and the App
@@ -6771,15 +6810,6 @@ def get_proxmox_vms():
app_list = lxc_app_map.get(str(resource.get('vmid'))) or []
if app_list:
vm_data['app_watches'] = app_list
# Apps dashboard reads this to build
# weblinks. Only paid on CTs that have
# registered apps; the IP is cached
# indefinitely and invalidated by the
# guest lifecycle hook on start/stop/reboot.
if vm_type == 'lxc' and resource.get('status') == 'running':
_ip = _get_lxc_primary_ip_cached(resource.get('vmid'))
if _ip:
vm_data['ip'] = _ip
docker_inventory = lxc_docker_map.get(str(resource.get('vmid')))
# Docker image drift is an Updates-tab feature,
# not an automatic app detection. Do not attach
@@ -0,0 +1,93 @@
"""Contracts for LXC IP inventory returned by the compact `/api/vms` list."""
import ast
import threading
import unittest
from pathlib import Path
SERVER_SOURCE = Path(__file__).resolve().parents[1] / "flask_server.py"
def load_server_functions(*names):
"""Compile selected production functions without importing host-bound Flask state."""
tree = ast.parse(SERVER_SOURCE.read_text())
functions = {
node.name: node
for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name in names
}
missing = set(names) - functions.keys()
if missing:
raise AssertionError(f"Missing production functions: {sorted(missing)}")
module = ast.Module(body=[functions[name] for name in names], type_ignores=[])
ast.fix_missing_locations(module)
namespace = {}
exec(compile(module, str(SERVER_SOURCE), "exec"), namespace)
return namespace
class LxcVmIpInventoryTests(unittest.TestCase):
def setUp(self):
self.server = load_server_functions("_normalise_lxc_ips", "get_proxmox_vms")
self.server.update({
"_get_lxc_update_status_map": lambda: {},
"_get_lxc_app_watch_map": lambda: {},
"_get_lxc_docker_inventory_map": lambda: {},
"get_proxmox_node_name": lambda: "pve",
"_get_cached_vm_list_search_notes": lambda _resources, _node: {},
"get_cached_vm_disk": lambda _vmid: None,
"_guest_lifecycle_lock": threading.RLock(),
"_guest_modal_cache_revision": {},
"_guest_modal_cache_epoch": 1,
})
def list_rows(self, resources, ip_info_by_vmid):
self.server["get_cached_pvesh_cluster_resources_vm"] = lambda: resources
self.server["_get_lxc_ip_info_cached"] = lambda vmid: ip_info_by_vmid.get(vmid)
return self.server["get_proxmox_vms"]()
@staticmethod
def running_lxc(vmid):
return {
"type": "lxc", "node": "pve", "status": "running", "vmid": vmid,
"name": f"ct-{vmid}", "cpu": 0, "mem": 0, "maxmem": 0,
"disk": 0, "maxdisk": 0, "uptime": 0,
}
def test_unregistered_lxc_keeps_its_primary_ip_in_the_list(self):
rows = self.list_rows([self.running_lxc(300)], {
300: {
"all_ips": ["10.100.100.218"],
"real_ips": ["10.100.100.218"],
"docker_ips": [],
"primary_ip": "10.100.100.218",
},
})
self.assertEqual(rows[0]["ip"], "10.100.100.218")
self.assertEqual(rows[0]["ips"], ["10.100.100.218"])
self.assertNotIn("app_watches", rows[0])
def test_lxc_with_multiple_ips_preserves_order_and_docker_classification_output(self):
rows = self.list_rows([self.running_lxc(301)], {
301: {
"all_ips": ["10.100.100.219", "10.100.100.220", "172.17.0.1"],
"real_ips": ["10.100.100.219", "10.100.100.220"],
"docker_ips": ["172.17.0.1"],
"primary_ip": "10.100.100.219",
},
})
self.assertEqual(rows[0]["ip"], "10.100.100.219")
self.assertEqual(rows[0]["ips"], ["10.100.100.219", "10.100.100.220", "172.17.0.1"])
def test_lxc_without_an_ip_returns_an_empty_ips_list(self):
rows = self.list_rows([self.running_lxc(302)], {302: None})
self.assertEqual(rows[0]["ips"], [])
self.assertNotIn("ip", rows[0])
if __name__ == "__main__":
unittest.main()
+42
View File
@@ -0,0 +1,42 @@
const assert = require("assert")
const fs = require("fs")
const path = require("path")
const ts = require("../../node_modules/typescript")
const sourcePath = path.join(__dirname, "../../lib/vm-search.ts")
const source = fs.readFileSync(sourcePath, "utf8")
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017 },
}).outputText
const moduleShim = { exports: {} }
new Function("exports", "require", "module", "__filename", "__dirname", compiled)(
moduleShim.exports,
require,
moduleShim,
sourcePath,
path.dirname(sourcePath),
)
const { matchesVmSearch, normalizeVmSearchValue } = moduleShim.exports
const synonyms = { lxc: "container", qemu: "virtual machine" }
const multiIpLxc = {
vmid: 301,
name: "database",
type: "lxc",
ip: "10.100.100.219",
ips: ["10.100.100.219", "10.100.100.220"],
}
assert.equal(
matchesVmSearch(multiIpLxc, [normalizeVmSearchValue("10.100.100.220")], synonyms),
true,
"A secondary LXC IP must be searchable",
)
assert.equal(
matchesVmSearch(multiIpLxc, [normalizeVmSearchValue("10.100.100.218")], synonyms),
false,
"Unrelated IPs must not match",
)
console.log("VM IP search tests passed")
+8
View File
@@ -2360,6 +2360,7 @@
"GPU kept in source VM config": "GPU wird in der Quell-VM-Konfiguration beibehalten",
"GPU not driven by this version": "GPU wird von dieser Version nicht angetrieben",
"GPU not found with slot": "GPU mit Steckplatz nicht gefunden",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "GPU-Passthrough-Assistent nicht gefunden. Öffnen Sie später {menu} → {action} im Hauptmenü, um es erneut zu versuchen.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "GPU-Passthrough-Assistent nicht gefunden. Sie können es später über Hardware Graphics ausführen.",
"GPU passthrough changes require a host reboot.": "GPU-Passthrough-Änderungen erfordern einen Neustart des Hosts.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "Die GPU-Passthrough-Konfiguration wird jetzt fortgesetzt und nach dem Neustart des Hosts wirksam.",
@@ -2677,6 +2678,7 @@
"If upgrade fails:": "Wenn das Upgrade fehlschlägt:",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Wenn Sie Nein antworten, wird Glances ohne Privilegien installiert und überwacht NUR seinen eigenen LXC, nicht Proxmox.",
"If you are sure you want to use it, please remove the": "Wenn Sie sicher sind, dass Sie es verwenden möchten, entfernen Sie bitte das",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "Wenn Sie sure verwenden möchten, entfernen Sie die RAID-Metadaten oder formatieren Sie sie manuell mit externen Tools.",
"If you choose No, install": "Wenn Sie „Nein“ wählen, installieren Sie es",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Wenn Sie fortfahren, werden möglicherweise einige Anpassungen dupliziert oder stehen in Konflikt mit den bereits von xshok vorgenommenen.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "Wenn Sie ein Coral M.2/PCIe-Gerät haben, das physisch installiert ist, aber von lspci nicht erkannt wird, brechen Sie hier ab und überprüfen Sie zunächst Ihre Hardware, bevor Sie fortfahren.",
@@ -3508,6 +3510,7 @@
"Mounting container filesystem": "Container-Dateisystem mounten",
"Mounting disk...": "Montagescheibe...",
"Mounting existing": "Montage vorhanden",
"Mounting existing filesystem ({filesystem})...": "Vorhandenes Dateisystem ({filesystem}) mounten...",
"Mounting here will hide existing files until unmounted.": "Beim Mounten hier werden vorhandene Dateien ausgeblendet, bis die Bereitstellung aufgehoben wird.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "Verschieben Sie diese Kopie an einen anderen Standort (USB, Passwort-Manager, ein anderer Host).Löschen Sie es aus diesem Pfad, wenn Sie fertig sind.",
"Move to target VM (remove from source VM config)": "Zur Ziel-VM verschieben (aus der Quell-VM-Konfiguration entfernen)",
@@ -4082,6 +4085,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "Wenn Sie fertig sind, führen Sie die „PVE 8 bis 9-Überprüfung“ erneut aus, um sicherzustellen, dass alle Probleme behoben sind \n bevor Sie das PVE 8 → PVE 9-Upgrade durchführen.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "Wenn Sie fertig sind, führen Sie das Skript „PVE 8 to 9 check“ erneut aus, um zu überprüfen, ob alle Probleme behoben sind \n vor dem Neustart.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "Wenn Sie fertig sind, führen Sie das Skript „PVE 8 to 9 check“ erneut aus, um zu überprüfen, ob alle Probleme vorliegen.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "Wenn Sie fertig sind, wiederholen Sie die Prüfung ({check}), um alle verbleibenden Probleme zu überprüfen, bevor Sie neu starten.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "Sobald Sie fertig sind, wiederholen Sie die Prüfung ({check}), um alle verbleibenden Probleme zu überprüfen, bevor Sie mit dem Upgrade von PVE 8 → PVE 9 beginnen.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "Wenn Sie fertig sind, wiederholen Sie die Prüfung ({check}), um alle verbleibenden Probleme zu überprüfen.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "Öffnen Sie nach der Installation die VirtIO-ISO und führen Sie das Installationsprogramm aus, um die Treibereinrichtung abzuschließen.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "Eine oder mehrere NVIDIA-GPUs sind derzeit für VM-Passthrough (vfio-pci) konfiguriert:",
"Online retro games emulator": "Online Retro Spiele Emulator",
@@ -4446,6 +4452,7 @@
"Privileged containers have full root access to the host system!": "Privilegierte Container haben vollen Root-Zugriff auf das Hostsystem!",
"Privileged installation declined": "Privilegierte Installation abgelehnt",
"Privileged: Full host access (less secure)": "Privilegiert: Vollständiger Hostzugriff (weniger sicher)",
"Probe failed": "Probe fehlgeschlagen",
"Proceed": "Fortfahren",
"Proceed with removal": "Fahren Sie mit der Entfernung fort",
"Proceed?": "Fortfahren?",
@@ -5548,6 +5555,7 @@
"Steps that will run:": "Schritte, die ausgeführt werden:",
"Still has issues": "Es gibt immer noch Probleme",
"Stop it first and run this option again.": "Stoppen Sie es zuerst und führen Sie diese Option erneut aus.",
"Stop that VM, then choose:": "Stoppen Sie VM und wählen Sie dann:",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Stoppen Sie den CT, unmounten Sie die Festplatte auf dem HOST und mounten Sie sie erneut mit:",
"Stop the VM/CT before formatting this disk.": "Stoppen Sie die VM/CT, bevor Sie diese Festplatte formatieren.",
"Stop the container before the NVIDIA refresh": "Stoppen Sie den Container, bevor Sie die NVIDIA aktualisieren",
+8
View File
@@ -2363,6 +2363,7 @@
"GPU kept in source VM config": "GPU mantenida en la configuración de la VM de origen",
"GPU not driven by this version": "GPU no impulsada por esta versión",
"GPU not found with slot": "GPU no encontrada con ranura",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "No se encontró el asistente de paso GPU.Luego, abra {menu} → {action} desde el menú principal para volver a intentarlo.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "No se encontró el asistente de transferencia de GPU. Puede ejecutarlo más tarde desde Hardware Graphics.",
"GPU passthrough changes require a host reboot.": "Los cambios de transferencia de GPU requieren reiniciar el host.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "La configuración de transferencia de GPU continuará ahora y entrará en vigor después de reiniciar el host.",
@@ -2680,6 +2681,7 @@
"If upgrade fails:": "Si la actualización falla:",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Si responde No, Glances se instala sin privilegios y monitoriza SOLO su propio LXC, no Proxmox.",
"If you are sure you want to use it, please remove the": "Si está seguro de que desea utilizarlo, elimine el",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "si es sure y desea usarlo, elimine los metadatos RAID o formatéelo manualmente usando herramientas externas.",
"If you choose No, install": "Si elige No, instale",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Si continúa, es posible que algunos ajustes se dupliquen o entren en conflicto con los que ya realizó xshok.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "Si tiene un dispositivo Coral M.2/PCIe instalado físicamente que lspci no detecta, cancele esta operación y revise primero el hardware.",
@@ -3511,6 +3513,7 @@
"Mounting container filesystem": "Montaje del sistema de archivos del contenedor",
"Mounting disk...": "Disco de montaje...",
"Mounting existing": "Montaje existente",
"Mounting existing filesystem ({filesystem})...": "Montando el sistema de archivos existente ({filesystem})...",
"Mounting here will hide existing files until unmounted.": "Montar aquí ocultará los archivos existentes hasta que se desmonten.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "mueva esa copia fuera del sitio (USB, administrador de contraseñas, otro host).Elimínelo de esta ruta cuando haya terminado.",
"Move to target VM (remove from source VM config)": "Mover a la VM de destino (eliminar de la configuración de la VM de origen)",
@@ -4085,6 +4088,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "Una vez terminado, vuelva a ejecutar la 'verificación PVE 8 a 9' para verificar que todos los problemas estén resueltos. \n antes de ejecutar la actualización PVE 8 → PVE 9.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "Una vez terminado, vuelva a ejecutar el script 'PVE 8 to 9 check' para verificar que todos los problemas estén resueltos. \n antes de reiniciar.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "Una vez terminado, vuelva a ejecutar el script 'PVE 8 to 9 check' para verificar que todos los problemas.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "una vez terminado, repita la verificación ({check}) para revisar cualquier problema restante antes de reiniciar.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "una vez terminado, repita la verificación ({check}) para revisar cualquier problema restante antes de iniciar la actualización PVE 8 → PVE 9.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "una vez terminado, repita la verificación ({check}) para revisar los problemas restantes.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "Una vez instalado, abra VirtIO ISO y ejecute el instalador para completar la configuración del controlador.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "Actualmente, una o más GPU NVIDIA están configuradas para el paso a través de VM (vfio-pci):",
"Online retro games emulator": "emulador de juegos retro en línea",
@@ -4449,6 +4455,7 @@
"Privileged containers have full root access to the host system!": "¡Los contenedores privilegiados tienen acceso raíz completo al sistema host!",
"Privileged installation declined": "Instalación privilegiada rechazada",
"Privileged: Full host access (less secure)": "Privilegiado: acceso completo al host (menos seguro)",
"Probe failed": "Falló la sonda",
"Proceed": "Proceder",
"Proceed with removal": "Proceder con la eliminación",
"Proceed?": "¿Proceder?",
@@ -5552,6 +5559,7 @@
"Steps that will run:": "Pasos que se ejecutarán:",
"Still has issues": "Todavía tiene problemas",
"Stop it first and run this option again.": "Deténgalo primero y ejecute esta opción nuevamente.",
"Stop that VM, then choose:": "Detenga ese VM, luego elija:",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Detenga el CT, desmonte el disco en el HOST y vuelva a montarlo con:",
"Stop the VM/CT before formatting this disk.": "Detenga el VM/CT antes de formatear este disco.",
"Stop the container before the NVIDIA refresh": "Detén el contenedor antes de la actualización de NVIDIA",
+8
View File
@@ -2360,6 +2360,7 @@
"GPU kept in source VM config": "GPU conservé dans la configuration de la VM source",
"GPU not driven by this version": "GPU non piloté par cette version",
"GPU not found with slot": "GPU introuvable avec le slot",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "Assistant de relais GPU introuvable. Plus tard, ouvrez {menu} → {action} depuis le menu principal pour réessayer.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "Assistant de relais GPU introuvable. Vous pouvez l'exécuter plus tard à partir de Hardware Graphics.",
"GPU passthrough changes require a host reboot.": "Les modifications du relais GPU nécessitent un redémarrage de l’hôte.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "La configuration du relais GPU continuera maintenant et deviendra effective après le redémarrage de l'hôte.",
@@ -2677,6 +2678,7 @@
"If upgrade fails:": "Si la mise à niveau échoue :",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Si vous répondez Non, Glances est installé sans privilèges et surveille SEULEMENT ses propres LXC, pas Proxmox.",
"If you are sure you want to use it, please remove the": "Si vous êtes sûr de vouloir l'utiliser, veuillez supprimer le",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "Si vous êtes sure et que vous souhaitez l'utiliser, supprimez les métadonnées RAID ou formatez-les manuellement à l'aide d'outils externes.",
"If you choose No, install": "Si vous choisissez Non, installez",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Si vous continuez, certains ajustements peuvent être dupliqués ou entrer en conflit avec ceux déjà effectués par xshok.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "Si vous disposez d'un périphérique Coral M.2 / PCIe physiquement installé mais non détecté par lspci, annulez ici et vérifiez d'abord votre matériel avant de continuer.",
@@ -3508,6 +3510,7 @@
"Mounting container filesystem": "Montage du système de fichiers du conteneur",
"Mounting disk...": "Disque de montage...",
"Mounting existing": "Montage existant",
"Mounting existing filesystem ({filesystem})...": "Montage du système de fichiers existant ({filesystem})...",
"Mounting here will hide existing files until unmounted.": "Le montage ici masquera les fichiers existants jusqu'à leur démontage.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "déplacez cette copie hors site (USB, gestionnaire de mots de passe, un autre hôte).Supprimez-le de ce chemin une fois terminé.",
"Move to target VM (remove from source VM config)": "Déplacer vers la VM cible (supprimer de la configuration de la VM source)",
@@ -4082,6 +4085,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "Une fois terminé, réexécutez la « vérification PVE 8 à 9 » pour vérifier que tous les problèmes sont résolus. \n avant d'exécuter la mise à niveau PVE 8 → PVE 9.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "Une fois terminé, réexécutez le script 'PVE 8 to 9 check' pour vérifier que tous les problèmes sont résolus. \n avant de redémarrer.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "Une fois terminé, réexécutez le script « PVE 8 to 9 check » pour vérifier que tous les problèmes sont résolus.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "Une fois terminé, répétez la vérification ({check}) pour examiner les problèmes restants avant de redémarrer.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "Une fois terminé, répétez la vérification ({check}) pour examiner les problèmes restants avant de démarrer la mise à niveau PVE 8 → PVE 9.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "une fois terminé, répétez la vérification ({check}) pour examiner les problèmes restants.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "Une fois installé, ouvrez l'ISO VirtIO et exécutez le programme d'installation pour terminer la configuration du pilote.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "Un ou plusieurs GPU NVIDIA sont actuellement configurés pour le relais de VM (vfio-pci) :",
"Online retro games emulator": "Jeux rétro en ligne émulateur",
@@ -4446,6 +4452,7 @@
"Privileged containers have full root access to the host system!": "Les conteneurs privilégiés ont un accès root complet au système hôte !",
"Privileged installation declined": "Défaut d'installation privilégié",
"Privileged: Full host access (less secure)": "Privilégié : accès complet à l'hôte (moins sécurisé)",
"Probe failed": "Échec de la sonde",
"Proceed": "Procéder",
"Proceed with removal": "Procéder à la suppression",
"Proceed?": "Procéder?",
@@ -5548,6 +5555,7 @@
"Steps that will run:": "Étapes qui s'exécuteront :",
"Still has issues": "A encore des problèmes",
"Stop it first and run this option again.": "Arrêtez-le d'abord et réexécutez cette option.",
"Stop that VM, then choose:": "Arrêtez ce VM, puis choisissez :",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Arrêtez le CT, démontez le disque sur le HOST et remontez avec :",
"Stop the VM/CT before formatting this disk.": "Arrêtez la VM/CT avant de formater ce disque.",
"Stop the container before the NVIDIA refresh": "Arrêter le récipient avant le rafraîchissement NVIDIA",
+8
View File
@@ -2360,6 +2360,7 @@
"GPU kept in source VM config": "GPU mantenuta nella configurazione della VM di origine",
"GPU not driven by this version": "GPU non supportata da questa versione",
"GPU not found with slot": "GPU non trovata con lo slot",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "GPU assistente passthrough non trovato. Successivamente, aprire {menu} → {action} dal menu principale per riprovare.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "Assistente passthrough GPU non trovato. Puoi eseguirlo in seguito da Hardware Graphics.",
"GPU passthrough changes require a host reboot.": "Le modifiche al passthrough della GPU richiedono il riavvio dell'host.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "La configurazione del passthrough GPU continuerà ora e diventerà effettiva dopo il riavvio dell'host.",
@@ -2677,6 +2678,7 @@
"If upgrade fails:": "Se l'aggiornamento fallisce:",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Se rispondete a No, Glances è installato senza privilegi e monitor SOLO il proprio LXC, non Proxmox.",
"If you are sure you want to use it, please remove the": "Se sei sicuro di volerlo utilizzare, rimuovi il file",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "Se sei sure e desideri utilizzarlo, rimuovi i metadati RAID o formattalo manualmente utilizzando strumenti esterni.",
"If you choose No, install": "Se scegli No, installa",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Se continui, alcune modifiche potrebbero essere duplicate o entrare in conflitto con quelle già apportate da xshok.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "se disponi di un dispositivo Coral M.2 / PCIe installato fisicamente ma non rilevato da lspci, annulla qui e controlla l'hardware prima di procedere.",
@@ -3508,6 +3510,7 @@
"Mounting container filesystem": "Montaggio del filesystem del contenitore",
"Mounting disk...": "Disco di montaggio...",
"Mounting existing": "Montaggio esistente",
"Mounting existing filesystem ({filesystem})...": "Montaggio del filesystem esistente ({filesystem})...",
"Mounting here will hide existing files until unmounted.": "Il montaggio qui nasconderà i file esistenti finché non verranno smontati.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "sposta la copia fuori sede (USB, gestore password, un altro host).Eliminalo da questo percorso una volta terminato.",
"Move to target VM (remove from source VM config)": "Sposta nella VM di destinazione (rimuovi dalla configurazione della VM di origine)",
@@ -4082,6 +4085,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "Una volta terminato, esegui nuovamente il \"controllo PVE da 8 a 9\" per verificare che tutti i problemi siano stati risolti \n prima di eseguire l'aggiornamento PVE 8 → PVE 9.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "Una volta terminato, eseguire nuovamente lo script \"PVE 8 to 9 check\" per verificare che tutti i problemi siano stati risolti \n prima di riavviare.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "Una volta terminato, eseguire nuovamente lo script \"PVE 8 to 9 check\" per verificare che tutti i problemi siano corretti.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "una volta terminato, ripetere il controllo ({check}) per esaminare eventuali problemi rimanenti prima di riavviare.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "una volta terminato, ripetere il controllo ({check}) per esaminare eventuali problemi rimanenti prima di iniziare l'aggiornamento PVE 8 → PVE 9.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "una volta terminato, ripetere il controllo ({check}) per esaminare eventuali problemi rimanenti.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "Una volta installato, apri l'ISO VirtIO ed esegui il programma di installazione per completare la configurazione del driver.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "Una o più GPU NVIDIA sono attualmente configurate per il passthrough VM (vfio-pci):",
"Online retro games emulator": "Emulatore di giochi retrò online",
@@ -4446,6 +4452,7 @@
"Privileged containers have full root access to the host system!": "I contenitori privilegiati hanno pieno accesso root al sistema host!",
"Privileged installation declined": "Installazione privilegiata declinata",
"Privileged: Full host access (less secure)": "Privilegiato: accesso completo all'host (meno sicuro)",
"Probe failed": "Sonda fallita",
"Proceed": "Procedere",
"Proceed with removal": "Procedere con la rimozione",
"Proceed?": "Procedere?",
@@ -5548,6 +5555,7 @@
"Steps that will run:": "Passaggi che verranno eseguiti:",
"Still has issues": "Ha ancora problemi",
"Stop it first and run this option again.": "Interrompilo prima ed esegui nuovamente questa opzione.",
"Stop that VM, then choose:": "arresta VM, quindi scegli:",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Arrestare il CT, smontare il disco sull'HOST e rimontarlo con:",
"Stop the VM/CT before formatting this disk.": "Arrestare il VM/CT prima di formattare questo disco.",
"Stop the container before the NVIDIA refresh": "Fermare il contenitore prima del rinfresco NVIDIA",
+8
View File
@@ -2360,6 +2360,7 @@
"GPU kept in source VM config": "GPU mantida na configuração da VM de origem",
"GPU not driven by this version": "GPU não controlada por esta versão",
"GPU not found with slot": "GPU não encontrada com slot",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "assistente de passagem GPU não encontrado. Mais tarde, abra {menu} → {action} no menu principal para tentar novamente.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "Assistente de passagem de GPU não encontrado. Você pode executá-lo mais tarde em Hardware Graphics.",
"GPU passthrough changes require a host reboot.": "As alterações de passagem da GPU exigem uma reinicialização do host.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "A configuração de passagem de GPU continuará agora e entrará em vigor após a reinicialização do host.",
@@ -2677,6 +2678,7 @@
"If upgrade fails:": "Se a atualização falhar:",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Se responder Não, o Glances é instalado sem privilégios e monitora SOMENTE o seu próprio LXC, não o Proxmox.",
"If you are sure you want to use it, please remove the": "Se você tem certeza de que deseja usá-lo, remova o",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "Se você é sure e deseja usá-lo, remova os metadados RAID ou formate-os manualmente usando ferramentas externas.",
"If you choose No, install": "Se você escolher Não, instale",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Se você continuar, alguns ajustes poderão ser duplicados ou entrar em conflito com aqueles já feitos pelo xshok.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "Se você possui um dispositivo Coral M.2 / PCIe instalado fisicamente, mas não detectado pelo lspci, cancele aqui e verifique seu hardware antes de continuar.",
@@ -3508,6 +3510,7 @@
"Mounting container filesystem": "Montando sistema de arquivos contêiner",
"Mounting disk...": "Montando disco...",
"Mounting existing": "Montagem existente",
"Mounting existing filesystem ({filesystem})...": "Montando sistema de arquivos existente ({filesystem})...",
"Mounting here will hide existing files until unmounted.": "A montagem aqui ocultará os arquivos existentes até serem desmontados.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "Mova essa cópia para fora do local (USB, gerenciador de senhas, outro host).Exclua-o deste caminho quando terminar.",
"Move to target VM (remove from source VM config)": "Mover para a VM de destino (remover da configuração da VM de origem)",
@@ -4082,6 +4085,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "Quando terminar, execute novamente a 'verificação PVE 8 a 9' para verificar se todos os problemas foram resolvidos \n antes de executar a atualização PVE 8 → PVE 9.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "Quando terminar, execute novamente o script 'PVE 8 to 9 check' para verificar se todos os problemas foram resolvidos \n antes de reiniciar.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "Quando terminar, execute novamente o script 'PVE 8 to 9 check' para verificar todos os problemas.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "Quando terminar, repita a verificação ({check}) para revisar quaisquer problemas restantes antes de reiniciar.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "Quando terminar, repita a verificação ({check}) para revisar quaisquer problemas restantes antes de iniciar a atualização PVE 8 → PVE 9.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "Ao terminar, repita a verificação ({check}) para revisar quaisquer problemas restantes.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "Depois de instalado, abra o VirtIO ISO e execute o instalador para concluir a configuração do driver.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "Uma ou mais GPUs NVIDIA estão atualmente configuradas para passagem de VM (vfio-pci):",
"Online retro games emulator": "Emulador de jogos retro online",
@@ -4446,6 +4452,7 @@
"Privileged containers have full root access to the host system!": "Contêineres privilegiados têm acesso root total ao sistema host!",
"Privileged installation declined": "Instalação privada declinou",
"Privileged: Full host access (less secure)": "Privilegiado: acesso total ao host (menos seguro)",
"Probe failed": "falha na sonda",
"Proceed": "Prosseguir",
"Proceed with removal": "Prossiga com a remoção",
"Proceed?": "Continuar?",
@@ -5548,6 +5555,7 @@
"Steps that will run:": "Etapas que serão executadas:",
"Still has issues": "Ainda tem problemas",
"Stop it first and run this option again.": "Pare primeiro e execute esta opção novamente.",
"Stop that VM, then choose:": "Pare esse VM e escolha:",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Pare o CT, desmonte o disco no HOST e remonte com:",
"Stop the VM/CT before formatting this disk.": "Pare a VM/CT antes de formatar este disco.",
"Stop the container before the NVIDIA refresh": "Pare o recipiente antes da atualização do NVIDIA",
+8
View File
@@ -2360,6 +2360,7 @@
"GPU kept in source VM config": "GPU ponechaná v nastavení zdrojovej VM",
"GPU not driven by this version": "GPU nepoháňa táto verzia",
"GPU not found with slot": "GPU sa nenašla na slote",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "Asistent prechodu GPU sa nenašiel. Neskôr otvorte {menu} → {action} z hlavnej ponuky a skúste to znova.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "Sprievodca priamym priradením GPU sa nenašiel. Môžete ho spustiť neskôr z časti Hardware Graphics.",
"GPU passthrough changes require a host reboot.": "Zmeny pre priame priradenie GPU vyžadujú reštart servera.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "Nastavenie GPU passthrough bude teraz pokračovať a prejaví sa po reštarte hosta.",
@@ -2677,6 +2678,7 @@
"If upgrade fails:": "Ak aktualizácia zlyhá:",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Ak odpoviete nie, Glances je nainštalovaný bez oprávnení a monitoruje IBA svoje vlastné LXC, nie Proxmox.",
"If you are sure you want to use it, please remove the": "Ak ste si istý, že ho chcete použiť, odstráňte",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "Ak ste sure, chcete ho použiť, odstráňte metadáta RAID alebo ich naformátujte manuálne pomocou externých nástrojov.",
"If you choose No, install": "Ak vyberiete Nie, nainštalujte",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Ak budete pokračovať, niektoré úpravy sa môžu zdvojiť alebo byť v konflikte s tým, čo už urobil xshok.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "Ak máte zariadenie Coral M.2 / PCIe, ktoré je fyzicky nainštalované, ale nie je detekované lspci, zrušte to tu a pred pokračovaním najskôr skontrolujte hardvér.",
@@ -3508,6 +3510,7 @@
"Mounting container filesystem": "Pripájam súborový systém kontajnera",
"Mounting disk...": "Pripájam disk...",
"Mounting existing": "Pripájam existujúci",
"Mounting existing filesystem ({filesystem})...": "Pripája sa existujúci súborový systém ({filesystem})...",
"Mounting here will hide existing files until unmounted.": "Pripojenie do tohto priečinka dočasne skryje existujúce súbory, kým zdieľanie neodpojíte.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "Presuňte túto kópiu mimo servera (USB, správca hesiel, iný host). Po dokončení ju z tejto cesty odstráňte.",
"Move to target VM (remove from source VM config)": "Presunúť do cieľovej VM (odstrániť zo zdrojovej VM)",
@@ -4082,6 +4085,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "Po dokončení znova spustite 'PVE 8 to 9 check', aby ste overili, že všetky problémy sú vyriešené \n ešte pred spustením aktualizácie z PVE 8 na PVE 9.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "Po dokončení znova spustite skript 'PVE 8 to 9 check', aby ste overili, že všetky problémy sú vyriešené \n ešte pred reštartom.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "Po dokončení spustite skript 'PVE 8 to 9 check' znova, aby ste overili všetky problémy.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "Po dokončení zopakujte kontrolu ({check}) a skontrolujte všetky zostávajúce problémy pred reštartovaním.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "Po dokončení zopakujte kontrolu ({check}), aby ste skontrolovali všetky zostávajúce problémy pred spustením inovácie PVE 8 → PVE 9.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "Po dokončení zopakujte kontrolu ({check}), aby ste skontrolovali všetky zostávajúce problémy.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "Po nainštalovaní otvorte VirtIO ISO a spustite inštalátor na dokončenie ovládačov.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "Jedna alebo viac NVIDIA GPU je aktuálne nastavených na VM passthrough (vfio-pci):",
"Online retro games emulator": "Online retro hry emulátor",
@@ -4446,6 +4452,7 @@
"Privileged containers have full root access to the host system!": "Privilegované kontajnery majú plný root prístup k hostiteľskému systému!",
"Privileged installation declined": "Privilegovaná inštalácia odmietla",
"Privileged: Full host access (less secure)": "Privilegovaný: plný prístup k serveru (menej bezpečné)",
"Probe failed": "Sonda zlyhala",
"Proceed": "Pokračovať",
"Proceed with removal": "Pokračovať v odstránení",
"Proceed?": "Pokračovať?",
@@ -5548,6 +5555,7 @@
"Steps that will run:": "Kroky, ktoré sa vykonajú:",
"Still has issues": "Stále má problémy",
"Stop it first and run this option again.": "Najprv ju zastavte a potom túto možnosť spustite znova.",
"Stop that VM, then choose:": "Zastavte VM a potom vyberte:",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Zastavte CT, odpojte disk na HOSTE a pripojte ho znova pomocou:",
"Stop the VM/CT before formatting this disk.": "Pred formátovaním tohto disku zastavte VM/CT.",
"Stop the container before the NVIDIA refresh": "Zastavte nádobu pred obnovou NVIDIA",
+8
View File
@@ -2360,6 +2360,7 @@
"GPU kept in source VM config": "GPU hålls i käll-VM-konfiguration",
"GPU not driven by this version": "GPU drivs inte av den här versionen",
"GPU not found with slot": "GPU hittades inte med kortplats",
"GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.": "GPU passthrough-assistent hittades inte. Senare, öppna {menu} → {action} från huvudmenyn för att försöka igen.",
"GPU passthrough assistant not found. You can run it later from Hardware Graphics.": "GPU passthrough-assistent hittades inte. Du kan köra det senare från Hardware Graphics.",
"GPU passthrough changes require a host reboot.": "GPU-genomföringsändringar kräver omstart av värddatorn.",
"GPU passthrough configuration will continue now and will become effective after host reboot.": "GPU-genomkopplingskonfigurationen fortsätter nu och träder i kraft efter omstart av värddatorn.",
@@ -2677,6 +2678,7 @@
"If upgrade fails:": "Om uppgraderingen misslyckas:",
"If you answer No, Glances is installed without privileges and monitors ONLY its own LXC, not Proxmox.": "Om du svarar nej installeras Glances utan privilegier och övervakar ENDAST sin egen LXC, inte Proxmox.",
"If you are sure you want to use it, please remove the": "Om du är säker på att du vill använda den, vänligen ta bort",
"If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.": "Om du är sure vill du använda den, ta bort RAID-metadata eller formatera den manuellt med hjälp av externa verktyg.",
"If you choose No, install": "Om du väljer Nej, installera",
"If you continue, some adjustments may be duplicated or conflict with those already made by xshok.": "Om du fortsätter kan vissa justeringar dupliceras eller komma i konflikt med de som redan gjorts av xshok.",
"If you have a Coral M.2 / PCIe device that is physically installed but not detected by lspci, cancel here and check your hardware first before proceeding.": "Om du har en Coral M.2/PCIe-enhet som är fysiskt installerad men inte detekteras av lspci, avbryt här och kontrollera din hårdvara innan du fortsätter.",
@@ -3508,6 +3510,7 @@
"Mounting container filesystem": "Montering av containerfilsystem",
"Mounting disk...": "Monteringsskiva...",
"Mounting existing": "Montering befintlig",
"Mounting existing filesystem ({filesystem})...": "Montering av befintligt filsystem ({filesystem})...",
"Mounting here will hide existing files until unmounted.": "Montering här kommer att dölja befintliga filer tills de avmonteras.",
"Move that copy offsite (USB, password manager, another host). Delete it from this path when done.": "Flytta kopian från platsen (USB, lösenordshanterare, en annan värd). Ta bort den från den här sökvägen när du är klar.",
"Move to target VM (remove from source VM config)": "Flytta till mål-VM (ta bort från käll-VM-konfiguration)",
@@ -4082,6 +4085,9 @@
"Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.": "När du är klar, kör \"PVE 8 till 9-kontroll\" igen för att verifiera att alla problem är lösta \n innan du kör PVE 8 → PVE 9-uppgraderingen.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.": "När du är klar, kör skriptet \"PVE 8 till 9 check\" igen för att verifiera att alla problem är lösta \n innan du startar om.",
"Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.": "När du är klar, kör skriptet 'PVE 8 till 9 check' igen för att verifiera att alla problem.",
"Once finished, repeat the check ({check}) to review any remaining issues before rebooting.": "När du är klar, upprepa kontrollen ({check}) för att granska eventuella återstående problem innan du startar om.",
"Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.": "När du är klar, upprepa kontrollen ({check}) för att granska eventuella återstående problem innan du startar PVE 8 → PVE 9-uppgraderingen.",
"Once finished, repeat the check ({check}) to review any remaining issues.": "När du är klar, upprepa kontrollen ({check}) för att granska eventuella återstående problem.",
"Once installed, open the VirtIO ISO and run the installer to complete driver setup.": "När det är installerat, öppna VirtIO ISO och kör installationsprogrammet för att slutföra drivrutinsinstallationen.",
"One or more NVIDIA GPUs are currently configured for VM passthrough (vfio-pci):": "En eller flera NVIDIA GPU:er är för närvarande konfigurerade för VM-passthrough (vfio-pci):",
"Online retro games emulator": "Online retro spel emulator",
@@ -4446,6 +4452,7 @@
"Privileged containers have full root access to the host system!": "Privilegerade behållare har full root-åtkomst till värdsystemet!",
"Privileged installation declined": "Privilegierad installation minskade",
"Privileged: Full host access (less secure)": "Privilegerad: Full värdåtkomst (mindre säker)",
"Probe failed": "Sonden misslyckades",
"Proceed": "Fortsätta",
"Proceed with removal": "Fortsätt med borttagning",
"Proceed?": "Fortsätta?",
@@ -5548,6 +5555,7 @@
"Steps that will run:": "Steg som kommer att köras:",
"Still has issues": "Har fortfarande problem",
"Stop it first and run this option again.": "Stoppa det först och kör det här alternativet igen.",
"Stop that VM, then choose:": "Stoppa den VM och välj sedan:",
"Stop the CT, unmount the disk on the HOST, and remount with:": "Stoppa CT, avmontera disken på HOST och montera om med:",
"Stop the VM/CT before formatting this disk.": "Stoppa VM/CT innan du formaterar denna disk.",
"Stop the container before the NVIDIA refresh": "Stoppa behållaren innan NVIDIA-uppdateringen",
+2 -3
View File
@@ -1459,9 +1459,8 @@ check_switch_mode() {
msg="\n$(translate 'The selected GPU is already assigned to another VM that is currently running:')\n\n"
msg+=" VM ${vm_src_id} (${vm_src_name:-VM-${vm_src_id}})\n\n"
msg+="$(translate 'The same GPU cannot be used by two VMs at the same time.')\n\n"
msg+="$(translate 'Next step: stop that VM first, then run')\n"
msg+=" Hardware Graphics → Add GPU to VM\n"
msg+="$(translate 'to move the GPU safely.')"
msg+="$(translate 'Stop that VM, then choose:')\n"
msg+=" $(translate 'Hardware: GPUs and Coral-TPU') → $(translate 'Add GPU to VM (Intel | AMD | NVIDIA)')\n"
_pmx_msgbox "$(translate 'GPU Busy in Running VM')" "$msg" 16 78
exit 0
+7 -1
View File
@@ -646,7 +646,13 @@ mount_existing_disk() {
mkdir -p "$mount_path"
msg_ok "$(translate "Mount point created")"
msg_info "$(translate "Mounting existing") $existing_fs $(translate "filesystem...")"
local mount_message
mount_message="$(translate "Mounting existing filesystem ({filesystem})...")"
if [[ "$mount_message" != *'{filesystem}'* ]]; then
mount_message="Mounting existing filesystem ({filesystem})..."
fi
mount_message="${mount_message//\{filesystem\}/$existing_fs}"
msg_info "$mount_message"
if ! mount "$disk" "$mount_path" 2>/dev/null; then
msg_error "$(translate "Failed to mount disk")"
return 1
+1 -1
View File
@@ -471,7 +471,7 @@ test_iscsi_connectivity() {
if nc -z -w 2 "$portal_host" "$portal_port" 2>/dev/null; then
echo -e " | iSCSI port $portal_port: ${GN}$(translate "Open")${CL}"
else
echo -e " | iSCSI port $portal_port: ${RD}$(translate "Closed")${CL}"
echo -e " | iSCSI port $portal_port: ${RD}$(translate "Probe failed")${CL}"
fi
else
echo -e "${RD}$(translate "Unreachable")${CL}"
+1 -1
View File
@@ -678,7 +678,7 @@ test_nfs_connectivity() {
if pct exec "$CTID" -- nc -z -w 2 "$server" 2049 2>/dev/null; then
echo -e "\033[1;92m$(translate "Open")\033[0m"
else
echo -e "\033[1;91m$(translate "Closed")\033[0m"
echo -e "\033[1;91m$(translate "Probe failed")\033[0m"
fi
# Try to list exports
+2 -2
View File
@@ -839,7 +839,7 @@ test_nfs_connectivity() {
if nc -z -w 2 "$server" 2049 2>/dev/null; then
echo -e " | NFS port 2049: ${GN}$(translate "Open")${CL}"
else
echo -e " | NFS port 2049: ${RD}$(translate "Closed")${CL}"
echo -e " | NFS port 2049: ${RD}$(translate "Probe failed")${CL}"
fi
if showmount -e "$server" >/dev/null 2>&1; then
@@ -882,7 +882,7 @@ test_nfs_connectivity() {
if nc -z -w 2 "$server" 2049 2>/dev/null; then
echo -e " | NFS port 2049: ${GN}$(translate "Open")${CL}"
else
echo -e " | NFS port 2049: ${RD}$(translate "Closed")${CL}"
echo -e " | NFS port 2049: ${RD}$(translate "Probe failed")${CL}"
fi
else
echo -e "${RD}$(translate "Unreachable")${CL}"
+1 -1
View File
@@ -1044,7 +1044,7 @@ test_samba_connectivity() {
elif pct exec "$CTID" -- nc -z -w 2 "$server" 139 2>/dev/null; then
echo " $(translate "NetBIOS port 139:"): ${GN}$(translate "Open")${CL}"
else
echo " $(translate "SMB ports:"): ${RD}$(translate "Closed")${CL}"
echo " $(translate "SMB ports:"): ${RD}$(translate "Probe failed")${CL}"
fi
+2 -2
View File
@@ -918,7 +918,7 @@ test_samba_connectivity() {
elif nc -z -w 2 "$server" 139 2>/dev/null; then
echo -e " | NetBIOS 139: ${GN}$(translate "Open")${CL}"
else
echo -e " | SMB ports: ${RD}$(translate "Closed")${CL}"
echo -e " | SMB ports: ${RD}$(translate "Probe failed")${CL}"
fi
echo -n " $(translate "Guest access test:"): "
@@ -964,7 +964,7 @@ test_samba_connectivity() {
elif nc -z -w 2 "$server" 139 2>/dev/null; then
echo -e " | NetBIOS 139: ${GN}$(translate "Open")${CL}"
else
echo -e " | SMB ports: ${RD}$(translate "Closed")${CL}"
echo -e " | SMB ports: ${RD}$(translate "Probe failed")${CL}"
fi
else
echo -e "${RD}$(translate "Unreachable")${CL}"
+1 -1
View File
@@ -457,7 +457,7 @@ for DISK in $SELECTED; do
if lsblk "$DISK" | grep -q "raid" || grep -q "${DISK##*/}" /proc/mdstat; then
dialog --backtitle "$BACKTITLE" \
--title "$(translate "RAID Detected")" \
--msgbox "\n$(translate "The disk") $DISK_INFO $(translate "appears to be part of a") RAID. $(translate "For security reasons, the system cannot format it.")\\n\\n$(translate "If you are sure you want to use it, please remove the") RAID metadata $(translate "or format it manually using external tools.")\\n\\n$(translate "After that, run this script again to add it.")" $UI_RESULT_H $UI_RESULT_W
--msgbox "\n$(translate "The disk") $DISK_INFO $(translate "appears to be part of a") RAID. $(translate "For security reasons, the system cannot format it.")\\n\\n$(translate "If you are sure you want to use it, remove the RAID metadata or format it manually using external tools.")\\n\\n$(translate "After that, run this script again to add it.")" $UI_RESULT_H $UI_RESULT_W
continue
fi
+5 -1
View File
@@ -187,7 +187,11 @@ run_pve8to9_check2() {
echo -e
done
echo -e
msg_info2 "$(translate "Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues.")"
local check_help
check_help="$(translate "Once finished, repeat the check ({check}) to review any remaining issues.")"
[[ "$check_help" == *'{check}'* ]] || check_help="Once finished, repeat the check ({check}) to review any remaining issues."
check_help="${check_help//\{check\}/$(translate "Run PVE 8 to 9 check")}"
msg_info2 "$check_help"
echo -e
msg_success "$(translate "Press Enter to exit the script after reading instructions...")"
read -r
+12 -4
View File
@@ -135,7 +135,7 @@ ask_run_mode() {
--menu "$text" 20 70 8 \
1 "$(translate "Automatic/Unattended")" \
2 "$(translate "Interactive (guided, prompts visible)")" \
3 "$(translate "Run PVE 8 to 9") check" \
3 "$(translate "Run PVE 8 to 9 check")" \
4 "$(translate "Manual upgrade guide step by step")" \
3>&1 1>&2 2>&3
); status=$?
@@ -178,7 +178,7 @@ ask_run_mode() {
--menu "$(translate "Choose how to perform the upgrade:")" 20 70 8 \
"1" "$(translate "Automatic/Unattended")" \
"2" "$(translate "Interactive (guided, prompts visible)")" \
"3" "$(translate "Run PVE 8 to 9") check" \
"3" "$(translate "Run PVE 8 to 9 check")" \
"4" "$(translate "Manual upgrade guide step by step")" \
3>&1 1>&2 2>&3
); then
@@ -777,7 +777,11 @@ run_pve8to9_check() {
echo -e
done
echo -e
msg_info2 "$(translate "Once finished, re-run 'PVE 8 to 9 check' to verify that all issues are resolved \n before executing the PVE 8 → PVE 9 upgrade.")"
local check_help
check_help="$(translate "Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.")"
[[ "$check_help" == *'{check}'* ]] || check_help="Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade."
check_help="${check_help//\{check\}/$(translate "Run PVE 8 to 9 check")}"
msg_info2 "$check_help"
echo -e
msg_success "$(translate "Press Enter to exit the script after reading instructions...")"
read -r
@@ -1143,7 +1147,11 @@ run_pve8to9_check2() {
echo -e
done
echo -e
msg_info2 "$(translate "Once finished, re-run the script 'PVE 8 to 9 check' to verify that all issues are resolved \n before rebooting.")"
local check_help
check_help="$(translate "Once finished, repeat the check ({check}) to review any remaining issues before rebooting.")"
[[ "$check_help" == *'{check}'* ]] || check_help="Once finished, repeat the check ({check}) to review any remaining issues before rebooting."
check_help="${check_help//\{check\}/$(translate "Run PVE 8 to 9 check")}"
msg_info2 "$check_help"
echo -e
msg_success "$(translate "Press Enter to exit the script after reading instructions...")"
read -r
+6 -1
View File
@@ -179,7 +179,12 @@ function run_gpu_passthrough_wizard() {
fi
if [[ ! -f "$gpu_script" ]]; then
msg_warn "$(translate "GPU passthrough assistant not found. You can run it later from Hardware Graphics.")"
local gpu_notice
gpu_notice="$(translate "GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.")"
[[ "$gpu_notice" == *'{menu}'* && "$gpu_notice" == *'{action}'* ]] || gpu_notice="GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again."
gpu_notice="${gpu_notice//\{menu\}/$(translate "Hardware: GPUs and Coral-TPU")}"
gpu_notice="${gpu_notice//\{action\}/$(translate "Add GPU to VM (Intel | AMD | NVIDIA)")}"
msg_warn "$gpu_notice"
WIZARD_GPU_RESULT="cancelled"
return 0
fi
+6 -1
View File
@@ -234,7 +234,12 @@ function run_gpu_passthrough_wizard() {
fi
if [[ ! -f "$gpu_script" ]]; then
msg_warn "$(translate "GPU passthrough assistant not found. You can run it later from Hardware Graphics.")"
local gpu_notice
gpu_notice="$(translate "GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.")"
[[ "$gpu_notice" == *'{menu}'* && "$gpu_notice" == *'{action}'* ]] || gpu_notice="GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again."
gpu_notice="${gpu_notice//\{menu\}/$(translate "Hardware: GPUs and Coral-TPU")}"
gpu_notice="${gpu_notice//\{action\}/$(translate "Add GPU to VM (Intel | AMD | NVIDIA)")}"
msg_warn "$gpu_notice"
return 0
fi
+6 -1
View File
@@ -171,7 +171,12 @@ function run_gpu_passthrough_wizard() {
fi
if [[ ! -f "$gpu_script" ]]; then
msg_warn "$(translate "GPU passthrough assistant not found. You can run it later from Hardware Graphics.")"
local gpu_notice
gpu_notice="$(translate "GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.")"
[[ "$gpu_notice" == *'{menu}'* && "$gpu_notice" == *'{action}'* ]] || gpu_notice="GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again."
gpu_notice="${gpu_notice//\{menu\}/$(translate "Hardware: GPUs and Coral-TPU")}"
gpu_notice="${gpu_notice//\{action\}/$(translate "Add GPU to VM (Intel | AMD | NVIDIA)")}"
msg_warn "$gpu_notice"
WIZARD_GPU_RESULT="cancelled"
return 0
fi
+135
View File
@@ -0,0 +1,135 @@
// Source-extracted, inert UI/message seams; never source administrative scripts.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const cp = require('node:child_process');
const root = path.resolve(__dirname, '..');
const read = p => fs.readFileSync(path.join(root, p), 'utf8');
const bash = (code, env = {}) => {
const r = cp.spawnSync('bash', ['-c', code], {encoding: 'utf8', env: {...process.env, ...env}});
assert.equal(r.status, 0, `bash fixture failed: ${r.stderr}`);
return r.stdout.trim();
};
const func = (source, name) => {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const m = source.match(new RegExp(`(?:^|\\n)(?:function )?${escaped}\\(\\) \\{[\\s\\S]*?\\n\\}`, 'm'));
assert.ok(m, `function ${name} present`);
return m[0];
};
const lookup = func(read('scripts/utils.sh'), 'translate');
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'pmx-tui-wording-'));
const it = JSON.parse(read('lang/it.json'));
const newKeys = [
'Once finished, repeat the check ({check}) to review any remaining issues.',
'Once finished, repeat the check ({check}) to review any remaining issues before starting the PVE 8 → PVE 9 upgrade.',
'Once finished, repeat the check ({check}) to review any remaining issues before rebooting.',
'GPU passthrough assistant not found. Later, open {menu} → {action} from the main menu to try again.',
];
const marker = Object.fromEntries(newKeys.map((k, i) => [k, i === 3
? 'TRADOTTO_3 {action} {menu}' : `TRADOTTO_${i} {check} fine`]));
const setup = `LANG_DIR=${JSON.stringify(temp)}; LANGUAGE="${'${LANGUAGE:-en}'}"; ${lookup}\nmsg_warn() { printf '%s\\n' "$1"; }; msg_info2() { printf '%s\\n' "$1"; }; msg_title() { printf '%s\\n' "$1"; };`;
const store = (locale, entries) => fs.writeFileSync(path.join(temp, `${locale}.json`), JSON.stringify(entries));
store('it', it); store('synthetic', {
...marker,
'Run PVE 8 to 9 check': 'CONTROLLO_LOCALIZZATO',
'Hardware: GPUs and Coral-TPU': 'HARDWARE_LOCALIZZATO',
'Add GPU to VM (Intel | AMD | NVIDIA)': 'AZIONE_LOCALIZZATA',
'Stop that VM, then choose:': 'SCELTA_LOCALIZZATA:',
});
const menuSrc = read('scripts/utilities/upgrade_pve8_to_pve9.sh');
const menuLines = menuSrc.split('\n').filter(l => /(?:^|\s)(?:3|"3") "\$\(translate .*PVE 8 to 9/.test(l));
assert.equal(menuLines.length, 2, 'both actual menu consumers');
const renderMenu = locale => menuLines.map(l => bash(setup + '\nprintf \'%s\\n\' ' + l.trim().replace(/\\$/, '').replace(/^(?:3|"3") /, '') + '\n', {LANGUAGE: locale}));
assert.deepEqual(renderMenu('en'), ['Run PVE 8 to 9 check', 'Run PVE 8 to 9 check']);
assert.deepEqual(renderMenu('it'), [it['Run PVE 8 to 9 check'], it['Run PVE 8 to 9 check']]);
assert.deepEqual(renderMenu('synthetic'), ['CONTROLLO_LOCALIZZATO', 'CONTROLLO_LOCALIZZATO']);
const locales = ['es', 'fr', 'de', 'it', 'pt', 'sk', 'sv'];
for (const locale of locales) {
const catalog = JSON.parse(read(`lang/${locale}.json`));
store(locale, catalog);
const label = catalog['Run PVE 8 to 9 check'] || 'Run PVE 8 to 9 check';
assert.deepEqual(renderMenu(locale), [label, label], `${locale}: both menus`);
}
const checker = read('scripts/utilities/pve8to9_check.sh');
const title = checker.split('\n').find(l => /^msg_title "\$\(translate "Run PVE 8 to 9 check"\)"$/.test(l));
assert.ok(title);
const checkHelp = checker.match(/^\s*check_help="\$\(translate "Once finished,[^\n]+\n(?:[^\n]*\n){2}\s*msg_info2 "\$check_help"/m)?.[0];
const upgradeHelp = [...menuSrc.matchAll(/^\s*check_help="\$\(translate "Once finished,[^\n]+\n(?:[^\n]*\n){2}\s*msg_info2 "\$check_help"/gm)].map(m => m[0]);
assert.equal(upgradeHelp.length, 2);
const helpLines = [checkHelp, ...upgradeHelp];
const renderHelp = (locale, entries) => {
if (entries) store(locale, entries);
return helpLines.map(l => bash(setup + '\n' + l.trim() + '\n', {LANGUAGE: locale}));
};
const helpEn = renderHelp('en');
assert.equal(helpEn.length, 3);
for (const line of helpEn) {
assert.ok(line.includes('Run PVE 8 to 9 check'), line);
assert.ok(line.includes('repeat the check (Run PVE 8 to 9 check) to review any remaining issues'), line);
assert.ok(!line.includes('{check}') && !line.includes('all issues are resolved') && !line.includes('run Run'), line);
}
const helpIt = renderHelp('it'); // New keys absent from shipped Italian cache: complete English fallback.
for (const line of helpIt) assert.ok(line.includes(it['Run PVE 8 to 9 check']), line);
for (const locale of locales) {
const catalog = JSON.parse(read(`lang/${locale}.json`));
store(locale, catalog);
const label = catalog['Run PVE 8 to 9 check'] || 'Run PVE 8 to 9 check';
for (const line of renderHelp(locale)) assert.ok(line.includes(`(${label})`) && !line.includes('{check}'), `${locale}: ${line}`);
}
const helpSynthetic = renderHelp('synthetic');
for (const [i, line] of helpSynthetic.entries()) assert.equal(line, `TRADOTTO_${i} CONTROLLO_LOCALIZZATO fine`);
const brokenHelp = renderHelp('malformed', {...marker, 'Run PVE 8 to 9 check': 'CONTROLLO_LOCALIZZATO', [newKeys[0]]: 'NON_VALIDA {wrong}'});
assert.ok(brokenHelp[0].includes('Once finished, repeat the check (CONTROLLO_LOCALIZZATO)'), brokenHelp[0]);
const vmPaths = ['scripts/vm/synology.sh', 'scripts/vm/vm_creator.sh', 'scripts/vm/zimaos.sh'];
const vmFunctions = vmPaths.map(p => func(read(p), 'run_gpu_passthrough_wizard'));
const renderVm = locale => vmFunctions.map(fn => bash(setup + `\nLOCAL_SCRIPTS=${JSON.stringify(path.join(temp, 'absent'))}; WIZARD_ADD_GPU=yes; ` + fn + '\nrun_gpu_passthrough_wizard\n', {LANGUAGE: locale}));
for (const line of renderVm('en')) {
assert.ok(line.includes('Hardware: GPUs and Coral-TPU → Add GPU to VM (Intel | AMD | NVIDIA)'), line);
assert.ok(!line.includes('Hardware Graphics') && !line.includes('{menu}') && !line.includes('{action}'), line);
}
for (const line of renderVm('it')) {
assert.ok(line.includes(`${it['Hardware: GPUs and Coral-TPU']} → ${it['Add GPU to VM (Intel | AMD | NVIDIA)']}`), line);
assert.ok(!line.includes('Hardware Graphics'), line);
}
for (const locale of locales) {
const catalog = JSON.parse(read(`lang/${locale}.json`));
const menu = catalog['Hardware: GPUs and Coral-TPU'] || 'Hardware: GPUs and Coral-TPU';
const action = catalog['Add GPU to VM (Intel | AMD | NVIDIA)'] || 'Add GPU to VM (Intel | AMD | NVIDIA)';
for (const line of renderVm(locale)) assert.ok(line.includes(`${menu} → ${action}`) && !line.includes('{menu}') && !line.includes('{action}'), `${locale}: ${line}`);
}
for (const line of renderVm('synthetic')) assert.ok(line.includes('AZIONE_LOCALIZZATA HARDWARE_LOCALIZZATO'), line);
store('malformed', {...marker, 'Hardware: GPUs and Coral-TPU': 'MENU', 'Add GPU to VM (Intel | AMD | NVIDIA)': 'ACTION', [newKeys[3]]: 'BROKEN {menu} {wrong}'});
for (const line of renderVm('malformed')) assert.ok(line.startsWith('GPU passthrough assistant not found.') && line.includes('MENU → ACTION'), line);
store('malformed', {...marker, 'Hardware: GPUs and Coral-TPU': 'MENU', 'Add GPU to VM (Intel | AMD | NVIDIA)': 'ACTION', [newKeys[3]]: 'BROKEN {action} {wrong}'});
for (const line of renderVm('malformed')) assert.ok(line.startsWith('GPU passthrough assistant not found.') && line.includes('MENU → ACTION'), line);
store('malformed', {...marker, 'Hardware: GPUs and Coral-TPU': 'MENU', 'Add GPU to VM (Intel | AMD | NVIDIA)': 'ACTION', [newKeys[3]]: 'BROKEN {wrong}'});
for (const line of renderVm('malformed')) assert.ok(line.startsWith('GPU passthrough assistant not found.') && line.includes('MENU → ACTION'), line);
const additionalLead = read('scripts/gpu_tpu/add_gpu_vm.sh').split('\n').find(l => l.includes('Stop that VM, then choose:') && l.includes('msg+='));
assert.ok(additionalLead, 'busy-VM guidance has a standalone instruction before the rendered labels');
assert.equal(bash(setup + '\n' + additionalLead.trim() + '\nprintf \'%b\\n\' "$msg"', {LANGUAGE: 'synthetic'}), 'SCELTA_LOCALIZZATA:');
assert.ok(!read('scripts/gpu_tpu/add_gpu_vm.sh').includes("msg+=\"$(translate 'to move the GPU safely.')\""), 'no clipped tail follows the route');
const additionalNav = read('scripts/gpu_tpu/add_gpu_vm.sh').split('\n').find(l => l.includes('→') && l.includes('Add GPU to VM') && l.includes('msg+='));
assert.ok(additionalNav?.includes("$(translate 'Hardware: GPUs and Coral-TPU')"), 'busy-VM guidance uses actual menu label');
assert.ok(additionalNav?.includes("$(translate 'Add GPU to VM (Intel | AMD | NVIDIA)')"), 'busy-VM guidance uses actual action label');
for (const locale of ['en', 'it', 'synthetic']) {
const label = locale === 'en' ? 'Hardware: GPUs and Coral-TPU' : locale === 'it' ? it['Hardware: GPUs and Coral-TPU'] : 'HARDWARE_LOCALIZZATO';
const action = locale === 'en' ? 'Add GPU to VM (Intel | AMD | NVIDIA)' : locale === 'it' ? it['Add GPU to VM (Intel | AMD | NVIDIA)'] : 'AZIONE_LOCALIZZATA';
const rendered = bash(setup + `\nmsg=''; ${additionalNav.trim()}; printf '%b\\n' "$msg"`, {LANGUAGE: locale});
assert.equal(rendered, `${label} → ${action}`);
}
// Actual checker disables localization before both title and help; no behavior changed here.
const bypass = func(checker, 'disable_translation_post_upgrade');
assert.equal(bash(setup + `\n${bypass}\ndisable_translation_post_upgrade\n` + title), 'Run PVE 8 to 9 check');
assert.equal(bash(setup + `\n${bypass}\ndisable_translation_post_upgrade\n` + checkHelp), helpEn[0]);
const extraction = cp.spawnSync('python3', ['-c', `import importlib.util,pathlib
p=pathlib.Path('.github/scripts/build_translation_cache.py'); s=importlib.util.spec_from_file_location('cache',p); m=importlib.util.module_from_spec(s); s.loader.exec_module(m)
keys=set(m.extract_translate_texts(pathlib.Path('scripts')))
required=${JSON.stringify(['Run PVE 8 to 9 check', ...newKeys, 'Hardware: GPUs and Coral-TPU', 'Add GPU to VM (Intel | AMD | NVIDIA)'])}
assert set(required)<=keys, set(required)-keys
print('EXTRACTED',len(required))`], {cwd: root, encoding: 'utf8'});
assert.equal(extraction.status, 0, extraction.stderr);
// No source catalog edits; synthetic translation and malformed placeholders only live in scratch.
assert.ok(!Object.keys(it).some(k => newKeys.includes(k)));
fs.rmSync(temp, {recursive: true, force: true});
console.log('PASS: extracted upgrade/GPU consumers, seven caches/fallbacks, synthetic and malformed placeholders, real extractor');