mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-29 10:06:41 +00:00
Merge develop: combine the i18n test fixture with #363
Both message batches seeded locale copies at the same point of test_command_descriptions.py. The two blocks are independent — one seeds the backup messages, the other the storage ones — so they are merged into a single block that reads and writes the fixture once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,18 +90,31 @@ class CommandDescriptionsTests(unittest.TestCase):
|
||||
path = root / lang / "common.json"
|
||||
path.parent.mkdir()
|
||||
shutil.copyfile(ROOT / f"AppImage/messages/{lang}/common.json", path)
|
||||
# Model steady state after automation fills this intentional new key.
|
||||
# Runtime missing-key fallback is tested separately in test_storage_messages.cjs.
|
||||
# This steady-state fixture assumes generation already finished.
|
||||
# Seed only the keys the two message PRs introduce; keep the
|
||||
# recovered command arrays and the repo catalogs exact. Runtime
|
||||
# missing-key fallback is covered by the JSX seam tests and by
|
||||
# test_storage_messages.cjs.
|
||||
if lang != "en":
|
||||
messages = json.loads(path.read_text())
|
||||
messages["storage"].setdefault("savedSmartData", catalog("en")["storage"]["savedSmartData"])
|
||||
report = messages["storage"]["smartReport"]
|
||||
temporary = json.loads(path.read_text())
|
||||
for section, key in (
|
||||
("archives", "emptyMessage"),
|
||||
("destinations", "backupsKeptMessage"),
|
||||
("destinations", "localAttachHelpMessage"),
|
||||
):
|
||||
temporary["backup"][section].setdefault(
|
||||
key, catalog("en")["backup"][section][key])
|
||||
temporary["storage"].setdefault(
|
||||
"savedSmartData", catalog("en")["storage"]["savedSmartData"])
|
||||
report = temporary["storage"]["smartReport"]
|
||||
source_report = catalog("en")["storage"]["smartReport"]
|
||||
for key in ("passedAssessment", "noReallocatedSectorsReported", "passedMeaningTitle", "passedMeaning"):
|
||||
for key in ("passedAssessment", "noReallocatedSectorsReported",
|
||||
"passedMeaningTitle", "passedMeaning"):
|
||||
report.setdefault(key, source_report[key])
|
||||
for key in ("passedTitle", "passedText"):
|
||||
report["recommendations"].setdefault(key, source_report["recommendations"][key])
|
||||
path.write_text(json.dumps(messages))
|
||||
report["recommendations"].setdefault(
|
||||
key, source_report["recommendations"][key])
|
||||
path.write_text(json.dumps(temporary, ensure_ascii=False))
|
||||
before = {p: p.read_bytes() for p in root.glob("*/common.json")}
|
||||
argv = [str(SCRIPT), "--source", str(root / "en/common.json"),
|
||||
"--messages-dir", str(root), "--languages", languages, "--sleep", "0"]
|
||||
|
||||
@@ -1300,7 +1300,7 @@ export function HostBackup() {
|
||||
</div>
|
||||
) : unifiedArchives.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground py-4">
|
||||
{t("backup.archives.emptyBefore")} <span className="font-medium">{t("backup.manual.run")}</span> {t("backup.archives.emptyAfter")}
|
||||
{t("backup.archives.emptyMessage")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
@@ -4089,7 +4089,7 @@ function CreateJobDialog({
|
||||
className="font-mono"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("backup.destinations.localAttachHelpBefore")} <code className="font-mono">/dump</code> {t("backup.destinations.localAttachHelpMiddle")} <code className="font-mono">/var/lib/vz/dump</code>.
|
||||
{t("backup.destinations.localAttachHelpMessage")}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
@@ -5709,7 +5709,7 @@ function DestinationsSection({
|
||||
)}
|
||||
{backups > 0 && (
|
||||
<div className="px-3 py-2 rounded-md border border-blue-500/40 bg-blue-500/5">
|
||||
{t("backup.destinations.backupsKeptBefore", { count: backups })} <span className="font-medium">{t("backup.destinations.kept")}</span>.
|
||||
{t("backup.destinations.backupsKeptMessage")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,4 +7,22 @@ The ProxMenux Monitor dashboard uses a small client-side i18n layer.
|
||||
|
||||
To improve a translation, edit the values in your locale's `common.json` — keep placeholders such as `{uptime}`, `{vmid}` or `{count}` unchanged, and don't translate brand or product names (`ProxMenux Monitor`, `Proxmox Backup Server`, `Secure Gateway`, `Tailscale`, etc.). Missing keys fall back to English at runtime, so a partial refresh is always safe to merge.
|
||||
|
||||
## Backup message composition regressions
|
||||
|
||||
From the repository root, run:
|
||||
|
||||
```sh
|
||||
node tests/test_backup_archives_empty.cjs
|
||||
node tests/test_backup_destination_messages.cjs
|
||||
```
|
||||
|
||||
Prerequisites: Node.js and the `typescript` development dependency from
|
||||
`AppImage/package.json` must be installed/resolvable (normally in
|
||||
`AppImage/node_modules`). These tests compile actual JSX message branches and
|
||||
pure i18n lookup functions without importing the component or calling a backend.
|
||||
They cover whole English messages, missing-key locale fallback, loading/error
|
||||
branches, nonempty lists, the removal notice at zero/one/multiple archives,
|
||||
local-attach help, and unchanged shared labels. They do not replace browser or
|
||||
real-host acceptance. Keep literal paths in the local-attach help unchanged.
|
||||
|
||||
To add a new locale, see [§11 → Adding a new locale](../../CONTRIBUTING.md#adding-a-new-locale) in the Contributing Guide.
|
||||
|
||||
@@ -3845,7 +3845,8 @@
|
||||
"restoreTitle": "Von diesem Backup wiederherstellen",
|
||||
"startingExport": "Download wird vorbereitet...",
|
||||
"title": "Backups",
|
||||
"viewContentsTitle": "Sicherungsinhalte anzeigen"
|
||||
"viewContentsTitle": "Sicherungsinhalte anzeigen",
|
||||
"emptyMessage": "Keine Sicherungsarchive gefunden."
|
||||
},
|
||||
"backends": {
|
||||
"borgDescription": "Borg-Repository für deduplizierte Host-Backups.",
|
||||
@@ -3961,7 +3962,9 @@
|
||||
"sshUserHelpBefore": "Benutzer auf dem Remote-Host,",
|
||||
"title": "Reiseziele",
|
||||
"unmountTitle": "Hängen Sie dieses Ziel aus",
|
||||
"whereIsBorgRepo": "Wo ist das Borg-Repository?"
|
||||
"whereIsBorgRepo": "Wo ist das Borg-Repository?",
|
||||
"backupsKeptMessage": "Vorhandene Backup-Archive werden nicht gelöscht.",
|
||||
"localAttachHelpMessage": "Mounten Sie zuerst USB-Laufwerke.Lassen Sie das Feld leer, um den PVE-Speicherpfad + /dump zu verwenden (Fallback: /var/lib/vz/dump)."
|
||||
},
|
||||
"encryption": {
|
||||
"autoDetectedFromPveStorage": "Aus PVE-Speicher erkannt",
|
||||
|
||||
@@ -3835,6 +3835,7 @@
|
||||
"downloadTitle": "Download this backup",
|
||||
"emptyAfter": "backups yet.",
|
||||
"emptyBefore": "No",
|
||||
"emptyMessage": "No backup archives found.",
|
||||
"encrypted": "Encrypted",
|
||||
"encryptedTitle": "This backup is encrypted",
|
||||
"filesInBackup": "Files in backup",
|
||||
@@ -3910,6 +3911,7 @@
|
||||
"addPbsRepository": "Add PBS repository",
|
||||
"appendAuthorizedKeyBefore": "Append the generated public key to",
|
||||
"backupsKeptBefore": "Existing backups are kept in",
|
||||
"backupsKeptMessage": "Existing backup archives are not deleted.",
|
||||
"borgEncryptionInherited": "Borg encryption is controlled by the repository.",
|
||||
"builtIn": "Built-in",
|
||||
"builtInDefault": "Built-in default",
|
||||
@@ -3930,6 +3932,7 @@
|
||||
"leavePassphraseBlank": "Leave blank to keep the saved passphrase.",
|
||||
"leavePasswordBlank": "Leave blank to keep the saved password.",
|
||||
"localAttachHelpBefore": "Use a mounted local path. For USB drives, mount them first in",
|
||||
"localAttachHelpMessage": "Mount USB drives first. Leave blank to use the PVE storage path + /dump (fallback: /var/lib/vz/dump).",
|
||||
"localAttachHelpMiddle": "USB drives",
|
||||
"localManualPickHelp": "Pick a local path for one-shot backup output.",
|
||||
"localPathHelpAfter": "or another mounted path.",
|
||||
|
||||
@@ -3845,7 +3845,8 @@
|
||||
"restoreTitle": "Restaurar desde esta copia de seguridad",
|
||||
"startingExport": "Preparando descarga...",
|
||||
"title": "Copias de seguridad",
|
||||
"viewContentsTitle": "Ver contenidos de la copia de seguridad"
|
||||
"viewContentsTitle": "Ver contenidos de la copia de seguridad",
|
||||
"emptyMessage": "No se encontraron archivos de respaldo."
|
||||
},
|
||||
"backends": {
|
||||
"borgDescription": "Repositorio Borg para copias de seguridad de host deduplicadas.",
|
||||
@@ -3961,7 +3962,9 @@
|
||||
"sshUserHelpBefore": "Usuario en el host remoto,",
|
||||
"title": "Destinos",
|
||||
"unmountTitle": "Desmontar este destino",
|
||||
"whereIsBorgRepo": "¿Dónde está el repositorio Borg?"
|
||||
"whereIsBorgRepo": "¿Dónde está el repositorio Borg?",
|
||||
"backupsKeptMessage": "los archivos de copia de seguridad existentes no se eliminan.",
|
||||
"localAttachHelpMessage": "Monte primero las unidades USB.Déjelo en blanco para usar la ruta de almacenamiento PVE + /dump (alternativa: /var/lib/vz/dump)."
|
||||
},
|
||||
"encryption": {
|
||||
"autoDetectedFromPveStorage": "Detectado desde el almacenamiento PVE",
|
||||
|
||||
@@ -3845,7 +3845,8 @@
|
||||
"restoreTitle": "Restaurer à partir de cette sauvegarde",
|
||||
"startingExport": "Préparation du téléchargement...",
|
||||
"title": "Sauvegardes",
|
||||
"viewContentsTitle": "Afficher le contenu de la sauvegarde"
|
||||
"viewContentsTitle": "Afficher le contenu de la sauvegarde",
|
||||
"emptyMessage": "Aucune archive de sauvegarde trouvée."
|
||||
},
|
||||
"backends": {
|
||||
"borgDescription": "Référentiel Borg pour les sauvegardes d'hôtes dédupliquées.",
|
||||
@@ -3961,7 +3962,9 @@
|
||||
"sshUserHelpBefore": "Utilisateur sur l'hôte distant,",
|
||||
"title": "Destinations",
|
||||
"unmountTitle": "Démonter cette destination",
|
||||
"whereIsBorgRepo": "Où se trouve le référentiel Borg ?"
|
||||
"whereIsBorgRepo": "Où se trouve le référentiel Borg ?",
|
||||
"backupsKeptMessage": "Les archives de sauvegarde existantes ne sont pas supprimées.",
|
||||
"localAttachHelpMessage": "Montez d’abord les clés USB.Laissez vide pour utiliser le chemin de stockage PVE + /dump (repli : /var/lib/vz/dump)."
|
||||
},
|
||||
"encryption": {
|
||||
"autoDetectedFromPveStorage": "Détecté depuis le stockage PVE",
|
||||
|
||||
@@ -3845,7 +3845,8 @@
|
||||
"restoreTitle": "Ripristina da questo backup",
|
||||
"startingExport": "Preparazione download...",
|
||||
"title": "Backup",
|
||||
"viewContentsTitle": "Visualizza i contenuti del backup"
|
||||
"viewContentsTitle": "Visualizza i contenuti del backup",
|
||||
"emptyMessage": "nessun archivio di backup trovato."
|
||||
},
|
||||
"backends": {
|
||||
"borgDescription": "Repository Borg per backup host deduplicati.",
|
||||
@@ -3961,7 +3962,9 @@
|
||||
"sshUserHelpBefore": "Utente sull'host remoto,",
|
||||
"title": "Destinazioni",
|
||||
"unmountTitle": "Smonta questa destinazione",
|
||||
"whereIsBorgRepo": "Dov'è il deposito Borg?"
|
||||
"whereIsBorgRepo": "Dov'è il deposito Borg?",
|
||||
"backupsKeptMessage": "gli archivi di backup esistenti non vengono eliminati.",
|
||||
"localAttachHelpMessage": "monta prima le unità USB.Lascia vuoto per utilizzare il percorso di archiviazione PVE + /dump (fallback: /var/lib/vz/dump)."
|
||||
},
|
||||
"encryption": {
|
||||
"autoDetectedFromPveStorage": "Rilevato dall'archivio PVE",
|
||||
|
||||
@@ -3845,7 +3845,8 @@
|
||||
"restoreTitle": "Restaurar deste backup",
|
||||
"startingExport": "Preparando download...",
|
||||
"title": "Cópias de segurança",
|
||||
"viewContentsTitle": "Ver o conteúdo do backup"
|
||||
"viewContentsTitle": "Ver o conteúdo do backup",
|
||||
"emptyMessage": "Nenhum arquivo de backup encontrado."
|
||||
},
|
||||
"backends": {
|
||||
"borgDescription": "Repositório Borg para backups de host desduplicados.",
|
||||
@@ -3961,7 +3962,9 @@
|
||||
"sshUserHelpBefore": "Usuário no host remoto,",
|
||||
"title": "Destinos",
|
||||
"unmountTitle": "Desmontar este destino",
|
||||
"whereIsBorgRepo": "Onde está o repositório Borg?"
|
||||
"whereIsBorgRepo": "Onde está o repositório Borg?",
|
||||
"backupsKeptMessage": "os arquivos de backup existentes não são excluídos.",
|
||||
"localAttachHelpMessage": "Monte as unidades USB primeiro.Deixe em branco para usar o caminho de armazenamento PVE + /dump (fallback: /var/lib/vz/dump)."
|
||||
},
|
||||
"encryption": {
|
||||
"autoDetectedFromPveStorage": "Detectado no armazenamento PVE",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3845,7 +3845,8 @@
|
||||
"startingExport": "Förbereder nedladdning...",
|
||||
"title": "Säkerhetskopieringar",
|
||||
"viewContentsTitle": "Visa säkerhetskopieringsinnehåll",
|
||||
"descriptionAfter": ""
|
||||
"descriptionAfter": "",
|
||||
"emptyMessage": "Inga backup-arkiv hittades."
|
||||
},
|
||||
"backends": {
|
||||
"borgDescription": "Borg-arkiv för deduplicerade värdsäkerhetskopior.",
|
||||
@@ -3961,7 +3962,9 @@
|
||||
"sshUserHelpBefore": "Användare på fjärrvärden,",
|
||||
"title": "Destinationer",
|
||||
"unmountTitle": "Avmontera denna destination",
|
||||
"whereIsBorgRepo": "Var finns Borgarkivet?"
|
||||
"whereIsBorgRepo": "Var finns Borgarkivet?",
|
||||
"backupsKeptMessage": "Befintliga säkerhetskopieringsarkiv tas inte bort.",
|
||||
"localAttachHelpMessage": "Montera USB-enheter först.Lämna tomt för att använda PVE-lagringssökvägen + /dump (fallback: /var/lib/vz/dump)."
|
||||
},
|
||||
"encryption": {
|
||||
"autoDetectedFromPveStorage": "Upptäckt från PVE-lagring",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "Erfolgreich abgeschlossen!",
|
||||
"Completed with errors —": "Mit Fehlern abgeschlossen –",
|
||||
"Completed.": "Vollendet.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "Abgeschlossen. Zur VM {vmid} hinzugefügte Geräte: {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Vollendet. Drücken Sie die Eingabetaste, um zum Menü zurückzukehren...",
|
||||
"Completing pending package configurations...": "Ausstehende Paketkonfigurationen werden abgeschlossen...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "Konformitätsprüfung (PCI-DSS, HIPAA usw.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Aktueller Benutzer",
|
||||
"Current user UID, GID and groups": "Aktuelle Benutzer-UID, GID und Gruppen",
|
||||
"Current version:": "Aktuelle Version:",
|
||||
"Currently": "Momentan",
|
||||
"Currently Mounted:": "Derzeit montiert:",
|
||||
"Currently configured target:": "Derzeit konfiguriertes Ziel:",
|
||||
"Currently mounted:": "Derzeit montiert:",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Benutzerdefinierter Pfad",
|
||||
"Custom path...": "Benutzerdefinierter Pfad...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "Benutzerdefinierte Pfade sind SOWOHL in den Standard- als auch in den benutzerdefinierten Sicherungsprofilen enthalten.",
|
||||
"Custom paths currently saved: {count}.": "Derzeit gespeicherte benutzerdefinierte Pfade: {count}.",
|
||||
"Custom restore by components": "Benutzerdefinierte Wiederherstellung nach Komponenten",
|
||||
"Custom restore by paths": "Benutzerdefinierte Wiederherstellung nach Pfaden",
|
||||
"Custom scripts and ProxMenux files": "Benutzerdefinierte Skripte und ProxMenux-Dateien",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Installiert",
|
||||
"Installed at:": "Installiert unter:",
|
||||
"Installed components:": "Installierte Komponenten:",
|
||||
"Installed:": "Installiert:",
|
||||
"Installer already downloaded and verified.": "Das Installationsprogramm wurde bereits heruntergeladen und überprüft.",
|
||||
"Installer copied to container.": "Installer in Container kopiert.",
|
||||
"Installer downloaded.": "Installer heruntergeladen.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Paketaktualisierung mit Warnungen abgeschlossen, Fortsetzung ...",
|
||||
"Package update had issues, checking details...": "Bei der Paketaktualisierung gab es Probleme, beim Überprüfen der Details ...",
|
||||
"Packages from backup to install:": "Pakete vom Backup zur Installation:",
|
||||
"Packages installed: {count}.": "Installierte Pakete: {count}.",
|
||||
"Packages upgrade successfull": "Paketaktualisierung erfolgreich",
|
||||
"Packages upgraded": "Pakete aktualisiert",
|
||||
"Packages:": "Pakete:",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod fehlgeschlagen – wahrscheinlich unprivilegierter CT gegen Host-Bind-Mount. Zurückgreifen auf ACL.",
|
||||
"content:": "Inhalt:",
|
||||
"could not be compiled for kernel": "konnte nicht für den Kernel kompiliert werden",
|
||||
"custom path(s) saved.": "Benutzerdefinierte(r) Pfad(e) gespeichert.",
|
||||
"default": "Standard",
|
||||
"delete the credentials file (if any)": "Löschen Sie die Anmeldeinformationsdatei (falls vorhanden).",
|
||||
"delete the matching line from /etc/fstab": "Löschen Sie die entsprechende Zeile aus /etc/fstab",
|
||||
"descriptor + VMDK files": "Deskriptor + VMDK-Dateien",
|
||||
"device(s) added to VM": "Gerät(e) zur VM hinzugefügt",
|
||||
"devices": "Geräte",
|
||||
"did not become ready. Skipping.": "nicht fertig geworden. Überspringen.",
|
||||
"disk(s) added to CT": "Festplatte(n) zu CT hinzugefügt",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "Verwaiste Iface-Zeilen, keine Auswirkung auf die Wiederherstellung",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "andere .tar-Archive – keine ProxMenux-Host-Backups (z. B. PVE vzdump oder nicht verwandte Tarballs).",
|
||||
"packages (this may take a few minutes)...": "Pakete (dies kann einige Minuten dauern)...",
|
||||
"packages.": "Pakete.",
|
||||
"parent PF:": "Eltern-PF:",
|
||||
"partition(s). Partition table preserved.": "Partition(en). Partitionstabelle bleibt erhalten.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "Pfade für den nächsten Start (/etc/pve, Gäste, Treiber, ...)",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "¡Completado con éxito!",
|
||||
"Completed with errors —": "Completado con errores.",
|
||||
"Completed.": "Terminado.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "Completado. Dispositivos agregados a la VM {vmid}: {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Terminado. Presione Enter para regresar al menú...",
|
||||
"Completing pending package configurations...": "Completando las configuraciones de paquetes pendientes...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "Comprobación de cumplimiento (PCI-DSS, HIPAA, etc.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Usuario actual",
|
||||
"Current user UID, GID and groups": "UID, GID y grupos del usuario actual",
|
||||
"Current version:": "Versión actual:",
|
||||
"Currently": "actualmente",
|
||||
"Currently Mounted:": "Actualmente montado:",
|
||||
"Currently configured target:": "Destino actualmente configurado:",
|
||||
"Currently mounted:": "Actualmente montado:",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Ruta personalizada",
|
||||
"Custom path...": "Ruta personalizada...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "Las rutas personalizadas se incluyen tanto en los perfiles de copia de seguridad predeterminados como en los personalizados.",
|
||||
"Custom paths currently saved: {count}.": "Rutas personalizadas guardadas actualmente: {count}.",
|
||||
"Custom restore by components": "Restauración personalizada por componentes",
|
||||
"Custom restore by paths": "restauración personalizada por rutas",
|
||||
"Custom scripts and ProxMenux files": "Scripts personalizados y archivos ProxMenux",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Instalado",
|
||||
"Installed at:": "Instalado en:",
|
||||
"Installed components:": "Componentes instalados:",
|
||||
"Installed:": "Instalado:",
|
||||
"Installer already downloaded and verified.": "Instalador ya descargado y verificado.",
|
||||
"Installer copied to container.": "Instalador copiado al contenedor.",
|
||||
"Installer downloaded.": "Instalador descargado.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Actualización del paquete completada con advertencias, continúa...",
|
||||
"Package update had issues, checking details...": "La actualización del paquete tuvo problemas, comprobando detalles...",
|
||||
"Packages from backup to install:": "Paquetes de copia de seguridad para instalar:",
|
||||
"Packages installed: {count}.": "Paquetes instalados: {count}.",
|
||||
"Packages upgrade successfull": "Actualización de paquetes exitosa",
|
||||
"Packages upgraded": "Paquetes actualizados",
|
||||
"Packages:": "Paquetes:",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod falló: probablemente CT sin privilegios contra el montaje de enlace del host. Regresando a ACL.",
|
||||
"content:": "contenido:",
|
||||
"could not be compiled for kernel": "no se pudo compilar para el kernel",
|
||||
"custom path(s) saved.": "rutas personalizadas guardadas.",
|
||||
"default": "por defecto",
|
||||
"delete the credentials file (if any)": "eliminar el archivo de credenciales (si corresponde)",
|
||||
"delete the matching line from /etc/fstab": "elimine la línea coincidente de /etc/fstab",
|
||||
"descriptor + VMDK files": "descriptor + archivos VMDK",
|
||||
"device(s) added to VM": "Dispositivo(s) agregado(s) a la VM",
|
||||
"devices": "dispositivos",
|
||||
"did not become ready. Skipping.": "no estuvo listo. Salto a la comba.",
|
||||
"disk(s) added to CT": "disco(s) agregado(s) a CT",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "líneas de iface huérfanas, sin impacto en la restauración",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "otros archivos .tar, no copias de seguridad del host ProxMenux (por ejemplo, PVE vzdump o archivos tar no relacionados).",
|
||||
"packages (this may take a few minutes)...": "paquetes (esto puede tardar unos minutos)...",
|
||||
"packages.": "paquetes.",
|
||||
"parent PF:": "padre PF:",
|
||||
"partition(s). Partition table preserved.": "partición(es). Se conserva la tabla de particiones.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "rutas para el próximo arranque (/etc/pve, invitados, controladores, ...)",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "Terminé avec succès !",
|
||||
"Completed with errors —": "Complété avec des erreurs —",
|
||||
"Completed.": "Complété.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "Terminé. Appareils ajoutés à la VM {vmid} : {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Complété. Appuyez sur Entrée pour revenir au menu...",
|
||||
"Completing pending package configurations...": "Finalisation des configurations de packages en attente...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "Vérification de la conformité (PCI-DSS, HIPAA, etc.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Utilisateur actuel",
|
||||
"Current user UID, GID and groups": "UID, GID et groupes de l'utilisateur actuel",
|
||||
"Current version:": "Version actuelle :",
|
||||
"Currently": "Actuellement",
|
||||
"Currently Mounted:": "Actuellement monté :",
|
||||
"Currently configured target:": "cible actuellement configurée :",
|
||||
"Currently mounted:": "Actuellement monté :",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Chemin personnalisé",
|
||||
"Custom path...": "Chemin personnalisé...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "Les chemins personnalisés sont inclus dans les profils de sauvegarde par défaut et personnalisés.",
|
||||
"Custom paths currently saved: {count}.": "Chemins personnalisés actuellement enregistrés : {count}.",
|
||||
"Custom restore by components": "Restauration personnalisée par composants",
|
||||
"Custom restore by paths": "Restauration personnalisée par chemins",
|
||||
"Custom scripts and ProxMenux files": "Scripts personnalisés et fichiers ProxMenux",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Installé",
|
||||
"Installed at:": "Installé à :",
|
||||
"Installed components:": "Composants installés :",
|
||||
"Installed:": "Installé:",
|
||||
"Installer already downloaded and verified.": "Installateur déjà téléchargé et vérifié.",
|
||||
"Installer copied to container.": "Programme d'installation copié dans le conteneur.",
|
||||
"Installer downloaded.": "Installateur téléchargé.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Mise à jour du package terminée avec des avertissements, suite...",
|
||||
"Package update had issues, checking details...": "La mise à jour du package a rencontré des problèmes, vérification des détails...",
|
||||
"Packages from backup to install:": "Packages de la sauvegarde à installer :",
|
||||
"Packages installed: {count}.": "Packages installés : {count}.",
|
||||
"Packages upgrade successfull": "Mise à jour des packages réussie",
|
||||
"Packages upgraded": "Forfaits mis à niveau",
|
||||
"Packages:": "Forfaits :",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod a échoué - CT probablement non privilégié contre le montage de liaison de l'hôte. Revenir à ACL.",
|
||||
"content:": "contenu:",
|
||||
"could not be compiled for kernel": "n'a pas pu être compilé pour le noyau",
|
||||
"custom path(s) saved.": "chemin(s) personnalisé(s) enregistré(s).",
|
||||
"default": "défaut",
|
||||
"delete the credentials file (if any)": "supprimer le fichier d'informations d'identification (le cas échéant)",
|
||||
"delete the matching line from /etc/fstab": "supprimez la ligne correspondante de /etc/fstab",
|
||||
"descriptor + VMDK files": "descripteur + fichiers VMDK",
|
||||
"device(s) added to VM": "périphérique(s) ajouté(s) à la VM",
|
||||
"devices": "appareils",
|
||||
"did not become ready. Skipping.": "n'est pas devenu prêt. Saut.",
|
||||
"disk(s) added to CT": "disque(s) ajouté(s) à CT",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "lignes iface orphelines, aucun impact sur la restauration",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "autres archives .tar - pas les sauvegardes de l'hôte ProxMenux (par exemple, vzdump PVE ou archives tar sans rapport).",
|
||||
"packages (this may take a few minutes)...": "packages (cela peut prendre quelques minutes)...",
|
||||
"packages.": "paquets.",
|
||||
"parent PF:": "PF parent :",
|
||||
"partition(s). Partition table preserved.": "partition(s). Table de partition conservée.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "chemins pour le prochain démarrage (/etc/pve, invités, pilotes, ...)",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "Completato con successo!",
|
||||
"Completed with errors —": "Completato con errori —",
|
||||
"Completed.": "Completato.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "completato. Dispositivi aggiunti alla VM {vmid}: {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Completato. Premere Invio per tornare al menu...",
|
||||
"Completing pending package configurations...": "completamento delle configurazioni dei pacchetti in sospeso...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "Controllo della conformità (PCI-DSS, HIPAA, ecc.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Utente attuale",
|
||||
"Current user UID, GID and groups": "UID, GID e gruppi dell'utente corrente",
|
||||
"Current version:": "Versione attuale:",
|
||||
"Currently": "Attualmente",
|
||||
"Currently Mounted:": "Attualmente montato:",
|
||||
"Currently configured target:": "destinazione attualmente configurata:",
|
||||
"Currently mounted:": "Attualmente montato:",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Percorso personalizzato",
|
||||
"Custom path...": "Percorso personalizzato...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "I percorsi personalizzati sono inclusi SIA nei profili di backup predefiniti che in quelli personalizzati.",
|
||||
"Custom paths currently saved: {count}.": "percorsi personalizzati attualmente salvati: {count}.",
|
||||
"Custom restore by components": "Ripristino personalizzato per componenti",
|
||||
"Custom restore by paths": "ripristino personalizzato per percorsi",
|
||||
"Custom scripts and ProxMenux files": "Script personalizzati e file ProxMenux",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Installato",
|
||||
"Installed at:": "Installato presso:",
|
||||
"Installed components:": "Componenti installati:",
|
||||
"Installed:": "Installato:",
|
||||
"Installer already downloaded and verified.": "Programma di installazione già scaricato e verificato.",
|
||||
"Installer copied to container.": "Programma di installazione copiato nel contenitore.",
|
||||
"Installer downloaded.": "Programma di installazione scaricato.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Aggiornamento del pacchetto completato con avvisi, continuando...",
|
||||
"Package update had issues, checking details...": "L'aggiornamento del pacchetto presentava problemi, verifica dei dettagli...",
|
||||
"Packages from backup to install:": "pacchetti dal backup all'installazione:",
|
||||
"Packages installed: {count}.": "pacchetti installati: {count}.",
|
||||
"Packages upgrade successfull": "Aggiornamento dei pacchetti riuscito",
|
||||
"Packages upgraded": "Pacchetti aggiornati",
|
||||
"Packages:": "Pacchetti:",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod non è riuscito: probabilmente CT non privilegiato contro il montaggio del collegamento dell'host. Ritornando all'ACL.",
|
||||
"content:": "contenuto:",
|
||||
"could not be compiled for kernel": "non può essere compilato per il kernel",
|
||||
"custom path(s) saved.": "percorsi personalizzati salvati.",
|
||||
"default": "predefinito",
|
||||
"delete the credentials file (if any)": "eliminare il file delle credenziali (se presente)",
|
||||
"delete the matching line from /etc/fstab": "elimina la riga corrispondente da /etc/fstab",
|
||||
"descriptor + VMDK files": "descrittore + file VMDK",
|
||||
"device(s) added to VM": "dispositivi aggiunti alla VM",
|
||||
"devices": "dispositivi",
|
||||
"did not become ready. Skipping.": "non è diventato pronto. Saltare.",
|
||||
"disk(s) added to CT": "disco(i) aggiunto(i) a CT",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "Linee iface orfane, nessun impatto sul ripristino",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "altri archivi .tar — non backup dell'host ProxMenux (ad esempio PVE vzdump o tarball non correlati).",
|
||||
"packages (this may take a few minutes)...": "pacchetti (l'operazione potrebbe richiedere alcuni minuti)...",
|
||||
"packages.": "pacchetti.",
|
||||
"parent PF:": "PF genitore:",
|
||||
"partition(s). Partition table preserved.": "partizione(i). Tabella delle partizioni conservata.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "percorsi per l'avvio successivo (/etc/pve, guest, driver, ...)",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "Concluído com sucesso!",
|
||||
"Completed with errors —": "Concluído com erros -",
|
||||
"Completed.": "Concluído.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "Concluído. Dispositivos adicionados à VM {vmid}: {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Concluído. Pressione Enter para retornar ao menu...",
|
||||
"Completing pending package configurations...": "Concluindo configurações de pacotes pendentes...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "Verificação de conformidade (PCI-DSS, HIPAA, etc.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Usuário atual",
|
||||
"Current user UID, GID and groups": "UID, GID e grupos do usuário atual",
|
||||
"Current version:": "Versão atual:",
|
||||
"Currently": "Atualmente",
|
||||
"Currently Mounted:": "Atualmente montado:",
|
||||
"Currently configured target:": "destino atualmente configurado:",
|
||||
"Currently mounted:": "Atualmente montado:",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Caminho personalizado",
|
||||
"Custom path...": "Caminho personalizado...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "Os caminhos personalizados estão incluídos em AMBOS os perfis de backup padrão e personalizados.",
|
||||
"Custom paths currently saved: {count}.": "caminhos personalizados salvos atualmente: {count}.",
|
||||
"Custom restore by components": "Restauração personalizada por componentes",
|
||||
"Custom restore by paths": "restauração personalizada por caminhos",
|
||||
"Custom scripts and ProxMenux files": "Scripts personalizados e arquivos ProxMenux",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Instalado",
|
||||
"Installed at:": "Instalado em:",
|
||||
"Installed components:": "Componentes instalados:",
|
||||
"Installed:": "Instalado:",
|
||||
"Installer already downloaded and verified.": "Instalador já baixado e verificado.",
|
||||
"Installer copied to container.": "Instalador copiado para contêiner.",
|
||||
"Installer downloaded.": "Instalador baixado.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Atualização do pacote concluída com avisos, continuando...",
|
||||
"Package update had issues, checking details...": "A atualização do pacote teve problemas, verificando detalhes...",
|
||||
"Packages from backup to install:": "Pacotes do backup para instalação:",
|
||||
"Packages installed: {count}.": "Pacotes instalados: {count}.",
|
||||
"Packages upgrade successfull": "Atualização de pacotes bem-sucedida",
|
||||
"Packages upgraded": "Pacotes atualizados",
|
||||
"Packages:": "Pacotes:",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod falhou — provavelmente CT sem privilégios contra montagem de ligação do host. Voltando ao ACL.",
|
||||
"content:": "contente:",
|
||||
"could not be compiled for kernel": "não foi possível compilar para o kernel",
|
||||
"custom path(s) saved.": "caminhos personalizados salvos.",
|
||||
"default": "padrão",
|
||||
"delete the credentials file (if any)": "exclua o arquivo de credenciais (se houver)",
|
||||
"delete the matching line from /etc/fstab": "exclua a linha correspondente de /etc/fstab",
|
||||
"descriptor + VMDK files": "descritor + arquivos VMDK",
|
||||
"device(s) added to VM": "dispositivos adicionados à VM",
|
||||
"devices": "dispositivos",
|
||||
"did not become ready. Skipping.": "não ficou pronto. Pulando.",
|
||||
"disk(s) added to CT": "disco(s) adicionado(s) ao CT",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "linhas iface órfãs, sem impacto na restauração",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "outros arquivos .tar — não backups de host ProxMenux (por exemplo, PVE vzdump ou tarballs não relacionados).",
|
||||
"packages (this may take a few minutes)...": "pacotes (isso pode levar alguns minutos)...",
|
||||
"packages.": "pacotes.",
|
||||
"parent PF:": "pai PF:",
|
||||
"partition(s). Partition table preserved.": "partição(ões). Tabela de partição preservada.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "caminhos para a próxima inicialização (/etc/pve, convidados, drivers, ...)",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "Úspešne dokončené.",
|
||||
"Completed with errors —": "Dokončené s chybami -",
|
||||
"Completed.": "Dokončené.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "Dokončené. Zariadenia pridané do VM {vmid}: {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Dokončené. Stlačte Enter pre návrat do menu...",
|
||||
"Completing pending package configurations...": "Dokončenie čakajúcich konfigurácií balíka...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "kontroly súladu (PCI-DSS, HIPAA atď.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Aktuálny používateľ",
|
||||
"Current user UID, GID and groups": "UID, GID a skupiny aktuálneho používateľa",
|
||||
"Current version:": "Aktuálna verzia:",
|
||||
"Currently": "Aktuálne",
|
||||
"Currently Mounted:": "Aktuálne pripojené:",
|
||||
"Currently configured target:": "Aktuálne nastavený cieľ:",
|
||||
"Currently mounted:": "Aktuálne pripojené:",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Vlastná cesta",
|
||||
"Custom path...": "Vlastná cesta...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "Vlastné cesty sú zahrnuté v predvolenom aj vlastnom profile zálohy.",
|
||||
"Custom paths currently saved: {count}.": "Aktuálne uložené vlastné cesty: {count}.",
|
||||
"Custom restore by components": "Vlastná obnova podľa súčastí",
|
||||
"Custom restore by paths": "Vlastná obnova podľa ciest",
|
||||
"Custom scripts and ProxMenux files": "Vlastné skripty a súbory ProxMenux",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Nainštalované",
|
||||
"Installed at:": "Nainštalované v:",
|
||||
"Installed components:": "Nainštalované súčasti:",
|
||||
"Installed:": "Nainštalované:",
|
||||
"Installer already downloaded and verified.": "Inštalátor už je stiahnutý a overený.",
|
||||
"Installer copied to container.": "Inštalátor skopírovaný do kontajnera.",
|
||||
"Installer downloaded.": "Inštalátor bol stiahnutý.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Aktualizácia balíkov skončila s upozorneniami, pokračujem...",
|
||||
"Package update had issues, checking details...": "Pri aktualizácii balíkov nastali problémy, kontrolujem podrobnosti...",
|
||||
"Packages from backup to install:": "Balíky zo zálohy na inštaláciu:",
|
||||
"Packages installed: {count}.": "Nainštalované balíky: {count}.",
|
||||
"Packages upgrade successfull": "Aktualizácia balíkov bola úspešná",
|
||||
"Packages upgraded": "Aktualizované balíky",
|
||||
"Packages:": "Balíky:",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod zlyhalo - pravdepodobne ide o neprivilegovaný CT nad bind mountom z hosta. Skúšam náhradné riešenie cez ACL.",
|
||||
"content:": "obsah:",
|
||||
"could not be compiled for kernel": "nepodarilo sa skompilovať pre jadro",
|
||||
"custom path(s) saved.": "uložených vlastných ciest.",
|
||||
"default": "predvolené",
|
||||
"delete the credentials file (if any)": "vymazať súbor s prihlasovacími údajmi (ak existuje)",
|
||||
"delete the matching line from /etc/fstab": "odstrániť zodpovedajúci riadok z /etc/fstab",
|
||||
"descriptor + VMDK files": "popisovač + VMDK súbory",
|
||||
"device(s) added to VM": "zariadení pridaných do VM",
|
||||
"devices": "zariadenia",
|
||||
"did not become ready. Skipping.": "nebol pripravený. Preskakujem.",
|
||||
"disk(s) added to CT": "diskov pridaných do CT",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "osirelé iface riadky, bez vplyvu na obnovu",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "iné .tar archívy - nie sú to ProxMenux zálohy hosta (napr. PVE vzdump alebo nesúvisiace tar archívy).",
|
||||
"packages (this may take a few minutes)...": "balíkov (môže to trvať pár minút)...",
|
||||
"packages.": "balíkov.",
|
||||
"parent PF:": "rodičovská PF:",
|
||||
"partition(s). Partition table preserved.": "oddielov. Tabuľka oddielov zostala zachovaná.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "cesty na najbližší štart (/etc/pve, hostia, ovládače, ...)",
|
||||
|
||||
+3
-5
@@ -644,6 +644,7 @@
|
||||
"Completed Successfully!": "Avslutad framgångsrikt!",
|
||||
"Completed with errors —": "Kompletterad med fel —",
|
||||
"Completed.": "Avslutad.",
|
||||
"Completed. Devices added to VM {vmid}: {count}.": "Klar. Enheter som har lagts till VM {vmid}: {count}.",
|
||||
"Completed. Press Enter to return to menu...": "Avslutad. Tryck på Enter för att återgå till menyn...",
|
||||
"Completing pending package configurations...": "Slutför väntande paketkonfigurationer...",
|
||||
"Compliance checking (PCI-DSS, HIPAA, etc.)": "Överensstämmelsekontroll (PCI-DSS, HIPAA, etc.)",
|
||||
@@ -1045,7 +1046,6 @@
|
||||
"Current user": "Nuvarande användare",
|
||||
"Current user UID, GID and groups": "Aktuell användar-UID, GID och grupper",
|
||||
"Current version:": "Aktuell version:",
|
||||
"Currently": "För närvarande",
|
||||
"Currently Mounted:": "För närvarande monterad:",
|
||||
"Currently configured target:": "För närvarande konfigurerat mål:",
|
||||
"Currently mounted:": "För närvarande monterad:",
|
||||
@@ -1068,6 +1068,7 @@
|
||||
"Custom path": "Anpassad sökväg",
|
||||
"Custom path...": "Anpassad sökväg...",
|
||||
"Custom paths are included in BOTH default and custom backup profiles.": "Anpassade sökvägar ingår i BÅDE standard- och anpassade säkerhetskopieringsprofiler.",
|
||||
"Custom paths currently saved: {count}.": "Anpassade sökvägar sparade för närvarande: {count}.",
|
||||
"Custom restore by components": "Anpassad återställning av komponenter",
|
||||
"Custom restore by paths": "Anpassad återställning av sökvägar",
|
||||
"Custom scripts and ProxMenux files": "Anpassade skript och ProxMenux-filer",
|
||||
@@ -2220,7 +2221,6 @@
|
||||
"Installed": "Installerad",
|
||||
"Installed at:": "Installerad på:",
|
||||
"Installed components:": "Installerade komponenter:",
|
||||
"Installed:": "Installerat:",
|
||||
"Installer already downloaded and verified.": "Installationsprogrammet har redan laddats ner och verifierats.",
|
||||
"Installer copied to container.": "Installatören kopierad till behållaren.",
|
||||
"Installer downloaded.": "Installationsprogrammet nedladdat.",
|
||||
@@ -3178,6 +3178,7 @@
|
||||
"Package update completed with warnings, continuing...": "Paketuppdateringen slutförd med varningar, fortsätter...",
|
||||
"Package update had issues, checking details...": "Paketuppdateringen hade problem, kontrollerade detaljer...",
|
||||
"Packages from backup to install:": "Paket från säkerhetskopia till installation:",
|
||||
"Packages installed: {count}.": "Paket installerade: {count}.",
|
||||
"Packages upgrade successfull": "Paketuppgraderingen lyckades",
|
||||
"Packages upgraded": "Paketen har uppgraderats",
|
||||
"Packages:": "Paket:",
|
||||
@@ -5057,12 +5058,10 @@
|
||||
"chown/chmod failed — likely unprivileged CT against host bind mount. Falling back to ACL.": "chown/chmod misslyckades — sannolikt oprivilegierad CT mot värdbindningsmontering. Faller tillbaka till ACL.",
|
||||
"content:": "innehåll:",
|
||||
"could not be compiled for kernel": "kunde inte kompileras för kärnan",
|
||||
"custom path(s) saved.": "anpassade sökvägar har sparats.",
|
||||
"default": "standard",
|
||||
"delete the credentials file (if any)": "radera autentiseringsfilen (om någon)",
|
||||
"delete the matching line from /etc/fstab": "ta bort den matchande raden från /etc/fstab",
|
||||
"descriptor + VMDK files": "descriptor + VMDK-filer",
|
||||
"device(s) added to VM": "enhet(er) har lagts till i VM",
|
||||
"devices": "enheter",
|
||||
"did not become ready. Skipping.": "blev inte redo. Hoppa över.",
|
||||
"disk(s) added to CT": "disk(ar) har lagts till i CT",
|
||||
@@ -5238,7 +5237,6 @@
|
||||
"orphan iface lines, no impact on restore": "föräldralösa iface-linjer, ingen inverkan på återställning",
|
||||
"other .tar archive(s) — not ProxMenux host backups (e.g. PVE vzdump or unrelated tarballs).": "andra .tar-arkiv – inte ProxMenux-värdsäkerhetskopior (t.ex. PVE vzdump eller orelaterade tarballs).",
|
||||
"packages (this may take a few minutes)...": "paket (detta kan ta några minuter)...",
|
||||
"packages.": "paket.",
|
||||
"parent PF:": "förälder PF:",
|
||||
"partition(s). Partition table preserved.": "partition(er). Partitionstabell bevarad.",
|
||||
"paths for next boot (/etc/pve, guests, drivers, ...)": "sökvägar för nästa start (/etc/pve, gäster, drivrutin, ...)",
|
||||
|
||||
@@ -919,7 +919,11 @@ _bk_manage_extra_paths() {
|
||||
if (( count == 0 )); then
|
||||
preview="$(hb_translate "You haven't added any custom paths yet.")"
|
||||
else
|
||||
preview="$(hb_translate "Currently"): \Zb\Z4${count}\Zn $(hb_translate "custom path(s) saved.")"
|
||||
preview="$(hb_translate "Custom paths currently saved: {count}.")"
|
||||
if [[ "$preview" != *'{count}'* ]]; then
|
||||
preview="Custom paths currently saved: {count}."
|
||||
fi
|
||||
preview="${preview//\{count\}/"\Zb\Z4${count}\Zn"}"
|
||||
fi
|
||||
preview+=$'\n\n'"$(hb_translate "Custom paths are included in BOTH default and custom backup profiles.")"
|
||||
|
||||
@@ -3878,7 +3882,13 @@ _rs_run_complete_extras() {
|
||||
"${installable[@]}" >>"$apt_log" 2>&1
|
||||
local apt_rc=$?
|
||||
if (( apt_rc == 0 )); then
|
||||
msg_ok "$(translate "Installed:") ${#installable[@]} $(translate "packages.")"
|
||||
local installed_message
|
||||
installed_message="$(translate "Packages installed: {count}.")"
|
||||
if [[ "$installed_message" != *'{count}'* ]]; then
|
||||
installed_message="Packages installed: {count}."
|
||||
fi
|
||||
installed_message="${installed_message//\{count\}/${#installable[@]}}"
|
||||
msg_ok "$installed_message"
|
||||
else
|
||||
msg_warn "$(translate "apt-get exited") ${apt_rc} — $(translate "see log:") $apt_log"
|
||||
fi
|
||||
|
||||
@@ -694,7 +694,14 @@ apply_assignment() {
|
||||
echo -e "${TAB}${BL}Log: ${LOG_FILE}${CL}"
|
||||
|
||||
if [[ "$assigned_count" -gt 0 ]]; then
|
||||
msg_ok "$(translate "Completed.") $assigned_count $(translate "device(s) added to VM") ${SELECTED_VMID}."
|
||||
local completed_message
|
||||
completed_message="$(translate "Completed. Devices added to VM {vmid}: {count}.")"
|
||||
if [[ "$completed_message" != *'{vmid}'* || "$completed_message" != *'{count}'* ]]; then
|
||||
completed_message="Completed. Devices added to VM {vmid}: {count}."
|
||||
fi
|
||||
completed_message="${completed_message//\{vmid\}/$SELECTED_VMID}"
|
||||
completed_message="${completed_message//\{count\}/$assigned_count}"
|
||||
msg_ok "$completed_message"
|
||||
else
|
||||
msg_warn "$(translate "No new Controller/NVMe entries were added.")"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Offline JSX seam, following the TypeScript compiler harness in adjacent tests.
|
||||
// No component imports, React effects, API calls or host-management execution.
|
||||
// Run from the repository root: node tests/test_backup_archives_empty.cjs
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { createRequire } = require('node:module');
|
||||
const app = path.resolve(__dirname, '../AppImage');
|
||||
const ts = createRequire(path.join(app, 'package.json'))('typescript');
|
||||
const read = file => fs.readFileSync(path.join(app, file), 'utf8');
|
||||
const parse = file => ts.createSourceFile(file, read(file), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
||||
const compile = text => ts.transpileModule(text, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, jsx: ts.JsxEmit.React },
|
||||
}).outputText;
|
||||
const printer = ts.createPrinter();
|
||||
const provider = parse('lib/i18n/provider.tsx');
|
||||
const pure = provider.statements.filter(node => ts.isFunctionDeclaration(node) &&
|
||||
['getMessage', 'interpolate'].includes(node.name?.text));
|
||||
assert.equal(pure.length, 2);
|
||||
let lookup;
|
||||
function visitLookup(node) {
|
||||
if (ts.isVariableDeclaration(node) && node.name.getText(provider) === 't' &&
|
||||
node.initializer && ts.isCallExpression(node.initializer) &&
|
||||
node.initializer.expression.getText(provider) === 'useCallback') lookup = node.initializer.arguments[0];
|
||||
ts.forEachChild(node, visitLookup);
|
||||
}
|
||||
visitLookup(provider);
|
||||
assert.ok(lookup, 'real provider lookup callback');
|
||||
const lookupCode = pure.map(node => printer.printNode(ts.EmitHint.Unspecified, node, provider)).join('\n') +
|
||||
'\nconst t = ' + printer.printNode(ts.EmitHint.Expression, lookup, provider) + ';';
|
||||
const catalogs = Object.fromEntries(fs.readdirSync(path.join(app, 'messages')).filter(locale =>
|
||||
fs.existsSync(path.join(app, 'messages', locale, 'common.json'))).map(locale =>
|
||||
[locale, JSON.parse(read(`messages/${locale}/common.json`))]));
|
||||
// Compile only trusted, checked-out source; no catalog/input strings enter code.
|
||||
const translator = (language, messages = catalogs) =>
|
||||
new Function('MESSAGE_CATALOG', 'language', compile(lookupCode) + '\nreturn t;')(messages, language);
|
||||
|
||||
const source = parse('components/host-backup.tsx');
|
||||
let branch;
|
||||
function visitBranch(node) {
|
||||
if (ts.isConditionalExpression(node) && node.condition.getText(source) === 'archivesErr && remoteArchivesErr') branch = node;
|
||||
ts.forEachChild(node, visitBranch);
|
||||
}
|
||||
visitBranch(source);
|
||||
assert.ok(branch, 'real archives loading/error/empty decision');
|
||||
// Keep the actual guards and JSX messages. Only replace the nonempty archive
|
||||
// list with a sentinel: rendering its controls is outside this message test.
|
||||
const transformed = ts.transform(branch, [context => root => {
|
||||
const visit = node => {
|
||||
if (ts.isConditionalExpression(node) && node.condition.getText(source) === 'unifiedArchives.length === 0') {
|
||||
return ts.factory.updateConditionalExpression(node, node.condition, node.questionToken,
|
||||
node.whenTrue, node.colonToken, ts.factory.createStringLiteral('NONEMPTY_ARCHIVES'));
|
||||
}
|
||||
return ts.visitEachChild(node, visit, context);
|
||||
};
|
||||
return ts.visitNode(root, visit);
|
||||
}]);
|
||||
const expression = printer.printNode(ts.EmitHint.Expression, transformed.transformed[0], source);
|
||||
transformed.dispose();
|
||||
const render = new Function('React', 'Loader2', 't', 'archivesErr', 'remoteArchivesErr',
|
||||
'archivesResp', 'remoteArchivesResp', 'unifiedArchives', compile(`const result = ${expression};`) + '\nreturn result;');
|
||||
// Text-only JSX sink: TypeScript performs the real JSX whitespace conversion.
|
||||
const React = { createElement: (tag, props, ...children) => children.flat(Infinity).filter(x => x != null && x !== false).join('') };
|
||||
const text = (locale = 'en', overrides = {}, messages = catalogs) => {
|
||||
const state = { archivesErr: null, remoteArchivesErr: null, archivesResp: { archives: [] },
|
||||
remoteArchivesResp: { snapshots: [] }, unifiedArchives: [], ...overrides };
|
||||
return render(React, 'spinner', translator(locale, messages), state.archivesErr, state.remoteArchivesErr,
|
||||
state.archivesResp, state.remoteArchivesResp, state.unifiedArchives);
|
||||
};
|
||||
|
||||
assert.equal(text(), 'No backup archives found.');
|
||||
assert.equal(text('it'), 'No backup archives found.', 'Italian catalog uses English fallback');
|
||||
// Keep fallback coverage even after all shipped locales gain the new key.
|
||||
assert.equal(text('missing', {}, { ...catalogs, missing: {} }), 'No backup archives found.');
|
||||
for (const locale of Object.keys(catalogs)) {
|
||||
// Existing catalogs intentionally have no new key; actual provider falls back.
|
||||
if (catalogs[locale].backup?.archives?.emptyMessage === undefined) {
|
||||
assert.equal(text(locale), 'No backup archives found.', `${locale}: missing-key fallback`);
|
||||
}
|
||||
}
|
||||
for (const count of [1, 3]) assert.equal(text('en', { unifiedArchives: Array(count).fill({}) }), 'NONEMPTY_ARCHIVES');
|
||||
assert.equal(text('en', { archivesResp: undefined, remoteArchivesResp: undefined }), translator('en')('backup.common.loading'));
|
||||
assert.equal(text('en', { archivesErr: true, remoteArchivesErr: true }), translator('en')('backup.archives.loadFailed'));
|
||||
// Preserve existing partial-source semantics; no claim that every source was queried.
|
||||
assert.equal(text('en', { archivesErr: true, archivesResp: undefined }), 'No backup archives found.');
|
||||
assert.equal(text('en', { remoteArchivesErr: true, remoteArchivesResp: undefined }), 'No backup archives found.');
|
||||
assert.equal(translator('en')('backup.manual.run'), 'Run');
|
||||
assert.ok(read('components/host-backup.tsx').includes('{t("backup.manual.run")}'), 'manual action remains');
|
||||
console.log('PASS: actual archives JSX seam, zero/one/multiple, loading/errors, locale fallback, Run unchanged');
|
||||
// Export only test seams for separate, unapplied linguistic proposal checks.
|
||||
module.exports = { text, translator, catalogs };
|
||||
@@ -0,0 +1,56 @@
|
||||
// Offline actual JSX/provider seams; no component/backend imports or effects.
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { createRequire } = require('node:module');
|
||||
const { translator, catalogs } = require('./test_backup_archives_empty.cjs');
|
||||
const app = path.resolve(__dirname, '../AppImage');
|
||||
const ts = createRequire(path.join(app, 'package.json'))('typescript');
|
||||
const source = ts.createSourceFile('host-backup.tsx', fs.readFileSync(path.join(app, 'components/host-backup.tsx'), 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
||||
const printer = ts.createPrinter();
|
||||
const React = { createElement: (tag, props, ...children) => children.flat(Infinity).filter(x => x != null && x !== false).join('') };
|
||||
function find(predicate) {
|
||||
const found = [];
|
||||
function visit(node) { if (predicate(node)) found.push(node); ts.forEachChild(node, visit); }
|
||||
visit(source);
|
||||
assert.equal(found.length, 1, 'unique actual message seam');
|
||||
return found[0];
|
||||
}
|
||||
function render(node, locale, backups = 1, messages = catalogs) {
|
||||
const code = ts.transpileModule(`const result = ${printer.printNode(ts.EmitHint.Expression, node, source)};`, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2020, jsx: ts.JsxEmit.React },
|
||||
}).outputText;
|
||||
// Only trusted checked-out AST enters executable code, never catalog values.
|
||||
return new Function('React', 't', 'backups', code + '\nreturn result;')(React, translator(locale, messages), backups);
|
||||
}
|
||||
const kept = find(node => ts.isBinaryExpression(node) && node.left.getText(source) === 'backups > 0');
|
||||
const keptMessage = 'Existing backup archives are not deleted.';
|
||||
for (const count of [1, 3]) assert.equal(render(kept, 'it', count), keptMessage, 'Italian catalog uses English fallback');
|
||||
assert.equal(render(kept, 'en', 0), false);
|
||||
for (const locale of Object.keys(catalogs)) {
|
||||
if (catalogs[locale].backup?.destinations?.backupsKeptMessage !== undefined && locale !== 'en') continue;
|
||||
for (const count of [1, 3]) assert.equal(render(kept, locale, count), keptMessage, `${locale}: kept whole-message fallback`);
|
||||
}
|
||||
console.log('PASS: destination removal JSX guard 0/1/N and whole-message locale fallback');
|
||||
// Select the actual help paragraph, keeping its literal paths/JSX whitespace.
|
||||
const help = find(node => ts.isJsxElement(node) && node.openingElement.tagName.getText(source) === 'p' &&
|
||||
/backup\.destinations\.localAttachHelp(?:Before|Message)/.test(node.getText(source)));
|
||||
const helpMessage = 'Mount USB drives first. Leave blank to use the PVE storage path + /dump (fallback: /var/lib/vz/dump).';
|
||||
assert.equal(render(help, 'it'), helpMessage, 'Italian catalog uses English fallback');
|
||||
for (const locale of Object.keys(catalogs)) {
|
||||
if (catalogs[locale].backup?.destinations?.localAttachHelpMessage !== undefined && locale !== 'en') continue;
|
||||
assert.equal(render(help, locale), helpMessage, `${locale}: local attach help fallback`);
|
||||
}
|
||||
// Missing-key fallback must remain covered after shipped catalogs are complete.
|
||||
const missing = { ...catalogs, missing: {} };
|
||||
assert.equal(render(kept, 'missing', 1, missing), keptMessage);
|
||||
assert.equal(render(help, 'missing', 1, missing), helpMessage);
|
||||
// Synthetic locale proves complete messages are translatable, not hardcoded.
|
||||
const synthetic = { ...catalogs, fixture: { backup: { destinations: {
|
||||
backupsKeptMessage: 'Fixture kept.', localAttachHelpMessage: 'Fixture help.',
|
||||
} } } };
|
||||
assert.equal(render(kept, 'fixture', 1, synthetic), 'Fixture kept.');
|
||||
assert.equal(render(help, 'fixture', 1, synthetic), 'Fixture help.');
|
||||
assert.equal(catalogs.en.backup.destinations.kept, 'kept');
|
||||
assert.equal(catalogs.en.backup.destinations.localAttachHelpMiddle, 'USB drives');
|
||||
console.log('PASS: local attach JSX whole-message fallback and literal paths');
|
||||
@@ -0,0 +1,206 @@
|
||||
"""Offline message seams: no full administrative script is sourced or run.
|
||||
|
||||
Run: python3 -m unittest discover -s tests -p test_tui_count_messages.py -v
|
||||
Requires Bash and jq. Only extracted translation functions and bounded message
|
||||
consumers execute. PATH contains jq only; VM/disk/backup commands are unavailable.
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VM = "scripts/storage/add_controller_nvme_vm.sh"
|
||||
BACKUP = "scripts/backup_restore/backup_host.sh"
|
||||
|
||||
|
||||
def between(path, start, end):
|
||||
text = (ROOT / path).read_text()
|
||||
assert text.count(start) == 1, (path, start)
|
||||
tail = text.split(start, 1)[1]
|
||||
assert end in tail, (path, end)
|
||||
return start + tail.split(end, 1)[0]
|
||||
|
||||
|
||||
def function(path, name):
|
||||
# These two small functions have no unindented nested closing braces.
|
||||
text = (ROOT / path).read_text()
|
||||
match = re.search(r"^" + re.escape(name) + r"\(\) \{\n.*?^\}", text, re.M | re.S)
|
||||
assert match, (path, name)
|
||||
return match[0]
|
||||
|
||||
|
||||
def lookup(catalog, text):
|
||||
return (catalog or {}).get(text) or text
|
||||
|
||||
|
||||
class CountMessages(unittest.TestCase):
|
||||
def locales(self, key, translated):
|
||||
return (("en", None), ("it", {}), ("zz", None),
|
||||
("it", json.loads((ROOT / "lang/it.json").read_text())),
|
||||
("xx", {key: translated}),
|
||||
# Presence is the bounded contract: repeated tokens are replaced
|
||||
# everywhere; unrelated braces remain literal, not a format DSL.
|
||||
("repeat", {key: translated + " / again " + key}),
|
||||
("extra", {key: translated + " / {unknown} $(literal) %s"})
|
||||
) + self.malformed_locales(key)
|
||||
|
||||
def malformed_locales(self, key):
|
||||
required = ("{count}", "{vmid}") if "{vmid}" in key else ("{count}",)
|
||||
# These cached values must fall back as a whole, not lose a value.
|
||||
malformed = ["Translated without values."]
|
||||
for token in required:
|
||||
malformed.extend((key.replace(token, ""),
|
||||
key.replace(token, "{renamed}"),
|
||||
key.replace(token, token.upper())))
|
||||
return tuple(("bad", {key: text}) for text in malformed)
|
||||
|
||||
def test_new_messages_are_discovered_by_cache_builder(self):
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"translation_cache", ROOT / ".github/scripts/build_translation_cache.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
keys = module.extract_translate_texts(ROOT / "scripts")
|
||||
for key in ("Completed. Devices added to VM {vmid}: {count}.",
|
||||
"Custom paths currently saved: {count}.", "Packages installed: {count}."):
|
||||
self.assertIn(key, keys)
|
||||
|
||||
def render(self, seam, setup, language="en", catalog=None):
|
||||
jq = shutil.which("jq")
|
||||
self.assertIsNotNone(jq, "jq is required for actual translation lookup")
|
||||
with tempfile.TemporaryDirectory(prefix="pmx-count-messages-") as tmp:
|
||||
tmp = Path(tmp)
|
||||
(tmp / "bin").mkdir()
|
||||
(tmp / "bin" / "jq").symlink_to(jq)
|
||||
(tmp / "lang").mkdir()
|
||||
if catalog is not None:
|
||||
(tmp / "lang" / f"{language}.json").write_text(json.dumps(catalog))
|
||||
shell = function("scripts/utils.sh", "translate") + "\n"
|
||||
shell += function("scripts/backup_restore/lib_host_backup_common.sh", "hb_translate")
|
||||
shell += '''
|
||||
msg_ok() { printf 'OK:%s\\n' "$1"; }
|
||||
msg_warn() { printf 'WARN:%s\\n' "$1"; }
|
||||
consumer() {
|
||||
'''
|
||||
shell += setup + "\n" + seam + "\n}\nconsumer\n"
|
||||
env = {"PATH": str(tmp / "bin"), "LANGUAGE": language,
|
||||
"LANG_DIR": str(tmp / "lang"), "LC_ALL": "C"}
|
||||
result = subprocess.run(["/bin/bash", "--noprofile", "--norc", "-c", shell],
|
||||
env=env, text=True, capture_output=True, timeout=10)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(result.stderr, "")
|
||||
return result.stdout.rstrip("\n")
|
||||
|
||||
def test_vm_devices_zero_one_many_and_translation(self):
|
||||
seam = between(VM, ' if [[ "$assigned_count" -gt 0 ]]; then',
|
||||
' if [[ "${IOMMU_ALREADY_ACTIVE:-0}" == "1" ]]; then')
|
||||
key = "Completed. Devices added to VM {vmid}: {count}."
|
||||
translated = "Devices: {count} / VM {vmid} / done 100%."
|
||||
for count in (0, 1, 3):
|
||||
for vmid in (101, 987654):
|
||||
for language, catalog in self.locales(key, translated):
|
||||
with self.subTest(count=count, vmid=vmid, language=language):
|
||||
actual = self.render(seam, f"assigned_count={count}; SELECTED_VMID={vmid}",
|
||||
language, catalog)
|
||||
if count == 0:
|
||||
expected = "WARN:" + lookup(catalog, "No new Controller/NVMe entries were added.")
|
||||
elif language == "xx":
|
||||
expected = f"OK:Devices: {count} / VM {vmid} / done 100%."
|
||||
elif language == "bad":
|
||||
expected = f"OK:Completed. Devices added to VM {vmid}: {count}."
|
||||
else:
|
||||
expected = "OK:" + lookup(catalog, key).replace("{vmid}", str(vmid)).replace("{count}", str(count))
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_vm_summary_counts_successful_new_assignments_only(self):
|
||||
seam = between(VM, ' local pci bdf assigned_count=0',
|
||||
' if [[ "${IOMMU_ALREADY_ACTIVE:-0}" == "1" ]]; then')
|
||||
for devices, expected_count in (([], 0), (["ok1"], 1),
|
||||
(["ok1", "ok2", "ok3"], 3),
|
||||
(["existing", "failed"], 0),
|
||||
(["existing", "ok1", "failed", "ok2"], 2)):
|
||||
with self.subTest(devices=devices):
|
||||
setup = 'SELECTED_CONTROLLER_PCIS=(' + ' '.join(devices) + ')\n'
|
||||
setup += '''SELECTED_VMID=101; hostpci_idx=0; LOG_FILE="$LANG_DIR/vm.log"
|
||||
msg_info() { :; }
|
||||
msg_error() { printf 'ERROR:%s\\n' "$1"; }
|
||||
_pci_function_assigned_to_vm() { [[ "$1" == existing ]]; }
|
||||
_pci_storage_display_name() { printf '%s' "$1"; }
|
||||
qm() {
|
||||
[[ "$1 $2" == 'set 101' && "$3" == "--hostpci${hostpci_idx}" ]] || return 99
|
||||
[[ "$4" != 'failed,pcie=1' ]]
|
||||
}
|
||||
'''
|
||||
actual = self.render(seam, setup).splitlines()[-1]
|
||||
expected = (f"OK:Completed. Devices added to VM 101: {expected_count}." if expected_count
|
||||
else "WARN:No new Controller/NVMe entries were added.")
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_custom_paths_zero_one_many_and_translation(self):
|
||||
seam = between(BACKUP, ' local -a paths=()', ' local choice')
|
||||
seam += '\n printf "%s" "$preview"\n'
|
||||
key = "Custom paths currently saved: {count}."
|
||||
translated = "Saved 100%: {count} custom paths."
|
||||
suffix = "Custom paths are included in BOTH default and custom backup profiles."
|
||||
for count in (0, 1, 3):
|
||||
setup = 'hb_load_extra_paths() {\n'
|
||||
setup += ''.join(f"printf '%s\\n' '/fixture/path {i}'\n" for i in range(count))
|
||||
setup += ':;\n}\n'
|
||||
for language, catalog in self.locales(key, translated):
|
||||
with self.subTest(count=count, language=language):
|
||||
actual = self.render(seam, setup, language, catalog)
|
||||
colored_count = rf"\Zb\Z4{count}\Zn"
|
||||
if count == 0:
|
||||
expected = lookup(catalog, "You haven't added any custom paths yet.")
|
||||
elif language == "xx":
|
||||
expected = f"Saved 100%: {colored_count} custom paths."
|
||||
elif language == "bad":
|
||||
expected = f"Custom paths currently saved: {colored_count}."
|
||||
else:
|
||||
expected = lookup(catalog, key).replace("{count}", colored_count)
|
||||
self.assertEqual(actual, expected + "\n\n" + lookup(catalog, suffix))
|
||||
|
||||
def test_installed_packages_zero_one_many_success_and_failure(self):
|
||||
# Keep the actual nonempty guard and apt return-code consumer; omit
|
||||
# unrelated preview/cache/log setup. apt-get is a function stub only.
|
||||
guard = ' if (( ${#installable[@]} > 0 )); then\n'
|
||||
self.assertEqual((ROOT / BACKUP).read_text().count(guard), 1)
|
||||
seam = guard + between(BACKUP, ' DEBIAN_FRONTEND=noninteractive \\\n',
|
||||
' if (( ${#unknown[@]} > 0 )); then')
|
||||
key = "Packages installed: {count}."
|
||||
translated = "Done 100% / {count} installed packages."
|
||||
for count in (0, 1, 3):
|
||||
for status in (0, 100):
|
||||
for language, catalog in self.locales(key, translated):
|
||||
with self.subTest(count=count, status=status, language=language):
|
||||
setup = f'installable=({" ".join("pkg" + str(i) for i in range(count))}); apt_status={status}\n'
|
||||
setup += '''apt_log="$LANG_DIR/apt.log"
|
||||
apt-get() {
|
||||
[[ "$DEBIAN_FRONTEND" == noninteractive ]] || return 99
|
||||
[[ "$1 $2 $3 $4 $5 $6" == 'install -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' ]] || return 99
|
||||
shift 6
|
||||
[[ "$*" == "${installable[*]}" ]] || return 99
|
||||
printf 'CALLED\\n' > "$LANG_DIR/calls"
|
||||
return "$apt_status"
|
||||
}
|
||||
'''
|
||||
check_call = '\nif [[ -f "$LANG_DIR/calls" ]]; then printf "CALLED\\n"; fi\n'
|
||||
actual = self.render(seam + check_call, setup, language, catalog)
|
||||
if count == 0:
|
||||
self.assertEqual(actual, "")
|
||||
elif status:
|
||||
prefix = "WARN:" + lookup(catalog, "apt-get exited") + " 100 — " + lookup(catalog, "see log:")
|
||||
self.assertRegex(actual, "^" + re.escape(prefix) + r" .*/lang/apt.log\nCALLED$")
|
||||
else:
|
||||
message = (f"Done 100% / {count} installed packages." if language == "xx"
|
||||
else f"Packages installed: {count}." if language == "bad"
|
||||
else lookup(catalog, key).replace("{count}", str(count)))
|
||||
self.assertEqual(actual, f"OK:{message}\nCALLED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user