mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-19 13:17:23 +00:00
Merge pull request #351 from f3rs3n/feature/f05-i18n-arrays
fix: preserve i18n arrays and restore command descriptions
This commit is contained in:
@@ -4,10 +4,19 @@ Auto-translate missing keys in AppImage/messages/<locale>/common.json
|
||||
against the English source (AppImage/messages/en/common.json).
|
||||
|
||||
Guardrails:
|
||||
- Keys already translated in a target locale are PRESERVED. A key is
|
||||
considered "already translated" when the target value is non-empty
|
||||
AND differs from the English source. This protects human-curated
|
||||
locales (Vaso73's sk) from being overwritten.
|
||||
- Non-empty string translations are PRESERVED, including values equal
|
||||
to English. --refresh explicitly opts into replacing valid strings.
|
||||
- Arrays, objects and non-string leaves retain their JSON types. Array
|
||||
entries are translated individually, never as serialized Python text.
|
||||
- Preflight all locales; skip locales with existing type mismatches without
|
||||
provider calls or writes, including with --refresh. Valid locales proceed,
|
||||
but any skipped locale makes the run exit 1 (partial failure).
|
||||
The diagnostic identifies the locale
|
||||
and JSON Pointer requiring manual reconciliation. No automatic migration
|
||||
of serialized arrays is attempted: parseability or equal length cannot
|
||||
prove correspondence to current English indices. Recover human text only
|
||||
after checking its original source/order; do not delete it to force refill.
|
||||
Null string leaves remain eligible for filling, as in earlier versions.
|
||||
- `{placeholder}` tokens (next-intl style: `{vmid}`, `{appName}`, etc.)
|
||||
are extracted before translation and restored afterwards, so the
|
||||
interpolation contract stays intact regardless of what the
|
||||
@@ -16,6 +25,13 @@ Guardrails:
|
||||
Vaso73 has curated by hand; auto-translation only fills the keys
|
||||
that are still on the English fallback in sk.
|
||||
|
||||
Workflow limitation: build-i18n-messages.yml runs Commit + push only after
|
||||
successful generation. Exit 1 for skipped locales prevents that step, even
|
||||
when valid locales made local progress. This does not restore automatic
|
||||
publication for the default locale set until mismatches are reconciled or
|
||||
workflow policy is separately changed. Invalid JSON syntax still aborts
|
||||
preflight globally; per-locale skipping covers parsed catalog type mismatches.
|
||||
|
||||
Reuses the same translation providers as build_translation_cache.py so
|
||||
the CI environment (googletrans pinning, AppImage provider) stays
|
||||
identical.
|
||||
@@ -58,36 +74,38 @@ DEFAULT_CONTEXT = "Context: Technical UI text for a Proxmox management dashboard
|
||||
PLACEHOLDER_RE = re.compile(r"\{[A-Za-z_][A-Za-z0-9_]*\}")
|
||||
|
||||
|
||||
def flatten(node: dict, prefix: str = "") -> dict[str, str]:
|
||||
"""Depth-first flatten of a nested dict into ``{"a.b.c": "value"}``.
|
||||
Non-string leaves are coerced to str (should not happen in messages
|
||||
catalogs, but keeps the function total)."""
|
||||
out: dict[str, str] = {}
|
||||
for key, value in node.items():
|
||||
path = f"{prefix}{key}" if not prefix else f"{prefix}.{key}"
|
||||
if isinstance(value, dict):
|
||||
out.update(flatten(value, path))
|
||||
elif value is None:
|
||||
out[path] = ""
|
||||
else:
|
||||
out[path] = str(value)
|
||||
CatalogPath = tuple[str | int, ...]
|
||||
|
||||
|
||||
def flatten(node: object, prefix: CatalogPath = ()) -> dict[CatalogPath, object]:
|
||||
"""Keep typed path segments and empty container markers, without coercion."""
|
||||
if isinstance(node, dict):
|
||||
out = {prefix: {}}
|
||||
children = node.items()
|
||||
elif isinstance(node, list):
|
||||
out = {prefix: []}
|
||||
children = enumerate(node)
|
||||
else:
|
||||
return {prefix: node}
|
||||
for key, value in children:
|
||||
out.update(flatten(value, prefix + (key,)))
|
||||
return out
|
||||
|
||||
|
||||
def unflatten(flat: dict[str, str]) -> dict:
|
||||
"""Inverse of ``flatten``: rebuild nested structure from dotted keys."""
|
||||
out: dict = {}
|
||||
for path, value in flat.items():
|
||||
parts = path.split(".")
|
||||
cursor = out
|
||||
for part in parts[:-1]:
|
||||
existing = cursor.get(part)
|
||||
if not isinstance(existing, dict):
|
||||
existing = {}
|
||||
cursor[part] = existing
|
||||
cursor = existing
|
||||
cursor[parts[-1]] = value
|
||||
return out
|
||||
def unflatten(flat: dict[CatalogPath, object]) -> object:
|
||||
"""Rebuild a tree from typed paths; parents precede children by depth."""
|
||||
nodes = {}
|
||||
for path in sorted(flat, key=len):
|
||||
value = flat[path]
|
||||
nodes[path] = {} if isinstance(value, dict) else [] if isinstance(value, list) else value
|
||||
if path:
|
||||
parent = nodes[path[:-1]]
|
||||
key = path[-1]
|
||||
if isinstance(parent, list):
|
||||
while len(parent) <= key:
|
||||
parent.append(None)
|
||||
parent[key] = nodes[path]
|
||||
return nodes[()]
|
||||
|
||||
|
||||
def protect_placeholders(text: str) -> tuple[str, list[str]]:
|
||||
@@ -240,11 +258,38 @@ def main() -> int:
|
||||
print(f"Provider: {args.provider}", flush=True)
|
||||
print(f"Sleep between calls: {args.sleep}s", flush=True)
|
||||
|
||||
total_failures: list[tuple[str, str, str]] = []
|
||||
total_failures: list[tuple[str, CatalogPath, str]] = []
|
||||
|
||||
# Preflight every locale before any provider call or write. Serialized
|
||||
# arrays have no trustworthy index correspondence, even if JSON parses.
|
||||
# Never guess, evaluate Python repr, or discard human text on --refresh.
|
||||
targets = {}
|
||||
skipped = set()
|
||||
for lang in languages:
|
||||
locale_path = messages_dir / lang / "common.json"
|
||||
target_flat = flatten(read_json(locale_path))
|
||||
targets[lang] = target_flat
|
||||
for key, en_value in en_flat.items():
|
||||
if key not in target_flat:
|
||||
continue
|
||||
existing = target_flat[key]
|
||||
if isinstance(en_value, str) and existing is None:
|
||||
continue # Historical null string leaves are treated as empty.
|
||||
if type(existing) is not type(en_value):
|
||||
pointer = "".join("/" + str(part).replace("~", "~0").replace("/", "~1") for part in key)
|
||||
location = f"JSON Pointer {json.dumps(pointer)}" + (" (root)" if not key else "")
|
||||
print(
|
||||
f"{locale_path}: {location}: expected {type(en_value).__name__}, "
|
||||
f"found {type(existing).__name__}; manual reconciliation required. "
|
||||
"Locale skipped; its catalog will not be written.", file=sys.stderr,
|
||||
)
|
||||
skipped.add(lang)
|
||||
|
||||
for lang in languages:
|
||||
if lang in skipped:
|
||||
continue
|
||||
locale_path = messages_dir / lang / "common.json"
|
||||
target_flat = targets[lang]
|
||||
|
||||
# Decide what needs translating. Same rule as
|
||||
# build_translation_cache.py: only touch keys whose target
|
||||
@@ -263,9 +308,19 @@ def main() -> int:
|
||||
# steady-state, not a bug.
|
||||
# When someone genuinely wants to redo everything, --refresh
|
||||
# is still available (and is destructive by design).
|
||||
missing: list[str] = []
|
||||
# Seed structural nodes and non-text leaves before any checkpoint.
|
||||
# Only strings are sent to a translation provider.
|
||||
original_flat = target_flat.copy()
|
||||
for key, en_value in en_flat.items():
|
||||
if not en_value:
|
||||
if not isinstance(en_value, str) or not en_value:
|
||||
target_flat.setdefault(key, en_value)
|
||||
elif key and isinstance(key[-1], int):
|
||||
# Null keeps an untranslated array position addressable while
|
||||
# allowing the runtime English fallback and a later retry.
|
||||
target_flat.setdefault(key, None)
|
||||
missing: list[CatalogPath] = []
|
||||
for key, en_value in en_flat.items():
|
||||
if not isinstance(en_value, str) or not en_value:
|
||||
continue
|
||||
existing = target_flat.get(key, "")
|
||||
if args.refresh or not existing:
|
||||
@@ -276,10 +331,12 @@ def main() -> int:
|
||||
|
||||
print(f"\n=== {lang}: {len(missing)} keys to translate ===", flush=True)
|
||||
if not missing:
|
||||
print(f" {lang}: nothing to do", flush=True)
|
||||
if target_flat != original_flat:
|
||||
write_json(locale_path, unflatten(target_flat))
|
||||
print(f" {lang}: nothing to translate", flush=True)
|
||||
continue
|
||||
|
||||
failures_for_lang: list[tuple[str, str, str]] = []
|
||||
failures_for_lang: list[tuple[str, CatalogPath, str]] = []
|
||||
|
||||
for index, key in enumerate(missing, start=1):
|
||||
en_value = en_flat[key]
|
||||
@@ -336,7 +393,18 @@ def main() -> int:
|
||||
# Previously we returned 2, which failed the whole run and
|
||||
# discarded 36 out of 38 successful translations because
|
||||
# 2 googletrans timeouts hit sv at the start of the burst.
|
||||
return 0
|
||||
if not skipped:
|
||||
return 0
|
||||
|
||||
if skipped:
|
||||
names = ', '.join(dict.fromkeys(lang for lang in languages if lang in skipped))
|
||||
print(
|
||||
f"\nPartial failure: skipped {len(skipped)} locale(s): {names}. "
|
||||
"Valid locales processed; skipped catalogs unchanged. "
|
||||
"Manual reconciliation required; exiting 1.",
|
||||
file=sys.stderr, flush=True,
|
||||
)
|
||||
return 1
|
||||
|
||||
print("\ni18n messages generated successfully.", flush=True)
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"pin": "dede876f0b92f8a571325e96ed7591b30a85368e",
|
||||
"fallback_indices": [
|
||||
9,
|
||||
10,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
58,
|
||||
59
|
||||
],
|
||||
"locales": {
|
||||
"es": [
|
||||
"Listar todos los nodos Proxmox",
|
||||
"Listar VM en un nodo",
|
||||
"Listar contenedores LXC en un nodo",
|
||||
"Listar almacenamiento en un nodo",
|
||||
"Listar interfaces de red",
|
||||
"Listar todas las máquinas virtuales QEMU/KVM",
|
||||
"Iniciar una máquina virtual",
|
||||
"Detener una máquina virtual",
|
||||
"Apagar una máquina virtual de forma segura",
|
||||
"Mostrar estado de VM",
|
||||
"Mostrar configuración de VM",
|
||||
"Crear una instantánea de VM",
|
||||
"Listar todos los contenedores LXC",
|
||||
"Iniciar un contenedor LXC",
|
||||
"Detener un contenedor LXC",
|
||||
"Abrir una consola de contenedor LXC",
|
||||
"Mostrar configuración del contenedor",
|
||||
"Mostrar estado de almacenamiento",
|
||||
"Enumerar el contenido del almacenamiento",
|
||||
"Probar el rendimiento del sistema Proxmox",
|
||||
"Mostrar la versión de Proxmox VE",
|
||||
"Verificar el estado del clúster",
|
||||
"Mostrar el estado del clúster",
|
||||
"Enumerar los nodos del clúster",
|
||||
"Mostrar el estado del grupo ZFS",
|
||||
"Enumerar todos los grupos ZFS",
|
||||
"Enumerar todo Conjuntos de datos ZFS",
|
||||
"Enumerar todos los archivos con detalles",
|
||||
"Cambiar directorio",
|
||||
"Crear un directorio",
|
||||
"Eliminar un directorio de forma recursiva",
|
||||
"Copiar archivos o directorios",
|
||||
"Mover o cambiar el nombre de archivos",
|
||||
"Mostrar el contenido del archivo",
|
||||
"Buscar un patrón en un archivo",
|
||||
"Buscar archivos por nombre",
|
||||
"Cambiar permisos de archivos",
|
||||
"Cambiar el propietario del archivo",
|
||||
"Extraer un archivo tar.gz",
|
||||
"Crear un tar.gz archivo",
|
||||
"Mostrar uso del disco",
|
||||
"Mostrar tamaños de directorio",
|
||||
"Mostrar uso de memoria",
|
||||
"Mostrar procesos en ejecución",
|
||||
"Buscar un proceso en ejecución",
|
||||
"Forzar la eliminación de un proceso",
|
||||
"Verificar el estado del servicio",
|
||||
"Iniciar un servicio",
|
||||
"Detener un servicio",
|
||||
"Reiniciar un servicio",
|
||||
"Actualizar paquetes de Debian/Ubuntu",
|
||||
"Instalar un paquete de Debian/Ubuntu",
|
||||
"Eliminar un paquete",
|
||||
"Listar contenedores en ejecución",
|
||||
"Listar Docker imágenes",
|
||||
"Abrir un shell en un contenedor",
|
||||
"Mostrar direcciones IP",
|
||||
"Probar conectividad de red",
|
||||
"Obtener encabezados HTTP",
|
||||
"Descargar un archivo",
|
||||
"Conectarse a través de SSH",
|
||||
"Copiar un archivo a través de SSH",
|
||||
"Seguir un archivo de registro en tiempo real",
|
||||
"Mostrar historial de comandos",
|
||||
"Borrar la pantalla del terminal"
|
||||
],
|
||||
"de": [
|
||||
"Alle Proxmox-Knoten auflisten",
|
||||
"VMs auf einem Knoten auflisten",
|
||||
"LXC-Container auf einem Knoten auflisten",
|
||||
"Speicher auf einem Knoten auflisten",
|
||||
"Netzwerkschnittstellen auflisten",
|
||||
"Alle virtuellen QEMU/KVM-Maschinen auflisten",
|
||||
"Eine virtuelle Maschine starten",
|
||||
"Eine virtuelle Maschine stoppen",
|
||||
"Eine virtuelle Maschine sicher herunterfahren",
|
||||
"VM-Status anzeigen",
|
||||
"VM-Konfiguration anzeigen",
|
||||
"Einen VM-Snapshot erstellen",
|
||||
"Alle LXC-Container auflisten",
|
||||
"Einen LXC-Container starten",
|
||||
"Einen LXC-Container stoppen",
|
||||
"Eine LXC-Containerkonsole öffnen",
|
||||
"Containerkonfiguration anzeigen",
|
||||
"Speicherstatus anzeigen",
|
||||
"Speicherinhalte auflisten",
|
||||
"Proxmox-Systemleistung testen",
|
||||
"Proxmox VE-Version anzeigen",
|
||||
"Clusterstatus prüfen",
|
||||
"Clusterstatus anzeigen",
|
||||
"Clusterknoten auflisten",
|
||||
"ZFS-Poolstatus anzeigen",
|
||||
"Alle ZFS-Pools auflisten",
|
||||
"Alle ZFS-Datensätze auflisten",
|
||||
"Alle Dateien mit Details auflisten",
|
||||
"Verzeichnis ändern",
|
||||
"Verzeichnis erstellen",
|
||||
"Verzeichnis rekursiv entfernen",
|
||||
"Dateien oder Verzeichnisse kopieren",
|
||||
"Dateien verschieben oder umbenennen",
|
||||
"Dateiinhalte anzeigen",
|
||||
"Nach einem Muster in einer Datei suchen",
|
||||
"Dateien nach Namen suchen",
|
||||
"Dateiberechtigungen ändern",
|
||||
"Dateieigentümer ändern",
|
||||
"Tar.gz-Archiv extrahieren",
|
||||
"Tar.gz-Archiv erstellen",
|
||||
"Festplattennutzung anzeigen",
|
||||
"Verzeichnisgrößen anzeigen",
|
||||
"Speichernutzung anzeigen",
|
||||
"Laufende Prozesse anzeigen",
|
||||
"Laufenden Prozess suchen",
|
||||
"Prozess beenden",
|
||||
"Dienststatus prüfen",
|
||||
"Dienst starten",
|
||||
"Dienst stoppen",
|
||||
"Dienst neu starten",
|
||||
"Debian/Ubuntu-Pakete aktualisieren",
|
||||
"Debian/Ubuntu-Paket installieren",
|
||||
"Paket entfernen",
|
||||
"Laufende Container auflisten",
|
||||
"Docker-Images auflisten",
|
||||
"Eine Shell in einem Container öffnen",
|
||||
"IP-Adressen anzeigen",
|
||||
"Netzwerkkonnektivität testen",
|
||||
"HTTP-Header abrufen",
|
||||
"Eine Datei herunterladen",
|
||||
"Über SSH verbinden",
|
||||
"Eine Datei über SSH kopieren",
|
||||
"Eine Protokolldatei in Echtzeit verfolgen",
|
||||
"Befehlsverlauf anzeigen",
|
||||
"Terminalbildschirm löschen"
|
||||
],
|
||||
"fr": [
|
||||
"Liste de tous les nœuds Proxmox",
|
||||
"Liste des machines virtuelles sur un nœud",
|
||||
"Liste des conteneurs LXC sur un nœud",
|
||||
"Liste du stockage sur un nœud",
|
||||
"Liste des interfaces réseau",
|
||||
"Liste de toutes les machines virtuelles QEMU/KVM",
|
||||
"Démarrer une machine virtuelle",
|
||||
"Arrêter une machine virtuelle",
|
||||
"Arrêter une machine virtuelle en toute sécurité",
|
||||
"Afficher l'état de la VM",
|
||||
"Afficher la VM configuration",
|
||||
"Créer un instantané de VM",
|
||||
"Liste de tous les conteneurs LXC",
|
||||
"Démarrer un conteneur LXC",
|
||||
"Arrêter un conteneur LXC",
|
||||
"Ouvrir une console de conteneur LXC",
|
||||
"Afficher la configuration du conteneur",
|
||||
"Afficher l'état du stockage",
|
||||
"Liste du contenu du stockage",
|
||||
"Tester les performances du système Proxmox",
|
||||
"Afficher la version Proxmox VE",
|
||||
"Vérifier l'état du cluster",
|
||||
"Afficher l'état du cluster",
|
||||
"Liste des nœuds du cluster",
|
||||
"Afficher l'état du pool ZFS",
|
||||
"Liste de tous les pools ZFS",
|
||||
"Liste de tous les ensembles de données ZFS",
|
||||
"Liste de tous les fichiers avec détails",
|
||||
"Changer de répertoire",
|
||||
"Créer un répertoire",
|
||||
"Supprimer un répertoire de manière récursive",
|
||||
"Copier des fichiers ou des répertoires",
|
||||
"Déplacer ou renommer des fichiers",
|
||||
"Afficher le contenu du fichier",
|
||||
"Rechercher un modèle dans un fichier",
|
||||
"Rechercher des fichiers par nom",
|
||||
"Modifier les autorisations du fichier",
|
||||
"Modifier le propriétaire du fichier",
|
||||
"Extraire une archive tar.gz",
|
||||
"Créer une archive tar.gz",
|
||||
"Afficher l'utilisation du disque",
|
||||
"Afficher la taille des répertoires",
|
||||
"Afficher l'utilisation de la mémoire",
|
||||
"Afficher les processus en cours d'exécution",
|
||||
"Trouver un processus en cours d'exécution",
|
||||
"Forcer l'arrêt d'un processus",
|
||||
"Vérifier le service\". statut",
|
||||
"Démarrer un service",
|
||||
"Arrêter un service",
|
||||
"Redémarrer un service",
|
||||
"Mettre à jour les packages Debian/Ubuntu",
|
||||
"Installer un package Debian/Ubuntu",
|
||||
"Supprimer un package",
|
||||
"Liste des conteneurs en cours d'exécution",
|
||||
"Liste des images Docker",
|
||||
"Ouvrir un shell dans un conteneur",
|
||||
"Afficher les adresses IP",
|
||||
"Tester la connectivité réseau",
|
||||
"Obtenir les en-têtes HTTP",
|
||||
"Télécharger un fichier",
|
||||
"Connecter via SSH",
|
||||
"Copier un fichier via SSH",
|
||||
"Suivre un fichier journal en temps réel",
|
||||
"Afficher l'historique des commandes",
|
||||
"Effacer l'écran du terminal"
|
||||
],
|
||||
"it": [
|
||||
"Elenca tutti i nodi Proxmox",
|
||||
"Elenca VM su un nodo",
|
||||
"Elenca contenitori LXC su un nodo",
|
||||
"Elenca spazio di archiviazione su un nodo",
|
||||
"Elenca interfacce di rete",
|
||||
"Elenca tutte le macchine virtuali QEMU/KVM",
|
||||
"Avvia una macchina virtuale",
|
||||
"Arresta una macchina virtuale",
|
||||
"Spegni una macchina virtuale in modo sicuro",
|
||||
"Mostra stato VM",
|
||||
"Mostra configurazione VM",
|
||||
"Crea un'istantanea VM",
|
||||
"Elenca tutti i contenitori LXC",
|
||||
"Avvia un contenitore LXC",
|
||||
"Arresta un contenitore LXC",
|
||||
"Apri una console del contenitore LXC",
|
||||
"Mostra configurazione contenitore",
|
||||
"Mostra stato archiviazione",
|
||||
"Elenca contenuto archiviazione",
|
||||
"Verifica prestazioni del sistema Proxmox",
|
||||
"Mostra versione Proxmox VE",
|
||||
"Verifica stato cluster",
|
||||
"Mostra stato cluster",
|
||||
"Elenco nodi cluster",
|
||||
"Mostra stato pool ZFS",
|
||||
"Elenca tutti i pool ZFS",
|
||||
"Elenca tutti i set di dati ZFS",
|
||||
"Elenca tutti i file con dettagli",
|
||||
"Cambia directory",
|
||||
"Crea una directory",
|
||||
"Rimuovi una directory in modo ricorsivo",
|
||||
"Copia file o directory",
|
||||
"Sposta o rinomina file",
|
||||
"Visualizza contenuto file",
|
||||
"Cerca un modello in un file",
|
||||
"Trova file per nome",
|
||||
"Cambia permessi file",
|
||||
"Cambia proprietario del file",
|
||||
"Estrai un archivio tar.gz",
|
||||
"Crea un archivio tar.gz",
|
||||
"Mostra utilizzo disco",
|
||||
"Mostra dimensioni directory",
|
||||
"Mostra utilizzo memoria",
|
||||
"Mostra processi in esecuzione",
|
||||
"Trova un processo in esecuzione",
|
||||
"Termina forzatamente un processo",
|
||||
"Controlla stato del servizio",
|
||||
"Avvia un servizio",
|
||||
"Interrompi un servizio",
|
||||
"Riavvia un servizio",
|
||||
"Aggiorna pacchetti Debian/Ubuntu",
|
||||
"Installa un pacchetto Debian/Ubuntu",
|
||||
"Rimuovi un pacchetto",
|
||||
"Elenca contenitori in esecuzione",
|
||||
"Elenca immagini Docker",
|
||||
"Apri una shell in un contenitore",
|
||||
"Mostra indirizzi IP",
|
||||
"Verifica connettività di rete",
|
||||
"Ottieni intestazioni HTTP",
|
||||
"Scarica un file",
|
||||
"Connetti tramite SSH",
|
||||
"Copia un file tramite SSH",
|
||||
"Segui un file di registro in tempo reale",
|
||||
"Mostra cronologia comandi",
|
||||
"Cancella lo schermo del terminale"
|
||||
],
|
||||
"pt": [
|
||||
"Listar todos os nós Proxmox",
|
||||
"Listar VMs em um nó",
|
||||
"Listar contêineres LXC em um nó",
|
||||
"Listar armazenamento em um nó",
|
||||
"Listar interfaces de rede",
|
||||
"Listar todas as máquinas virtuais QEMU/KVM",
|
||||
"Iniciar uma máquina virtual",
|
||||
"Parar uma máquina virtual",
|
||||
"Desligar uma máquina virtual com segurança",
|
||||
"Mostrar status da VM",
|
||||
"Mostrar configuração da VM",
|
||||
"Criar um instantâneo de VM",
|
||||
"Listar todos os LXC containers",
|
||||
"Iniciar um contêiner LXC",
|
||||
"Parar um contêiner LXC",
|
||||
"Abrir um console de contêiner LXC",
|
||||
"Mostrar configuração do contêiner",
|
||||
"Mostrar status de armazenamento",
|
||||
"Listar conteúdo de armazenamento",
|
||||
"Testar desempenho do sistema Proxmox",
|
||||
"Mostrar a versão Proxmox VE",
|
||||
"Verificar status do cluster",
|
||||
"Mostrar status do cluster",
|
||||
"Listar nós do cluster",
|
||||
"Mostrar status do pool ZFS",
|
||||
"Listar todos os pools ZFS",
|
||||
"Listar todos os conjuntos de dados ZFS",
|
||||
"Listar todos os arquivos com detalhes",
|
||||
"Alterar diretório",
|
||||
"Criar um diretório",
|
||||
"Remover um diretório recursivamente",
|
||||
"Copiar arquivos ou diretórios",
|
||||
"Mover ou renomear arquivos",
|
||||
"Exibir conteúdo do arquivo",
|
||||
"Pesquisar um padrão em um arquivo",
|
||||
"Encontrar arquivos por nome",
|
||||
"Alterar permissões de arquivo",
|
||||
"Alterar o proprietário do arquivo",
|
||||
"Extrair um arquivo tar.gz",
|
||||
"Criar um arquivo tar.gz",
|
||||
"Mostrar uso de disco",
|
||||
"Mostrar tamanhos de diretório",
|
||||
"Mostrar uso de memória",
|
||||
"Mostrar processos em execução",
|
||||
"Encontrar um processo em execução",
|
||||
"Forçar a eliminação de um processo",
|
||||
"Verificar status do serviço",
|
||||
"Iniciar um serviço",
|
||||
"Parar um serviço",
|
||||
"Reiniciar um serviço",
|
||||
"Atualizar pacotes Debian/Ubuntu",
|
||||
"Instalar um pacote Debian/Ubuntu",
|
||||
"Remover um pacote",
|
||||
"Listar contêineres em execução",
|
||||
"Listar imagens do Docker",
|
||||
"Abrir um shell em um contêiner",
|
||||
"Mostrar endereços IP",
|
||||
"Testar conectividade de rede",
|
||||
"Obter cabeçalhos HTTP",
|
||||
"Baixar um arquivo",
|
||||
"Conectar através de SSH",
|
||||
"Copiar um arquivo através de SSH",
|
||||
"Seguir um arquivo de log em tempo real",
|
||||
"Mostrar histórico de comandos",
|
||||
"Limpar a tela do terminal"
|
||||
],
|
||||
"sv": [
|
||||
"Lista alla Proxmox-noder",
|
||||
"Lista virtuella datorer på en nod",
|
||||
"Lista LXC-behållare på en nod",
|
||||
"Lista lagringsutrymme på en nod",
|
||||
"Lista nätverksgränssnitt",
|
||||
"Lista alla virtuella QEMU/KVM-maskiner",
|
||||
"Starta en virtuell maskin",
|
||||
"Stoppa en virtuell maskin",
|
||||
"Stäng av en virtuell maskin på ett säkert sätt",
|
||||
"Show VM status",
|
||||
"Show VM configuration",
|
||||
"Skapa en VM Snapshot",
|
||||
"Lista alla LXC-behållare",
|
||||
"Starta en LXC-behållare",
|
||||
"Stoppa en LXC-behållare",
|
||||
"Öppna en LXC-containerkonsol",
|
||||
"Visa behållarkonfiguration",
|
||||
"Visa lagringsstatus",
|
||||
"Lista lagringsinnehåll",
|
||||
"Test Proxmox system performance",
|
||||
"Show the Proxmox VE version",
|
||||
"Check cluster status",
|
||||
"Show cluster status",
|
||||
"Lista klusternoder",
|
||||
"Visa ZFS-poolstatus",
|
||||
"Lista alla ZFS-pooler",
|
||||
"Lista alla ZFS-datauppsättningar",
|
||||
"Lista alla filer med detaljer",
|
||||
"Ändra katalog",
|
||||
"Skapa en katalog",
|
||||
"Ta bort en katalog rekursivt",
|
||||
"Copy files or directories",
|
||||
"Move or rename files",
|
||||
"Display file contents",
|
||||
"Search for a pattern in a file",
|
||||
"Hitta filer efter namn",
|
||||
"Ändra filbehörigheter",
|
||||
"Ändra filägare",
|
||||
"Extrahera ett tar.gz-arkiv",
|
||||
"Skapa ett tar.gz-arkiv",
|
||||
"Visa diskanvändning",
|
||||
"Visa katalogstorlekar",
|
||||
"Visa minnesanvändning",
|
||||
"Show running processes",
|
||||
"Find a running process",
|
||||
"Force-kill a process",
|
||||
"Kontrollera tjänsten status",
|
||||
"Starta en tjänst",
|
||||
"Stoppa en tjänst",
|
||||
"Starta om en tjänst",
|
||||
"Uppdatera Debian/Ubuntu-paket",
|
||||
"Installera ett Debian/Ubuntu-paket",
|
||||
"Ta bort ett paket",
|
||||
"Lista behållare som körs",
|
||||
"Lista Docker-avbildningar",
|
||||
"Öppna ett skal i en behållare",
|
||||
"Visa IP-adresser",
|
||||
"Testa en nätverksanslutning",
|
||||
"Get HTTP headers",
|
||||
"Download a file",
|
||||
"Anslut via SSH",
|
||||
"Kopiera en fil via SSH",
|
||||
"Följ en loggfil i realtid",
|
||||
"Visa kommandohistorik",
|
||||
"Rensa terminalskärmen"
|
||||
]
|
||||
},
|
||||
"english": [
|
||||
"List all Proxmox nodes",
|
||||
"List VMs on a node",
|
||||
"List LXC containers on a node",
|
||||
"List storage on a node",
|
||||
"List network interfaces",
|
||||
"List all QEMU/KVM virtual machines",
|
||||
"Start a virtual machine",
|
||||
"Stop a virtual machine",
|
||||
"Shut down a virtual machine safely",
|
||||
"Show VM status",
|
||||
"Show VM configuration",
|
||||
"Create a VM Snapshot",
|
||||
"List all LXC containers",
|
||||
"Start an LXC container",
|
||||
"Stop an LXC container",
|
||||
"Open an LXC container console",
|
||||
"Show container configuration",
|
||||
"Show storage status",
|
||||
"List storage contents",
|
||||
"Test Proxmox system performance",
|
||||
"Show the Proxmox VE version",
|
||||
"Check cluster status",
|
||||
"Show cluster status",
|
||||
"List cluster nodes",
|
||||
"Show ZFS pool status",
|
||||
"List all ZFS pools",
|
||||
"List all ZFS datasets",
|
||||
"List all files with details",
|
||||
"Change directory",
|
||||
"Create a directory",
|
||||
"Remove a directory recursively",
|
||||
"Copy files or directories",
|
||||
"Move or rename files",
|
||||
"Display file contents",
|
||||
"Search for a pattern in a file",
|
||||
"Find files by name",
|
||||
"Change file permissions",
|
||||
"Change the file owner",
|
||||
"Extract a tar.gz archive",
|
||||
"Create a tar.gz archive",
|
||||
"Show disk usage",
|
||||
"Show directory sizes",
|
||||
"Show memory usage",
|
||||
"Show running processes",
|
||||
"Find a running process",
|
||||
"Force-kill a process",
|
||||
"Check service status",
|
||||
"Start a service",
|
||||
"Stop a service",
|
||||
"Restart a service",
|
||||
"Update Debian/Ubuntu packages",
|
||||
"Install a Debian/Ubuntu package",
|
||||
"Remove a package",
|
||||
"List running containers",
|
||||
"List Docker images",
|
||||
"Open a shell in a container",
|
||||
"Show IP addresses",
|
||||
"Test network connectivity",
|
||||
"Get HTTP headers",
|
||||
"Download a file",
|
||||
"Connect through SSH",
|
||||
"Copy a file through SSH",
|
||||
"Follow a log file in real time",
|
||||
"Show command history",
|
||||
"Clear the terminal screen"
|
||||
],
|
||||
"commands": [
|
||||
"pvesh get /nodes",
|
||||
"pvesh get /nodes/{node}/qemu",
|
||||
"pvesh get /nodes/{node}/lxc",
|
||||
"pvesh get /nodes/{node}/storage",
|
||||
"pvesh get /nodes/{node}/network",
|
||||
"qm list",
|
||||
"qm start <vmid>",
|
||||
"qm stop <vmid>",
|
||||
"qm shutdown <vmid>",
|
||||
"qm status <vmid>",
|
||||
"qm config <vmid>",
|
||||
"qm snapshot <vmid> <snapname>",
|
||||
"pct list",
|
||||
"pct start <vmid>",
|
||||
"pct stop <vmid>",
|
||||
"pct enter <vmid>",
|
||||
"pct config <vmid>",
|
||||
"pvesm status",
|
||||
"pvesm list <storage>",
|
||||
"pveperf",
|
||||
"pveversion",
|
||||
"systemctl status pve-cluster",
|
||||
"pvecm status",
|
||||
"pvecm nodes",
|
||||
"zpool status",
|
||||
"zpool list",
|
||||
"zfs list",
|
||||
"ls -la",
|
||||
"cd /path/to/dir",
|
||||
"mkdir dirname",
|
||||
"rm -rf dirname",
|
||||
"cp source dest",
|
||||
"mv source dest",
|
||||
"cat filename",
|
||||
"grep 'pattern' file",
|
||||
"find . -name 'file'",
|
||||
"chmod 755 file",
|
||||
"chown user:group file",
|
||||
"tar -xzf file.tar.gz",
|
||||
"tar -czf archive.tar.gz dir/",
|
||||
"df -h",
|
||||
"du -sh *",
|
||||
"free -h",
|
||||
"top",
|
||||
"ps aux | grep process",
|
||||
"kill -9 PID",
|
||||
"systemctl status service",
|
||||
"systemctl start service",
|
||||
"systemctl stop service",
|
||||
"systemctl restart service",
|
||||
"apt update && apt upgrade",
|
||||
"apt install package",
|
||||
"apt remove package",
|
||||
"docker ps",
|
||||
"docker images",
|
||||
"docker exec -it container bash",
|
||||
"ip addr show",
|
||||
"ping host",
|
||||
"curl -I url",
|
||||
"wget url",
|
||||
"ssh user@host",
|
||||
"scp file user@host:/path",
|
||||
"tail -f /var/log/syslog",
|
||||
"history",
|
||||
"clear"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
"""Offline regression tests for the Monitor catalog generator."""
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from contextlib import redirect_stdout, redirect_stderr
|
||||
|
||||
SCRIPT = Path(__file__).parents[1] / "build_i18n_messages.py"
|
||||
SPEC = importlib.util.spec_from_file_location("build_i18n_messages", SCRIPT)
|
||||
MODULE = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
class I18nMessagesTests(unittest.TestCase):
|
||||
def run_generator(self, source, target, *options, provider=None):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
en = root / "en" / "common.json"
|
||||
it = root / "it" / "common.json"
|
||||
en.parent.mkdir()
|
||||
it.parent.mkdir()
|
||||
en.write_text(json.dumps(source), encoding="utf-8")
|
||||
it.write_text(json.dumps(target), encoding="utf-8")
|
||||
before = it.read_bytes()
|
||||
argv = [str(SCRIPT), "--source", str(en), "--messages-dir", str(root),
|
||||
"--languages", "it", "--sleep", "0", *options]
|
||||
output = io.StringIO()
|
||||
with mock.patch.object(sys, "argv", argv), mock.patch.object(
|
||||
MODULE, "translate_one", side_effect=provider or (lambda text, *args: "IT " + text)
|
||||
) as translate, redirect_stdout(output), redirect_stderr(output):
|
||||
result = MODULE.main()
|
||||
return result, json.loads(it.read_text()), translate.call_args_list, output.getvalue(), before == it.read_bytes()
|
||||
|
||||
def test_generator_fills_array_leaves_preserving_existing_values(self):
|
||||
source = {"items": ["One", {"title": "Two", "children": ["Three"]}],
|
||||
"empty": [], "object": {}, "enabled": True, "count": 2, "none": None}
|
||||
target = {"items": ["Uno"], "extra": {"0": "Keep"}}
|
||||
result, actual, calls, _, _ = self.run_generator(source, target, "--save-every", "1")
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(actual, {**source, "items": ["Uno", {"title": "IT Two", "children": ["IT Three"]}],
|
||||
"extra": {"0": "Keep"}})
|
||||
self.assertEqual([call.args[0] for call in calls], ["Two", "Three"])
|
||||
|
||||
def test_wrong_types_defer_locale_without_calls_or_writes_even_on_refresh(self):
|
||||
source = {"new": "New", "terminal": {"commandDescriptions": ["One", "Two"]}}
|
||||
for malformed in ["['Uno', 'Due']", '["Uno", "Due"]', "ambiguous human text", {}, None, 7]:
|
||||
for options in [(), ("--refresh",)]:
|
||||
with self.subTest(malformed=malformed, options=options):
|
||||
target = {"terminal": {"commandDescriptions": malformed}, "human": "Keep"}
|
||||
result, actual, calls, output, unchanged = self.run_generator(source, target, *options)
|
||||
self.assertEqual(result, 1)
|
||||
self.assertEqual(actual, target)
|
||||
self.assertTrue(unchanged)
|
||||
self.assertEqual(calls, [])
|
||||
self.assertIn("/terminal/commandDescriptions", output)
|
||||
self.assertIn("manual", output)
|
||||
|
||||
def test_structure_only_catalog_is_written_without_translation(self):
|
||||
source = {"items": ["", [], {}, None, False, 0]}
|
||||
result, actual, calls, _, _ = self.run_generator(source, {})
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(actual, source)
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_limit_keeps_array_positions_as_null_fallbacks(self):
|
||||
result, actual, calls, _, _ = self.run_generator(
|
||||
{"items": ["One", "", "Three", "Four"]}, {}, "--limit", "1")
|
||||
self.assertEqual(actual, {"items": ["IT One", "", None, None]})
|
||||
self.assertEqual(len(calls), 1)
|
||||
|
||||
def test_existing_nonempty_including_english_and_stale_text_is_unchanged(self):
|
||||
source = {"items": ["Changed English", "CPU", "Three"]}
|
||||
target = {"items": ["Human translation", "CPU", "Tre", "Extra"]}
|
||||
result, actual, calls, _, unchanged = self.run_generator(source, target)
|
||||
self.assertEqual(actual, target)
|
||||
self.assertEqual(calls, [])
|
||||
self.assertTrue(unchanged)
|
||||
|
||||
def test_second_run_retries_null_placeholders_preserving_translations(self):
|
||||
source = {"items": ["One", "Two", "", "Three", "Four"]}
|
||||
target = {"items": ["Human"], "extra": "Keep"}
|
||||
result, first, calls, _, _ = self.run_generator(source, target, "--limit", "1")
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(first, {"items": ["Human", "IT Two", "", None, None], "extra": "Keep"})
|
||||
self.assertEqual([call.args[0] for call in calls], ["Two"])
|
||||
result, second, calls, _, _ = self.run_generator(source, first, "--save-every", "1")
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(second, {"items": ["Human", "IT Two", "", "IT Three", "IT Four"], "extra": "Keep"})
|
||||
self.assertEqual([call.args[0] for call in calls], ["Three", "Four"])
|
||||
|
||||
def test_refresh_and_provider_failure_keep_existing_fallback_semantics(self):
|
||||
def provider(text, *args):
|
||||
if text == "Two":
|
||||
raise RuntimeError("offline fixture failure")
|
||||
return "IT " + text
|
||||
result, actual, calls, output, _ = self.run_generator(
|
||||
{"items": ["One", "Two", "Three"]}, {"items": ["Uno", "Due"]},
|
||||
"--refresh", "--save-every", "1", provider=provider)
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(actual, {"items": ["IT One", "Due", "IT Three"]})
|
||||
self.assertEqual(len(calls), 3)
|
||||
self.assertIn("1 translation failures", output)
|
||||
|
||||
def test_null_and_empty_string_are_filled_and_tokens_restored(self):
|
||||
source = {"items": ["Hello {vmid}", "Use Proxmox"]}
|
||||
result, actual, calls, _, _ = self.run_generator(source, {"items": [None, ""]})
|
||||
self.assertEqual(actual, {"items": ["IT Hello {vmid}", "IT Use Proxmox"]})
|
||||
self.assertNotIn("{vmid}", calls[0].args[0])
|
||||
self.assertNotIn("Proxmox", calls[1].args[0])
|
||||
|
||||
def test_root_pointer_is_empty_not_empty_key_pointer(self):
|
||||
for source, target, pointer in [({"a": "Text"}, [], 'JSON Pointer "" (root)'),
|
||||
({"": []}, {"": "Text"}, 'JSON Pointer "/"'),
|
||||
({"a~/": []}, {"a~/": "Text"}, 'JSON Pointer "/a~0~1"')]:
|
||||
with self.subTest(pointer=pointer):
|
||||
result, _, calls, output, unchanged = self.run_generator(source, target)
|
||||
self.assertEqual(result, 1)
|
||||
self.assertEqual(calls, [])
|
||||
self.assertTrue(unchanged)
|
||||
self.assertIn(pointer + ": expected", output)
|
||||
|
||||
def test_wrong_scalar_and_nested_types_are_rejected(self):
|
||||
for source, target in [({"a": "Text"}, {"a": 42}),
|
||||
({"a": [{"b": "Text"}]}, {"a": ["human"]}),
|
||||
({"a": False}, {"a": 0}),
|
||||
({"a": {}}, {"a": []})]:
|
||||
with self.subTest(source=source):
|
||||
result, actual, calls, _, unchanged = self.run_generator(source, target)
|
||||
self.assertEqual(result, 1)
|
||||
self.assertTrue(unchanged)
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_real_english_array_round_trip_and_generation(self):
|
||||
catalog = json.loads((SCRIPT.parents[2] / "AppImage/messages/en/common.json").read_text())
|
||||
descriptions = catalog["terminal"]["commandDescriptions"]
|
||||
self.assertIsInstance(descriptions, list)
|
||||
self.assertTrue(descriptions)
|
||||
self.assertEqual(MODULE.unflatten(MODULE.flatten(catalog)), catalog)
|
||||
result, actual, calls, _, _ = self.run_generator({"terminal": {"commandDescriptions": descriptions}}, {})
|
||||
self.assertEqual(len(calls), sum(isinstance(text, str) and bool(text) for text in descriptions))
|
||||
self.assertEqual(actual["terminal"]["commandDescriptions"], ["IT " + text for text in descriptions])
|
||||
|
||||
def test_preflight_skips_invalid_locales_but_processes_valid_locales(self):
|
||||
for options in [(), ("--refresh",)]:
|
||||
with self.subTest(options=options), tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
catalogs = {"en": {"items": ["One", "Two"]},
|
||||
"sk": {"items": ["Human"]},
|
||||
"it": {"items": "Uno"}, "de": {"items": {}}}
|
||||
for lang, data in catalogs.items():
|
||||
path = root / lang / "common.json"
|
||||
path.parent.mkdir()
|
||||
path.write_text(json.dumps(data))
|
||||
before = {lang: (root / lang / "common.json").read_bytes() for lang in catalogs}
|
||||
reads = []
|
||||
real_read = MODULE.read_json
|
||||
|
||||
def read(path):
|
||||
reads.append(path.parent.name)
|
||||
return real_read(path)
|
||||
|
||||
def translate(text, lang, *args):
|
||||
self.assertEqual(reads, ["en", "sk", "it", "de"])
|
||||
self.assertEqual(lang, "sk")
|
||||
return "SK " + text
|
||||
|
||||
argv = [str(SCRIPT), "--source", str(root / "en/common.json"),
|
||||
"--messages-dir", str(root), "--languages", "sk,it,de",
|
||||
"--sleep", "0", "--save-every", "1", *options]
|
||||
output = io.StringIO()
|
||||
with mock.patch.object(sys, "argv", argv), \
|
||||
mock.patch.object(MODULE, "read_json", side_effect=read), \
|
||||
mock.patch.object(MODULE, "translate_one", side_effect=translate) as provider, \
|
||||
mock.patch.object(MODULE, "write_json", wraps=MODULE.write_json) as writer, \
|
||||
redirect_stderr(output), redirect_stdout(output):
|
||||
self.assertEqual(MODULE.main(), 1)
|
||||
self.assertEqual(json.loads((root / "sk/common.json").read_text()),
|
||||
{"items": ["SK One" if options else "Human", "SK Two"]})
|
||||
self.assertEqual(provider.call_count, 2 if options else 1)
|
||||
self.assertTrue(all(call.args[0].parent.name == "sk" for call in writer.call_args_list))
|
||||
for lang in ["en", "it", "de"]:
|
||||
self.assertEqual((root / lang / "common.json").read_bytes(), before[lang])
|
||||
self.assertIn("Partial failure: skipped 2 locale(s): it, de", output.getvalue())
|
||||
self.assertNotIn("generated successfully", output.getvalue())
|
||||
|
||||
def test_round_trip_preserves_json_types_and_literal_keys(self):
|
||||
catalog = {"terminal": {"commandDescriptions": ["One", {"nested": ["Two", [], {}]}]},
|
||||
"0": {"a.b": "literal", "": None}, "flags": [True, False, 2, 1.5]}
|
||||
self.assertEqual(MODULE.unflatten(MODULE.flatten(catalog)), catalog)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,149 @@
|
||||
"""Frozen conservative recovery: update the fixture only after explicit review.
|
||||
|
||||
Run offline with Python unittest and Node >=22.14 on PATH (or NODE_BINARY).
|
||||
The consumer test executes only extracted pure lookup functions, not the UI.
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stdout, redirect_stderr
|
||||
from unittest import mock
|
||||
|
||||
from test_build_i18n_messages import MODULE, SCRIPT
|
||||
|
||||
ROOT = SCRIPT.parents[2]
|
||||
FIXTURE = json.loads((Path(__file__).parent / "fixtures/command_descriptions.json").read_text())
|
||||
|
||||
|
||||
def require_node():
|
||||
diagnostic = "Node >=22.14 is required; install Node 22 LTS or set NODE_BINARY to its executable"
|
||||
node = os.environ.get("NODE_BINARY") or shutil.which("node")
|
||||
if not node:
|
||||
raise AssertionError(diagnostic)
|
||||
try:
|
||||
result = subprocess.run([node, "--version"], capture_output=True, text=True)
|
||||
except OSError as exc:
|
||||
raise AssertionError(f"{diagnostic}; cannot execute {node}: {exc}") from exc
|
||||
version = re.fullmatch(r"v(\d+)\.(\d+)\.(\d+)", result.stdout.strip())
|
||||
if result.returncode or not version or tuple(map(int, version.groups())) < (22, 14, 0):
|
||||
raise AssertionError(f"{diagnostic}; found {result.stdout.strip() or result.stderr.strip()!r}")
|
||||
return node
|
||||
|
||||
|
||||
def catalog(lang):
|
||||
return json.loads((ROOT / f"AppImage/messages/{lang}/common.json").read_text())
|
||||
|
||||
|
||||
class NodeRuntimeTests(unittest.TestCase):
|
||||
def test_missing_node_reports_setup_instructions(self):
|
||||
with mock.patch.dict(os.environ, {"PATH": "", "NODE_BINARY": ""}):
|
||||
with self.assertRaisesRegex(AssertionError, r"Node >=22\.14.*NODE_BINARY"):
|
||||
require_node()
|
||||
|
||||
def test_invalid_override_reports_setup_instructions(self):
|
||||
with mock.patch.dict(os.environ, {"NODE_BINARY": "/nonexistent/f05-node"}):
|
||||
with self.assertRaisesRegex(AssertionError, r"Node >=22\.14.*NODE_BINARY"):
|
||||
require_node()
|
||||
|
||||
def test_older_node_reports_detected_version(self):
|
||||
with mock.patch.dict(os.environ, {"NODE_BINARY": "node"}), \
|
||||
mock.patch.object(subprocess, "run", return_value=subprocess.CompletedProcess(
|
||||
["node", "--version"], 0, "v20.19.0\n", "")):
|
||||
with self.assertRaisesRegex(AssertionError, r"Node >=22\.14.*v20\.19\.0"):
|
||||
require_node()
|
||||
|
||||
|
||||
class CommandDescriptionsTests(unittest.TestCase):
|
||||
def test_real_catalogs_exact_indexed_recovery_and_source_order(self):
|
||||
self.assertEqual(catalog("en")["terminal"]["commandDescriptions"], FIXTURE["english"])
|
||||
consumer = (ROOT / "AppImage/components/terminal-panel.tsx").read_text()
|
||||
commands = re.search(r"const proxmoxCommands = (\[.*?\])", consumer, re.S).group(1)
|
||||
commands = json.loads(re.sub(r",\s*\]$", "]", commands))
|
||||
self.assertEqual(commands, FIXTURE["commands"])
|
||||
self.assertIn('desc: t(`terminal.commandDescriptions.${index}`)', consumer)
|
||||
for lang, expected in FIXTURE["locales"].items():
|
||||
with self.subTest(locale=lang):
|
||||
actual = catalog(lang)["terminal"]["commandDescriptions"]
|
||||
self.assertIsInstance(actual, list)
|
||||
self.assertEqual(len(actual), len(commands))
|
||||
self.assertEqual(actual, expected)
|
||||
self.assertTrue(all(isinstance(x, str) and x for x in actual))
|
||||
for index in FIXTURE["fallback_indices"]:
|
||||
self.assertEqual(catalog("sv")["terminal"]["commandDescriptions"][index], FIXTURE["english"][index])
|
||||
|
||||
def test_workflow_default_catalogs_rerun_without_provider_or_writes(self):
|
||||
workflow = (ROOT / ".github/workflows/build-i18n-messages.yml").read_text()
|
||||
languages = re.search(r'LANGS="\$\{LANGS:-(.*?)\}"', workflow).group(1)
|
||||
self.assertEqual(languages.split(","), list(MODULE.DEFAULT_LANGUAGES))
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
for lang in ["en", *languages.split(",")]:
|
||||
path = root / lang / "common.json"
|
||||
path.parent.mkdir()
|
||||
shutil.copyfile(ROOT / f"AppImage/messages/{lang}/common.json", path)
|
||||
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"]
|
||||
output = io.StringIO()
|
||||
with mock.patch.object(sys, "argv", argv), \
|
||||
mock.patch.object(MODULE, "translate_one", side_effect=AssertionError("provider forbidden")) as provider, \
|
||||
mock.patch.object(MODULE, "write_json", side_effect=AssertionError("writes forbidden")) as writer, \
|
||||
redirect_stdout(output), redirect_stderr(output):
|
||||
result = MODULE.main()
|
||||
self.assertEqual(result, 0, output.getvalue())
|
||||
provider.assert_not_called()
|
||||
writer.assert_not_called()
|
||||
self.assertEqual(before, {p: p.read_bytes() for p in before})
|
||||
for lang in languages.split(","):
|
||||
self.assertIn(f"{lang}: nothing to translate", output.getvalue())
|
||||
|
||||
def test_actual_consumer_lookup_null_fallback_and_explicit_english(self):
|
||||
node = require_node()
|
||||
source = (ROOT / "AppImage/lib/i18n/provider.tsx").read_text()
|
||||
functions = source[source.index("function getMessage("):source.index("export function I18nProvider(")]
|
||||
lookup = re.search(r" const localized =.*?return interpolate\(.*?\)\n", source, re.S).group(0)
|
||||
catalogs = {lang: catalog(lang) for lang in ["en", *FIXTURE["locales"]]}
|
||||
script = ('import assert from "node:assert/strict";\n'
|
||||
'type MessageTree = Record<string, unknown>;\n'
|
||||
'type TranslationParams = Record<string, string | number>;\n' + functions +
|
||||
'\nconst MESSAGE_CATALOG = ' + json.dumps(catalogs) + ';\n' +
|
||||
'function t(language: string, key: string, params?: TranslationParams) {\n' + lookup + '}\n' +
|
||||
'const fixture = ' + json.dumps(FIXTURE) + ';\n' + '''
|
||||
for (const [lang, texts] of Object.entries(fixture.locales)) {
|
||||
texts.forEach((text, i) => {
|
||||
const key = `terminal.commandDescriptions.${i}`;
|
||||
assert.equal(getMessage(MESSAGE_CATALOG[lang], key), text);
|
||||
assert.equal(t(lang, key), text);
|
||||
});
|
||||
}
|
||||
for (const i of fixture.fallback_indices) {
|
||||
const key = `terminal.commandDescriptions.${i}`;
|
||||
const selected = MESSAGE_CATALOG.sv.terminal.commandDescriptions[i];
|
||||
MESSAGE_CATALOG.en.terminal.commandDescriptions[i] = "CHANGED EN";
|
||||
assert.equal(t("sv", key), selected); // Explicit English is a locale value.
|
||||
MESSAGE_CATALOG.sv.terminal.commandDescriptions[i] = null;
|
||||
assert.equal(t("sv", key), "CHANGED EN"); // Null would dynamically fall back.
|
||||
MESSAGE_CATALOG.sv.terminal.commandDescriptions[i] = "";
|
||||
assert.equal(t("sv", key), ""); // Empty strings do NOT fall back.
|
||||
}
|
||||
assert.equal(t("sv", "missing.key"), "missing.key");
|
||||
console.log("390 indexed consumer lookups; 15 explicit-English/null/empty cases passed");
|
||||
''')
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "consumer.ts"
|
||||
path.write_text(script)
|
||||
result = subprocess.run([node, "--experimental-strip-types", str(path)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
self.assertIn("390 indexed consumer lookups", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,45 @@
|
||||
name: Offline i18n tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/scripts/**'
|
||||
- '.github/workflows/test-i18n.yml'
|
||||
- '.github/workflows/build-i18n-messages.yml'
|
||||
- 'AppImage/messages/**'
|
||||
- 'AppImage/lib/i18n/**'
|
||||
- 'AppImage/components/terminal-panel.tsx'
|
||||
- 'CONTRIBUTING.md'
|
||||
push:
|
||||
branches: [main, develop]
|
||||
paths:
|
||||
- '.github/scripts/**'
|
||||
- '.github/workflows/test-i18n.yml'
|
||||
- '.github/workflows/build-i18n-messages.yml'
|
||||
- 'AppImage/messages/**'
|
||||
- 'AppImage/lib/i18n/**'
|
||||
- 'AppImage/components/terminal-panel.tsx'
|
||||
- 'CONTRIBUTING.md'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
offline-i18n:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.14.0'
|
||||
- name: Run complete offline script test suite
|
||||
env:
|
||||
PYTHONDONTWRITEBYTECODE: '1'
|
||||
run: python3 -m unittest discover -s .github/scripts/tests -v
|
||||
@@ -4878,7 +4878,73 @@
|
||||
"connectionTimeout": "Zeitüberschreitung bei der Verbindung. Überprüfen Sie Ihr Netzwerk und versuchen Sie es erneut.",
|
||||
"vpnTip": "Wenn Sie ein VPN verwenden, stellen Sie sicher, dass die Verbindung stabil ist.",
|
||||
"websocketError": "WebSocket-Verbindungsfehler",
|
||||
"commandDescriptions": "['Alle Proxmox-Knoten auflisten', 'VMs auf einem Knoten auflisten', 'LXC-Container auf einem Knoten auflisten', 'Speicher auf einem Knoten auflisten', 'Netzwerkschnittstellen auflisten', 'Alle virtuellen QEMU/KVM-Maschinen auflisten', 'Eine virtuelle Maschine starten', 'Eine virtuelle Maschine stoppen', 'Eine virtuelle Maschine sicher herunterfahren', 'VM-Status anzeigen', 'VM-Konfiguration anzeigen', 'Einen VM-Snapshot erstellen', „Alle LXC-Container auflisten“, „Einen LXC-Container starten“, „Einen LXC-Container stoppen“, „Eine LXC-Containerkonsole öffnen“, „Containerkonfiguration anzeigen“, „Speicherstatus anzeigen“, „Speicherinhalte auflisten“, „Proxmox-Systemleistung testen“, „Proxmox VE-Version anzeigen“, „Clusterstatus prüfen“, „Clusterstatus anzeigen“, „Clusterknoten auflisten“, „ZFS-Poolstatus anzeigen“, „Alle ZFS-Pools auflisten“, „Alle ZFS-Datensätze auflisten“, „Alle Dateien mit Details auflisten“, „Verzeichnis ändern“, „Verzeichnis erstellen“, „Verzeichnis rekursiv entfernen“, „Dateien oder Verzeichnisse kopieren“, „Dateien verschieben oder umbenennen“, „Dateiinhalte anzeigen“, „Nach einem Muster in einer Datei suchen“, „Dateien nach Namen suchen“, „Dateiberechtigungen ändern“, „Dateieigentümer ändern“, „Tar.gz-Archiv extrahieren“, „Tar.gz-Archiv erstellen“, „Festplattennutzung anzeigen“, „Verzeichnisgrößen anzeigen“, „Speichernutzung anzeigen“, „Laufende Prozesse anzeigen“, „Laufenden Prozess suchen“, „Prozess beenden“, „Dienststatus prüfen“, „Dienst starten“, „Dienst stoppen“, „Dienst neu starten“, „Debian/Ubuntu-Pakete aktualisieren“, „Debian/Ubuntu-Paket installieren“, „Paket entfernen“, „Laufende Container auflisten“, „Docker-Images auflisten“, „Eine Shell in einem Container öffnen“, „IP-Adressen anzeigen“, „Netzwerkkonnektivität testen“, „HTTP-Header abrufen“, „Eine Datei herunterladen“, „Über SSH verbinden“, „Eine Datei über SSH kopieren“, „Eine Protokolldatei in Echtzeit verfolgen“, „Befehlsverlauf anzeigen“, „Terminalbildschirm löschen“]"
|
||||
"commandDescriptions": [
|
||||
"Alle Proxmox-Knoten auflisten",
|
||||
"VMs auf einem Knoten auflisten",
|
||||
"LXC-Container auf einem Knoten auflisten",
|
||||
"Speicher auf einem Knoten auflisten",
|
||||
"Netzwerkschnittstellen auflisten",
|
||||
"Alle virtuellen QEMU/KVM-Maschinen auflisten",
|
||||
"Eine virtuelle Maschine starten",
|
||||
"Eine virtuelle Maschine stoppen",
|
||||
"Eine virtuelle Maschine sicher herunterfahren",
|
||||
"VM-Status anzeigen",
|
||||
"VM-Konfiguration anzeigen",
|
||||
"Einen VM-Snapshot erstellen",
|
||||
"Alle LXC-Container auflisten",
|
||||
"Einen LXC-Container starten",
|
||||
"Einen LXC-Container stoppen",
|
||||
"Eine LXC-Containerkonsole öffnen",
|
||||
"Containerkonfiguration anzeigen",
|
||||
"Speicherstatus anzeigen",
|
||||
"Speicherinhalte auflisten",
|
||||
"Proxmox-Systemleistung testen",
|
||||
"Proxmox VE-Version anzeigen",
|
||||
"Clusterstatus prüfen",
|
||||
"Clusterstatus anzeigen",
|
||||
"Clusterknoten auflisten",
|
||||
"ZFS-Poolstatus anzeigen",
|
||||
"Alle ZFS-Pools auflisten",
|
||||
"Alle ZFS-Datensätze auflisten",
|
||||
"Alle Dateien mit Details auflisten",
|
||||
"Verzeichnis ändern",
|
||||
"Verzeichnis erstellen",
|
||||
"Verzeichnis rekursiv entfernen",
|
||||
"Dateien oder Verzeichnisse kopieren",
|
||||
"Dateien verschieben oder umbenennen",
|
||||
"Dateiinhalte anzeigen",
|
||||
"Nach einem Muster in einer Datei suchen",
|
||||
"Dateien nach Namen suchen",
|
||||
"Dateiberechtigungen ändern",
|
||||
"Dateieigentümer ändern",
|
||||
"Tar.gz-Archiv extrahieren",
|
||||
"Tar.gz-Archiv erstellen",
|
||||
"Festplattennutzung anzeigen",
|
||||
"Verzeichnisgrößen anzeigen",
|
||||
"Speichernutzung anzeigen",
|
||||
"Laufende Prozesse anzeigen",
|
||||
"Laufenden Prozess suchen",
|
||||
"Prozess beenden",
|
||||
"Dienststatus prüfen",
|
||||
"Dienst starten",
|
||||
"Dienst stoppen",
|
||||
"Dienst neu starten",
|
||||
"Debian/Ubuntu-Pakete aktualisieren",
|
||||
"Debian/Ubuntu-Paket installieren",
|
||||
"Paket entfernen",
|
||||
"Laufende Container auflisten",
|
||||
"Docker-Images auflisten",
|
||||
"Eine Shell in einem Container öffnen",
|
||||
"IP-Adressen anzeigen",
|
||||
"Netzwerkkonnektivität testen",
|
||||
"HTTP-Header abrufen",
|
||||
"Eine Datei herunterladen",
|
||||
"Über SSH verbinden",
|
||||
"Eine Datei über SSH kopieren",
|
||||
"Eine Protokolldatei in Echtzeit verfolgen",
|
||||
"Befehlsverlauf anzeigen",
|
||||
"Terminalbildschirm löschen"
|
||||
]
|
||||
},
|
||||
"scriptTerminal": {
|
||||
"processing": "Verarbeitung...",
|
||||
|
||||
@@ -4878,7 +4878,73 @@
|
||||
"connectionTimeout": "Se agotó el tiempo de conexión. Verifique su red e inténtelo nuevamente.",
|
||||
"vpnTip": "Si utiliza una VPN, asegúrese de que la conexión sea estable.",
|
||||
"websocketError": "Error de conexión WebSocket",
|
||||
"commandDescriptions": "['Listar todos los nodos Proxmox', 'Listar VM en un nodo', 'Listar contenedores LXC en un nodo', 'Listar almacenamiento en un nodo', 'Listar interfaces de red', 'Listar todas las máquinas virtuales QEMU/KVM', 'Iniciar una máquina virtual', 'Detener una máquina virtual', 'Apagar una máquina virtual de forma segura', 'Mostrar estado de VM', 'Mostrar configuración de VM', 'Crear una instantánea de VM', 'Listar todos los contenedores LXC', 'Iniciar un contenedor LXC', 'Detener un contenedor LXC', 'Abrir una consola de contenedor LXC', 'Mostrar configuración del contenedor', 'Mostrar estado de almacenamiento', 'Enumerar el contenido del almacenamiento', 'Probar el rendimiento del sistema Proxmox', 'Mostrar la versión de Proxmox VE', 'Verificar el estado del clúster', 'Mostrar el estado del clúster', 'Enumerar los nodos del clúster', 'Mostrar el estado del grupo ZFS', 'Enumerar todos los grupos ZFS', 'Enumerar todo Conjuntos de datos ZFS', 'Enumerar todos los archivos con detalles', 'Cambiar directorio', 'Crear un directorio', 'Eliminar un directorio de forma recursiva', 'Copiar archivos o directorios', 'Mover o cambiar el nombre de archivos', 'Mostrar el contenido del archivo', 'Buscar un patrón en un archivo', 'Buscar archivos por nombre', 'Cambiar permisos de archivos', 'Cambiar el propietario del archivo', 'Extraer un archivo tar.gz', 'Crear un tar.gz archivo', 'Mostrar uso del disco', 'Mostrar tamaños de directorio', 'Mostrar uso de memoria', 'Mostrar procesos en ejecución', 'Buscar un proceso en ejecución', 'Forzar la eliminación de un proceso', 'Verificar el estado del servicio', 'Iniciar un servicio', 'Detener un servicio', 'Reiniciar un servicio', 'Actualizar paquetes de Debian/Ubuntu', 'Instalar un paquete de Debian/Ubuntu', 'Eliminar un paquete', 'Listar contenedores en ejecución', 'Listar Docker imágenes', 'Abrir un shell en un contenedor', 'Mostrar direcciones IP', 'Probar conectividad de red', 'Obtener encabezados HTTP', 'Descargar un archivo', 'Conectarse a través de SSH', 'Copiar un archivo a través de SSH', 'Seguir un archivo de registro en tiempo real', 'Mostrar historial de comandos', 'Borrar la pantalla del terminal']"
|
||||
"commandDescriptions": [
|
||||
"Listar todos los nodos Proxmox",
|
||||
"Listar VM en un nodo",
|
||||
"Listar contenedores LXC en un nodo",
|
||||
"Listar almacenamiento en un nodo",
|
||||
"Listar interfaces de red",
|
||||
"Listar todas las máquinas virtuales QEMU/KVM",
|
||||
"Iniciar una máquina virtual",
|
||||
"Detener una máquina virtual",
|
||||
"Apagar una máquina virtual de forma segura",
|
||||
"Mostrar estado de VM",
|
||||
"Mostrar configuración de VM",
|
||||
"Crear una instantánea de VM",
|
||||
"Listar todos los contenedores LXC",
|
||||
"Iniciar un contenedor LXC",
|
||||
"Detener un contenedor LXC",
|
||||
"Abrir una consola de contenedor LXC",
|
||||
"Mostrar configuración del contenedor",
|
||||
"Mostrar estado de almacenamiento",
|
||||
"Enumerar el contenido del almacenamiento",
|
||||
"Probar el rendimiento del sistema Proxmox",
|
||||
"Mostrar la versión de Proxmox VE",
|
||||
"Verificar el estado del clúster",
|
||||
"Mostrar el estado del clúster",
|
||||
"Enumerar los nodos del clúster",
|
||||
"Mostrar el estado del grupo ZFS",
|
||||
"Enumerar todos los grupos ZFS",
|
||||
"Enumerar todo Conjuntos de datos ZFS",
|
||||
"Enumerar todos los archivos con detalles",
|
||||
"Cambiar directorio",
|
||||
"Crear un directorio",
|
||||
"Eliminar un directorio de forma recursiva",
|
||||
"Copiar archivos o directorios",
|
||||
"Mover o cambiar el nombre de archivos",
|
||||
"Mostrar el contenido del archivo",
|
||||
"Buscar un patrón en un archivo",
|
||||
"Buscar archivos por nombre",
|
||||
"Cambiar permisos de archivos",
|
||||
"Cambiar el propietario del archivo",
|
||||
"Extraer un archivo tar.gz",
|
||||
"Crear un tar.gz archivo",
|
||||
"Mostrar uso del disco",
|
||||
"Mostrar tamaños de directorio",
|
||||
"Mostrar uso de memoria",
|
||||
"Mostrar procesos en ejecución",
|
||||
"Buscar un proceso en ejecución",
|
||||
"Forzar la eliminación de un proceso",
|
||||
"Verificar el estado del servicio",
|
||||
"Iniciar un servicio",
|
||||
"Detener un servicio",
|
||||
"Reiniciar un servicio",
|
||||
"Actualizar paquetes de Debian/Ubuntu",
|
||||
"Instalar un paquete de Debian/Ubuntu",
|
||||
"Eliminar un paquete",
|
||||
"Listar contenedores en ejecución",
|
||||
"Listar Docker imágenes",
|
||||
"Abrir un shell en un contenedor",
|
||||
"Mostrar direcciones IP",
|
||||
"Probar conectividad de red",
|
||||
"Obtener encabezados HTTP",
|
||||
"Descargar un archivo",
|
||||
"Conectarse a través de SSH",
|
||||
"Copiar un archivo a través de SSH",
|
||||
"Seguir un archivo de registro en tiempo real",
|
||||
"Mostrar historial de comandos",
|
||||
"Borrar la pantalla del terminal"
|
||||
]
|
||||
},
|
||||
"scriptTerminal": {
|
||||
"processing": "Tratamiento...",
|
||||
|
||||
@@ -4878,7 +4878,73 @@
|
||||
"connectionTimeout": "La connexion a expiré. Vérifiez votre réseau et réessayez.",
|
||||
"vpnTip": "Si vous utilisez un VPN, assurez-vous que la connexion est stable.",
|
||||
"websocketError": "Erreur de connexion WebSocket",
|
||||
"commandDescriptions": "['Liste de tous les nœuds Proxmox', 'Liste des machines virtuelles sur un nœud', 'Liste des conteneurs LXC sur un nœud', 'Liste du stockage sur un nœud', 'Liste des interfaces réseau', 'Liste de toutes les machines virtuelles QEMU/KVM', 'Démarrer une machine virtuelle', 'Arrêter une machine virtuelle', 'Arrêter une machine virtuelle en toute sécurité', 'Afficher l'état de la VM', 'Afficher la VM configuration', 'Créer un instantané de VM', 'Liste de tous les conteneurs LXC', 'Démarrer un conteneur LXC', 'Arrêter un conteneur LXC', 'Ouvrir une console de conteneur LXC', 'Afficher la configuration du conteneur', 'Afficher l'état du stockage', 'Liste du contenu du stockage', 'Tester les performances du système Proxmox', 'Afficher la version Proxmox VE', 'Vérifier l'état du cluster', \"Afficher l'état du cluster\", \"Liste des nœuds du cluster\", \"Afficher l'état du pool ZFS\", \"Liste de tous les pools ZFS\", \"Liste de tous les ensembles de données ZFS\", \"Liste de tous les fichiers avec détails\", \"Changer de répertoire\", \"Créer un répertoire\", \"Supprimer un répertoire de manière récursive\", \"Copier des fichiers ou des répertoires\", \"Déplacer ou renommer des fichiers\", \"Afficher le contenu du fichier\", \"Rechercher un modèle dans un fichier\", \"Rechercher des fichiers par nom\", \"Modifier les autorisations du fichier\", \"Modifier le propriétaire du fichier\", \"Extraire une archive tar.gz\", \"Créer une archive tar.gz\", \"Afficher l'utilisation du disque\", \"Afficher la taille des répertoires\", \"Afficher l'utilisation de la mémoire\", \"Afficher les processus en cours d'exécution\", \"Trouver un processus en cours d'exécution\", \"Forcer l'arrêt d'un processus\", \"Vérifier le service\". statut\", \"Démarrer un service\", \"Arrêter un service\", \"Redémarrer un service\", \"Mettre à jour les packages Debian/Ubuntu\", \"Installer un package Debian/Ubuntu\", \"Supprimer un package\", \"Liste des conteneurs en cours d'exécution\", \"Liste des images Docker\", \"Ouvrir un shell dans un conteneur\", \"Afficher les adresses IP\", \"Tester la connectivité réseau\", \"Obtenir les en-têtes HTTP\", 'Télécharger un fichier', 'Connecter via SSH', 'Copier un fichier via SSH', 'Suivre un fichier journal en temps réel', 'Afficher l'historique des commandes', 'Effacer l'écran du terminal']"
|
||||
"commandDescriptions": [
|
||||
"Liste de tous les nœuds Proxmox",
|
||||
"Liste des machines virtuelles sur un nœud",
|
||||
"Liste des conteneurs LXC sur un nœud",
|
||||
"Liste du stockage sur un nœud",
|
||||
"Liste des interfaces réseau",
|
||||
"Liste de toutes les machines virtuelles QEMU/KVM",
|
||||
"Démarrer une machine virtuelle",
|
||||
"Arrêter une machine virtuelle",
|
||||
"Arrêter une machine virtuelle en toute sécurité",
|
||||
"Afficher l'état de la VM",
|
||||
"Afficher la VM configuration",
|
||||
"Créer un instantané de VM",
|
||||
"Liste de tous les conteneurs LXC",
|
||||
"Démarrer un conteneur LXC",
|
||||
"Arrêter un conteneur LXC",
|
||||
"Ouvrir une console de conteneur LXC",
|
||||
"Afficher la configuration du conteneur",
|
||||
"Afficher l'état du stockage",
|
||||
"Liste du contenu du stockage",
|
||||
"Tester les performances du système Proxmox",
|
||||
"Afficher la version Proxmox VE",
|
||||
"Vérifier l'état du cluster",
|
||||
"Afficher l'état du cluster",
|
||||
"Liste des nœuds du cluster",
|
||||
"Afficher l'état du pool ZFS",
|
||||
"Liste de tous les pools ZFS",
|
||||
"Liste de tous les ensembles de données ZFS",
|
||||
"Liste de tous les fichiers avec détails",
|
||||
"Changer de répertoire",
|
||||
"Créer un répertoire",
|
||||
"Supprimer un répertoire de manière récursive",
|
||||
"Copier des fichiers ou des répertoires",
|
||||
"Déplacer ou renommer des fichiers",
|
||||
"Afficher le contenu du fichier",
|
||||
"Rechercher un modèle dans un fichier",
|
||||
"Rechercher des fichiers par nom",
|
||||
"Modifier les autorisations du fichier",
|
||||
"Modifier le propriétaire du fichier",
|
||||
"Extraire une archive tar.gz",
|
||||
"Créer une archive tar.gz",
|
||||
"Afficher l'utilisation du disque",
|
||||
"Afficher la taille des répertoires",
|
||||
"Afficher l'utilisation de la mémoire",
|
||||
"Afficher les processus en cours d'exécution",
|
||||
"Trouver un processus en cours d'exécution",
|
||||
"Forcer l'arrêt d'un processus",
|
||||
"Vérifier le service\". statut",
|
||||
"Démarrer un service",
|
||||
"Arrêter un service",
|
||||
"Redémarrer un service",
|
||||
"Mettre à jour les packages Debian/Ubuntu",
|
||||
"Installer un package Debian/Ubuntu",
|
||||
"Supprimer un package",
|
||||
"Liste des conteneurs en cours d'exécution",
|
||||
"Liste des images Docker",
|
||||
"Ouvrir un shell dans un conteneur",
|
||||
"Afficher les adresses IP",
|
||||
"Tester la connectivité réseau",
|
||||
"Obtenir les en-têtes HTTP",
|
||||
"Télécharger un fichier",
|
||||
"Connecter via SSH",
|
||||
"Copier un fichier via SSH",
|
||||
"Suivre un fichier journal en temps réel",
|
||||
"Afficher l'historique des commandes",
|
||||
"Effacer l'écran du terminal"
|
||||
]
|
||||
},
|
||||
"scriptTerminal": {
|
||||
"processing": "Traitement...",
|
||||
|
||||
@@ -4878,7 +4878,73 @@
|
||||
"connectionTimeout": "Connessione scaduta. Controlla la tua rete e riprova.",
|
||||
"vpnTip": "Se utilizzi una VPN, assicurati che la connessione sia stabile.",
|
||||
"websocketError": "Errore di connessione WebSocket",
|
||||
"commandDescriptions": "['Elenca tutti i nodi Proxmox', 'Elenca VM su un nodo', 'Elenca contenitori LXC su un nodo', 'Elenca spazio di archiviazione su un nodo', 'Elenca interfacce di rete', 'Elenca tutte le macchine virtuali QEMU/KVM', 'Avvia una macchina virtuale', 'Arresta una macchina virtuale', 'Spegni una macchina virtuale in modo sicuro', 'Mostra stato VM', 'Mostra configurazione VM', 'Crea un'istantanea VM', 'Elenca tutti i contenitori LXC', 'Avvia un contenitore LXC', 'Arresta un contenitore LXC', 'Apri una console del contenitore LXC', 'Mostra configurazione contenitore', 'Mostra stato archiviazione', 'Elenca contenuto archiviazione', 'Verifica prestazioni del sistema Proxmox', 'Mostra versione Proxmox VE', 'Verifica stato cluster', 'Mostra stato cluster', 'Elenco nodi cluster', 'Mostra stato pool ZFS', 'Elenca tutti i pool ZFS', 'Elenca tutti i set di dati ZFS', 'Elenca tutti i file con dettagli', 'Cambia directory', 'Crea una directory', 'Rimuovi una directory in modo ricorsivo', 'Copia file o directory', 'Sposta o rinomina file', 'Visualizza contenuto file', 'Cerca un modello in un file', 'Trova file per nome', 'Cambia permessi file', 'Cambia proprietario del file', 'Estrai un archivio tar.gz', 'Crea un archivio tar.gz', 'Mostra utilizzo disco', 'Mostra dimensioni directory', 'Mostra utilizzo memoria', 'Mostra processi in esecuzione', 'Trova un processo in esecuzione', 'Termina forzatamente un processo', 'Controlla stato del servizio', 'Avvia un servizio', 'Interrompi un servizio', \"Riavvia un servizio\", \"Aggiorna pacchetti Debian/Ubuntu\", \"Installa un pacchetto Debian/Ubuntu\", \"Rimuovi un pacchetto\", \"Elenca contenitori in esecuzione\", \"Elenca immagini Docker\", \"Apri una shell in un contenitore\", \"Mostra indirizzi IP\", \"Verifica connettività di rete\", \"Ottieni intestazioni HTTP\", \"Scarica un file\", \"Connetti tramite SSH\", \"Copia un file tramite SSH', 'Segui un file di registro in tempo reale', 'Mostra cronologia comandi', 'Cancella lo schermo del terminale']"
|
||||
"commandDescriptions": [
|
||||
"Elenca tutti i nodi Proxmox",
|
||||
"Elenca VM su un nodo",
|
||||
"Elenca contenitori LXC su un nodo",
|
||||
"Elenca spazio di archiviazione su un nodo",
|
||||
"Elenca interfacce di rete",
|
||||
"Elenca tutte le macchine virtuali QEMU/KVM",
|
||||
"Avvia una macchina virtuale",
|
||||
"Arresta una macchina virtuale",
|
||||
"Spegni una macchina virtuale in modo sicuro",
|
||||
"Mostra stato VM",
|
||||
"Mostra configurazione VM",
|
||||
"Crea un'istantanea VM",
|
||||
"Elenca tutti i contenitori LXC",
|
||||
"Avvia un contenitore LXC",
|
||||
"Arresta un contenitore LXC",
|
||||
"Apri una console del contenitore LXC",
|
||||
"Mostra configurazione contenitore",
|
||||
"Mostra stato archiviazione",
|
||||
"Elenca contenuto archiviazione",
|
||||
"Verifica prestazioni del sistema Proxmox",
|
||||
"Mostra versione Proxmox VE",
|
||||
"Verifica stato cluster",
|
||||
"Mostra stato cluster",
|
||||
"Elenco nodi cluster",
|
||||
"Mostra stato pool ZFS",
|
||||
"Elenca tutti i pool ZFS",
|
||||
"Elenca tutti i set di dati ZFS",
|
||||
"Elenca tutti i file con dettagli",
|
||||
"Cambia directory",
|
||||
"Crea una directory",
|
||||
"Rimuovi una directory in modo ricorsivo",
|
||||
"Copia file o directory",
|
||||
"Sposta o rinomina file",
|
||||
"Visualizza contenuto file",
|
||||
"Cerca un modello in un file",
|
||||
"Trova file per nome",
|
||||
"Cambia permessi file",
|
||||
"Cambia proprietario del file",
|
||||
"Estrai un archivio tar.gz",
|
||||
"Crea un archivio tar.gz",
|
||||
"Mostra utilizzo disco",
|
||||
"Mostra dimensioni directory",
|
||||
"Mostra utilizzo memoria",
|
||||
"Mostra processi in esecuzione",
|
||||
"Trova un processo in esecuzione",
|
||||
"Termina forzatamente un processo",
|
||||
"Controlla stato del servizio",
|
||||
"Avvia un servizio",
|
||||
"Interrompi un servizio",
|
||||
"Riavvia un servizio",
|
||||
"Aggiorna pacchetti Debian/Ubuntu",
|
||||
"Installa un pacchetto Debian/Ubuntu",
|
||||
"Rimuovi un pacchetto",
|
||||
"Elenca contenitori in esecuzione",
|
||||
"Elenca immagini Docker",
|
||||
"Apri una shell in un contenitore",
|
||||
"Mostra indirizzi IP",
|
||||
"Verifica connettività di rete",
|
||||
"Ottieni intestazioni HTTP",
|
||||
"Scarica un file",
|
||||
"Connetti tramite SSH",
|
||||
"Copia un file tramite SSH",
|
||||
"Segui un file di registro in tempo reale",
|
||||
"Mostra cronologia comandi",
|
||||
"Cancella lo schermo del terminale"
|
||||
]
|
||||
},
|
||||
"scriptTerminal": {
|
||||
"processing": "Elaborazione...",
|
||||
|
||||
@@ -4879,7 +4879,73 @@
|
||||
"connectionTimeout": "A conexão expirou. Verifique sua rede e tente novamente.",
|
||||
"vpnTip": "Se você usa uma VPN, certifique-se de que a conexão esteja estável.",
|
||||
"websocketError": "Erro de conexão WebSocket",
|
||||
"commandDescriptions": "['Listar todos os nós Proxmox', 'Listar VMs em um nó', 'Listar contêineres LXC em um nó', 'Listar armazenamento em um nó', 'Listar interfaces de rede', 'Listar todas as máquinas virtuais QEMU/KVM', 'Iniciar uma máquina virtual', 'Parar uma máquina virtual', 'Desligar uma máquina virtual com segurança', 'Mostrar status da VM', 'Mostrar configuração da VM', 'Criar um instantâneo de VM', 'Listar todos os LXC containers', 'Iniciar um contêiner LXC', 'Parar um contêiner LXC', 'Abrir um console de contêiner LXC', 'Mostrar configuração do contêiner', 'Mostrar status de armazenamento', 'Listar conteúdo de armazenamento', 'Testar desempenho do sistema Proxmox', 'Mostrar a versão Proxmox VE', 'Verificar status do cluster', 'Mostrar status do cluster', 'Listar nós do cluster', 'Mostrar status do pool ZFS', 'Listar todos os pools ZFS', 'Listar todos os conjuntos de dados ZFS', 'Listar todos os arquivos com detalhes', 'Alterar diretório', 'Criar um diretório', 'Remover um diretório recursivamente', 'Copiar arquivos ou diretórios', 'Mover ou renomear arquivos', 'Exibir conteúdo do arquivo', 'Pesquisar um padrão em um arquivo', 'Encontrar arquivos por nome', 'Alterar permissões de arquivo', 'Alterar o proprietário do arquivo', 'Extrair um arquivo tar.gz', 'Criar um arquivo tar.gz', 'Mostrar uso de disco', 'Mostrar tamanhos de diretório', 'Mostrar uso de memória', 'Mostrar processos em execução', 'Encontrar um processo em execução', 'Forçar a eliminação de um processo', 'Verificar status do serviço', 'Iniciar um serviço', 'Parar um serviço', 'Reiniciar um serviço', 'Atualizar pacotes Debian/Ubuntu', 'Instalar um pacote Debian/Ubuntu', 'Remover um pacote', 'Listar contêineres em execução', 'Listar imagens do Docker', 'Abrir um shell em um contêiner', 'Mostrar endereços IP', 'Testar conectividade de rede', 'Obter cabeçalhos HTTP', 'Baixar um arquivo', 'Conectar através de SSH', 'Copiar um arquivo através de SSH', 'Seguir um arquivo de log em tempo real', 'Mostrar histórico de comandos', 'Limpar a tela do terminal']"
|
||||
"commandDescriptions": [
|
||||
"Listar todos os nós Proxmox",
|
||||
"Listar VMs em um nó",
|
||||
"Listar contêineres LXC em um nó",
|
||||
"Listar armazenamento em um nó",
|
||||
"Listar interfaces de rede",
|
||||
"Listar todas as máquinas virtuais QEMU/KVM",
|
||||
"Iniciar uma máquina virtual",
|
||||
"Parar uma máquina virtual",
|
||||
"Desligar uma máquina virtual com segurança",
|
||||
"Mostrar status da VM",
|
||||
"Mostrar configuração da VM",
|
||||
"Criar um instantâneo de VM",
|
||||
"Listar todos os LXC containers",
|
||||
"Iniciar um contêiner LXC",
|
||||
"Parar um contêiner LXC",
|
||||
"Abrir um console de contêiner LXC",
|
||||
"Mostrar configuração do contêiner",
|
||||
"Mostrar status de armazenamento",
|
||||
"Listar conteúdo de armazenamento",
|
||||
"Testar desempenho do sistema Proxmox",
|
||||
"Mostrar a versão Proxmox VE",
|
||||
"Verificar status do cluster",
|
||||
"Mostrar status do cluster",
|
||||
"Listar nós do cluster",
|
||||
"Mostrar status do pool ZFS",
|
||||
"Listar todos os pools ZFS",
|
||||
"Listar todos os conjuntos de dados ZFS",
|
||||
"Listar todos os arquivos com detalhes",
|
||||
"Alterar diretório",
|
||||
"Criar um diretório",
|
||||
"Remover um diretório recursivamente",
|
||||
"Copiar arquivos ou diretórios",
|
||||
"Mover ou renomear arquivos",
|
||||
"Exibir conteúdo do arquivo",
|
||||
"Pesquisar um padrão em um arquivo",
|
||||
"Encontrar arquivos por nome",
|
||||
"Alterar permissões de arquivo",
|
||||
"Alterar o proprietário do arquivo",
|
||||
"Extrair um arquivo tar.gz",
|
||||
"Criar um arquivo tar.gz",
|
||||
"Mostrar uso de disco",
|
||||
"Mostrar tamanhos de diretório",
|
||||
"Mostrar uso de memória",
|
||||
"Mostrar processos em execução",
|
||||
"Encontrar um processo em execução",
|
||||
"Forçar a eliminação de um processo",
|
||||
"Verificar status do serviço",
|
||||
"Iniciar um serviço",
|
||||
"Parar um serviço",
|
||||
"Reiniciar um serviço",
|
||||
"Atualizar pacotes Debian/Ubuntu",
|
||||
"Instalar um pacote Debian/Ubuntu",
|
||||
"Remover um pacote",
|
||||
"Listar contêineres em execução",
|
||||
"Listar imagens do Docker",
|
||||
"Abrir um shell em um contêiner",
|
||||
"Mostrar endereços IP",
|
||||
"Testar conectividade de rede",
|
||||
"Obter cabeçalhos HTTP",
|
||||
"Baixar um arquivo",
|
||||
"Conectar através de SSH",
|
||||
"Copiar um arquivo através de SSH",
|
||||
"Seguir um arquivo de log em tempo real",
|
||||
"Mostrar histórico de comandos",
|
||||
"Limpar a tela do terminal"
|
||||
]
|
||||
},
|
||||
"scriptTerminal": {
|
||||
"processing": "Processamento...",
|
||||
|
||||
@@ -4878,7 +4878,73 @@
|
||||
"connectionTimeout": "Anslutningen tog timeout. Kontrollera ditt nätverk och försök igen.",
|
||||
"vpnTip": "Om du använder ett VPN, se till att anslutningen är stabil.",
|
||||
"websocketError": "WebSocket-anslutningsfel",
|
||||
"commandDescriptions": "['Lista alla Proxmox-noder', 'Lista virtuella datorer på en nod', 'Lista LXC-behållare på en nod', 'Lista lagringsutrymme på en nod', 'Lista nätverksgränssnitt', 'Lista alla virtuella QEMU/KVM-maskiner', 'Starta en virtuell maskin', 'Stoppa en virtuell maskin', 'Stäng av en virtuell maskin på ett säkert sätt', 'Stäng av en virtuell maskin,' configuration', 'Skapa en VM Snapshot', 'Lista alla LXC-behållare', 'Starta en LXC-behållare', 'Stoppa en LXC-behållare', 'Öppna en LXC-containerkonsol', 'Visa behållarkonfiguration', 'Visa lagringsstatus', 'Lista lagringsinnehåll', 'Testa Proxmox-systemversionsstatus',SVE 'Visa klusterstatus', 'Lista klusternoder', 'Visa ZFS-poolstatus', 'Lista alla ZFS-pooler', 'Lista alla ZFS-datauppsättningar', 'Lista alla filer med detaljer', 'Ändra katalog', 'Skapa en katalog', 'Ta bort en katalog rekursivt', 'Kopiera filer, 'Sök efter'-filer, 'Sök filer', 'Sök efter' filer', för ett mönster i en fil', 'Hitta filer efter namn', 'Ändra filbehörigheter', 'Ändra filägare', 'Extrahera ett tar.gz-arkiv', 'Skapa ett tar.gz-arkiv', 'Visa diskanvändning', 'Visa katalogstorlekar', 'Visa minnesanvändning', 'Visa pågående processer'-, 'Klicka en process som körs', 'Kontrollera en process som körs', 'Kontrollera tjänsten status', 'Starta en tjänst', 'Stoppa en tjänst', 'Starta om en tjänst', 'Uppdatera Debian/Ubuntu-paket', 'Installera ett Debian/Ubuntu-paket', 'Ta bort ett paket', 'Lista behållare som körs', 'Lista Docker-avbildningar', 'Öppna ett skal i en behållare', 'Visa IP-adresser', 'Testa en nätverksanslutning', 'Testa nätverk', 'Test,' fil', 'Anslut via SSH', 'Kopiera en fil via SSH', 'Följ en loggfil i realtid', 'Visa kommandohistorik', 'Rensa terminalskärmen']"
|
||||
"commandDescriptions": [
|
||||
"Lista alla Proxmox-noder",
|
||||
"Lista virtuella datorer på en nod",
|
||||
"Lista LXC-behållare på en nod",
|
||||
"Lista lagringsutrymme på en nod",
|
||||
"Lista nätverksgränssnitt",
|
||||
"Lista alla virtuella QEMU/KVM-maskiner",
|
||||
"Starta en virtuell maskin",
|
||||
"Stoppa en virtuell maskin",
|
||||
"Stäng av en virtuell maskin på ett säkert sätt",
|
||||
"Show VM status",
|
||||
"Show VM configuration",
|
||||
"Skapa en VM Snapshot",
|
||||
"Lista alla LXC-behållare",
|
||||
"Starta en LXC-behållare",
|
||||
"Stoppa en LXC-behållare",
|
||||
"Öppna en LXC-containerkonsol",
|
||||
"Visa behållarkonfiguration",
|
||||
"Visa lagringsstatus",
|
||||
"Lista lagringsinnehåll",
|
||||
"Test Proxmox system performance",
|
||||
"Show the Proxmox VE version",
|
||||
"Check cluster status",
|
||||
"Show cluster status",
|
||||
"Lista klusternoder",
|
||||
"Visa ZFS-poolstatus",
|
||||
"Lista alla ZFS-pooler",
|
||||
"Lista alla ZFS-datauppsättningar",
|
||||
"Lista alla filer med detaljer",
|
||||
"Ändra katalog",
|
||||
"Skapa en katalog",
|
||||
"Ta bort en katalog rekursivt",
|
||||
"Copy files or directories",
|
||||
"Move or rename files",
|
||||
"Display file contents",
|
||||
"Search for a pattern in a file",
|
||||
"Hitta filer efter namn",
|
||||
"Ändra filbehörigheter",
|
||||
"Ändra filägare",
|
||||
"Extrahera ett tar.gz-arkiv",
|
||||
"Skapa ett tar.gz-arkiv",
|
||||
"Visa diskanvändning",
|
||||
"Visa katalogstorlekar",
|
||||
"Visa minnesanvändning",
|
||||
"Show running processes",
|
||||
"Find a running process",
|
||||
"Force-kill a process",
|
||||
"Kontrollera tjänsten status",
|
||||
"Starta en tjänst",
|
||||
"Stoppa en tjänst",
|
||||
"Starta om en tjänst",
|
||||
"Uppdatera Debian/Ubuntu-paket",
|
||||
"Installera ett Debian/Ubuntu-paket",
|
||||
"Ta bort ett paket",
|
||||
"Lista behållare som körs",
|
||||
"Lista Docker-avbildningar",
|
||||
"Öppna ett skal i en behållare",
|
||||
"Visa IP-adresser",
|
||||
"Testa en nätverksanslutning",
|
||||
"Get HTTP headers",
|
||||
"Download a file",
|
||||
"Anslut via SSH",
|
||||
"Kopiera en fil via SSH",
|
||||
"Följ en loggfil i realtid",
|
||||
"Visa kommandohistorik",
|
||||
"Rensa terminalskärmen"
|
||||
]
|
||||
},
|
||||
"scriptTerminal": {
|
||||
"processing": "Bearbetar...",
|
||||
|
||||
@@ -818,6 +818,38 @@ Note that the dev server has no Flask backend on the same host by default: the a
|
||||
|
||||
### Testing
|
||||
|
||||
#### Offline i18n script tests
|
||||
|
||||
From the repository root, run the complete script suite:
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s .github/scripts/tests -v
|
||||
```
|
||||
|
||||
Prerequisites: Python 3.11+ (standard library only) and Node **22.14+**
|
||||
(Node 22 LTS is the CI baseline). Node 20 can still build the Monitor, but
|
||||
cannot run this suite's native TypeScript consumer test. The test passes
|
||||
`--experimental-strip-types` explicitly and executes the actual pure lookup
|
||||
functions extracted from `AppImage/lib/i18n/provider.tsx`, not a reimplementation.
|
||||
It does not launch React or require `npm install`, `pip install`, a Proxmox host,
|
||||
API keys, or a translation provider. Generator tests use isolated temporary
|
||||
catalogs and fake/forbidden providers; repository catalogs are not regenerated.
|
||||
|
||||
If Node is not on `PATH`, select a local executable without a global install:
|
||||
|
||||
```bash
|
||||
NODE_BINARY=/path/to/node-v22.14.0/bin/node \
|
||||
python3 -m unittest discover -s .github/scripts/tests -v
|
||||
```
|
||||
|
||||
Missing, unusable, or older Node runtimes fail with setup instructions rather
|
||||
than silently skipping the consumer assertions. `.github/workflows/test-i18n.yml`
|
||||
provisions Python and Node and runs this same command on relevant pull requests
|
||||
and main/develop pushes (or manually). It is separate from the translation
|
||||
publication workflow and has read-only repository permissions.
|
||||
|
||||
#### Monitor checks
|
||||
|
||||
- **Python tests** — under `AppImage/scripts/tests/`. Run with `python3 -m unittest discover -s AppImage/scripts/tests`. Add a test file when you add non-trivial backend logic (auth, notifications, background checks).
|
||||
- **UI smoke test** — deploy the built AppImage to a test host, restart `proxmenux-monitor.service`, and walk through the affected views in the browser. There is no formal e2e suite yet; a real-host smoke pass is expected for any UI change.
|
||||
- **JSON parse** — after editing any i18n catalog, verify it parses: `python3 -c "import json; json.load(open('AppImage/messages/<lang>/common.json'))"`.
|
||||
|
||||
Reference in New Issue
Block a user