iobroker.script-restore 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/admin/tab_m.html CHANGED
@@ -356,6 +356,26 @@
356
356
  body.dark .restore-modal label { color: #aaa; }
357
357
  body.dark .restore-modal input[type=text] { background: #1a1a2e; border-color: #4a4a6a; color: #e0e0e0; }
358
358
  body.dark .restore-modal-preview { color: #7a7aaa; }
359
+ .restore-modal-warning {
360
+ background: #fff3cd; border: 1px solid #ffc107; border-radius: 6px;
361
+ padding: 12px 14px; margin-top: 14px; display: none;
362
+ }
363
+ .restore-modal-warning p { margin: 0 0 10px; font-size: 0.88rem; color: #664d03; line-height: 1.5; }
364
+ .restore-modal-warning code { background: rgba(0,0,0,0.08); padding: 1px 5px; border-radius: 3px; font-family: 'Consolas', monospace; word-break: break-all; font-size: 0.85em; }
365
+ .restore-modal-warning .rmw-actions { display: flex; gap: 8px; justify-content: flex-end; }
366
+ .btn-danger { background: #dc3545; color: white; border-color: #dc3545; }
367
+ .btn-danger:hover { background: #bb2d3b; border-color: #b02a37; }
368
+ body.dark .restore-modal-warning { background: #2d1f00; border-color: #7a5f00; }
369
+ body.dark .restore-modal-warning p { color: #ffd966; }
370
+ body.dark .restore-modal-warning code { background: rgba(255,255,255,0.1); color: #ffb347; }
371
+ .restore-modal-success {
372
+ background: #d1e7dd; border: 1px solid #a3cfbb; border-radius: 6px;
373
+ padding: 12px 14px; margin-top: 14px; display: none;
374
+ }
375
+ .restore-modal-success p { margin: 0 0 10px; font-size: 0.88rem; color: #0a3622; line-height: 1.5; }
376
+ .restore-modal-success .rmw-actions { display: flex; gap: 8px; justify-content: flex-end; }
377
+ body.dark .restore-modal-success { background: #0d2b1f; border-color: #1a5c38; }
378
+ body.dark .restore-modal-success p { color: #6fdc9e; }
359
379
 
360
380
  /* Responsive */
361
381
  @media (max-width: 768px) {
@@ -373,25 +393,39 @@
373
393
  <div class="progress-inner"><span id="progressPercent">0%</span></div>
374
394
  </div>
375
395
  <div id="spinnerEl" class="spinner" style="display:none;"></div>
376
- <div id="loaderText">Lade Backup...</div>
396
+ <div id="loaderText" data-i18n="loaderLoading">Lade Backup...</div>
377
397
  </div>
378
398
 
379
399
  <!-- Restore Modal -->
380
400
  <div class="restore-modal-overlay" id="restoreModalOverlay" onclick="closeRestoreModal(event)">
381
401
  <div class="restore-modal" onclick="event.stopPropagation()">
382
- <h3>In ioBroker laden</h3>
402
+ <h3 data-i18n="modalTitle">In ioBroker laden</h3>
383
403
  <div class="restore-modal-meta">
384
- <div>Skript: <strong id="rmScriptName"></strong></div>
385
- <div>Pfad: <strong id="rmScriptPath"></strong></div>
386
- <div>Typ: <strong id="rmScriptType"></strong></div>
404
+ <div><span data-i18n="modalLabelScript">Skript:</span> <strong id="rmScriptName"></strong></div>
405
+ <div><span data-i18n="modalLabelPath">Pfad:</span> <strong id="rmScriptPath"></strong></div>
406
+ <div><span data-i18n="modalLabelType">Typ:</span> <strong id="rmScriptType"></strong></div>
387
407
  </div>
388
- <label for="rmSuffix">Suffix (wird an den Skriptnamen angehängt)</label>
389
- <input type="text" id="rmSuffix" value="_rcvr" oninput="updateRestorePreview()">
408
+ <label for="rmSuffix" data-i18n="modalLabelSuffix">Suffix (wird an den Skriptnamen angehängt)</label>
409
+ <input type="text" id="rmSuffix" value="_rcvr" oninput="updateRestorePreview();">
390
410
  <div class="restore-modal-preview" id="rmPreview"></div>
411
+ <div class="restore-modal-warning" id="rmWarning">
412
+ <p></p>
413
+ <div class="rmw-actions">
414
+ <button class="btn btn-outline" onclick="dismissWarning()" data-i18n="modalDismiss">Anderen Suffix wählen</button>
415
+ <button class="btn btn-danger" onclick="overwriteConfirmed()" data-i18n="modalOverwrite">Überschreiben</button>
416
+ </div>
417
+ </div>
418
+ <div class="restore-modal-success" id="rmSuccess">
419
+ <p data-i18n-html="modalSuccess">✓ Erfolgreich wiederhergestellt.<br>Soll das Skript jetzt gestartet werden?</p>
420
+ <div class="rmw-actions">
421
+ <button class="btn btn-outline" onclick="closeRestoreModal()" data-i18n="modalClose">Nein danke</button>
422
+ <button class="btn btn-outline-success" id="rmStartBtn" onclick="startRestoredScript()" data-i18n="modalStart">Jetzt starten</button>
423
+ </div>
424
+ </div>
391
425
  <div class="restore-modal-footer">
392
426
  <span id="rmMsg" style="flex:1;font-size:0.85rem;align-self:center;"></span>
393
- <button class="btn btn-outline" onclick="closeRestoreModal()">Abbrechen</button>
394
- <button class="btn btn-outline-success" id="rmConfirmBtn" onclick="confirmRestoreScript()">Laden</button>
427
+ <button class="btn btn-outline" onclick="closeRestoreModal()">Schließen</button>
428
+ <button class="btn btn-outline-success" id="rmConfirmBtn" onclick="confirmRestoreScript(false)" data-i18n="modalLoad">Laden</button>
395
429
  </div>
396
430
  </div>
397
431
  </div>
@@ -399,56 +433,56 @@
399
433
  <div class="toolbar">
400
434
  <div class="toolbar-left">
401
435
  <label class="file-input-label">
402
- 📂 Backup hochladen
436
+ <span data-i18n="uploadLabel">📂 Backup hochladen</span>
403
437
  <input type="file" id="fileInput" accept=".tar,.gz,.tar.gz,.json,.jsonl">
404
438
  </label>
405
439
  <div class="dropdown-wrapper" id="localDropdown">
406
- <button class="btn btn-outline" onclick="toggleDropdown('local')">
440
+ <button class="btn btn-outline" onclick="toggleDropdown('local')" data-i18n="localDropdown">
407
441
  🗂️ Lokale Backups ▾
408
442
  </button>
409
443
  <div class="dropdown-menu" id="localMenu"></div>
410
444
  </div>
411
445
  <div class="dropdown-wrapper" id="ftpDropdown" style="display:none;">
412
- <button class="btn btn-outline" onclick="toggleDropdown('ftp')">
446
+ <button class="btn btn-outline" onclick="toggleDropdown('ftp')" data-i18n="ftpDropdown">
413
447
  🌐 FTP Backups ▾
414
448
  </button>
415
449
  <div class="dropdown-menu" id="ftpMenu"></div>
416
450
  </div>
417
451
  <div class="dropdown-wrapper" id="smbDropdown" style="display:none;">
418
- <button class="btn btn-outline" onclick="toggleDropdown('smb')">
452
+ <button class="btn btn-outline" onclick="toggleDropdown('smb')" data-i18n="smbDropdown">
419
453
  🗄️ SMB Backups ▾
420
454
  </button>
421
455
  <div class="dropdown-menu" id="smbMenu"></div>
422
456
  </div>
423
457
  <div class="dropdown-wrapper" id="sftpDropdown" style="display:none;">
424
- <button class="btn btn-outline" onclick="toggleDropdown('sftp')">
458
+ <button class="btn btn-outline" onclick="toggleDropdown('sftp')" data-i18n="sftpDropdown">
425
459
  🔒 SFTP Backups ▾
426
460
  </button>
427
461
  <div class="dropdown-menu" id="sftpMenu"></div>
428
462
  </div>
429
463
  <div class="dropdown-wrapper" id="webdavDropdown" style="display:none;">
430
- <button class="btn btn-outline" onclick="toggleDropdown('webdav')">
464
+ <button class="btn btn-outline" onclick="toggleDropdown('webdav')" data-i18n="webdavDropdown">
431
465
  ☁️ WebDAV Backups ▾
432
466
  </button>
433
467
  <div class="dropdown-menu" id="webdavMenu"></div>
434
468
  </div>
435
469
  <div id="httpInputWrapper" style="display:none; align-items:center; gap:6px;">
436
470
  <input type="text" id="httpUrlInput" placeholder="https://..." style="padding:0.35rem 0.6rem; border:1px solid #ced4da; border-radius:4px; font-size:0.875rem; min-width:260px; font-family:inherit;">
437
- <button type="button" class="btn btn-outline" onclick="loadHttpUrl()">🌐 URL laden</button>
471
+ <button type="button" class="btn btn-outline" onclick="loadHttpUrl()" data-i18n="httpLoadBtn">🌐 URL laden</button>
438
472
  </div>
439
- <button id="zipBtn" class="btn btn-outline" onclick="downloadZip()" style="display:none;">📦 ZIP</button>
440
- <span class="status-msg" id="statusMsg">Backup laden oder Quelle wählen</span>
473
+ <button id="zipBtn" class="btn btn-outline" onclick="downloadZip()" style="display:none;" data-i18n="zipBtn">📦 ZIP</button>
474
+ <span class="status-msg" id="statusMsg" data-i18n="statusDefault">Backup laden oder Quelle wählen</span>
441
475
  </div>
442
476
  </div>
443
477
 
444
478
  <div class="main-container">
445
479
  <div class="sidebar" id="sidebar">
446
480
  <div class="sidebar-header">
447
- <input type="text" id="q" placeholder="Suche in Namen, Ordner &amp; Code...">
448
- <button class="btn-icon" id="expandToggleBtn" onclick="toggleExpandAll()" title="Alle Ordner aufklappen">📂</button>
481
+ <input type="text" id="q" data-i18n-placeholder="searchPlaceholder" placeholder="Suche in Namen, Ordner &amp; Code...">
482
+ <button class="btn-icon" id="expandToggleBtn" onclick="toggleExpandAll()" data-i18n-title="expandAll" title="Alle Ordner aufklappen">📂</button>
449
483
  </div>
450
484
  <div class="type-filter-bar">
451
- <button class="btn-type active" onclick="setTypeFilter(this, '')">Alle</button>
485
+ <button class="btn-type active" onclick="setTypeFilter(this, '')" data-i18n="typeAll">Alle</button>
452
486
  <button class="btn-type" onclick="setTypeFilter(this, 'JS')"><span class="type-badge badge-JS">JS</span></button>
453
487
  <button class="btn-type" onclick="setTypeFilter(this, 'TypeScript')"><span class="type-badge badge-TypeScript">TS</span></button>
454
488
  <button class="btn-type" onclick="setTypeFilter(this, 'Blockly')"><span class="type-badge badge-Blockly">Blockly</span></button>
@@ -464,17 +498,16 @@
464
498
  <div class="action-bar-inner">
465
499
  <div class="btn-group" id="viewSwitcher"></div>
466
500
  <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
467
- <button onclick="openRestoreModal()" class="btn btn-outline-success" id="restoreBtn">In ioBroker laden</button>
468
- <button onclick="copyCode(this)" class="btn btn-outline-light">Code Kopieren</button>
469
- <button onclick="downloadActive()" class="btn btn-primary" id="dlBtn">Download</button>
501
+ <button onclick="openRestoreModal()" class="btn btn-outline-success" id="restoreBtn" data-i18n="restoreBtn">In ioBroker laden</button>
502
+ <button onclick="copyCode(this)" class="btn btn-outline-light" data-i18n="copyBtnAction">Code Kopieren</button>
503
+ <button onclick="downloadActive()" class="btn btn-primary" id="dlBtn" data-i18n="downloadBtn">Download</button>
470
504
  </div>
471
505
  </div>
472
506
  </div>
473
507
  <div id="codeContainer" class="code-empty">
474
508
  <div>
475
509
  <strong>ioBroker Script Restore</strong><br><br>
476
- Lade ein Backup hoch oder wähle eine lokale Datei,<br>
477
- um Skripte anzuzeigen und wiederherzustellen.
510
+ <span data-i18n-html="welcomeText">Lade ein Backup hoch oder wähle eine lokale Datei,<br>um Skripte anzuzeigen und wiederherzustellen.</span>
478
511
  </div>
479
512
  </div>
480
513
  </div>
@@ -486,6 +519,534 @@
486
519
  const instance = urlParams.get('instance') || '0';
487
520
  const adapterInst = 'script-restore.' + instance;
488
521
 
522
+ // === i18n ===
523
+ const TRANSLATIONS = {
524
+ de: {
525
+ loaderLoading: 'Lade Backup...',
526
+ loaderProcessing: 'Verarbeite...',
527
+ modalTitle: 'In ioBroker laden',
528
+ modalLabelScript: 'Skript:',
529
+ modalLabelPath: 'Pfad:',
530
+ modalLabelType: 'Typ:',
531
+ modalLabelSuffix: 'Suffix (wird an den Skriptnamen angehängt)',
532
+ modalWarning: '⚠ Ein Skript unter {path} existiert bereits.<br>Soll es wirklich überschrieben werden?',
533
+ modalDismiss: 'Anderen Suffix wählen',
534
+ modalOverwrite: 'Überschreiben',
535
+ modalSuccess: '✓ Erfolgreich wiederhergestellt.<br>Soll das Skript jetzt gestartet werden?',
536
+ modalClose: 'Nein danke',
537
+ modalStart: 'Jetzt starten',
538
+ modalLoad: 'Laden',
539
+ modalLoading: '⏳ Laden...',
540
+ modalStarting: '⏳ Starte...',
541
+ modalStarted: '✓ Gestartet',
542
+ modalLoaded: '✓ Geladen!',
543
+ modalUnknownError: '✗ Unbekannter Fehler',
544
+ modalErrorPrefix: '✗ Fehler: ',
545
+ copyBtn: 'Code Kopieren',
546
+ copyBtnAction: 'Code Kopieren',
547
+ copied: '✓ Kopiert!',
548
+ uploadLabel: '📂 Backup hochladen',
549
+ localDropdown: '🗂️ Lokale Backups ▾',
550
+ ftpDropdown: '🌐 FTP Backups ▾',
551
+ smbDropdown: '🗄️ SMB Backups ▾',
552
+ sftpDropdown: '🔒 SFTP Backups ▾',
553
+ webdavDropdown: '☁️ WebDAV Backups ▾',
554
+ httpLoadBtn: '🌐 URL laden',
555
+ zipBtn: '📦 ZIP',
556
+ statusDefault: 'Backup laden oder Quelle wählen',
557
+ searchPlaceholder: 'Suche in Namen, Ordner & Code...',
558
+ expandAll: 'Alle aufklappen',
559
+ collapseAll: 'Alle einklappen',
560
+ typeAll: 'Alle',
561
+ restoreBtn: 'In ioBroker laden',
562
+ downloadBtn: 'Download',
563
+ welcomeText: 'Lade ein Backup hoch oder wähle eine lokale Datei,<br>um Skripte anzuzeigen und wiederherzustellen.',
564
+ noSocket: 'Kein Socket. Bitte prüfen ob script-restore.{i} läuft.',
565
+ timeout: 'Timeout: Adapter antwortet nicht. Läuft script-restore.{i}?',
566
+ },
567
+ en: {
568
+ loaderLoading: 'Loading backup...',
569
+ loaderProcessing: 'Processing...',
570
+ modalTitle: 'Load into ioBroker',
571
+ modalLabelScript: 'Script:',
572
+ modalLabelPath: 'Path:',
573
+ modalLabelType: 'Type:',
574
+ modalLabelSuffix: 'Suffix (appended to the script name)',
575
+ modalWarning: '⚠ A script at {path} already exists.<br>Do you really want to overwrite it?',
576
+ modalDismiss: 'Choose different suffix',
577
+ modalOverwrite: 'Overwrite',
578
+ modalSuccess: '✓ Successfully restored.<br>Do you want to start the script now?',
579
+ modalClose: 'No thanks',
580
+ modalStart: 'Start now',
581
+ modalLoad: 'Load',
582
+ modalLoading: '⏳ Loading...',
583
+ modalStarting: '⏳ Starting...',
584
+ modalStarted: '✓ Started',
585
+ modalLoaded: '✓ Loaded!',
586
+ modalUnknownError: '✗ Unknown error',
587
+ modalErrorPrefix: '✗ Error: ',
588
+ copyBtn: 'Copy Code',
589
+ copyBtnAction: 'Copy Code',
590
+ copied: '✓ Copied!',
591
+ uploadLabel: '📂 Upload backup',
592
+ localDropdown: '🗂️ Local Backups ▾',
593
+ ftpDropdown: '🌐 FTP Backups ▾',
594
+ smbDropdown: '🗄️ SMB Backups ▾',
595
+ sftpDropdown: '🔒 SFTP Backups ▾',
596
+ webdavDropdown: '☁️ WebDAV Backups ▾',
597
+ httpLoadBtn: '🌐 Load URL',
598
+ zipBtn: '📦 ZIP',
599
+ statusDefault: 'Load a backup or select a source',
600
+ searchPlaceholder: 'Search in names, folders & code...',
601
+ expandAll: 'Expand all',
602
+ collapseAll: 'Collapse all',
603
+ typeAll: 'All',
604
+ restoreBtn: 'Load into ioBroker',
605
+ downloadBtn: 'Download',
606
+ welcomeText: 'Upload a backup or select a local file,<br>to view and restore scripts.',
607
+ noSocket: 'No socket. Please check if script-restore.{i} is running.',
608
+ timeout: 'Timeout: Adapter not responding. Is script-restore.{i} running?',
609
+ },
610
+ fr: {
611
+ loaderLoading: 'Chargement de la sauvegarde...',
612
+ loaderProcessing: 'Traitement en cours...',
613
+ modalTitle: 'Charger dans ioBroker',
614
+ modalLabelScript: 'Script :',
615
+ modalLabelPath: 'Chemin :',
616
+ modalLabelType: 'Type :',
617
+ modalLabelSuffix: 'Suffixe (ajouté au nom du script)',
618
+ modalWarning: '⚠ Un script à {path} existe déjà.<br>Voulez-vous vraiment l\'écraser ?',
619
+ modalDismiss: 'Choisir un autre suffixe',
620
+ modalOverwrite: 'Écraser',
621
+ modalSuccess: '✓ Restauré avec succès.<br>Voulez-vous démarrer le script maintenant ?',
622
+ modalClose: 'Non merci',
623
+ modalStart: 'Démarrer maintenant',
624
+ modalLoad: 'Charger',
625
+ modalLoading: '⏳ Chargement...',
626
+ modalStarting: '⏳ Démarrage...',
627
+ modalStarted: '✓ Démarré',
628
+ modalLoaded: '✓ Chargé !',
629
+ modalUnknownError: '✗ Erreur inconnue',
630
+ modalErrorPrefix: '✗ Erreur : ',
631
+ copyBtn: 'Copier le code',
632
+ copyBtnAction: 'Copier le code',
633
+ copied: '✓ Copié !',
634
+ uploadLabel: '📂 Téléverser une sauvegarde',
635
+ localDropdown: '🗂️ Sauvegardes locales ▾',
636
+ ftpDropdown: '🌐 Sauvegardes FTP ▾',
637
+ smbDropdown: '🗄️ Sauvegardes SMB ▾',
638
+ sftpDropdown: '🔒 Sauvegardes SFTP ▾',
639
+ webdavDropdown: '☁️ Sauvegardes WebDAV ▾',
640
+ httpLoadBtn: '🌐 Charger URL',
641
+ zipBtn: '📦 ZIP',
642
+ statusDefault: 'Charger une sauvegarde ou choisir une source',
643
+ searchPlaceholder: 'Rechercher dans les noms, dossiers et code...',
644
+ expandAll: 'Tout développer',
645
+ collapseAll: 'Tout réduire',
646
+ typeAll: 'Tous',
647
+ restoreBtn: 'Charger dans ioBroker',
648
+ downloadBtn: 'Télécharger',
649
+ welcomeText: 'Téléversez une sauvegarde ou sélectionnez un fichier local,<br>pour afficher et restaurer des scripts.',
650
+ noSocket: 'Pas de socket. Vérifiez si script-restore.{i} est en cours d\'exécution.',
651
+ timeout: 'Délai d\'attente : l\'adaptateur ne répond pas. script-restore.{i} est-il en cours d\'exécution ?',
652
+ },
653
+ es: {
654
+ loaderLoading: 'Cargando copia de seguridad...',
655
+ loaderProcessing: 'Procesando...',
656
+ modalTitle: 'Cargar en ioBroker',
657
+ modalLabelScript: 'Script:',
658
+ modalLabelPath: 'Ruta:',
659
+ modalLabelType: 'Tipo:',
660
+ modalLabelSuffix: 'Sufijo (se añade al nombre del script)',
661
+ modalWarning: '⚠ Ya existe un script en {path}.<br>¿Realmente desea sobrescribirlo?',
662
+ modalDismiss: 'Elegir otro sufijo',
663
+ modalOverwrite: 'Sobrescribir',
664
+ modalSuccess: '✓ Restaurado con éxito.<br>¿Desea iniciar el script ahora?',
665
+ modalClose: 'No gracias',
666
+ modalStart: 'Iniciar ahora',
667
+ modalLoad: 'Cargar',
668
+ modalLoading: '⏳ Cargando...',
669
+ modalStarting: '⏳ Iniciando...',
670
+ modalStarted: '✓ Iniciado',
671
+ modalLoaded: '✓ ¡Cargado!',
672
+ modalUnknownError: '✗ Error desconocido',
673
+ modalErrorPrefix: '✗ Error: ',
674
+ copyBtn: 'Copiar código',
675
+ copyBtnAction: 'Copiar código',
676
+ copied: '✓ ¡Copiado!',
677
+ uploadLabel: '📂 Subir copia de seguridad',
678
+ localDropdown: '🗂️ Copias locales ▾',
679
+ ftpDropdown: '🌐 Copias FTP ▾',
680
+ smbDropdown: '🗄️ Copias SMB ▾',
681
+ sftpDropdown: '🔒 Copias SFTP ▾',
682
+ webdavDropdown: '☁️ Copias WebDAV ▾',
683
+ httpLoadBtn: '🌐 Cargar URL',
684
+ zipBtn: '📦 ZIP',
685
+ statusDefault: 'Cargar una copia de seguridad o seleccionar una fuente',
686
+ searchPlaceholder: 'Buscar en nombres, carpetas y código...',
687
+ expandAll: 'Expandir todo',
688
+ collapseAll: 'Contraer todo',
689
+ typeAll: 'Todos',
690
+ restoreBtn: 'Cargar en ioBroker',
691
+ downloadBtn: 'Descargar',
692
+ welcomeText: 'Sube una copia de seguridad o selecciona un archivo local,<br>para ver y restaurar scripts.',
693
+ noSocket: 'Sin socket. Compruebe si script-restore.{i} está en ejecución.',
694
+ timeout: 'Tiempo de espera agotado: el adaptador no responde. ¿Está script-restore.{i} en ejecución?',
695
+ },
696
+ it: {
697
+ loaderLoading: 'Caricamento backup...',
698
+ loaderProcessing: 'Elaborazione...',
699
+ modalTitle: 'Carica in ioBroker',
700
+ modalLabelScript: 'Script:',
701
+ modalLabelPath: 'Percorso:',
702
+ modalLabelType: 'Tipo:',
703
+ modalLabelSuffix: 'Suffisso (aggiunto al nome dello script)',
704
+ modalWarning: '⚠ Esiste già uno script in {path}.<br>Vuoi davvero sovrascriverlo?',
705
+ modalDismiss: 'Scegli un suffisso diverso',
706
+ modalOverwrite: 'Sovrascrivi',
707
+ modalSuccess: '✓ Ripristinato con successo.<br>Vuoi avviare lo script adesso?',
708
+ modalClose: 'No grazie',
709
+ modalStart: 'Avvia ora',
710
+ modalLoad: 'Carica',
711
+ modalLoading: '⏳ Caricamento...',
712
+ modalStarting: '⏳ Avvio...',
713
+ modalStarted: '✓ Avviato',
714
+ modalLoaded: '✓ Caricato!',
715
+ modalUnknownError: '✗ Errore sconosciuto',
716
+ modalErrorPrefix: '✗ Errore: ',
717
+ copyBtn: 'Copia codice',
718
+ copyBtnAction: 'Copia codice',
719
+ copied: '✓ Copiato!',
720
+ uploadLabel: '📂 Carica backup',
721
+ localDropdown: '🗂️ Backup locali ▾',
722
+ ftpDropdown: '🌐 Backup FTP ▾',
723
+ smbDropdown: '🗄️ Backup SMB ▾',
724
+ sftpDropdown: '🔒 Backup SFTP ▾',
725
+ webdavDropdown: '☁️ Backup WebDAV ▾',
726
+ httpLoadBtn: '🌐 Carica URL',
727
+ zipBtn: '📦 ZIP',
728
+ statusDefault: 'Carica un backup o seleziona una sorgente',
729
+ searchPlaceholder: 'Cerca in nomi, cartelle e codice...',
730
+ expandAll: 'Espandi tutto',
731
+ collapseAll: 'Comprimi tutto',
732
+ typeAll: 'Tutti',
733
+ restoreBtn: 'Carica in ioBroker',
734
+ downloadBtn: 'Scarica',
735
+ welcomeText: 'Carica un backup o seleziona un file locale,<br>per visualizzare e ripristinare gli script.',
736
+ noSocket: 'Nessun socket. Verificare se script-restore.{i} è in esecuzione.',
737
+ timeout: 'Timeout: l\'adattatore non risponde. script-restore.{i} è in esecuzione?',
738
+ },
739
+ nl: {
740
+ loaderLoading: 'Back-up laden...',
741
+ loaderProcessing: 'Verwerken...',
742
+ modalTitle: 'Laden in ioBroker',
743
+ modalLabelScript: 'Script:',
744
+ modalLabelPath: 'Pad:',
745
+ modalLabelType: 'Type:',
746
+ modalLabelSuffix: 'Achtervoegsel (toegevoegd aan de scriptnaam)',
747
+ modalWarning: '⚠ Er bestaat al een script op {path}.<br>Wilt u het echt overschrijven?',
748
+ modalDismiss: 'Ander achtervoegsel kiezen',
749
+ modalOverwrite: 'Overschrijven',
750
+ modalSuccess: '✓ Succesvol hersteld.<br>Wilt u het script nu starten?',
751
+ modalClose: 'Nee bedankt',
752
+ modalStart: 'Nu starten',
753
+ modalLoad: 'Laden',
754
+ modalLoading: '⏳ Laden...',
755
+ modalStarting: '⏳ Starten...',
756
+ modalStarted: '✓ Gestart',
757
+ modalLoaded: '✓ Geladen!',
758
+ modalUnknownError: '✗ Onbekende fout',
759
+ modalErrorPrefix: '✗ Fout: ',
760
+ copyBtn: 'Code kopiëren',
761
+ copyBtnAction: 'Code kopiëren',
762
+ copied: '✓ Gekopieerd!',
763
+ uploadLabel: '📂 Back-up uploaden',
764
+ localDropdown: '🗂️ Lokale back-ups ▾',
765
+ ftpDropdown: '🌐 FTP back-ups ▾',
766
+ smbDropdown: '🗄️ SMB back-ups ▾',
767
+ sftpDropdown: '🔒 SFTP back-ups ▾',
768
+ webdavDropdown: '☁️ WebDAV back-ups ▾',
769
+ httpLoadBtn: '🌐 URL laden',
770
+ zipBtn: '📦 ZIP',
771
+ statusDefault: 'Laad een back-up of kies een bron',
772
+ searchPlaceholder: 'Zoeken in namen, mappen en code...',
773
+ expandAll: 'Alles uitklappen',
774
+ collapseAll: 'Alles inklappen',
775
+ typeAll: 'Alle',
776
+ restoreBtn: 'Laden in ioBroker',
777
+ downloadBtn: 'Downloaden',
778
+ welcomeText: 'Upload een back-up of selecteer een lokaal bestand,<br>om scripts te bekijken en te herstellen.',
779
+ noSocket: 'Geen socket. Controleer of script-restore.{i} actief is.',
780
+ timeout: 'Time-out: adapter reageert niet. Is script-restore.{i} actief?',
781
+ },
782
+ pl: {
783
+ loaderLoading: 'Wczytywanie kopii zapasowej...',
784
+ loaderProcessing: 'Przetwarzanie...',
785
+ modalTitle: 'Wczytaj do ioBroker',
786
+ modalLabelScript: 'Skrypt:',
787
+ modalLabelPath: 'Ścieżka:',
788
+ modalLabelType: 'Typ:',
789
+ modalLabelSuffix: 'Sufiks (dodawany do nazwy skryptu)',
790
+ modalWarning: '⚠ Skrypt w {path} już istnieje.<br>Czy na pewno chcesz go nadpisać?',
791
+ modalDismiss: 'Wybierz inny sufiks',
792
+ modalOverwrite: 'Nadpisz',
793
+ modalSuccess: '✓ Przywrócono pomyślnie.<br>Czy chcesz teraz uruchomić skrypt?',
794
+ modalClose: 'Nie, dziękuję',
795
+ modalStart: 'Uruchom teraz',
796
+ modalLoad: 'Wczytaj',
797
+ modalLoading: '⏳ Wczytywanie...',
798
+ modalStarting: '⏳ Uruchamianie...',
799
+ modalStarted: '✓ Uruchomiono',
800
+ modalLoaded: '✓ Wczytano!',
801
+ modalUnknownError: '✗ Nieznany błąd',
802
+ modalErrorPrefix: '✗ Błąd: ',
803
+ copyBtn: 'Kopiuj kod',
804
+ copyBtnAction: 'Kopiuj kod',
805
+ copied: '✓ Skopiowano!',
806
+ uploadLabel: '📂 Prześlij kopię zapasową',
807
+ localDropdown: '🗂️ Lokalne kopie ▾',
808
+ ftpDropdown: '🌐 Kopie FTP ▾',
809
+ smbDropdown: '🗄️ Kopie SMB ▾',
810
+ sftpDropdown: '🔒 Kopie SFTP ▾',
811
+ webdavDropdown: '☁️ Kopie WebDAV ▾',
812
+ httpLoadBtn: '🌐 Wczytaj URL',
813
+ zipBtn: '📦 ZIP',
814
+ statusDefault: 'Wczytaj kopię zapasową lub wybierz źródło',
815
+ searchPlaceholder: 'Szukaj w nazwach, folderach i kodzie...',
816
+ expandAll: 'Rozwiń wszystko',
817
+ collapseAll: 'Zwiń wszystko',
818
+ typeAll: 'Wszystkie',
819
+ restoreBtn: 'Wczytaj do ioBroker',
820
+ downloadBtn: 'Pobierz',
821
+ welcomeText: 'Prześlij kopię zapasową lub wybierz plik lokalny,<br>aby wyświetlić i przywrócić skrypty.',
822
+ noSocket: 'Brak gniazda. Sprawdź, czy script-restore.{i} działa.',
823
+ timeout: 'Przekroczono czas: adapter nie odpowiada. Czy script-restore.{i} działa?',
824
+ },
825
+ pt: {
826
+ loaderLoading: 'A carregar cópia de segurança...',
827
+ loaderProcessing: 'A processar...',
828
+ modalTitle: 'Carregar no ioBroker',
829
+ modalLabelScript: 'Script:',
830
+ modalLabelPath: 'Caminho:',
831
+ modalLabelType: 'Tipo:',
832
+ modalLabelSuffix: 'Sufixo (adicionado ao nome do script)',
833
+ modalWarning: '⚠ Já existe um script em {path}.<br>Tem a certeza que deseja substituí-lo?',
834
+ modalDismiss: 'Escolher outro sufixo',
835
+ modalOverwrite: 'Substituir',
836
+ modalSuccess: '✓ Restaurado com sucesso.<br>Deseja iniciar o script agora?',
837
+ modalClose: 'Não, obrigado',
838
+ modalStart: 'Iniciar agora',
839
+ modalLoad: 'Carregar',
840
+ modalLoading: '⏳ A carregar...',
841
+ modalStarting: '⏳ A iniciar...',
842
+ modalStarted: '✓ Iniciado',
843
+ modalLoaded: '✓ Carregado!',
844
+ modalUnknownError: '✗ Erro desconhecido',
845
+ modalErrorPrefix: '✗ Erro: ',
846
+ copyBtn: 'Copiar código',
847
+ copyBtnAction: 'Copiar código',
848
+ copied: '✓ Copiado!',
849
+ uploadLabel: '📂 Carregar cópia de segurança',
850
+ localDropdown: '🗂️ Cópias locais ▾',
851
+ ftpDropdown: '🌐 Cópias FTP ▾',
852
+ smbDropdown: '🗄️ Cópias SMB ▾',
853
+ sftpDropdown: '🔒 Cópias SFTP ▾',
854
+ webdavDropdown: '☁️ Cópias WebDAV ▾',
855
+ httpLoadBtn: '🌐 Carregar URL',
856
+ zipBtn: '📦 ZIP',
857
+ statusDefault: 'Carregar uma cópia de segurança ou selecionar uma fonte',
858
+ searchPlaceholder: 'Pesquisar em nomes, pastas e código...',
859
+ expandAll: 'Expandir tudo',
860
+ collapseAll: 'Recolher tudo',
861
+ typeAll: 'Todos',
862
+ restoreBtn: 'Carregar no ioBroker',
863
+ downloadBtn: 'Transferir',
864
+ welcomeText: 'Carregue uma cópia de segurança ou selecione um ficheiro local,<br>para ver e restaurar scripts.',
865
+ noSocket: 'Sem socket. Verifique se script-restore.{i} está em execução.',
866
+ timeout: 'Tempo esgotado: o adaptador não responde. O script-restore.{i} está em execução?',
867
+ },
868
+ ru: {
869
+ loaderLoading: 'Загрузка резервной копии...',
870
+ loaderProcessing: 'Обработка...',
871
+ modalTitle: 'Загрузить в ioBroker',
872
+ modalLabelScript: 'Скрипт:',
873
+ modalLabelPath: 'Путь:',
874
+ modalLabelType: 'Тип:',
875
+ modalLabelSuffix: 'Суффикс (добавляется к имени скрипта)',
876
+ modalWarning: '⚠ Скрипт по пути {path} уже существует.<br>Вы действительно хотите перезаписать его?',
877
+ modalDismiss: 'Выбрать другой суффикс',
878
+ modalOverwrite: 'Перезаписать',
879
+ modalSuccess: '✓ Успешно восстановлено.<br>Хотите запустить скрипт сейчас?',
880
+ modalClose: 'Нет, спасибо',
881
+ modalStart: 'Запустить сейчас',
882
+ modalLoad: 'Загрузить',
883
+ modalLoading: '⏳ Загрузка...',
884
+ modalStarting: '⏳ Запуск...',
885
+ modalStarted: '✓ Запущен',
886
+ modalLoaded: '✓ Загружен!',
887
+ modalUnknownError: '✗ Неизвестная ошибка',
888
+ modalErrorPrefix: '✗ Ошибка: ',
889
+ copyBtn: 'Копировать код',
890
+ copyBtnAction: 'Копировать код',
891
+ copied: '✓ Скопировано!',
892
+ uploadLabel: '📂 Загрузить резервную копию',
893
+ localDropdown: '🗂️ Локальные резервные копии ▾',
894
+ ftpDropdown: '🌐 Резервные копии FTP ▾',
895
+ smbDropdown: '🗄️ Резервные копии SMB ▾',
896
+ sftpDropdown: '🔒 Резервные копии SFTP ▾',
897
+ webdavDropdown: '☁️ Резервные копии WebDAV ▾',
898
+ httpLoadBtn: '🌐 Загрузить URL',
899
+ zipBtn: '📦 ZIP',
900
+ statusDefault: 'Загрузите резервную копию или выберите источник',
901
+ searchPlaceholder: 'Поиск по именам, папкам и коду...',
902
+ expandAll: 'Развернуть всё',
903
+ collapseAll: 'Свернуть всё',
904
+ typeAll: 'Все',
905
+ restoreBtn: 'Загрузить в ioBroker',
906
+ downloadBtn: 'Скачать',
907
+ welcomeText: 'Загрузите резервную копию или выберите локальный файл,<br>чтобы просмотреть и восстановить скрипты.',
908
+ noSocket: 'Нет сокета. Проверьте, запущен ли script-restore.{i}.',
909
+ timeout: 'Таймаут: адаптер не отвечает. Запущен ли script-restore.{i}?',
910
+ },
911
+ uk: {
912
+ loaderLoading: 'Завантаження резервної копії...',
913
+ loaderProcessing: 'Обробка...',
914
+ modalTitle: 'Завантажити в ioBroker',
915
+ modalLabelScript: 'Скрипт:',
916
+ modalLabelPath: 'Шлях:',
917
+ modalLabelType: 'Тип:',
918
+ modalLabelSuffix: 'Суфікс (додається до назви скрипта)',
919
+ modalWarning: '⚠ Скрипт за шляхом {path} вже існує.<br>Ви дійсно хочете його перезаписати?',
920
+ modalDismiss: 'Вибрати інший суфікс',
921
+ modalOverwrite: 'Перезаписати',
922
+ modalSuccess: '✓ Успішно відновлено.<br>Хочете запустити скрипт зараз?',
923
+ modalClose: 'Ні, дякую',
924
+ modalStart: 'Запустити зараз',
925
+ modalLoad: 'Завантажити',
926
+ modalLoading: '⏳ Завантаження...',
927
+ modalStarting: '⏳ Запуск...',
928
+ modalStarted: '✓ Запущено',
929
+ modalLoaded: '✓ Завантажено!',
930
+ modalUnknownError: '✗ Невідома помилка',
931
+ modalErrorPrefix: '✗ Помилка: ',
932
+ copyBtn: 'Копіювати код',
933
+ copyBtnAction: 'Копіювати код',
934
+ copied: '✓ Скопійовано!',
935
+ uploadLabel: '📂 Завантажити резервну копію',
936
+ localDropdown: '🗂️ Локальні резервні копії ▾',
937
+ ftpDropdown: '🌐 Резервні копії FTP ▾',
938
+ smbDropdown: '🗄️ Резервні копії SMB ▾',
939
+ sftpDropdown: '🔒 Резервні копії SFTP ▾',
940
+ webdavDropdown: '☁️ Резервні копії WebDAV ▾',
941
+ httpLoadBtn: '🌐 Завантажити URL',
942
+ zipBtn: '📦 ZIP',
943
+ statusDefault: 'Завантажте резервну копію або оберіть джерело',
944
+ searchPlaceholder: 'Пошук за іменами, папками та кодом...',
945
+ expandAll: 'Розгорнути все',
946
+ collapseAll: 'Згорнути все',
947
+ typeAll: 'Усі',
948
+ restoreBtn: 'Завантажити в ioBroker',
949
+ downloadBtn: 'Завантажити',
950
+ welcomeText: 'Завантажте резервну копію або оберіть локальний файл,<br>щоб переглянути та відновити скрипти.',
951
+ noSocket: 'Немає сокета. Перевірте, чи запущено script-restore.{i}.',
952
+ timeout: 'Час очікування вичерпано: адаптер не відповідає. Чи запущено script-restore.{i}?',
953
+ },
954
+ 'zh-cn': {
955
+ loaderLoading: '正在加载备份...',
956
+ loaderProcessing: '处理中...',
957
+ modalTitle: '加载到 ioBroker',
958
+ modalLabelScript: '脚本:',
959
+ modalLabelPath: '路径:',
960
+ modalLabelType: '类型:',
961
+ modalLabelSuffix: '后缀(附加到脚本名称)',
962
+ modalWarning: '⚠ {path} 下已存在一个脚本。<br>您真的要覆盖它吗?',
963
+ modalDismiss: '选择其他后缀',
964
+ modalOverwrite: '覆盖',
965
+ modalSuccess: '✓ 恢复成功。<br>是否立即启动该脚本?',
966
+ modalClose: '不,谢谢',
967
+ modalStart: '立即启动',
968
+ modalLoad: '加载',
969
+ modalLoading: '⏳ 加载中...',
970
+ modalStarting: '⏳ 启动中...',
971
+ modalStarted: '✓ 已启动',
972
+ modalLoaded: '✓ 已加载!',
973
+ modalUnknownError: '✗ 未知错误',
974
+ modalErrorPrefix: '✗ 错误:',
975
+ copyBtn: '复制代码',
976
+ copyBtnAction: '复制代码',
977
+ copied: '✓ 已复制!',
978
+ uploadLabel: '📂 上传备份',
979
+ localDropdown: '🗂️ 本地备份 ▾',
980
+ ftpDropdown: '🌐 FTP 备份 ▾',
981
+ smbDropdown: '🗄️ SMB 备份 ▾',
982
+ sftpDropdown: '🔒 SFTP 备份 ▾',
983
+ webdavDropdown: '☁️ WebDAV 备份 ▾',
984
+ httpLoadBtn: '🌐 加载 URL',
985
+ zipBtn: '📦 ZIP',
986
+ statusDefault: '加载备份或选择来源',
987
+ searchPlaceholder: '在名称、文件夹和代码中搜索...',
988
+ expandAll: '全部展开',
989
+ collapseAll: '全部折叠',
990
+ typeAll: '全部',
991
+ restoreBtn: '加载到 ioBroker',
992
+ downloadBtn: '下载',
993
+ welcomeText: '上传备份或选择本地文件,<br>以查看和恢复脚本。',
994
+ noSocket: '无套接字。请检查 script-restore.{i} 是否正在运行。',
995
+ timeout: '超时:适配器无响应。script-restore.{i} 是否正在运行?',
996
+ },
997
+ };
998
+
999
+ function detectLang() {
1000
+ // 1. URL parameter
1001
+ const lp = urlParams.get('lang') || urlParams.get('language');
1002
+ if (lp) return normLang(lp);
1003
+ // 2. localStorage
1004
+ try {
1005
+ const ls = localStorage.getItem('App.language') || localStorage.getItem('ioBroker.locale');
1006
+ if (ls) return normLang(ls);
1007
+ } catch(e) {}
1008
+ // 3. Parent frame
1009
+ try {
1010
+ const p = window.parent;
1011
+ if (p && (p.sysLang || p.systemLang)) return normLang(p.sysLang || p.systemLang);
1012
+ } catch(e) {}
1013
+ // 4. Navigator
1014
+ return normLang(navigator.language || 'en');
1015
+ }
1016
+
1017
+ function normLang(l) {
1018
+ if (!l) return 'en';
1019
+ const ll = l.toLowerCase().replace('_', '-');
1020
+ if (ll === 'zh-cn' || ll === 'zh') return 'zh-cn';
1021
+ return ll.slice(0, 2);
1022
+ }
1023
+
1024
+ const LANG = (function() {
1025
+ const detected = detectLang();
1026
+ return TRANSLATIONS[detected] ? detected : 'en';
1027
+ })();
1028
+
1029
+ function t(key) {
1030
+ return (TRANSLATIONS[LANG] && TRANSLATIONS[LANG][key]) || (TRANSLATIONS.en && TRANSLATIONS.en[key]) || key;
1031
+ }
1032
+
1033
+ function applyTranslations() {
1034
+ document.querySelectorAll('[data-i18n]').forEach(function(el) {
1035
+ el.textContent = t(el.getAttribute('data-i18n'));
1036
+ });
1037
+ document.querySelectorAll('[data-i18n-html]').forEach(function(el) {
1038
+ el.innerHTML = t(el.getAttribute('data-i18n-html'));
1039
+ });
1040
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el) {
1041
+ el.placeholder = t(el.getAttribute('data-i18n-placeholder'));
1042
+ });
1043
+ document.querySelectorAll('[data-i18n-title]').forEach(function(el) {
1044
+ el.title = t(el.getAttribute('data-i18n-title'));
1045
+ });
1046
+ }
1047
+
1048
+ applyTranslations();
1049
+
489
1050
  // === Dark Theme Detection ===
490
1051
  function applyTheme(isDark) {
491
1052
  document.body.classList.toggle('dark', !!isDark);
@@ -591,14 +1152,14 @@
591
1152
 
592
1153
  function sendTo(command, message, callback) {
593
1154
  if (!socket) {
594
- callback({ error: 'Kein Socket. Bitte prüfen ob script-restore.' + instance + ' läuft.' });
1155
+ callback({ error: t('noSocket').replace('{i}', instance) });
595
1156
  return;
596
1157
  }
597
1158
  let done = false;
598
1159
  const timer = setTimeout(() => {
599
1160
  if (done) return;
600
1161
  done = true;
601
- callback({ error: 'Timeout: Adapter antwortet nicht. Läuft script-restore.' + instance + '?' });
1162
+ callback({ error: t('timeout').replace('{i}', instance) });
602
1163
  }, 30000);
603
1164
  const wrapped = (result) => {
604
1165
  if (done) return;
@@ -1004,13 +1565,13 @@
1004
1565
  document.getElementById('spinnerEl').style.display = 'none';
1005
1566
  document.getElementById('progressCircle').style.background = 'conic-gradient(var(--primary) 0%, #e9ecef 0%)';
1006
1567
  document.getElementById('progressPercent').textContent = '0%';
1007
- document.getElementById('loaderText').textContent = text || 'Laden...';
1568
+ document.getElementById('loaderText').textContent = text || t('loaderLoading');
1008
1569
  document.getElementById('loader').style.display = 'flex';
1009
1570
  }
1010
1571
  function showLoaderSpinner(text) {
1011
1572
  document.getElementById('progressContainer').style.display = 'none';
1012
1573
  document.getElementById('spinnerEl').style.display = 'block';
1013
- document.getElementById('loaderText').textContent = text || 'Verarbeite...';
1574
+ document.getElementById('loaderText').textContent = text || t('loaderProcessing');
1014
1575
  document.getElementById('loader').style.display = 'flex';
1015
1576
  }
1016
1577
  function updateProgress(pct) {
@@ -1027,7 +1588,7 @@
1027
1588
  isAllExpanded = !isAllExpanded;
1028
1589
  const btn = document.getElementById('expandToggleBtn');
1029
1590
  btn.innerHTML = isAllExpanded ? '📁' : '📂';
1030
- btn.title = isAllExpanded ? 'Alle einklappen' : 'Alle aufklappen';
1591
+ btn.title = isAllExpanded ? t('collapseAll') : t('expandAll');
1031
1592
  document.querySelectorAll('.tree-folder').forEach(el => {
1032
1593
  const p = el.dataset.path;
1033
1594
  const icon = el.querySelector('.folder-icon');
@@ -1214,12 +1775,12 @@
1214
1775
  ta.value = txt; document.body.appendChild(ta); ta.select();
1215
1776
  try {
1216
1777
  document.execCommand('copy');
1217
- btn.textContent = '✓ Kopiert!';
1778
+ btn.textContent = t('copied');
1218
1779
  btn.classList.replace('btn-outline-light', 'btn-outline-success');
1219
1780
  } finally {
1220
1781
  document.body.removeChild(ta);
1221
1782
  setTimeout(() => {
1222
- btn.textContent = 'Code Kopieren';
1783
+ btn.textContent = t('copyBtnAction');
1223
1784
  btn.classList.replace('btn-outline-success', 'btn-outline-light');
1224
1785
  }, 1500);
1225
1786
  }
@@ -1245,6 +1806,8 @@
1245
1806
  }
1246
1807
 
1247
1808
  // === Restore Modal ===
1809
+ let _restoredScriptId = null;
1810
+
1248
1811
  function openRestoreModal() {
1249
1812
  if (cur.index < 0) return;
1250
1813
  const s = scriptsData[cur.index];
@@ -1252,9 +1815,13 @@
1252
1815
  document.getElementById('rmScriptPath').textContent = s.path;
1253
1816
  document.getElementById('rmScriptType').textContent = s.type;
1254
1817
  document.getElementById('rmSuffix').value = '_rcvr';
1818
+ document.getElementById('rmSuffix').disabled = false;
1255
1819
  document.getElementById('rmMsg').textContent = '';
1256
1820
  document.getElementById('rmMsg').style.color = '';
1821
+ document.getElementById('rmWarning').style.display = 'none';
1822
+ document.getElementById('rmSuccess').style.display = 'none';
1257
1823
  document.getElementById('rmConfirmBtn').disabled = false;
1824
+ _restoredScriptId = null;
1258
1825
  updateRestorePreview();
1259
1826
  document.getElementById('restoreModalOverlay').classList.add('open');
1260
1827
  setTimeout(() => document.getElementById('rmSuffix').focus(), 50);
@@ -1262,6 +1829,10 @@
1262
1829
 
1263
1830
  function closeRestoreModal(e) {
1264
1831
  if (e && e.target !== document.getElementById('restoreModalOverlay')) return;
1832
+ document.getElementById('rmWarning').style.display = 'none';
1833
+ document.getElementById('rmSuccess').style.display = 'none';
1834
+ document.getElementById('rmSuffix').disabled = false;
1835
+ _restoredScriptId = null;
1265
1836
  document.getElementById('restoreModalOverlay').classList.remove('open');
1266
1837
  }
1267
1838
 
@@ -1274,27 +1845,66 @@
1274
1845
  document.getElementById('rmPreview').textContent = '→ script.js.' + parts.join('.');
1275
1846
  }
1276
1847
 
1277
- function confirmRestoreScript() {
1848
+ function confirmRestoreScript(overwrite) {
1278
1849
  if (cur.index < 0) return;
1279
1850
  const s = scriptsData[cur.index];
1280
- const suffix = document.getElementById('rmSuffix').value.trim() || '_rcvr';
1851
+ const suffix = document.getElementById('rmSuffix').value.trim();
1281
1852
  const msgEl = document.getElementById('rmMsg');
1282
1853
  const confirmBtn = document.getElementById('rmConfirmBtn');
1283
1854
  msgEl.style.color = '#aaa';
1284
- msgEl.textContent = '⏳ Laden...';
1855
+ msgEl.textContent = t('modalLoading');
1285
1856
  confirmBtn.disabled = true;
1286
- sendTo('restoreScript', { path: s.path, name: s.name, type: s.type, source: s.source, suffix }, function(result) {
1287
- confirmBtn.disabled = false;
1288
- if (result && result.error) {
1857
+ sendTo('restoreScript', { path: s.path, name: s.name, type: s.type, source: s.source, suffix, overwrite: !!overwrite }, function(result) {
1858
+ if (result && result.exists) {
1859
+ document.querySelector('#rmWarning p').innerHTML = t('modalWarning').replace('{path}', '<code>' + escapeHTML(result.id.replace(/^script\.js\./, '')) + '</code>');
1860
+ document.getElementById('rmWarning').style.display = 'block';
1861
+ document.getElementById('rmSuffix').disabled = true;
1862
+ msgEl.textContent = '';
1863
+ } else if (result && result.error) {
1864
+ confirmBtn.disabled = false;
1289
1865
  msgEl.style.color = '#dc3545';
1290
- msgEl.textContent = '' + result.error;
1866
+ msgEl.textContent = t('modalErrorPrefix') + result.error;
1291
1867
  } else if (result && result.success) {
1292
- msgEl.style.color = '#198754';
1293
- msgEl.textContent = '✓ Geladen!';
1294
- setTimeout(() => document.getElementById('restoreModalOverlay').classList.remove('open'), 1200);
1868
+ _restoredScriptId = result.id;
1869
+ msgEl.textContent = '';
1870
+ document.getElementById('rmSuffix').disabled = true;
1871
+ document.getElementById('rmSuccess').style.display = 'block';
1295
1872
  } else {
1873
+ confirmBtn.disabled = false;
1296
1874
  msgEl.style.color = '#dc3545';
1297
- msgEl.textContent = '✗ Unbekannter Fehler';
1875
+ msgEl.textContent = t('modalUnknownError');
1876
+ }
1877
+ });
1878
+ }
1879
+
1880
+ function dismissWarning() {
1881
+ document.getElementById('rmWarning').style.display = 'none';
1882
+ document.getElementById('rmSuffix').disabled = false;
1883
+ document.getElementById('rmConfirmBtn').disabled = false;
1884
+ document.getElementById('rmMsg').textContent = '';
1885
+ }
1886
+
1887
+ function overwriteConfirmed() {
1888
+ document.getElementById('rmWarning').style.display = 'none';
1889
+ document.getElementById('rmSuffix').disabled = false;
1890
+ confirmRestoreScript(true);
1891
+ }
1892
+
1893
+ function startRestoredScript() {
1894
+ if (!_restoredScriptId) return;
1895
+ const startBtn = document.getElementById('rmStartBtn');
1896
+ startBtn.disabled = true;
1897
+ startBtn.textContent = t('modalStarting');
1898
+ sendTo('enableScript', { id: _restoredScriptId }, function(result) {
1899
+ if (result && result.success) {
1900
+ startBtn.textContent = t('modalStarted');
1901
+ setTimeout(() => closeRestoreModal(), 1000);
1902
+ } else {
1903
+ startBtn.disabled = false;
1904
+ startBtn.textContent = t('modalStart');
1905
+ const p = document.querySelector('#rmSuccess p');
1906
+ p.innerHTML = t('modalErrorPrefix') + escapeHTML((result && result.error) || 'Unbekannt');
1907
+ p.style.color = '#dc3545';
1298
1908
  }
1299
1909
  });
1300
1910
  }