Explorar o código

feat(i18n): complete all non-en locale translations

Alexander Korolev hai 2 meses
pai
achega
9cb0962722
Modificáronse 6 ficheiros con 616 adicións e 323 borrados
  1. 115 76
      locales/de/main.ftl
  2. 113 74
      locales/es/main.ftl
  3. 119 80
      locales/fr/main.ftl
  4. 114 75
      locales/it/main.ftl
  5. 57 18
      locales/ru/main.ftl
  6. 98 0
      scripts/verify-locales.sh

+ 115 - 76
locales/de/main.ftl

@@ -1,96 +1,135 @@
-cluster-not-found = Cluster "{ $name }" not found
-nccl-config-missing = NCCL config not found
-cluster-auto-fail = No suitable cluster found
-cluster-selected = ✅ Auto-selected cluster: { $name } ({ $nodes } { $nodes ->
-        [one] node
-       *[other] nodes
+cluster-not-found = Cluster "{ $name }" nicht gefunden
+nccl-config-missing = NCCL-Konfiguration nicht gefunden
+cluster-auto-fail = Kein geeigneter Cluster gefunden
+cluster-selected = ✅ Automatisch ausgewählter Cluster: { $name } ({ $nodes } { $nodes ->
+        [one] Knoten
+       *[other] Knoten
     })
-container-log-header = 📜 Latest container logs:
-cluster-stopping = 🛑 Stopping cluster '{ $name }'...
-cluster-all-stopped = ✅ All containers stopped.
+container-log-header = 📜 Neueste Container-Logs:
+cluster-stopping = 🛑 Cluster '{ $name }' wird gestoppt...
+cluster-all-stopped = ✅ Alle Container gestoppt.
 cluster-section = === Cluster: { $name } ===
-container-count = Total containers: { $count }
-no-sm121-containers = (no sm121 containers)
-docker-unavailable = ✗ Docker unavailable
-ssh-error = { $node } ({ $ip }): ✗ SSH error: { $error }
-task-join-error = ✗ Task join error: { $error }
-doctor-header = 🩺 Doctor: { $name } ({ $engine }) [backend: { $backend }]
+container-count = Container gesamt: { $count }
+no-sm121-containers = (keine sm121-Container)
+docker-unavailable = ✗ Docker nicht verfügbar
+ssh-error = { $node } ({ $ip }): ✗ SSH-Fehler: { $error }
+task-join-error = ✗ Fehler beim Aufgaben-Join: { $error }
+doctor-header = 🩺 Doctor: { $name } ({ $engine }) [Backend: { $backend }]
 doctor-node-section = --- { $name } ---
-doctor-all-passed = ✅ All checks passed!
-doctor-checks-failed = ⚠ Some checks failed. Run with --repair to auto-fix.
-doctor-repairing = 🔧 Repairing image...
-doctor-repaired = ✅ Repaired image: { $tag }
-doctor-dry-build = (dry-run) Would build { $tag } on { $node }
-logs-follow-head = 📜 Following logs for '{ $name }' (head node)...
+doctor-all-passed = ✅ Alle Prüfungen bestanden!
+doctor-checks-failed = ⚠ Einige Prüfungen fehlgeschlagen. Mit --repair zum Auto-Fix ausführen.
+doctor-repairing = 🔧 Image wird repariert...
+doctor-repaired = ✅ Image repariert: { $tag }
+doctor-dry-build = (dry-run) Würde { $tag } auf { $node } bauen
+logs-follow-head = 📜 Logs für '{ $name }' (Head-Knoten) werden verfolgt...
 logs-section = --- { $name } ---
-stop-failed = Stop failed on { $name }: { $error }
-remove-failed = Remove failed on { $name }: { $error }
-failed-to = Failed to { $action } on { $name }: { $error }
+stop-failed = Stop fehlgeschlagen auf { $name }: { $error }
+remove-failed = Remove fehlgeschlagen auf { $name }: { $error }
+failed-to = { $action } fehlgeschlagen auf { $name }: { $error }
 health-section = === Cluster: { $name } ===
-health-no-containers = (no containers)
-health-total = Total containers: { $count }
-health-docker-unavailable = ✗ Docker unavailable
+health-no-containers = (keine Container)
+health-total = Container gesamt: { $count }
+health-docker-unavailable = ✗ Docker nicht verfügbar
 docker-error-line = { $err }
-exec-header = ⚡ exec: { $cmd } ({ $nodes } node(s))
+exec-header = ⚡ exec: { $cmd } ({ $nodes } Knoten)
 exec-section = --- { $name } ---
 exec-failed = ✗ { $name } :: { $cmd } :: { $error }
-exec-completed-errors = exec completed with errors (some nodes reported failures)
-power-off-dryrun = dry-run: skipping actual shutdown + HA call
-power-off-would = would: { $action } { $target }
+exec-completed-errors = exec mit Fehlern abgeschlossen (einige Knoten meldeten Fehler)
+power-off-dryrun = dry-run: tatsächliches Herunterfahren + HA-Aufruf übersprungen
+power-off-would = würde: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }
-power-off-waiting = ⏳ waiting { $secs }s for nodes to settle
-power-off-no-ha = no [home_assistant] config; OS shutdown was performed above
-power-off-ssh-failures = some nodes reported SSH failures during shutdown
-power-on-dryrun = dry-run: skipping actual HA call
-power-on-would-on = would: HA turn_on { $entity }
-power-on-would-skip = would skip: { $name } (no entity)
-power-on-head = 💡 power on: { $nodes } node(s)
-ha-invalid-entity = { $name }: invalid entity '{ $entity }': { $error }
+power-off-waiting = ⏳ warte { $secs}s bis Knoten sich beruhigen
+power-off-no-ha = keine [home_assistant]-Konfig; OS-Herunterfahren oben ausgeführt
+power-off-ssh-failures = einige Knoten meldeten SSH-Fehler beim Herunterfahren
+power-on-dryrun = dry-run: tatsächlicher HA-Aufruf übersprungen
+power-on-would-on = würde: HA turn_on { $entity }
+power-on-would-skip = würde überspringen: { $name } (keine Entity)
+power-on-head = 💡 einschalten: { $nodes } Knoten
+ha-invalid-entity = { $name }: ungültige Entity '{ $entity }': { $error }
 ha-call-failed = HA { $service } { $entity }: { $error }
-ha-skip-no-entity = ⚠ { $name }: no `entity` configured, skipping HA
-ha-extra-invalid = { $label } ({ $extra }): invalid entity '{ $extra }'
+ha-skip-no-entity = ⚠ { $name }: keine `entity` konfiguriert, HA übersprungen
+ha-extra-invalid = { $label } ({ $extra }): ungültige Entity '{ $extra }'
 ha-extra-failed = HA { $service } { $extra } ({ $label }): { $error }
-power-switch-would-off = would: HA turn_off { $entity }
-power-switch-would-off-extra = would: HA turn_off { $extra } (from extras)
-power-switch-would-on = would: HA turn_on { $entity }
-power-switch-would-on-extra = would: HA turn_on { $extra } (to extras)
-power-switch-shutdown-failed = ✗ { $name } shutdown: { $error }
-power-switch-waiting = ⏳ waiting { $secs }s for shutdown
-ha-entity-error = { $name }: entity '{ $entity }': { $error }
-power-switch-no-ha = no [home_assistant] config; OS shutdown was performed
-power-off-entity-would = would: HA turn_off { $entity }
-pre-serve-skip = ⏭ Skipping pre-serve hooks (--skip-before-serve)
-pre-serve-failed = pre-serve: node { $name } failed: { $error }
+power-switch-would-off = würde: HA turn_off { $entity }
+power-switch-would-off-extra = würde: HA turn_off { $extra } (von Extras)
+power-switch-would-on = würde: HA turn_on { $entity }
+power-switch-would-on-extra = würde: HA turn_on { $extra } (zu Extras)
+power-switch-shutdown-failed = ✗ { $name } Herunterfahren: { $error }
+power-switch-waiting = ⏳ warte { $secs}s auf Herunterfahren
+ha-entity-error = { $name }: Entity '{ $entity }': { $error }
+power-switch-no-ha = keine [home_assistant]-Konfig; OS-Herunterfahren ausgeführt
+power-off-entity-would = würde: HA turn_off { $entity }
+pre-serve-skip = ⏭ Pre-Serve-Hooks übersprungen (--skip-before-serve)
+pre-serve-failed = pre-serve: Knoten { $name } fehlgeschlagen: { $error }
 pre-serve-run = { $node } ({ $tag }) :: { $cmd }
-pre-serve-ignored = ⚠ { $cmd } ignored: { $error }
-host-key-header = Discovered host keys:
+pre-serve-ignored = ⚠ { $cmd } ignoriert: { $error }
+host-key-header = Erkannte Host-Schlüssel:
 host-key-line = { $name } ({ $ip }) -> host_key = { $key }
-host-key-error = { $name } ({ $ip }) -> error: { $error }
-host-key-no-match = (no nodes matched)
-recipe-starting = 🚀 Starting recipe: { $name } ({ $engine }) [backend: { $backend }]
-logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
-engine-started = ✅ { $engine } started on { $name } (rank { $rank })
-engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
-cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+host-key-error = { $name } ({ $ip }) -> Fehler: { $error }
+host-key-no-match = (keine Knoten matched)
+recipe-starting = 🚀 Rezept wird gestartet: { $name } ({ $engine }) [Backend: { $backend }]
+cluster-switch-off-detected = ⚠️ Cluster-Schalter scheint aus zu sein
+cluster-switch-powered-on = ✅ Cluster-Schalter eingeschaltet
+cluster-waiting-for-nodes = ⏳ Warte, bis Knoten verfügbar sind...
+cluster-nodes-available = ✅ Alle Knoten sind jetzt verfügbar
+cluster-nodes-not-available-timeout = ❌ Timeout beim Warten auf Knoten-Verfügbarkeit
+ha-config-required-for-power-on = ❌ HA-Konfig für Einschalten erforderlich
+logs-header = 📜 Logs für Cluster '{ $name }' ({ $count } Knoten)
+engine-started = ✅ { $engine } auf { $name } gestartet (Rang { $rank })
+engine-start-failed = { $engine } auf { $name } (Rang { $rank }) konnte nicht gestartet werden: { $error }
+cluster-started = 🎉 Cluster gestartet! API → http://{ $ip }:{ $port }
 
-exec-both-specified = Specify either --cluster or --recipe, not both
-exec-missing = Must specify either --cluster or --recipe
-exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
-exec-cluster-empty = Cluster '{ $name }' has no nodes
-exec-cannot-use-alias = Cannot use --alias together with a trailing command
-exec-must-specify = Must specify either --alias <name> or a command after `--`
-exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
-exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }
+exec-both-specified = Entweder --cluster oder --recipe angeben, nicht beides
+exec-missing = Entweder --cluster oder --recipe angeben
+exec-node-not-found = Knoten '{ $name }' in Cluster '{ $cluster }' nicht gefunden
+exec-cluster-empty = Cluster '{ $name }' hat keine Knoten
+exec-cannot-use-alias = --alias nicht zusammen mit einem abschließenden Befehl verwenden
+exec-must-specify = Entweder --alias <Name> oder einen Befehl nach `--` angeben
+exec-unknown-alias-empty = Unbekannter Alias @{ $name } (keine Aliasse konfiguriert)
+exec-unknown-alias-list = Unbekannter Alias @{ $name }; bekannte Aliasse: { $aliases }
 mods-uploading = 📤 Lade Mods auf { $node } hoch...
 mods-uploaded = ✅ Mods auf { $node } hochgeladen
 mods-entrypoint-required = Rezept '{ $name }' hat Mods aber keinen Entrypoint; Entrypoint ist bei Mods erforderlich
-cluster-switch-off-detected = ⚠️ Cluster switch appears to be off
-cluster-switch-powered-on = ✅ Cluster switch powered on
-cluster-waiting-for-nodes = ⏳ Waiting for nodes to become available...
-cluster-nodes-available = ✅ All nodes are now available
-cluster-nodes-not-available-timeout = ❌ Timeout waiting for nodes to become available
-ha-config-required-for-power-on = ❌ HA config required for power-on
-power-off-container-stop-failed = ⚠️ Failed to stop containers on node { $name }: { $error}
+power-off-container-stop-failed = ⚠️ Container auf Knoten { $name } konnten nicht gestoppt werden: { $error}
+
+after-stop-failed = after_stop: Knoten { $name } fehlgeschlagen: { $error }
+after-stop-run = { $node } ({ $tag }) :: { $cmd }
+after-stop-ignored = ⚠ { $cmd } ignoriert: { $error }
+solo-node-selected = 🎯 Solo: Knoten { $name } in Cluster { $cluster } ausgewählt
+
+# --- Strings, die bisher das i18n-System umgingen (hardcodiert) ---
+boot-wait = ⏳ Warte { $secs}s auf Boot der Knoten...
+doctor-found-repaired = 🩺 Repariertes Image gefunden: { $tag }
+tmux-waiting-head = ⏳ Warte, bis Head-Knoten bereit ist...
+tmux-head-ready = ✅ Head-Knoten bereit. Worker-Knoten werden gestartet...
+tmux-head-api-ready = Head-Knoten-API bereit (Versuch { $attempt })
+tmux-session-started = ✅ Sitzung auf { $name } gestartet
+tmux-stopped = ✅ '{ $sess }' auf { $name } gestoppt
+tmux-all-stopped = ✅ Alle tmux-Sitzungen gestoppt.
+tmux-all-ok = ✅ ALLES OK
+tmux-issues = ⚠️  { $issues }
+tmux-cluster-healthy = ✅ Cluster ist gesund!
+tmux-node-section = --- { $name } ({ $sess }) ---
+tmux-node-no-output = --- { $name } — keine Ausgabe ---
+tmux-session-created = ✅ tmux-Sitzung auf { $name } erstellt
+tmux-no-pty = (russh unterstützt kein interaktives PTY. Befehl direkt ausführen:)
+sync-digest-match = '{ $name }' OK (Digest stimmt überein)
+sync-syncing = { $name } ({ $ip }): synchronisiere...
+sync-synced = { $name } OK
+health-node-offline =   ✗ { $name } ({ $ip }) — nicht erreichbar
+health-node-idle =   ○ { $name } ({ $ip }){ $net }
+health-node-online =   ● { $name } ({ $ip }){ $net }
+health-container-count = , { $count } Container
+power-off-head = 🛑 ausschalten: { $nodes } Knoten — sudo shutdown now
+power-switch-head = 🔸 umschalten: { $from } → { $to } ({ $nodes } Knoten + { $entities } Entities zu deaktivieren)
+power-switch-powering-on = 💡 umschalten: { $nodes } Knoten + { $entities } Entities einschalten nach { $to }
+health-task-join = Aufgaben-Join-Fehler: { $error }
+health-no-recipes = Keine sm121-Container laufen.
+health-running-recipes = Laufende Rezepte:
+health-recipe-line =   { $recipe } — { $nodes } Knoten: { $list }
+tmux-ray-backend = 🐍 Ray-Backend erkannt — Head-Knoten wird zuerst gestartet...
+tmux-mp-backend = 🔗 MP-Backend erkannt — alle Knoten werden parallel gestartet...
+tmux-following-logs = 📜 Logs von '{ $sess }' auf { $name } werden verfolgt...
 composed-dir-dry-run = [Testlauf] Zusammengestelltes Modellverzeichnis '{ $host }' auf '{ $node }' würde erstellt (Mount: { $mount }, { $sources } { $sources ->
         [one] Quelle
        *[other] Quellen

+ 113 - 74
locales/es/main.ftl

@@ -1,96 +1,135 @@
-cluster-not-found = Cluster "{ $name }" not found
-nccl-config-missing = NCCL config not found
-cluster-auto-fail = No suitable cluster found
-cluster-selected = ✅ Auto-selected cluster: { $name } ({ $nodes } { $nodes ->
-        [one] node
-       *[other] nodes
+cluster-not-found = Cluster "{ $name }" no encontrado
+nccl-config-missing = Configuración NCCL no encontrada
+cluster-auto-fail = No se encontró un cluster adecuado
+cluster-selected = ✅ Cluster seleccionado automáticamente: { $name } ({ $nodes } { $nodes ->
+        [one] nodo
+       *[other] nodos
     })
-container-log-header = 📜 Latest container logs:
-cluster-stopping = 🛑 Stopping cluster '{ $name }'...
-cluster-all-stopped = ✅ All containers stopped.
+container-log-header = 📜 Logs de contenedor más recientes:
+cluster-stopping = 🛑 Deteniendo cluster '{ $name }'...
+cluster-all-stopped = ✅ Todos los contenedores detenidos.
 cluster-section = === Cluster: { $name } ===
-container-count = Total containers: { $count }
-no-sm121-containers = (no sm121 containers)
-docker-unavailable = ✗ Docker unavailable
-ssh-error = { $node } ({ $ip }): ✗ SSH error: { $error }
-task-join-error = ✗ Task join error: { $error }
+container-count = Contenedores totales: { $count }
+no-sm121-containers = (sin contenedores sm121)
+docker-unavailable = ✗ Docker no disponible
+ssh-error = { $node } ({ $ip }): ✗ Error SSH: { $error }
+task-join-error = ✗ Error de unión de tarea: { $error }
 doctor-header = 🩺 Doctor: { $name } ({ $engine }) [backend: { $backend }]
 doctor-node-section = --- { $name } ---
-doctor-all-passed = ✅ All checks passed!
-doctor-checks-failed = ⚠ Some checks failed. Run with --repair to auto-fix.
-doctor-repairing = 🔧 Repairing image...
-doctor-repaired = ✅ Repaired image: { $tag }
-doctor-dry-build = (dry-run) Would build { $tag } on { $node }
-logs-follow-head = 📜 Following logs for '{ $name }' (head node)...
+doctor-all-passed = ✅ ¡Todas las comprobaciones superadas!
+doctor-checks-failed = ⚠ Algunas comprobaciones fallaron. Ejecute con --repair para auto-corregir.
+doctor-repairing = 🔧 Reparando imagen...
+doctor-repaired = ✅ Imagen reparada: { $tag }
+doctor-dry-build = (dry-run) Se construiría { $tag } en { $node }
+logs-follow-head = 📜 Siguiendo logs de '{ $name }' (nodo principal)...
 logs-section = --- { $name } ---
-stop-failed = Stop failed on { $name }: { $error }
-remove-failed = Remove failed on { $name }: { $error }
-failed-to = Failed to { $action } on { $name }: { $error }
+stop-failed = Detención falló en { $name }: { $error }
+remove-failed = Eliminación falló en { $name }: { $error }
+failed-to = Falló { $action } en { $name }: { $error }
 health-section = === Cluster: { $name } ===
-health-no-containers = (no containers)
-health-total = Total containers: { $count }
-health-docker-unavailable = ✗ Docker unavailable
+health-no-containers = (sin contenedores)
+health-total = Contenedores totales: { $count }
+health-docker-unavailable = ✗ Docker no disponible
 docker-error-line = { $err }
-exec-header = ⚡ exec: { $cmd } ({ $nodes } node(s))
+exec-header = ⚡ exec: { $cmd } ({ $nodes } nodo(s))
 exec-section = --- { $name } ---
 exec-failed = ✗ { $name } :: { $cmd } :: { $error }
-exec-completed-errors = exec completed with errors (some nodes reported failures)
-power-off-dryrun = dry-run: skipping actual shutdown + HA call
-power-off-would = would: { $action } { $target }
+exec-completed-errors = exec completó con errores (algunos nodos reportaron fallos)
+power-off-dryrun = dry-run: omitiendo apagado real + llamada HA
+power-off-would = sería: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }
-power-off-waiting = ⏳ waiting { $secs }s for nodes to settle
-power-off-no-ha = no [home_assistant] config; OS shutdown was performed above
-power-off-ssh-failures = some nodes reported SSH failures during shutdown
-power-on-dryrun = dry-run: skipping actual HA call
-power-on-would-on = would: HA turn_on { $entity }
-power-on-would-skip = would skip: { $name } (no entity)
-power-on-head = 💡 power on: { $nodes } node(s)
-ha-invalid-entity = { $name }: invalid entity '{ $entity }': { $error }
+power-off-waiting = ⏳ esperando { $secs}s para que los nodos se asienten
+power-off-no-ha = sin config [home_assistant]; el apagado de SO se realizó arriba
+power-off-ssh-failures = algunos nodos reportaron fallos SSH durante el apagado
+power-on-dryrun = dry-run: omitiendo llamada HA real
+power-on-would-on = sería: HA turn_on { $entity }
+power-on-would-skip = se omitiría: { $name } (sin entity)
+power-on-head = 💡 encender: { $nodes } nodo(s)
+ha-invalid-entity = { $name }: entity inválido '{ $entity }': { $error }
 ha-call-failed = HA { $service } { $entity }: { $error }
-ha-skip-no-entity = ⚠ { $name }: no `entity` configured, skipping HA
-ha-extra-invalid = { $label } ({ $extra }): invalid entity '{ $extra }'
+ha-skip-no-entity = ⚠ { $name }: sin `entity` configurado, omitiendo HA
+ha-extra-invalid = { $label } ({ $extra }): entity inválido '{ $extra }'
 ha-extra-failed = HA { $service } { $extra } ({ $label }): { $error }
-power-switch-would-off = would: HA turn_off { $entity }
-power-switch-would-off-extra = would: HA turn_off { $extra } (from extras)
-power-switch-would-on = would: HA turn_on { $entity }
-power-switch-would-on-extra = would: HA turn_on { $extra } (to extras)
-power-switch-shutdown-failed = ✗ { $name } shutdown: { $error }
-power-switch-waiting = ⏳ waiting { $secs }s for shutdown
-ha-entity-error = { $name }: entity '{ $entity }': { $error }
-power-switch-no-ha = no [home_assistant] config; OS shutdown was performed
-power-off-entity-would = would: HA turn_off { $entity }
-pre-serve-skip = ⏭ Skipping pre-serve hooks (--skip-before-serve)
-pre-serve-failed = pre-serve: node { $name } failed: { $error }
+power-switch-would-off = sería: HA turn_off { $entity }
+power-switch-would-off-extra = sería: HA turn_off { $extra } (de extras)
+power-switch-would-on = sería: HA turn_on { $entity }
+power-switch-would-on-extra = sería: HA turn_on { $extra } (a extras)
+power-switch-shutdown-failed = ✗ { $name } apagado: { $error }
+power-switch-waiting = ⏳ esperando { $secs}s para apagado
+ha-entity-error = { $name }: entidad '{ $entity }': { $error }
+power-switch-no-ha = sin config [home_assistant]; el apagado de SO se realizó
+power-off-entity-would = sería: HA turn_off { $entity }
+pre-serve-skip = ⏭ Omitiendo hooks pre-serve (--skip-before-serve)
+pre-serve-failed = pre-serve: nodo { $name } falló: { $error }
 pre-serve-run = { $node } ({ $tag }) :: { $cmd }
-pre-serve-ignored = ⚠ { $cmd } ignored: { $error }
-host-key-header = Discovered host keys:
+pre-serve-ignored = ⚠ { $cmd } ignorado: { $error }
+host-key-header = Claves de host descubiertas:
 host-key-line = { $name } ({ $ip }) -> host_key = { $key }
 host-key-error = { $name } ({ $ip }) -> error: { $error }
-host-key-no-match = (no nodes matched)
-recipe-starting = 🚀 Starting recipe: { $name } ({ $engine }) [backend: { $backend }]
-logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
-engine-started = ✅ { $engine } started on { $name } (rank { $rank })
-engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
-cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+host-key-no-match = (ningún nodo coincidió)
+recipe-starting = 🚀 Iniciando receta: { $name } ({ $engine }) [backend: { $backend }]
+cluster-switch-off-detected = ⚠️ El switch del cluster parece estar apagado
+cluster-switch-powered-on = ✅ Switch del cluster encendido
+cluster-waiting-for-nodes = ⏳ Esperando que los nodos estén disponibles...
+cluster-nodes-available = ✅ Todos los nodos ya están disponibles
+cluster-nodes-not-available-timeout = ❌ Tiempo de espera agotado para nodos disponibles
+ha-config-required-for-power-on = ❌ Config HA requerida para encender
+logs-header = 📜 Logs del cluster '{ $name }' ({ $count } nodos)
+engine-started = ✅ { $engine } iniciado en { $name } (rango { $rank })
+engine-start-failed = Falló iniciar { $engine } en { $name } (rango { $rank }): { $error }
+cluster-started = 🎉 ¡Cluster iniciado! API → http://{ $ip }:{ $port }
 
-exec-both-specified = Specify either --cluster or --recipe, not both
-exec-missing = Must specify either --cluster or --recipe
-exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
-exec-cluster-empty = Cluster '{ $name }' has no nodes
-exec-cannot-use-alias = Cannot use --alias together with a trailing command
-exec-must-specify = Must specify either --alias <name> or a command after `--`
-exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
-exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }
+exec-both-specified = Especifique --cluster o --recipe, no ambos
+exec-missing = Debe especificar --cluster o --recipe
+exec-node-not-found = Nodo '{ $name }' no encontrado en cluster '{ $cluster }'
+exec-cluster-empty = Cluster '{ $name }' no tiene nodos
+exec-cannot-use-alias = No se puede usar --alias junto con un comando final
+exec-must-specify = Debe especificar --alias <nombre> o un comando después de `--`
+exec-unknown-alias-empty = Alias desconocido @{ $name } (sin alias configurados)
+exec-unknown-alias-list = Alias desconocido @{ $name }; alias conocidos: { $aliases }
 mods-uploading = 📤 Subiendo mods a { $node }...
 mods-uploaded = ✅ Mods subidos a { $node }
 mods-entrypoint-required = La receta '{ $name }' tiene mods pero no tiene entrypoint; se requiere entrypoint cuando hay mods
-cluster-switch-off-detected = ⚠️ Cluster switch appears to be off
-cluster-switch-powered-on = ✅ Cluster switch powered on
-cluster-waiting-for-nodes = ⏳ Waiting for nodes to become available...
-cluster-nodes-available = ✅ All nodes are now available
-cluster-nodes-not-available-timeout = ❌ Timeout waiting for nodes to become available
-ha-config-required-for-power-on = ❌ HA config required for power-on
-power-off-container-stop-failed = ⚠️ Failed to stop containers on node { $name }: { $error}
+power-off-container-stop-failed = ⚠️ No se pudieron detener contenedores en el nodo { $name }: { $error}
+
+after-stop-failed = after_stop: nodo { $name } falló: { $error }
+after-stop-run = { $node } ({ $tag }) :: { $cmd }
+after-stop-ignored = ⚠ { $cmd } ignorado: { $error }
+solo-node-selected = 🎯 Solo: nodo { $name } seleccionado en cluster { $cluster }
+
+# --- Cadenas que antes eludían el sistema i18n (hardcodeadas) ---
+boot-wait = ⏳ Esperando { $secs}s para el arranque de nodos...
+doctor-found-repaired = 🩺 Imagen reparada encontrada: { $tag }
+tmux-waiting-head = ⏳ Esperando que el nodo principal esté listo...
+tmux-head-ready = ✅ Nodo principal listo. Iniciando nodos worker...
+tmux-head-api-ready = API del nodo principal lista (intento { $attempt })
+tmux-session-started = ✅ Sesión iniciada en { $name }
+tmux-stopped = ✅ '{ $sess }' detenida en { $name }
+tmux-all-stopped = ✅ Todas las sesiones tmux detenidas.
+tmux-all-ok = ✅ TODO OK
+tmux-issues = ⚠️  { $issues }
+tmux-cluster-healthy = ✅ ¡El cluster está sano!
+tmux-node-section = --- { $name } ({ $sess }) ---
+tmux-node-no-output = --- { $name } — sin salida ---
+tmux-session-created = ✅ Sesión tmux creada en { $name }
+tmux-no-pty = (russh no soporta PTY interactivo. Use el siguiente comando directamente:)
+sync-digest-match = '{ $name }' OK (digest coincide)
+sync-syncing = { $name } ({ $ip }): sincronizando...
+sync-synced = { $name } OK
+health-node-offline =   ✗ { $name } ({ $ip }) — desconectado
+health-node-idle =   ○ { $name } ({ $ip }){ $net }
+health-node-online =   ● { $name } ({ $ip }){ $net }
+health-container-count = , { $count } contenedor(es)
+power-off-head = 🛑 apagar: { $nodes } nodo(s) — sudo shutdown now
+power-switch-head = 🔌 conmutar: { $from } → { $to } ({ $nodes } nodo(s) + { $entities } entidade(s) a desactivar)
+power-switch-powering-on = 💡 conmutar: encendiendo { $nodes } nodo(s) + { $entities } entidade(s) a { $to }
+health-task-join = error de unión de tarea: { $error }
+health-no-recipes = No hay contenedores sm121 ejecutándose.
+health-running-recipes = Recetas en ejecución:
+health-recipe-line =   { $recipe } — { $nodes } nodo(s): { $list }
+tmux-ray-backend = 🐍 Backend Ray detectado — iniciando nodo principal primero...
+tmux-mp-backend = 🔗 Backend MP detectado — iniciando todos los nodos en paralelo...
+tmux-following-logs = 📜 Siguiendo logs de '{ $sess }' en { $name }...
 composed-dir-dry-run = [simulación] se crearía el directorio de modelo compuesto '{ $host }' en '{ $node }' (montaje: { $mount }, { $sources } { $sources ->
         [one] fuente
        *[other] fuentes

+ 119 - 80
locales/fr/main.ftl

@@ -1,96 +1,135 @@
-cluster-not-found = Cluster "{ $name }" not found
-nccl-config-missing = NCCL config not found
-cluster-auto-fail = No suitable cluster found
-cluster-selected = ✅ Auto-selected cluster: { $name } ({ $nodes } { $nodes ->
-        [one] node
-       *[other] nodes
+cluster-not-found = Cluster « { $name } » introuvable
+nccl-config-missing = Configuration NCCL introuvable
+cluster-auto-fail = Aucun cluster approprié trouvé
+cluster-selected = ✅ Cluster sélectionné automatiquement : { $name } ({ $nodes } { $nodes ->
+        [one] nœud
+       *[other] nœuds
     })
-container-log-header = 📜 Latest container logs:
-cluster-stopping = 🛑 Stopping cluster '{ $name }'...
-cluster-all-stopped = ✅ All containers stopped.
-cluster-section = === Cluster: { $name } ===
-container-count = Total containers: { $count }
-no-sm121-containers = (no sm121 containers)
-docker-unavailable = ✗ Docker unavailable
-ssh-error = { $node } ({ $ip }): ✗ SSH error: { $error }
-task-join-error = ✗ Task join error: { $error }
-doctor-header = 🩺 Doctor: { $name } ({ $engine }) [backend: { $backend }]
+container-log-header = 📜 Logs de conteneur les plus récents :
+cluster-stopping = 🛑 Arrêt du cluster '{ $name }'...
+cluster-all-stopped = ✅ Tous les conteneurs arrêtés.
+cluster-section = === Cluster : { $name } ===
+container-count = Conteneurs totaux : { $count }
+no-sm121-containers = (aucun conteneur sm121)
+docker-unavailable = ✗ Docker indisponible
+ssh-error = { $node } ({ $ip }) : ✗ Erreur SSH : { $error }
+task-join-error = ✗ Erreur de jointure de tâche : { $error }
+doctor-header = 🩺 Doctor : { $name } ({ $engine }) [backend : { $backend }]
 doctor-node-section = --- { $name } ---
-doctor-all-passed = ✅ All checks passed!
-doctor-checks-failed = ⚠ Some checks failed. Run with --repair to auto-fix.
-doctor-repairing = 🔧 Repairing image...
-doctor-repaired = ✅ Repaired image: { $tag }
-doctor-dry-build = (dry-run) Would build { $tag } on { $node }
-logs-follow-head = 📜 Following logs for '{ $name }' (head node)...
+doctor-all-passed = ✅ Toutes les vérifications réussies !
+doctor-checks-failed = ⚠ Certaines vérifications ont échoué. Lancez avec --repair pour la correction automatique.
+doctor-repairing = 🔧 Réparation de l'image...
+doctor-repaired = ✅ Image réparée : { $tag }
+doctor-dry-build = (dry-run) Construirait { $tag } sur { $node }
+logs-follow-head = 📜 Suivi des logs de '{ $name }' (nœud head)...
 logs-section = --- { $name } ---
-stop-failed = Stop failed on { $name }: { $error }
-remove-failed = Remove failed on { $name }: { $error }
-failed-to = Failed to { $action } on { $name }: { $error }
-health-section = === Cluster: { $name } ===
-health-no-containers = (no containers)
-health-total = Total containers: { $count }
-health-docker-unavailable = ✗ Docker unavailable
+stop-failed = Échec de l'arrêt sur { $name } : { $error }
+remove-failed = Échec de la suppression sur { $name } : { $error }
+failed-to = Échec de { $action } sur { $name } : { $error }
+health-section = === Cluster : { $name } ===
+health-no-containers = (aucun conteneur)
+health-total = Conteneurs totaux : { $count }
+health-docker-unavailable = ✗ Docker indisponible
 docker-error-line = { $err }
-exec-header = ⚡ exec: { $cmd } ({ $nodes } node(s))
+exec-header = ⚡ exec : { $cmd } ({ $nodes } nœud(s))
 exec-section = --- { $name } ---
 exec-failed = ✗ { $name } :: { $cmd } :: { $error }
-exec-completed-errors = exec completed with errors (some nodes reported failures)
-power-off-dryrun = dry-run: skipping actual shutdown + HA call
-power-off-would = would: { $action } { $target }
+exec-completed-errors = exec terminé avec des erreurs (certains nœuds ont signalé des échecs)
+power-off-dryrun = dry-run : extinction réelle + appel HA ignorés
+power-off-would = serait : { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }
-power-off-waiting = ⏳ waiting { $secs }s for nodes to settle
-power-off-no-ha = no [home_assistant] config; OS shutdown was performed above
-power-off-ssh-failures = some nodes reported SSH failures during shutdown
-power-on-dryrun = dry-run: skipping actual HA call
-power-on-would-on = would: HA turn_on { $entity }
-power-on-would-skip = would skip: { $name } (no entity)
-power-on-head = 💡 power on: { $nodes } node(s)
-ha-invalid-entity = { $name }: invalid entity '{ $entity }': { $error }
-ha-call-failed = HA { $service } { $entity }: { $error }
-ha-skip-no-entity = ⚠ { $name }: no `entity` configured, skipping HA
-ha-extra-invalid = { $label } ({ $extra }): invalid entity '{ $extra }'
-ha-extra-failed = HA { $service } { $extra } ({ $label }): { $error }
-power-switch-would-off = would: HA turn_off { $entity }
-power-switch-would-off-extra = would: HA turn_off { $extra } (from extras)
-power-switch-would-on = would: HA turn_on { $entity }
-power-switch-would-on-extra = would: HA turn_on { $extra } (to extras)
-power-switch-shutdown-failed = ✗ { $name } shutdown: { $error }
-power-switch-waiting = ⏳ waiting { $secs }s for shutdown
-ha-entity-error = { $name }: entity '{ $entity }': { $error }
-power-switch-no-ha = no [home_assistant] config; OS shutdown was performed
-power-off-entity-would = would: HA turn_off { $entity }
-pre-serve-skip = ⏭ Skipping pre-serve hooks (--skip-before-serve)
-pre-serve-failed = pre-serve: node { $name } failed: { $error }
+power-off-waiting = ⏳ attente { $secs}s pour stabilisation des nœuds
+power-off-no-ha = pas de config [home_assistant] ; l'extinction OS a été effectuée ci-dessus
+power-off-ssh-failures = certains nœuds ont signalé des erreurs SSH pendant l'extinction
+power-on-dryrun = dry-run : appel HA réel ignoré
+power-on-would-on = serait : HA turn_on { $entity }
+power-on-would-skip = serait ignoré : { $name } (pas d'entity)
+power-on-head = 💡 allumage : { $nodes } nœud(s)
+ha-invalid-entity = { $name } : entity invalide '{ $entity }' : { $error }
+ha-call-failed = HA { $service } { $entity } : { $error }
+ha-skip-no-entity = ⚠ { $name } : pas d'`entity` configurée, HA ignoré
+ha-extra-invalid = { $label } ({ $extra }) : entity invalide '{ $extra }'
+ha-extra-failed = HA { $service } { $extra } ({ $label }) : { $error }
+power-switch-would-off = serait : HA turn_off { $entity }
+power-switch-would-off-extra = serait : HA turn_off { $extra } (des extras)
+power-switch-would-on = serait : HA turn_on { $entity }
+power-switch-would-on-extra = serait : HA turn_on { $extra } (vers extras)
+power-switch-shutdown-failed = ✗ { $name } extinction : { $error }
+power-switch-waiting = ⏳ attente { $secs}s pour l'extinction
+ha-entity-error = { $name } : entité '{ $entity }' : { $error }
+power-switch-no-ha = pas de config [home_assistant] ; l'extinction OS a été effectuée
+power-off-entity-would = serait : HA turn_off { $entity }
+pre-serve-skip = ⏭ Hooks pre-serve ignorés (--skip-before-serve)
+pre-serve-failed = pre-serve : nœud { $name } échec : { $error }
 pre-serve-run = { $node } ({ $tag }) :: { $cmd }
-pre-serve-ignored = ⚠ { $cmd } ignored: { $error }
-host-key-header = Discovered host keys:
+pre-serve-ignored = ⚠ { $cmd } ignoré : { $error }
+host-key-header = Clés hôtes découvertes :
 host-key-line = { $name } ({ $ip }) -> host_key = { $key }
-host-key-error = { $name } ({ $ip }) -> error: { $error }
-host-key-no-match = (no nodes matched)
-recipe-starting = 🚀 Starting recipe: { $name } ({ $engine }) [backend: { $backend }]
-logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
-engine-started = ✅ { $engine } started on { $name } (rank { $rank })
-engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
-cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+host-key-error = { $name } ({ $ip }) -> erreur : { $error }
+host-key-no-match = (aucun nœud correspondant)
+recipe-starting = 🚀 Démarrage de la recette : { $name } ({ $engine }) [backend : { $backend }]
+cluster-switch-off-detected = ⚠️ L'interrupteur du cluster semble éteint
+cluster-switch-powered-on = ✅ Interrupteur du cluster allumé
+cluster-waiting-for-nodes = ⏳ En attente de la disponibilité des nœuds...
+cluster-nodes-available = ✅ Tous les nœuds sont maintenant disponibles
+cluster-nodes-not-available-timeout = ❌ Délai dépassé en attente des nœuds
+ha-config-required-for-power-on = ❌ Config HA requise pour l'allumage
+logs-header = 📜 Logs du cluster '{ $name }' ({ $count } nœuds)
+engine-started = ✅ { $engine } démarré sur { $name } (rang { $rank })
+engine-start-failed = Échec du démarrage de { $engine } sur { $name } (rang { $rank }) : { $error }
+cluster-started = 🎉 Cluster démarré ! API → http://{ $ip }:{ $port }
 
-exec-both-specified = Specify either --cluster or --recipe, not both
-exec-missing = Must specify either --cluster or --recipe
-exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
-exec-cluster-empty = Cluster '{ $name }' has no nodes
-exec-cannot-use-alias = Cannot use --alias together with a trailing command
-exec-must-specify = Must specify either --alias <name> or a command after `--`
-exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
-exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }
+exec-both-specified = Spécifier --cluster ou --recipe, pas les deux
+exec-missing = Spécifier --cluster ou --recipe
+exec-node-not-found = Nœud '{ $name }' introuvable dans le cluster '{ $cluster }'
+exec-cluster-empty = Le cluster '{ $name }' n'a pas de nœuds
+exec-cannot-use-alias = Impossible d'utiliser --alias avec une commande finale
+exec-must-specify = Spécifier --alias <nom> ou une commande après `--`
+exec-unknown-alias-empty = Alias inconnu @{ $name } (aucun alias configuré)
+exec-unknown-alias-list = Alias inconnu @{ $name } ; alias connus : { $aliases }
 mods-uploading = 📤 Téléchargement des mods vers { $node }...
 mods-uploaded = ✅ Mods téléchargés vers { $node }
 mods-entrypoint-required = La recette '{ $name }' a des mods mais pas d'entrypoint ; l'entrypoint est requis quand des mods sont présents
-cluster-switch-off-detected = ⚠️ Cluster switch appears to be off
-cluster-switch-powered-on = ✅ Cluster switch powered on
-cluster-waiting-for-nodes = ⏳ Waiting for nodes to become available...
-cluster-nodes-available = ✅ All nodes are now available
-cluster-nodes-not-available-timeout = ❌ Timeout waiting for nodes to become available
-ha-config-required-for-power-on = ❌ HA config required for power-on
-power-off-container-stop-failed = ⚠️ Failed to stop containers on node { $name }: { $error}
+power-off-container-stop-failed = ⚠️ Échec de l'arrêt des conteneurs sur le nœud { $name } : { $error}
+
+after-stop-failed = after_stop : nœud { $name } échec : { $error }
+after-stop-run = { $node } ({ $tag }) :: { $cmd }
+after-stop-ignored = ⚠ { $cmd } ignoré : { $error }
+solo-node-selected = 🎯 Solo : nœud { $name } sélectionné dans le cluster { $cluster }
+
+# --- Chaînes qui contornaient auparavant le système i18n (hardcodées) ---
+boot-wait = ⏳ Attente { $secs}s du démarrage des nœuds...
+doctor-found-repaired = 🩺 Image réparée trouvée : { $tag }
+tmux-waiting-head = ⏳ Attente de la disponibilité du nœud head...
+tmux-head-ready = ✅ Nœud head prêt. Démarrage des nœuds worker...
+tmux-head-api-ready = API du nœud head prête (tentative { $attempt })
+tmux-session-started = ✅ Session démarrée sur { $name }
+tmux-stopped = ✅ '{ $sess }' arrêtée sur { $name }
+tmux-all-stopped = ✅ Toutes les sessions tmux arrêtées.
+tmux-all-ok = ✅ TOUT EST OK
+tmux-issues = ⚠️  { $issues }
+tmux-cluster-healthy = ✅ Le cluster est sain !
+tmux-node-section = --- { $name } ({ $sess }) ---
+tmux-node-no-output = --- { $name } — aucune sortie ---
+tmux-session-created = ✅ Session tmux créée sur { $name }
+tmux-no-pty = (russh ne supporte pas le PTY interactif. Utilisez la commande suivante directement :)
+sync-digest-match = '{ $name }' OK (digest correspond)
+sync-syncing = { $name } ({ $ip }) : synchronisation...
+sync-synced = { $name } OK
+health-node-offline =   ✗ { $name } ({ $ip }) — hors ligne
+health-node-idle =   ○ { $name } ({ $ip }){ $net }
+health-node-online =   ● { $name } ({ $ip }){ $net }
+health-container-count = , { $count } conteneur(s)
+power-off-head = 🛑 extinction : { $nodes } nœud(s) — sudo shutdown now
+power-switch-head = 🔌 commutation : { $from } → { $to } ({ $nodes } nœud(s) + { $entities } entité(s) à désactiver)
+power-switch-powering-on = 💡 commutation : allumage de { $nodes } nœud(s) + { $entities } entité(s) vers { $to }
+health-task-join = erreur de jointure de tâche : { $error }
+health-no-recipes = Aucun conteneur sm121 en cours d'exécution.
+health-running-recipes = Recettes en cours d'exécution :
+health-recipe-line =   { $recipe } — { $nodes } nœud(s) : { $list }
+tmux-ray-backend = 🐍 Backend Ray détecté — démarrage du nœud head en premier...
+tmux-mp-backend = 🔗 Backend MP détecté — démarrage de tous les nœuds en parallèle...
+tmux-following-logs = 📜 Suivi des logs de '{ $sess }' sur { $name }...
 composed-dir-dry-run = [simulation] création du répertoire de modèle composé '{ $host }' sur '{ $node }' (montage : { $mount }, { $sources } { $sources ->
         [one] source
        *[other] sources

+ 114 - 75
locales/it/main.ftl

@@ -1,96 +1,135 @@
-cluster-not-found = Cluster "{ $name }" not found
-nccl-config-missing = NCCL config not found
-cluster-auto-fail = No suitable cluster found
-cluster-selected = ✅ Auto-selected cluster: { $name } ({ $nodes } { $nodes ->
-        [one] node
-       *[other] nodes
+cluster-not-found = Cluster "{ $name }" non trovato
+nccl-config-missing = Config NCCL non trovata
+cluster-auto-fail = Nessun cluster adatto trovato
+cluster-selected = ✅ Cluster selezionato automaticamente: { $name } ({ $nodes } { $nodes ->
+        [one] nodo
+       *[other] nodi
     })
-container-log-header = 📜 Latest container logs:
-cluster-stopping = 🛑 Stopping cluster '{ $name }'...
-cluster-all-stopped = ✅ All containers stopped.
+container-log-header = 📜 Log dei container più recenti:
+cluster-stopping = 🛑 Arresto del cluster '{ $name }'...
+cluster-all-stopped = ✅ Tutti i container arrestati.
 cluster-section = === Cluster: { $name } ===
-container-count = Total containers: { $count }
-no-sm121-containers = (no sm121 containers)
-docker-unavailable = ✗ Docker unavailable
-ssh-error = { $node } ({ $ip }): ✗ SSH error: { $error }
-task-join-error = ✗ Task join error: { $error }
+container-count = Container totali: { $count }
+no-sm121-containers = (nessun container sm121)
+docker-unavailable = ✗ Docker non disponibile
+ssh-error = { $node } ({ $ip }): ✗ Errore SSH: { $error }
+task-join-error = ✗ Errore di join dell'attività: { $error }
 doctor-header = 🩺 Doctor: { $name } ({ $engine }) [backend: { $backend }]
 doctor-node-section = --- { $name } ---
-doctor-all-passed = ✅ All checks passed!
-doctor-checks-failed = ⚠ Some checks failed. Run with --repair to auto-fix.
-doctor-repairing = 🔧 Repairing image...
-doctor-repaired = ✅ Repaired image: { $tag }
-doctor-dry-build = (dry-run) Would build { $tag } on { $node }
-logs-follow-head = 📜 Following logs for '{ $name }' (head node)...
+doctor-all-passed = ✅ Tutti i controlli superati!
+doctor-checks-failed = ⚠ Alcuni controlli falliti. Eseguire con --repair per l'auto-fix.
+doctor-repairing = 🔧 Riparazione dell'immagine...
+doctor-repaired = ✅ Immagine riparata: { $tag }
+doctor-dry-build = (dry-run) Costruirebbe { $tag } su { $node }
+logs-follow-head = 📜 Seguendo i log di '{ $name }' (nodo head)...
 logs-section = --- { $name } ---
-stop-failed = Stop failed on { $name }: { $error }
-remove-failed = Remove failed on { $name }: { $error }
-failed-to = Failed to { $action } on { $name }: { $error }
+stop-failed = Arresto fallito su { $name }: { $error }
+remove-failed = Rimozione fallita su { $name }: { $error }
+failed-to = { $action } fallita su { $name }: { $error }
 health-section = === Cluster: { $name } ===
-health-no-containers = (no containers)
-health-total = Total containers: { $count }
-health-docker-unavailable = ✗ Docker unavailable
+health-no-containers = (nessun container)
+health-total = Container totali: { $count }
+health-docker-unavailable = ✗ Docker non disponibile
 docker-error-line = { $err }
-exec-header = ⚡ exec: { $cmd } ({ $nodes } node(s))
+exec-header = ⚡ exec: { $cmd } ({ $nodes } nodo/i)
 exec-section = --- { $name } ---
 exec-failed = ✗ { $name } :: { $cmd } :: { $error }
-exec-completed-errors = exec completed with errors (some nodes reported failures)
-power-off-dryrun = dry-run: skipping actual shutdown + HA call
-power-off-would = would: { $action } { $target }
+exec-completed-errors = exec completato con errori (alcuni nodi hanno riportato fallimenti)
+power-off-dryrun = dry-run: saltando spegnimento reale + chiamata HA
+power-off-would = sarebbe: { $action } { $target }
 power-off-shutdown-failed = ✗ { $name } :: { $cmd } :: { $error }
-power-off-waiting = ⏳ waiting { $secs }s for nodes to settle
-power-off-no-ha = no [home_assistant] config; OS shutdown was performed above
-power-off-ssh-failures = some nodes reported SSH failures during shutdown
-power-on-dryrun = dry-run: skipping actual HA call
-power-on-would-on = would: HA turn_on { $entity }
-power-on-would-skip = would skip: { $name } (no entity)
-power-on-head = 💡 power on: { $nodes } node(s)
-ha-invalid-entity = { $name }: invalid entity '{ $entity }': { $error }
+power-off-waiting = ⏳ attesa { $secs}s per assestamento dei nodi
+power-off-no-ha = nessuna config [home_assistant]; spegnimento OS eseguito sopra
+power-off-ssh-failures = alcuni nodi hanno riportato errori SSH durante lo spegnimento
+power-on-dryrun = dry-run: saltando chiamata HA reale
+power-on-would-on = sarebbe: HA turn_on { $entity }
+power-on-would-skip = saltato: { $name } (nessuna entity)
+power-on-head = 💡 accensione: { $nodes } nodo/i
+ha-invalid-entity = { $name }: entity non valida '{ $entity }': { $error }
 ha-call-failed = HA { $service } { $entity }: { $error }
-ha-skip-no-entity = ⚠ { $name }: no `entity` configured, skipping HA
-ha-extra-invalid = { $label } ({ $extra }): invalid entity '{ $extra }'
+ha-skip-no-entity = ⚠ { $name }: nessuna `entity` configurata, salto HA
+ha-extra-invalid = { $label } ({ $extra }): entity non valida '{ $extra }'
 ha-extra-failed = HA { $service } { $extra } ({ $label }): { $error }
-power-switch-would-off = would: HA turn_off { $entity }
-power-switch-would-off-extra = would: HA turn_off { $extra } (from extras)
-power-switch-would-on = would: HA turn_on { $entity }
-power-switch-would-on-extra = would: HA turn_on { $extra } (to extras)
-power-switch-shutdown-failed = ✗ { $name } shutdown: { $error }
-power-switch-waiting = ⏳ waiting { $secs }s for shutdown
-ha-entity-error = { $name }: entity '{ $entity }': { $error }
-power-switch-no-ha = no [home_assistant] config; OS shutdown was performed
-power-off-entity-would = would: HA turn_off { $entity }
-pre-serve-skip = ⏭ Skipping pre-serve hooks (--skip-before-serve)
-pre-serve-failed = pre-serve: node { $name } failed: { $error }
+power-switch-would-off = sarebbe: HA turn_off { $entity }
+power-switch-would-off-extra = sarebbe: HA turn_off { $extra } (da extras)
+power-switch-would-on = sarebbe: HA turn_on { $entity }
+power-switch-would-on-extra = sarebbe: HA turn_on { $extra } (verso extras)
+power-switch-shutdown-failed = ✗ { $name } spegnimento: { $error }
+power-switch-waiting = ⏳ attesa { $secs}s per spegnimento
+ha-entity-error = { $name }: entità '{ $entity }': { $error }
+power-switch-no-ha = nessuna config [home_assistant]; spegnimento OS eseguito
+power-off-entity-would = sarebbe: HA turn_off { $entity }
+pre-serve-skip = ⏭ Saltando hook pre-serve (--skip-before-serve)
+pre-serve-failed = pre-serve: nodo { $name } fallito: { $error }
 pre-serve-run = { $node } ({ $tag }) :: { $cmd }
-pre-serve-ignored = ⚠ { $cmd } ignored: { $error }
-host-key-header = Discovered host keys:
+pre-serve-ignored = ⚠ { $cmd } ignorato: { $error }
+host-key-header = Chiavi host scoperte:
 host-key-line = { $name } ({ $ip }) -> host_key = { $key }
-host-key-error = { $name } ({ $ip }) -> error: { $error }
-host-key-no-match = (no nodes matched)
-recipe-starting = 🚀 Starting recipe: { $name } ({ $engine }) [backend: { $backend }]
-logs-header = 📜 Logs for cluster '{ $name }' ({ $count } nodes)
-engine-started = ✅ { $engine } started on { $name } (rank { $rank })
-engine-start-failed = Failed to start { $engine } on { $name } (rank { $rank }): { $error }
-cluster-started = 🎉 Cluster started! API → http://{ $ip }:{ $port }
+host-key-error = { $name } ({ $ip }) -> errore: { $error }
+host-key-no-match = (nessun nodo corrisponde)
+recipe-starting = 🚀 Avvio ricetta: { $name } ({ $engine }) [backend: { $backend }]
+cluster-switch-off-detected = ⚠️ Lo switch del cluster sembra spento
+cluster-switch-powered-on = ✅ Switch del cluster acceso
+cluster-waiting-for-nodes = ⏳ In attesa che i nodi siano disponibili...
+cluster-nodes-available = ✅ Tutti i nodi ora sono disponibili
+cluster-nodes-not-available-timeout = ❌ Timeout nell'attesa della disponibilità dei nodi
+ha-config-required-for-power-on = ❌ Config HA richiesta per l'accensione
+logs-header = 📜 Log del cluster '{ $name }' ({ $count } nodi)
+engine-started = ✅ { $engine } avviato su { $name } (rango { $rank })
+engine-start-failed = Avvio di { $engine } su { $name } (rango { $rank }) fallito: { $error }
+cluster-started = 🎉 Cluster avviato! API → http://{ $ip }:{ $port }
 
-exec-both-specified = Specify either --cluster or --recipe, not both
-exec-missing = Must specify either --cluster or --recipe
-exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
-exec-cluster-empty = Cluster '{ $name }' has no nodes
-exec-cannot-use-alias = Cannot use --alias together with a trailing command
-exec-must-specify = Must specify either --alias <name> or a command after `--`
-exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
-exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }
+exec-both-specified = Specificare --cluster o --recipe, non entrambi
+exec-missing = Specificare --cluster o --recipe
+exec-node-not-found = Nodo '{ $name }' non trovato nel cluster '{ $cluster }'
+exec-cluster-empty = Il cluster '{ $name }' non ha nodi
+exec-cannot-use-alias = Impossibile usare --alias con un comando finale
+exec-must-specify = Specificare --alias <nome> o un comando dopo `--`
+exec-unknown-alias-empty = Alias sconosciuto @{ $name } (nessun alias configurato)
+exec-unknown-alias-list = Alias sconosciuto @{ $name }; alias noti: { $aliases }
 mods-uploading = 📤 Caricamento mod su { $node }...
 mods-uploaded = ✅ Mod caricati su { $node }
 mods-entrypoint-required = La ricetta '{ $name }' ha mod ma nessun entrypoint; l'entrypoint è obbligatorio quando sono presenti mod
-cluster-switch-off-detected = ⚠️ Cluster switch appears to be off
-cluster-switch-powered-on = ✅ Cluster switch powered on
-cluster-waiting-for-nodes = ⏳ Waiting for nodes to become available...
-cluster-nodes-available = ✅ All nodes are now available
-cluster-nodes-not-available-timeout = ❌ Timeout waiting for nodes to become available
-ha-config-required-for-power-on = ❌ HA config required for power-on
-power-off-container-stop-failed = ⚠️ Failed to stop containers on node { $name }: { $error}
+power-off-container-stop-failed = ⚠️ Impossibile arrestare i container sul nodo { $name }: { $error}
+
+after-stop-failed = after_stop: nodo { $name } fallito: { $error }
+after-stop-run = { $node } ({ $tag }) :: { $cmd }
+after-stop-ignored = ⚠ { $cmd } ignorato: { $error }
+solo-node-selected = 🎯 Solo: nodo { $name } selezionato nel cluster { $cluster }
+
+# --- Stringhe che prima eludevano il sistema i18n (hardcodate) ---
+boot-wait = ⏳ Attesa { $secs}s per l'avvio dei nodi...
+doctor-found-repaired = 🩺 Trovata immagine riparata: { $tag }
+tmux-waiting-head = ⏳ In attesa che il nodo head sia pronto...
+tmux-head-ready = ✅ Nodo head pronto. Avvio dei nodi worker...
+tmux-head-api-ready = API del nodo head pronta (tentativo { $attempt })
+tmux-session-started = ✅ Sessione avviata su { $name }
+tmux-stopped = ✅ '{ $sess }' arrestata su { $name }
+tmux-all-stopped = ✅ Tutte le sessioni tmux arrestate.
+tmux-all-ok = ✅ TUTTO OK
+tmux-issues = ⚠️  { $issues }
+tmux-cluster-healthy = ✅ Il cluster è sano!
+tmux-node-section = --- { $name } ({ $sess }) ---
+tmux-node-no-output = --- { $name } — nessun output ---
+tmux-session-created = ✅ Sessione tmux creata su { $name }
+tmux-no-pty = (russh non supporta PTY interattivo. Usa il seguente comando direttamente:)
+sync-digest-match = '{ $name }' OK (digest corrisponde)
+sync-syncing = { $name } ({ $ip }): sincronizzazione...
+sync-synced = { $name } OK
+health-node-offline =   ✗ { $name } ({ $ip }) — offline
+health-node-idle =   ○ { $name } ({ $ip }){ $net }
+health-node-online =   ● { $name } ({ $ip }){ $net }
+health-container-count = , { $count } container
+power-off-head = 🛑 spegnimento: { $nodes } nodo/i — sudo shutdown now
+power-switch-head = 🔌 commuta: { $from } → { $to } ({ $nodes } nodo/i + { $entities } entità da disattivare)
+power-switch-powering-on = 💡 commuta: accensione { $nodes } nodo/i + { $entities } entità verso { $to }
+health-task-join = errore di join dell'attività: { $error }
+health-no-recipes = Nessun container sm121 in esecuzione.
+health-running-recipes = Ricette in esecuzione:
+health-recipe-line =   { $recipe } — { $nodes } nodo/i: { $list }
+tmux-ray-backend = 🐍 Backend Ray rilevato — avvio del nodo head per primo...
+tmux-mp-backend = 🔗 Backend MP rilevato — avvio di tutti i nodi in parallelo...
+tmux-following-logs = 📜 Seguendo i log da '{ $sess }' su { $name }...
 composed-dir-dry-run = [prova] verrebbe creata la directory del modello composto '{ $host }' su '{ $node }' (montaggio: { $mount }, { $sources } { $sources ->
         [one] sorgente
        *[other] sorgenti

+ 57 - 18
locales/ru/main.ftl

@@ -14,7 +14,7 @@ container-count = Всего контейнеров: { $count }
 no-sm121-containers = (нет контейнеров sm121)
 docker-unavailable = ✗ Docker недоступен
 ssh-error = { $node } ({ $ip }): ✗ SSH ошибка: { $error }
-task-join-error = ✗ Task join error: { $error }
+task-join-error = ✗ Ошибка присоединения задачи: { $error }
 doctor-header = 🩺 Диагностика: { $name } ({ $engine }) [backend: { $backend }]
 doctor-node-section = --- { $name } ---
 doctor-all-passed = ✅ Все проверки пройдены!
@@ -49,7 +49,7 @@ power-on-head = 💡 включение питания: { $nodes } узлов
 ha-invalid-entity = { $name }: неверный entity '{ $entity }': { $error }
 ha-call-failed = HA { $service } { $entity }: { $error }
 ha-skip-no-entity = ⚠ { $name }: нет `entity`, пропускаем HA
-ha-extra-invalid = { $label } ({ $extra }): неверный entity
+ha-extra-invalid = { $label } ({ $extra }): неверный entity '{ $extra }'
 ha-extra-failed = HA { $service } { $extra } ({ $label }): { $error }
 power-switch-would-off = будет: HA turn_off { $entity }
 power-switch-would-off-extra = будет: HA turn_off { $extra } (из extras)
@@ -57,7 +57,7 @@ power-switch-would-on = будет: HA turn_on { $entity }
 power-switch-would-on-extra = будет: HA turn_on { $extra } (в extras)
 power-switch-shutdown-failed = ✗ { $name } выключение: { $error }
 power-switch-waiting = ⏳ ждем { $secs }с для остановки
-ha-entity-error = { $name }: entity '{ $entity }': { $error }
+ha-entity-error = { $name }: сущность '{ $entity }': { $error }
 power-switch-no-ha = нет [home_assistant] конфига; OS shutdown выполнен
 power-off-entity-would = будет: HA turn_off { $entity }
 pre-serve-skip = ⏭ Пропускаем pre-serve хуки (--skip-before-serve)
@@ -69,29 +69,68 @@ host-key-line = { $name } ({ $ip }) -> host_key = { $key }
 host-key-error = { $name } ({ $ip }) -> ошибка: { $error }
 host-key-no-match = (нет подходящих узлов)
 recipe-starting = 🚀 Запуск рецепта: { $name } ({ $engine }) [backend: { $backend }]
+cluster-switch-off-detected = ⚠️ Похоже, переключатель кластера выключен
+cluster-switch-powered-on = ✅ Переключатель кластера включен
+cluster-waiting-for-nodes = ⏳ Ожидание доступности узлов...
+cluster-nodes-available = ✅ Все узлы теперь доступны
+cluster-nodes-not-available-timeout = ❌ Превышено время ожидания доступности узлов
+ha-config-required-for-power-on = ❌ Для включения питания требуется HA конфиг
 logs-header = 📜 Логи кластера '{ $name }' ({ $count } узлов)
 engine-started = ✅ { $engine } запущен на { $name } (rank { $rank })
 engine-start-failed = Не удалось запустить { $engine } на { $name } (rank { $rank }): { $error }
 cluster-started = 🎉 Кластер успешно запущен! API → http://{ $ip }:{ $port }
 
-exec-both-specified = Specify either --cluster or --recipe, not both
-exec-missing = Must specify either --cluster or --recipe
-exec-node-not-found = Node '{ $name }' not found in cluster '{ $cluster }'
-exec-cluster-empty = Cluster '{ $name }' has no nodes
-exec-cannot-use-alias = Cannot use --alias together with a trailing command
-exec-must-specify = Must specify either --alias <name> or a command after `--`
-exec-unknown-alias-empty = Unknown alias @{ $name } (no aliases configured)
-exec-unknown-alias-list = Unknown alias @{ $name }; known aliases: { $aliases }
+exec-both-specified = Укажите либо --cluster, либо --recipe, но не оба
+exec-missing = Нужно указать либо --cluster, либо --recipe
+exec-node-not-found = Узел '{ $name }' не найден в кластере '{ $cluster }'
+exec-cluster-empty = В кластере '{ $name }' нет узлов
+exec-cannot-use-alias = Нельзя использовать --alias вместе с командой в конце
+exec-must-specify = Нужно указать либо --alias <имя>, либо команду после `--`
+exec-unknown-alias-empty = Неизвестный псевдоним @{ $name } (псевдонимы не настроены)
+exec-unknown-alias-list = Неизвестный псевдоним @{ $name }; известные псевдонимы: { $aliases }
 mods-uploading = 📤 Загрузка модов на { $node }...
 mods-uploaded = ✅ Моды загружены на { $node }
 mods-entrypoint-required = Рецепт '{ $name }' содержит моды, но не имеет entrypoint; entrypoint обязателен при наличии модов
-cluster-switch-off-detected = ⚠️ Cluster switch appears to be off
-cluster-switch-powered-on = ✅ Cluster switch powered on
-cluster-waiting-for-nodes = ⏳ Waiting for nodes to become available...
-cluster-nodes-available = ✅ All nodes are now available
-cluster-nodes-not-available-timeout = ❌ Timeout waiting for nodes to become available
-ha-config-required-for-power-on = ❌ HA config required for power-on
-power-off-container-stop-failed = ⚠️ Failed to stop containers on node { $name }: { $error}
+power-off-container-stop-failed = ⚠️ Не удалось остановить контейнеры на узле { $name }: { $error}
+
+after-stop-failed = after_stop: узел { $name } ошибка: { $error }
+after-stop-run = { $node } ({ $tag }) :: { $cmd }
+after-stop-ignored = ⚠ { $cmd } проигнорировано: { $error }
+solo-node-selected = 🎯 Соло: выбран узел { $name } в кластере { $cluster }
+
+# --- Строки, которые ранее обходили систему i18n (захардкожены) ---
+boot-wait = ⏳ Ожидание { $secs }с загрузки узлов...
+doctor-found-repaired = 🩺 Найден восстановленный образ: { $tag }
+tmux-waiting-head = ⏳ Ожидание готовности главного узла...
+tmux-head-ready = ✅ Главный узел готов. Запускаю рабочие узлы...
+tmux-head-api-ready = API главного узла готов (попытка { $attempt })
+tmux-session-started = ✅ Сессия запущена на { $name }
+tmux-stopped = ✅ Остановлена '{ $sess }' на { $name }
+tmux-all-stopped = ✅ Все tmux сессии остановлены.
+tmux-all-ok = ✅ ВСЁ ОК
+tmux-issues = ⚠️  { $issues }
+tmux-cluster-healthy = ✅ Кластер здоров!
+tmux-node-section = --- { $name } ({ $sess }) ---
+tmux-node-no-output = --- { $name } — нет вывода ---
+tmux-session-created = ✅ tmux сессия создана на { $name }
+tmux-no-pty = (russh не поддерживает интерактивный PTY. Используйте следующую команду напрямую:)
+sync-digest-match = '{ $name }' OK (дайджест совпадает)
+sync-syncing = { $name } ({ $ip }): синхронизация...
+sync-synced = { $name } OK
+health-node-offline =   ✗ { $name } ({ $ip }) — офлайн
+health-node-idle =   ○ { $name } ({ $ip }){ $net }
+health-node-online =   ● { $name } ({ $ip }){ $net }
+health-container-count = , { $count } контейнер(ов)
+power-off-head = 🛑 выключение: { $nodes } узлов — sudo shutdown now
+power-switch-head = 🔌 переключение: { $from } → { $to } ({ $nodes } узлов + { $entities } сущностей для отключения)
+power-switch-powering-on = 💡 переключение: включение { $nodes } узлов + { $entities } сущностей в { $to }
+health-task-join = ошибка присоединения задачи: { $error }
+health-no-recipes = Нет запущенных контейнеров sm121.
+health-running-recipes = Запущенные рецепты:
+health-recipe-line =   { $recipe } — { $nodes } узлов: { $list }
+tmux-ray-backend = 🐍 Обнаружен Ray backend — запуск главного узла первым...
+tmux-mp-backend = 🔗 Обнаружен MP backend — запуск всех узлов параллельно...
+tmux-following-logs = 📜 Наблюдаю за логами '{ $sess }' на { $name }...
 composed-dir-dry-run = [пробный запуск] будет создан каталог составной модели '{ $host }' на '{ $node }' (точка монтирования: { $mount }, { $sources } { $sources ->
         [one] источник
         [few] источника

+ 98 - 0
scripts/verify-locales.sh

@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# verify-locales.sh — completeness gate for locale FTL files.
+#
+# Asserts two invariants for every non-en locale:
+#   1. COVERAGE — every key in en/main.ftl exists in the locale.
+#   2. TRANSLATION — no key's value is byte-identical to the en value
+#      (catches untranslated English left in a non-en file).
+#
+# Ignores: lines that are comments (#), blank, or Fluent selectors
+# (contain `->` / `[one]` / `*[other]` etc. — those are plural branches,
+# not standalone values, and may legitimately match across locales).
+#
+# Usage: bash scripts/verify-locales.sh [locales_dir]
+# Exit 0 = all locales complete; 1 = gaps found.
+set -euo pipefail
+
+LOCALES="${1:-locales}"
+EN="$LOCALES/en/main.ftl"
+FAIL=0
+
+# Loanwords: technical terms identical across all locales (en/ru/es/it/fr/de).
+# A value whose only prose words are loanwords is legitimately the same
+# in every language and must not be flagged as untranslated.
+LOANWORDS='cluster offline online node nodes image container backend api ssh ha tmux ray mp doctor recipe exec mod mods hook hooks rank dry sync digest repair log logs session pty alias aliases error'
+is_loanword() { echo "$LOANWORDS" | grep -qwi "$1"; }
+
+# Extract top-level key names (lines starting with `identifier =`).
+keys_of() { grep -oE '^[a-z0-9-]+ = ' "$1" | sed 's/ = $//'; }
+
+# Extract the value portion of a given key (first match, trailing whitespace
+# trimmed). Returns empty if the key is absent.
+value_of() {
+  local file="$1" key="$2"
+  awk -v k="^$key = " '$0 ~ k { sub(/^[^=]+= /, ""); print; exit }' "$file" \
+    | sed 's/[[:space:]]*$//'
+}
+
+en_keys=$(keys_of "$EN" | sort -u)
+
+if [ -z "$en_keys" ]; then
+  echo "FAIL: no keys found in $EN"
+  exit 1
+fi
+
+for loc in ru es it fr de; do
+  f="$LOCALES/$loc/main.ftl"
+  [ -f "$f" ] || { echo "FAIL: missing $f"; FAIL=1; continue; }
+
+  loc_keys=$(keys_of "$f" | sort -u)
+
+  # 1. Coverage: every en key must be present.
+  missing=$(comm -23 <(echo "$en_keys") <(echo "$loc_keys"))
+  if [ -n "$missing" ]; then
+    cnt=$(echo "$missing" | wc -l | tr -d ' ')
+    echo "FAIL: $loc missing $cnt key(s):"
+    echo "$missing" | sed 's/^/    /'
+    FAIL=1
+  fi
+
+  # 2. Translation: no present key's value may equal the en value,
+  #    UNLESS the en value is pure placeholders/symbols (no translatable
+  #    prose). A value is "translatable" if, after stripping { $x }
+  #    placeholders and non-alphanumerics, it contains at least one
+  #    alphabetic word of length >= 3.
+  untranslated=""
+  while IFS= read -r key; do
+    [ -z "$key" ] && continue
+    en_val=$(value_of "$EN" "$key")
+    loc_val=$(value_of "$f" "$key")
+    [ -z "$loc_val" ] && continue
+    [ -z "$en_val" ] && continue
+    # Skip untranslatable values (pure placeholders/symbols/identifiers).
+    # Strip { $x } placeholders, then non-alpha. Words containing '_'
+    # are config/code identifiers (host_key, etc.) — not translatable prose.
+    prose=$(echo "$en_val" | sed -E 's/\{ \$[a-z_]+ \}//g; s/[^[:alpha:]_]/ /g; s/[a-z]+_[a-z_]+//g')
+    translatable=0
+    for w in $prose; do
+      if [ ${#w} -ge 3 ] && ! is_loanword "$w"; then translatable=1; break; fi
+    done
+    [ "$translatable" -eq 0 ] && continue
+    if [ "$en_val" = "$loc_val" ]; then
+      untranslated="$untranslated$key"$'\n'
+    fi
+  done < <(echo "$en_keys")
+  untranslated=$(echo "$untranslated" | sed '/^$/d')
+  if [ -n "$untranslated" ]; then
+    cnt=$(echo "$untranslated" | wc -l | tr -d ' ')
+    echo "FAIL: $loc has $cnt untranslated key(s) (English value identical to en):"
+    echo "$untranslated" | sed 's/^/    /'
+    FAIL=1
+  fi
+done
+
+if [ "$FAIL" -eq 0 ]; then
+  echo "OK: all non-en locales complete (coverage + translation)."
+  exit 0
+fi
+exit 1