diff --git a/.github/scripts/run_offline_qualified.py b/.github/scripts/run_offline_qualified.py
index 8a9548cd..586ff3f5 100644
--- a/.github/scripts/run_offline_qualified.py
+++ b/.github/scripts/run_offline_qualified.py
@@ -13,6 +13,9 @@ import sys
import tempfile
PYTHON_FILES = (
+ "AppImage/scripts/tests/test_pbs_i18n.py",
+ "AppImage/scripts/tests/test_monitor_links_i18n.py",
+ "AppImage/scripts/tests/test_monitor_links.py",
"AppImage/scripts/tests/test_local_app_icon_isolation.py",
"AppImage/scripts/tests/test_local_app_icons.py",
"AppImage/scripts/tests/test_docker_app_discovery.py",
@@ -28,6 +31,9 @@ PYTHON_FILES = (
"AppImage/scripts/tests/test_health_persistence_recurrence.py",
)
NODE_FILES = (
+ "tests/test_host_backup_messages.cjs",
+ "tests/test_pbs_i18n.cjs",
+ "tests/test_monitor_links_i18n.cjs",
"tests/lxc_updates/test_local_app_icons_ui.cjs",
"tests/lxc_updates/test_docker_app_discovery_ui.cjs",
"tests/lxc_updates/test_native_app_discovery_ui.cjs",
diff --git a/.github/workflows/test-offline-qualified.yml b/.github/workflows/test-offline-qualified.yml
index 1993c320..53880af5 100644
--- a/.github/workflows/test-offline-qualified.yml
+++ b/.github/workflows/test-offline-qualified.yml
@@ -16,6 +16,7 @@ on:
- 'AppImage/scripts/tests/test_local_app_icons.py'
- 'AppImage/scripts/tests/test_local_app_icon_isolation.py'
- 'AppImage/scripts/tests/test_docker_app_discovery.py'
+ - 'AppImage/scripts/tests/test_pbs_i18n.py'
- 'AppImage/scripts/tests/fixtures/docker_discovery_inspect.json'
- 'AppImage/app/**'
- 'AppImage/components/**'
@@ -41,6 +42,7 @@ on:
- 'AppImage/scripts/tests/test_local_app_icons.py'
- 'AppImage/scripts/tests/test_local_app_icon_isolation.py'
- 'AppImage/scripts/tests/test_docker_app_discovery.py'
+ - 'AppImage/scripts/tests/test_pbs_i18n.py'
- 'AppImage/scripts/tests/fixtures/docker_discovery_inspect.json'
- 'AppImage/app/**'
- 'AppImage/components/**'
diff --git a/.github/workflows/test-pbs.yml b/.github/workflows/test-pbs.yml
index 2dbc71b0..84a23736 100644
--- a/.github/workflows/test-pbs.yml
+++ b/.github/workflows/test-pbs.yml
@@ -6,6 +6,7 @@ on:
- '.github/workflows/test-pbs.yml'
- 'AppImage/scripts/*pbs*.py'
- 'AppImage/scripts/tests/test_pbs.py'
+ - 'AppImage/scripts/tests/test_pbs_i18n.py'
- 'AppImage/scripts/health_monitor.py'
- 'AppImage/scripts/auth_manager.py'
- 'AppImage/scripts/build_appimage.sh'
@@ -51,6 +52,7 @@ jobs:
- name: Platform, real TLS, APIs, health and AppImage packaging
run: |
python -B -m unittest discover -s AppImage/scripts/tests -p test_pbs.py -v
+ python -B -m unittest discover -s AppImage/scripts/tests -p test_pbs_i18n.py -v
python -B -m unittest discover -s AppImage/scripts/tests -p test_auth_manager_ssl_reload.py -v
for script in scripts/global/platform-functions.sh scripts/global/repository-functions.sh scripts/menus/pbs_menu.sh scripts/utilities/pbs_update.sh scripts/menus/main_menu.sh scripts/post_install/auto_post_install.sh scripts/post_install/customizable_post_install.sh scripts/post_install/uninstall-tools.sh scripts/global/update-pbs.sh scripts/menus/menu_post_install.sh; do
bash -n "$script"
diff --git a/AppImage/components/host-backup.tsx b/AppImage/components/host-backup.tsx
index 07e10b94..30434ff7 100644
--- a/AppImage/components/host-backup.tsx
+++ b/AppImage/components/host-backup.tsx
@@ -48,6 +48,7 @@ import {
import { ScriptTerminalModal } from "./script-terminal-modal"
import { RestoreProgressCard } from "./restore-progress-card"
import { fetchApi, getApiUrl } from "../lib/api-config"
+import { hostBackupErrorText } from "../lib/host-backup-messages"
import { fetchTerminalTicket } from "../lib/terminal-ws"
import { formatStorage, formatBytes, cancelButtonClass } from "../lib/utils"
import { getStorageUsageColor } from "../lib/storage-usage-color"
@@ -525,7 +526,7 @@ function PbsKeyfileActions({ pbsRepository }: { pbsRepository?: string }) {
await mutateInfo()
closeUpload()
} catch (e) {
- setErr(e instanceof Error ? e.message : String(e))
+ setErr(hostBackupErrorText(e, t))
setBusy(false)
}
}
@@ -542,7 +543,7 @@ function PbsKeyfileActions({ pbsRepository }: { pbsRepository?: string }) {
await mutateInfo()
closeDelete()
} catch (e) {
- setErr(e instanceof Error ? e.message : String(e))
+ setErr(hostBackupErrorText(e, t))
setBusy(false)
}
}
@@ -765,7 +766,7 @@ function KeyfileActionsBar({
setPass1("")
setPass2("")
} catch (e) {
- setErr(e instanceof Error ? e.message : String(e))
+ setErr(hostBackupErrorText(e, t))
} finally {
setBusy(false)
}
@@ -956,7 +957,7 @@ export function HostBackup() {
mutateJobs()
setJobToDelete(null)
} catch (e) {
- setActionError(t("backup.errors.deleteFailed", { error: `"${id}": ${e instanceof Error ? e.message : String(e)}` }))
+ setActionError(t("backup.errors.deleteFailed", { error: `"${id}": ${hostBackupErrorText(e, t)}` }))
} finally {
setBusyJobId(null)
}
@@ -1334,7 +1335,7 @@ export function HostBackup() {
{t("backup.archives.remoteQueryWarning")}
{remoteArchivesResp.errors.map((e, i) => (
- {e.backend}/{e.repo_name}: {e.error}
+ {e.backend}/{e.repo_name}: {hostBackupErrorText(e.error, t)}
))}
@@ -1761,7 +1762,7 @@ function InspectModal({
setImportFile(null)
setImportPath("")
} catch (e) {
- setImportError(e instanceof Error ? e.message : String(e))
+ setImportError(hostBackupErrorText(e, t))
} finally {
setImporting(false)
}
@@ -1812,7 +1813,7 @@ function InspectModal({
: undefined,
})
} catch (e) {
- setRestoreError(e instanceof Error ? e.message : String(e))
+ setRestoreError(hostBackupErrorText(e, t))
} finally {
setRestorePreparing(false)
}
@@ -1841,7 +1842,7 @@ function InspectModal({
a.click()
document.body.removeChild(a)
} catch (e) {
- setError(t("backup.errors.downloadFailed", { error: e instanceof Error ? e.message : String(e) }))
+ setError(t("backup.errors.downloadFailed", { error: hostBackupErrorText(e, t) }))
} finally {
// The download itself runs in the browser's network stack;
// we just initiated it. Clear the spinner immediately.
@@ -1914,7 +1915,7 @@ function InspectModal({
document.body.removeChild(a)
setExportTask(null)
} catch (e) {
- setError(t("backup.errors.downloadFailed", { error: e instanceof Error ? e.message : String(e) }))
+ setError(t("backup.errors.downloadFailed", { error: hostBackupErrorText(e, t) }))
} finally {
setDownloading(false)
}
@@ -1962,7 +1963,7 @@ function InspectModal({
setShowDeleteArchiveConfirm(false)
if (onDeleted) onDeleted(); else onClose()
} catch (e) {
- setError(t("backup.errors.deleteFailed", { error: e instanceof Error ? e.message : String(e) }))
+ setError(t("backup.errors.deleteFailed", { error: hostBackupErrorText(e, t) }))
} finally {
setDeletingArchive(false)
}
@@ -1989,7 +1990,7 @@ function InspectModal({
) as NonNullable
setInstructions(res)
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setFetchingInstructions(false)
}
@@ -2021,7 +2022,7 @@ function InspectModal({
)
setReport(res)
} catch (e: any) {
- setError(e?.message || t("backup.errors.preflightFailed"))
+ setError(hostBackupErrorText(e, t))
} finally {
setRunning(false)
}
@@ -2164,7 +2165,7 @@ function InspectModal({
— {exportTask.message}
{exportTask.state === "failed" && exportTask.error && (
- {exportTask.error}
+ {hostBackupErrorText(exportTask.error, t)}
)}
{exportTask.state === "completed" && exportTask.size_bytes > 0 && (
{t("backup.archives.packedSizeLabel")} {formatBytes(exportTask.size_bytes)}
@@ -2706,7 +2707,7 @@ function PreflightReportView({ report }: { report: PreflightReport }) {
{c.id}
- {c.message}
+ {hostBackupErrorText(c.message, t)}
)
@@ -3326,9 +3327,9 @@ function CreateJobDialog({
} catch (e) {
const err = e as Error & { body?: { tool_output?: string; tool_exit_code?: number } }
const detail = err.body?.tool_output
- ? `${err.message}\n\n${t("backup.errors.proxmoxBackupClientOutput")}:\n${err.body.tool_output}`
- : err.message
- setError(t("backup.errors.encryptionSetupFailed", { detail: detail || String(e) }))
+ ? `${hostBackupErrorText(err, t)}\n\n${t("backup.errors.proxmoxBackupClientOutput")}:\n${err.body.tool_output}`
+ : hostBackupErrorText(err, t)
+ setError(t("backup.errors.encryptionSetupFailed", { error: detail || hostBackupErrorText(e, t) }))
setPbsImportBusy(false)
setSubmitting(false)
return
@@ -3391,7 +3392,7 @@ function CreateJobDialog({
})
onCreated()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setSubmitting(false)
}
@@ -3764,7 +3765,7 @@ function CreateJobDialog({
>
) : (
- {t("backup.validation.invalidLabel")} {calendarPreview.error}
+ {t("backup.validation.invalidLabel")} {hostBackupErrorText(calendarPreview.error, t)}
)
) : (
@@ -4696,9 +4697,9 @@ function ManualBackupDialog({
} catch (e) {
const err = e as Error & { body?: { tool_output?: string; tool_exit_code?: number } }
const detail = err.body?.tool_output
- ? `${err.message}\n\n${t("backup.errors.proxmoxBackupClientOutput")}:\n${err.body.tool_output}`
- : err.message
- setError(t("backup.errors.encryptionSetupFailed", { detail: detail || String(e) }))
+ ? `${hostBackupErrorText(err, t)}\n\n${t("backup.errors.proxmoxBackupClientOutput")}:\n${err.body.tool_output}`
+ : hostBackupErrorText(err, t)
+ setError(t("backup.errors.encryptionSetupFailed", { error: detail || hostBackupErrorText(e, t) }))
setPbsImportBusy(false)
setSubmitting(false)
return
@@ -4738,7 +4739,7 @@ function ManualBackupDialog({
)
onLaunched(resp.job_id)
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setSubmitting(false)
}
@@ -5542,7 +5543,7 @@ function DestinationsSection({
await fetchApi(`/api/host-backups/destinations/pbs/${encodeURIComponent(name)}${force ? "?force=true" : ""}`, { method: "DELETE" })
onChanged()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -5554,7 +5555,7 @@ function DestinationsSection({
await fetchApi(`/api/host-backups/destinations/borg/${encodeURIComponent(name)}${force ? "?force=true" : ""}`, { method: "DELETE" })
onChanged()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -5571,7 +5572,7 @@ function DestinationsSection({
)
onChanged()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -5599,7 +5600,7 @@ function DestinationsSection({
})
onChanged()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -6042,7 +6043,7 @@ function DestinationRow({
{capacity?.error && (
- {t("backup.destinations.capacityUnavailable", { error: capacity.error })}
+ {t("backup.destinations.capacityUnavailable", { error: hostBackupErrorText(capacity.error, t) })}
)}
{!capacity && (
@@ -6341,7 +6342,7 @@ function AddDestinationDialog({
)
setGeneratedKey(resp)
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setGeneratingKey(false)
}
@@ -6405,7 +6406,7 @@ function AddDestinationDialog({
}
onSaved(savedRepo)
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setSubmitting(false)
}
@@ -6756,7 +6757,7 @@ function UsbPicker({
mutate()
if (resp?.mountpoint) onPick(resp.mountpoint)
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -6784,7 +6785,7 @@ function UsbPicker({
setFormatTyped("")
if (resp?.mountpoint) onPick(resp.mountpoint)
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -7001,7 +7002,7 @@ function ExtraPathsSection() {
setNewPath("")
mutate()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setSubmitting(false)
}
@@ -7014,7 +7015,7 @@ function ExtraPathsSection() {
await fetchApi(`/api/host-backups/extra-paths?path=${encodeURIComponent(p)}`, { method: "DELETE" })
mutate()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyPath(null)
}
@@ -7180,7 +7181,7 @@ function UsbDrivesSection() {
})
mutate()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -7198,7 +7199,7 @@ function UsbDrivesSection() {
})
mutate()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -7222,7 +7223,7 @@ function UsbDrivesSection() {
setFormatTarget(null)
setFormatTyped("")
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusyKey(null)
}
@@ -7508,7 +7509,7 @@ function JobDetailModal({
setRunning(false)
setRunBaseline(null)
setRunBaselineLogPath(null)
- setActionError(t("backup.errors.runFailed", { error: e instanceof Error ? e.message : String(e) }))
+ setActionError(t("backup.errors.runFailed", { error: hostBackupErrorText(e, t) }))
}
}
@@ -7522,7 +7523,7 @@ function JobDetailModal({
await refetch()
onChanged()
} catch (e) {
- setActionError(t("backup.errors.toggleFailed", { error: e instanceof Error ? e.message : String(e) }))
+ setActionError(t("backup.errors.toggleFailed", { error: hostBackupErrorText(e, t) }))
} finally {
setBusy("")
}
@@ -8155,7 +8156,7 @@ function PbsKeyfileRecoveryDialog({
})
onRecovered()
} catch (e) {
- setError(e instanceof Error ? e.message : String(e))
+ setError(hostBackupErrorText(e, t))
} finally {
setBusy(false)
}
@@ -8342,7 +8343,7 @@ function ArchiveContentsModal({
body: JSON.stringify(body),
})
.then((r: any) => setData(r))
- .catch((e) => setError(e instanceof Error ? e.message : String(e)))
+ .catch((e) => setError(hostBackupErrorText(e, t)))
.finally(() => setLoading(false))
}, [open, source, repo_name, snapshot, path])
@@ -8571,7 +8572,7 @@ function PlanSummary({ plan }: { plan: any }) {
{t("backup.plan.blockers")}
{blockers.map((b, i) => (
- - {typeof b === "string" ? b : (b.message || JSON.stringify(b))}
+ - {hostBackupErrorText(typeof b === "string" ? b : (b.message || JSON.stringify(b)), t)}
))}
@@ -8581,7 +8582,7 @@ function PlanSummary({ plan }: { plan: any }) {
{t("backup.plan.warnings")}
{warnings.map((w, i) => (
- - {typeof w === "string" ? w : (w.message || JSON.stringify(w))}
+ - {hostBackupErrorText(typeof w === "string" ? w : (w.message || JSON.stringify(w)), t)}
))}
@@ -8611,7 +8612,7 @@ function StorageSection({ storage }: { storage: any }) {
{e.name || e.storage || `entry ${i}`}
{e.type &&
{e.type}}
- {e.message &&
{e.message}
}
+ {e.message &&
{hostBackupErrorText(e.message, t)}
}
)
diff --git a/AppImage/components/monitor-switcher.tsx b/AppImage/components/monitor-switcher.tsx
index 89f8d249..7fb88eb9 100644
--- a/AppImage/components/monitor-switcher.tsx
+++ b/AppImage/components/monitor-switcher.tsx
@@ -14,6 +14,7 @@ import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,
} from "./ui/dropdown-menu"
import { fetchApi } from "../lib/api-config"
+import { monitorLinkError } from "../lib/monitor-link-errors"
import { useT } from "../lib/i18n/provider"
interface MonitorLink {
@@ -185,8 +186,9 @@ export function MonitorSwitcher({ nodeName, compact = false }: { nodeName: strin
await refreshCache("/api/monitor-links")
setOfferOrigin(false)
} catch (err) {
- await refreshCache("/api/monitor-links")
- setAddError(err instanceof Error ? err.message : t("monitorLinks.saveFailed"))
+ setAddError(monitorLinkError(err, t))
+ try { await refreshCache("/api/monitor-links") }
+ catch { /* SWR shows the load error; keep the localized mutation error. */ }
} finally { setAdding(false) }
}
const notNow = () => {
@@ -260,7 +262,7 @@ export function MonitorSwitcher({ nodeName, compact = false }: { nodeName: strin
{manageOpen && }
{origin &&