diff --git a/Dockerfile b/docker/Dockerfile similarity index 100% rename from Dockerfile rename to docker/Dockerfile diff --git a/README-DOCKER.md b/docker/README-DOCKER.md similarity index 100% rename from README-DOCKER.md rename to docker/README-DOCKER.md diff --git a/docker-entrypoint.sh b/docker/docker-entrypoint.sh similarity index 100% rename from docker-entrypoint.sh rename to docker/docker-entrypoint.sh diff --git a/ports/RGSX/languages/de.json b/ports/RGSX/languages/de.json index 6ea56e2..5c36d06 100644 --- a/ports/RGSX/languages/de.json +++ b/ports/RGSX/languages/de.json @@ -134,13 +134,12 @@ "network_auth_required": "Authentifizierung erforderlich (HTTP {0})", "network_access_denied": "Zugriff verweigert (HTTP {0})", "network_server_error": "Serverfehler (HTTP {0})", - "network_download_ok": "Download erfolgreich: {0}", - "download_already_present": " (bereits vorhanden)", + "network_download_already_queued": "Dieser Download läuft bereits", + "utils_extracted": "Extrahiert: {0}", "download_already_extracted": " (bereits extrahiert)", "download_in_progress": "Download läuft...", "download_queued": "In Download-Warteschlange", "download_started": "Download gestartet", - "utils_extracted": "Extrahiert: {0}", "utils_corrupt_zip": "Beschädigtes ZIP-Archiv: {0}", "utils_permission_denied": "Berechtigung während der Extraktion verweigert: {0}", "utils_extraction_failed": "Extraktion fehlgeschlagen: {0}", @@ -175,181 +174,188 @@ "api_key_empty_suffix": "leer", "menu_hide_premium_systems": "Premium-Systeme ausblenden", "popup_hide_premium_on": "Premium-Systeme ausgeblendet", - "popup_hide_premium_off": "Premium-Systeme sichtbar" - ,"submenu_display_font_family": "Schrift" - ,"popup_font_family_changed": "Schrift geändert: {0}" - ,"instruction_pause_language": "Sprache der Oberfläche ändern" - ,"instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen" - ,"instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren" - ,"instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren" - ,"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus" - ,"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden" - ,"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen" - ,"instruction_pause_quit": "RGSX Anwendung beenden" - ,"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen" - ,"instruction_controls_remap": "Tasten / Buttons neu zuordnen" - ,"instruction_generic_back": "Zum vorherigen Menü zurückkehren" - ,"instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten" - ,"instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen" - ,"instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln" - ,"instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden" - ,"instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten" - ,"instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}" - ,"instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind" - ,"instruction_games_history": "Vergangene Downloads und Status anzeigen" - ,"instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln" - ,"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren" - ,"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren" - ,"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten" - ,"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen" - ,"instruction_settings_web_service": "Web-Dienst Autostart beim Booten aktivieren/deaktivieren" - ,"settings_web_service": "Web-Dienst beim Booten" - ,"settings_web_service_enabled": "Aktiviert" - ,"settings_web_service_disabled": "Deaktiviert" - ,"settings_web_service_enabling": "Web-Dienst wird aktiviert..." - ,"settings_web_service_disabling": "Web-Dienst wird deaktiviert..." - ,"settings_web_service_success_enabled": "Web-Dienst beim Booten aktiviert" - ,"settings_web_service_success_disabled": "Web-Dienst beim Booten deaktiviert" - ,"settings_web_service_error": "Fehler: {0}" - ,"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)" - ,"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)" - ,"controls_desc_up": "UP ↑" - ,"controls_desc_down": "DOWN ↓" - ,"controls_desc_left": "LEFT ←" - ,"controls_desc_right": "RIGHT →" - ,"controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)" - ,"controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)" - ,"controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)" - ,"controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)" - ,"controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)" - ,"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)" - ,"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)" - ,"controls_desc_start": "Pausenmenü öffnen (z.B. Start)" - ,"controls_mapping_title": "Steuerungszuordnung" - ,"controls_mapping_instruction": "Zum Bestätigen gedrückt halten:" - ,"controls_mapping_waiting": "Warte auf eine Taste oder einen Button..." - ,"controls_mapping_press": "Drücke eine Taste oder einen Button" - ,"status_already_present": "Bereits Vorhanden" - ,"footer_joystick": "Joystick: {0}" - ,"history_game_options_title": "Spiel Optionen" - ,"history_option_download_folder": "Datei lokalisieren" - ,"history_option_extract_archive": "Archiv extrahieren" - ,"history_option_scraper": "Metadaten scrapen" - ,"history_option_delete_game": "Spiel löschen" - ,"history_option_error_info": "Fehlerdetails" - ,"history_option_retry": "Download wiederholen" - ,"history_option_back": "Zurück" - ,"history_folder_path_label": "Zielpfad:" - ,"history_scraper_not_implemented": "Scraper noch nicht implementiert" - ,"history_confirm_delete": "Dieses Spiel von der Festplatte löschen?" - ,"history_file_not_found": "Datei nicht gefunden" - ,"history_extracting": "Extrahieren..." - ,"history_extracted": "Extrahiert" - ,"history_delete_success": "Spiel erfolgreich gelöscht" - ,"history_delete_error": "Fehler beim Löschen des Spiels: {0}" - ,"history_error_details_title": "Fehlerdetails" - ,"history_no_error_message": "Keine Fehlermeldung verfügbar" - ,"web_title": "RGSX Web-Oberfläche" - ,"web_tab_platforms": "Systemliste" - ,"web_tab_downloads": "Downloads" - ,"web_tab_history": "Verlauf" - ,"web_tab_settings": "Einstellungen" - ,"web_tab_update": "Liste aktualisieren" - ,"web_tooltip_platforms": "Systemliste" - ,"web_tooltip_downloads": "Downloads" - ,"web_tooltip_history": "Verlauf" - ,"web_tooltip_settings": "Einstellungen" - ,"web_tooltip_update": "Spieleliste aktualisieren" - ,"web_search_platform": "Systeme oder Spiele suchen..." - ,"web_search_game": "Spiel suchen..." - ,"web_search_results": "Ergebnisse für" - ,"web_no_results": "Keine Ergebnisse gefunden" - ,"web_platforms": "Systeme" - ,"web_games": "Spiele" - ,"web_error_search": "Suchfehler" - ,"web_back_platforms": "Zurück zu Plattformen" - ,"web_back": "Zurück" - ,"web_game_count": "{0} ({1} Spiele)" - ,"web_download": "Herunterladen" - ,"web_cancel": "Abbrechen" - ,"web_download_canceled": "Download abgebrochen" - ,"web_confirm_cancel": "Möchten Sie diesen Download wirklich abbrechen?" - ,"web_update_title": "Spieleliste wird aktualisiert..." - ,"web_update_message": "Cache wird gelöscht und Daten neu geladen..." - ,"web_update_wait": "Dies kann 10-30 Sekunden dauern" - ,"web_error": "Fehler" - ,"web_error_unknown": "Unbekannter Fehler" - ,"web_error_update": "Fehler beim Aktualisieren der Liste: {0}" - ,"web_error_download": "Fehler: {0}" - ,"web_history_clear": "Verlauf löschen" - ,"web_history_cleared": "Verlauf erfolgreich gelöscht!" - ,"web_error_clear_history": "Fehler beim Löschen des Verlaufs: {0}" - ,"web_settings_title": "Info & Einstellungen" - ,"web_settings_roms_folder": "Benutzerdefinierter ROMs-Ordner" - ,"web_settings_roms_placeholder": "Leer lassen für Standard" - ,"web_settings_browse": "Durchsuchen" - ,"web_settings_language": "Sprache" - ,"web_settings_font_scale": "Schriftgröße" - ,"web_settings_grid": "Rasterlayout" - ,"web_settings_font_family": "Schriftart" - ,"web_settings_music": "Musik" - ,"web_settings_symlink": "Symlink-Modus" - ,"web_settings_source_mode": "Spielequelle" - ,"web_settings_custom_url": "Benutzerdefinierte URL" - ,"web_settings_custom_url_placeholder": "https://beispiel.com/spiele.zip" - ,"web_settings_save": "Einstellungen speichern" - ,"web_settings_saved": "Einstellungen erfolgreich gespeichert!" - ,"web_settings_saved_restart": "Einstellungen erfolgreich gespeichert!\\n\\n⚠️ Einige Einstellungen erfordern einen Serverneustart:\\n- Benutzerdefinierter ROMs-Ordner\\n- Sprache\\n\\nBitte starten Sie den Webserver neu, um diese Änderungen anzuwenden." - ,"web_error_save_settings": "Fehler beim Speichern der Einstellungen: {0}" - ,"web_browse_title": "Verzeichnisse durchsuchen" - ,"web_browse_select_drive": "Laufwerk auswählen..." - ,"web_browse_drives": "Laufwerke" - ,"web_browse_parent": "Übergeordnet" - ,"web_browse_select": "Diesen Ordner auswählen" - ,"web_browse_cancel": "Abbrechen" - ,"web_browse_empty": "Keine Unterverzeichnisse gefunden" - ,"web_browse_alert_restart": "Wichtig: Sie müssen die Einstellungen SPEICHERN und dann den Webserver NEUSTARTEN, damit der benutzerdefinierte ROMs-Ordner wirksam wird.\\n\\n📝 Schritte:\\n1. Klicken Sie unten auf 'Einstellungen speichern'\\n2. Stoppen Sie den Webserver (Strg+C im Terminal)\\n3. Starten Sie den Webserver neu\\n\\nAusgewählter Pfad: {0}" - ,"web_error_browse": "Fehler beim Durchsuchen der Verzeichnisse: {0}" - ,"web_loading_platforms": "Lade Plattformen..." - ,"web_loading_games": "Lade Spiele..." - ,"web_no_platforms": "Keine Plattformen gefunden" - ,"web_no_downloads": "Keine Downloads im Gange" - ,"web_history_empty": "Keine abgeschlossenen Downloads" - ,"web_history_platform": "Plattform" - ,"web_history_size": "Größe" - ,"web_history_status_completed": "Abgeschlossen" - ,"web_history_status_error": "Fehler" - ,"web_settings_os": "Betriebssystem" - ,"web_settings_platforms_count": "Anzahl der Plattformen" - ,"web_settings_show_unsupported": "Nicht unterstützte Plattformen anzeigen (System fehlt in es_systems.cfg)" - ,"web_settings_allow_unknown": "Unbekannte Erweiterungen erlauben (keine Warnungen anzeigen)" - ,"web_restart_confirm_title": "Anwendung neu starten?" - ,"web_restart_confirm_message": "Die Einstellungen wurden gespeichert. Möchten Sie die Anwendung jetzt neu starten, um die Änderungen anzuwenden?" - ,"web_restart_yes": "Ja, neu starten" - ,"web_restart_no": "Nein, später" - ,"web_restart_success": "Neustart läuft..." - ,"web_restart_error": "Fehler beim Neustart: {0}" - ,"web_support": "Support" - ,"web_support_title": "📦 Support-Datei erstellt" - ,"web_support_message": "Support-Datei erfolgreich erstellt!\\n\\n📁 Inhalt:\\n• Steuerungskonfiguration\\n• Download-Verlauf\\n• RGSX-Einstellungen\\n• Anwendungsprotokolle\\n• Webserver-Protokolle\\n\\n💬 Um Hilfe zu erhalten:\\n1. Trete dem RGSX Discord bei\\n2. Beschreibe dein Problem\\n3. Teile diese ZIP-Datei\\n\\nDownload startet..." - ,"web_support_generating": "Support-Datei wird generiert..." - ,"web_support_download": "Support-Datei herunterladen" - ,"web_support_error": "Fehler beim Erstellen der Support-Datei: {0}" - ,"web_tab_queue": "Warteschlange" - ,"web_tooltip_queue": "Download-Warteschlange" - ,"web_queue_active_download": "⏳ Ein Download ist aktiv" - ,"web_queue_no_active": "✓ Kein aktiver Download" - ,"web_queue_title": "Download-Warteschlange" - ,"web_queue_empty": "Keine Elemente in der Warteschlange" - ,"web_queue_clear": "Warteschlange löschen" - ,"web_queue_cleared": "Warteschlange erfolgreich gelöscht!" - ,"web_confirm_remove_queue": "Dieses Element aus der Warteschlange entfernen?" - ,"web_confirm_clear_queue": "Gesamte Warteschlange löschen?" - ,"web_remove": "Entfernen" - ,"web_loading": "Lädt..." - ,"web_sort": "Sortieren nach" - ,"web_sort_name_asc": "A-Z (Name)" - ,"web_sort_name_desc": "Z-A (Name)" - ,"web_sort_size_asc": "Größe +- (Klein zuerst)" - ,"web_sort_size_desc": "Größe -+ (Groß zuerst)" + "popup_hide_premium_off": "Premium-Systeme sichtbar", + "submenu_display_font_family": "Schrift", + "popup_font_family_changed": "Schrift geändert: {0}", + "instruction_pause_language": "Sprache der Oberfläche ändern", + "instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen", + "instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren", + "instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren", + "instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus", + "instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden", + "instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen", + "instruction_pause_quit": "RGSX Anwendung beenden", + "instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen", + "instruction_controls_remap": "Tasten / Buttons neu zuordnen", + "instruction_generic_back": "Zum vorherigen Menü zurückkehren", + "instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten", + "instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen", + "instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln", + "instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden", + "instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten", + "instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}", + "instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind", + "instruction_games_history": "Vergangene Downloads und Status anzeigen", + "instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln", + "instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren", + "instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren", + "instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten", + "instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen", + "instruction_settings_web_service": "Web-Dienst Autostart beim Booten aktivieren/deaktivieren", + "settings_web_service": "Web-Dienst beim Booten", + "settings_web_service_enabled": "Aktiviert", + "settings_web_service_disabled": "Deaktiviert", + "settings_web_service_enabling": "Web-Dienst wird aktiviert...", + "settings_web_service_disabling": "Web-Dienst wird deaktiviert...", + "settings_web_service_success_enabled": "Web-Dienst beim Booten aktiviert", + "settings_web_service_success_disabled": "Web-Dienst beim Booten deaktiviert", + "settings_web_service_error": "Fehler: {0}", + "controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)", + "controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)", + "controls_desc_up": "UP ↑", + "controls_desc_down": "DOWN ↓", + "controls_desc_left": "LEFT ←", + "controls_desc_right": "RIGHT →", + "controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)", + "controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)", + "controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)", + "controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)", + "controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)", + "controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)", + "controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)", + "controls_desc_start": "Pausenmenü öffnen (z.B. Start)", + "controls_mapping_title": "Steuerungszuordnung", + "controls_mapping_instruction": "Zum Bestätigen gedrückt halten:", + "controls_mapping_waiting": "Warte auf eine Taste oder einen Button...", + "controls_mapping_press": "Drücke eine Taste oder einen Button", + "status_already_present": "Bereits Vorhanden", + "footer_joystick": "Joystick: {0}", + "history_game_options_title": "Spiel Optionen", + "history_option_download_folder": "Datei lokalisieren", + "history_option_extract_archive": "Archiv extrahieren", + "history_option_scraper": "Metadaten scrapen", + "history_option_delete_game": "Spiel löschen", + "history_option_error_info": "Fehlerdetails", + "history_option_retry": "Download wiederholen", + "history_option_back": "Zurück", + "history_folder_path_label": "Zielpfad:", + "history_scraper_not_implemented": "Scraper noch nicht implementiert", + "history_confirm_delete": "Dieses Spiel von der Festplatte löschen?", + "history_file_not_found": "Datei nicht gefunden", + "history_extracting": "Extrahieren...", + "history_extracted": "Extrahiert", + "history_delete_success": "Spiel erfolgreich gelöscht", + "history_delete_error": "Fehler beim Löschen des Spiels: {0}", + "history_error_details_title": "Fehlerdetails", + "history_no_error_message": "Keine Fehlermeldung verfügbar", + "web_title": "RGSX Web-Oberfläche", + "web_tab_platforms": "Systemliste", + "web_tab_downloads": "Downloads", + "web_tab_history": "Verlauf", + "web_tab_settings": "Einstellungen", + "web_tab_update": "Liste aktualisieren", + "web_tooltip_platforms": "Systemliste", + "web_tooltip_downloads": "Downloads", + "web_tooltip_history": "Verlauf", + "web_tooltip_settings": "Einstellungen", + "web_tooltip_update": "Spieleliste aktualisieren", + "web_search_platform": "Systeme oder Spiele suchen...", + "web_search_game": "Spiel suchen...", + "web_search_results": "Ergebnisse für", + "web_no_results": "Keine Ergebnisse gefunden", + "web_platforms": "Systeme", + "web_games": "Spiele", + "web_error_search": "Suchfehler", + "web_back_platforms": "Zurück zu Plattformen", + "web_back": "Zurück", + "web_game_count": "{0} ({1} Spiele)", + "web_download": "Herunterladen", + "web_cancel": "Abbrechen", + "web_download_canceled": "Download abgebrochen", + "web_confirm_cancel": "Möchten Sie diesen Download wirklich abbrechen?", + "web_update_title": "Spieleliste wird aktualisiert...", + "web_update_message": "Cache wird gelöscht und Daten neu geladen...", + "web_update_wait": "Dies kann 10-30 Sekunden dauern", + "web_error": "Fehler", + "web_error_unknown": "Unbekannter Fehler", + "web_error_update": "Fehler beim Aktualisieren der Liste: {0}", + "web_error_download": "Fehler: {0}", + "web_history_clear": "Verlauf löschen", + "web_history_cleared": "Verlauf erfolgreich gelöscht!", + "web_error_clear_history": "Fehler beim Löschen des Verlaufs: {0}", + "web_settings_title": "Info & Einstellungen", + "web_settings_roms_folder": "Benutzerdefinierter ROMs-Ordner", + "web_settings_roms_placeholder": "Leer lassen für Standard", + "web_settings_browse": "Durchsuchen", + "web_settings_language": "Sprache", + "web_settings_font_scale": "Schriftgröße", + "web_settings_grid": "Rasterlayout", + "web_settings_font_family": "Schriftart", + "web_settings_music": "Musik", + "web_settings_symlink": "Symlink-Modus", + "web_settings_source_mode": "Spielequelle", + "web_settings_custom_url": "Benutzerdefinierte URL", + "web_settings_custom_url_placeholder": "https://beispiel.com/spiele.zip", + "web_settings_save": "Einstellungen speichern", + "web_settings_saved": "Einstellungen erfolgreich gespeichert!", + "web_settings_saved_restart": "Einstellungen erfolgreich gespeichert!\\n\\n⚠️ Einige Einstellungen erfordern einen Serverneustart:\\n- Benutzerdefinierter ROMs-Ordner\\n- Sprache\\n\\nBitte starten Sie den Webserver neu, um diese Änderungen anzuwenden.", + "web_error_save_settings": "Fehler beim Speichern der Einstellungen: {0}", + "web_browse_title": "Verzeichnisse durchsuchen", + "web_browse_select_drive": "Laufwerk auswählen...", + "web_browse_drives": "Laufwerke", + "web_browse_parent": "Übergeordnet", + "web_browse_select": "Diesen Ordner auswählen", + "web_browse_cancel": "Abbrechen", + "web_browse_empty": "Keine Unterverzeichnisse gefunden", + "web_browse_alert_restart": "Wichtig: Sie müssen die Einstellungen SPEICHERN und dann den Webserver NEUSTARTEN, damit der benutzerdefinierte ROMs-Ordner wirksam wird.\\n\\n📝 Schritte:\\n1. Klicken Sie unten auf 'Einstellungen speichern'\\n2. Stoppen Sie den Webserver (Strg+C im Terminal)\\n3. Starten Sie den Webserver neu\\n\\nAusgewählter Pfad: {0}", + "web_error_browse": "Fehler beim Durchsuchen der Verzeichnisse: {0}", + "web_loading_platforms": "Lade Plattformen...", + "web_loading_games": "Lade Spiele...", + "web_no_platforms": "Keine Plattformen gefunden", + "web_no_downloads": "Keine Downloads im Gange", + "web_history_empty": "Keine abgeschlossenen Downloads", + "web_history_platform": "Plattform", + "web_history_size": "Größe", + "web_history_status_completed": "Abgeschlossen", + "web_history_status_error": "Fehler", + "web_settings_os": "Betriebssystem", + "web_settings_platforms_count": "Anzahl der Plattformen", + "web_settings_show_unsupported": "Nicht unterstützte Plattformen anzeigen (System fehlt in es_systems.cfg)", + "web_settings_allow_unknown": "Unbekannte Erweiterungen erlauben (keine Warnungen anzeigen)", + "web_restart_confirm_title": "Anwendung neu starten?", + "web_restart_confirm_message": "Die Einstellungen wurden gespeichert. Möchten Sie die Anwendung jetzt neu starten, um die Änderungen anzuwenden?", + "web_restart_yes": "Ja, neu starten", + "web_restart_no": "Nein, später", + "web_restart_success": "Neustart läuft...", + "web_restart_error": "Fehler beim Neustart: {0}", + "web_support": "Support", + "web_support_title": "📦 Support-Datei erstellt", + "web_support_message": "Support-Datei erfolgreich erstellt!\\n\\n📁 Inhalt:\\n• Steuerungskonfiguration\\n• Download-Verlauf\\n• RGSX-Einstellungen\\n• Anwendungsprotokolle\\n• Webserver-Protokolle\\n\\n💬 Um Hilfe zu erhalten:\\n1. Trete dem RGSX Discord bei\\n2. Beschreibe dein Problem\\n3. Teile diese ZIP-Datei\\n\\nDownload startet...", + "web_support_generating": "Support-Datei wird generiert...", + "web_support_download": "Support-Datei herunterladen", + "web_support_error": "Fehler beim Erstellen der Support-Datei: {0}", + "web_tab_queue": "Warteschlange", + "web_tooltip_queue": "Download-Warteschlange", + "web_queue_active_download": "⏳ Ein Download ist aktiv", + "web_queue_no_active": "✓ Kein aktiver Download", + "web_queue_title": "Download-Warteschlange", + "web_queue_empty": "Keine Elemente in der Warteschlange", + "web_queue_clear": "Warteschlange löschen", + "web_queue_cleared": "Warteschlange erfolgreich gelöscht!", + "web_confirm_remove_queue": "Dieses Element aus der Warteschlange entfernen?", + "web_confirm_clear_queue": "Gesamte Warteschlange löschen?", + "web_remove": "Entfernen", + "web_loading": "Lädt...", + "web_sort": "Sortieren nach", + "web_sort_name_asc": "A-Z (Name)", + "web_sort_name_desc": "Z-A (Name)", + "web_sort_size_asc": "Größe +- (Klein zuerst)", + "web_sort_size_desc": "Größe -+ (Groß zuerst)", + "download_already_present": " (bereits vorhanden)", + "network_download_ok": "Download erfolgreich: {0}", + "web_filter_region": "Region", + "web_filter_hide_non_release": "Demos/Betas/Protos ausblenden", + "web_filter_regex_mode": "Regex-Suche aktivieren", + "web_filter_one_rom_per_game": "Eine ROM pro Spiel", + "web_filter_configure_priority": "Regions-Prioritätsreihenfolge konfigurieren" } \ No newline at end of file diff --git a/ports/RGSX/languages/en.json b/ports/RGSX/languages/en.json index 446f568..c57faea 100644 --- a/ports/RGSX/languages/en.json +++ b/ports/RGSX/languages/en.json @@ -353,4 +353,9 @@ ,"web_sort_name_desc": "Z-A (Name)" ,"web_sort_size_asc": "Size +- (Small first)" ,"web_sort_size_desc": "Size -+ (Large first)" + ,"web_filter_region": "Region" + ,"web_filter_hide_non_release": "Hide Demos/Betas/Protos" + ,"web_filter_regex_mode": "Enable Regex Search" + ,"web_filter_one_rom_per_game": "One ROM Per Game" + ,"web_filter_configure_priority": "Configure region priority order" } \ No newline at end of file diff --git a/ports/RGSX/languages/es.json b/ports/RGSX/languages/es.json index 6f508f5..cdfcd74 100644 --- a/ports/RGSX/languages/es.json +++ b/ports/RGSX/languages/es.json @@ -89,7 +89,7 @@ "popup_restarting": "Reiniciando...", "controls_action_clear_history": "Vaciar historial", "controls_action_history": "Historial / Descargas", - "controls_action_close_history": "Cerrar Historial", + "controls_action_close_history": "Cerrar Historial", "controls_action_delete": "Eliminar", "controls_action_space": "Espacio", "controls_action_start": "Ayuda / Configuración", @@ -140,6 +140,7 @@ "download_in_progress": "Descarga en curso...", "download_queued": "En cola de descarga", "download_started": "Descarga iniciada", + "network_download_already_queued": "Esta descarga ya está en curso", "utils_extracted": "Extraído: {0}", "utils_corrupt_zip": "Archivo ZIP corrupto: {0}", "utils_permission_denied": "Permiso denegado durante la extracción: {0}", @@ -175,181 +176,186 @@ "api_key_empty_suffix": "vacío", "menu_hide_premium_systems": "Ocultar sistemas Premium", "popup_hide_premium_on": "Sistemas Premium ocultos", - "popup_hide_premium_off": "Sistemas Premium visibles" - ,"submenu_display_font_family": "Fuente" - ,"popup_font_family_changed": "Fuente cambiada: {0}" - ,"instruction_pause_language": "Cambiar el idioma de la interfaz" - ,"instruction_pause_controls": "Ver esquema de controles o remapear" - ,"instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas" - ,"instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista" - ,"instruction_pause_settings": "Música, opción symlink y estado de claves API" - ,"instruction_pause_restart": "Reiniciar RGSX para recargar configuración" - ,"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte" - ,"instruction_pause_quit": "Salir de la aplicación RGSX" - ,"instruction_controls_help": "Mostrar referencia completa de mando y teclado" - ,"instruction_controls_remap": "Cambiar asignación de botones / teclas" - ,"instruction_generic_back": "Volver al menú anterior" - ,"instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)" - ,"instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad" - ,"instruction_display_font_family": "Cambiar entre familias de fuentes disponibles" - ,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg" - ,"instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg" - ,"instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}" - ,"instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles" - ,"instruction_games_history": "Ver descargas pasadas y su estado" - ,"instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada" - ,"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos" - ,"instruction_settings_music": "Activar o desactivar música de fondo" - ,"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones" - ,"instruction_settings_api_keys": "Ver claves API premium detectadas" - ,"instruction_settings_web_service": "Activar/desactivar inicio automático del servicio web" - ,"settings_web_service": "Servicio Web al Inicio" - ,"settings_web_service_enabled": "Activado" - ,"settings_web_service_disabled": "Desactivado" - ,"settings_web_service_enabling": "Activando servicio web..." - ,"settings_web_service_disabling": "Desactivando servicio web..." - ,"settings_web_service_success_enabled": "Servicio web activado al inicio" - ,"settings_web_service_success_disabled": "Servicio web desactivado al inicio" - ,"settings_web_service_error": "Error: {0}" - ,"controls_desc_confirm": "Confirmar (ej. A/Cruz)" - ,"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)" - ,"controls_desc_up": "UP ↑" - ,"controls_desc_down": "DOWN ↓" - ,"controls_desc_left": "LEFT ←" - ,"controls_desc_right": "RIGHT →" - ,"controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)" - ,"controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)" - ,"controls_desc_history": "Abrir historial (ej. Y/Triángulo)" - ,"controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)" - ,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)" - ,"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)" - ,"controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)" - ,"controls_desc_start": "Abrir menú pausa (ej. Start)" - ,"controls_mapping_title": "Asignación de controles" - ,"controls_mapping_instruction": "Mantén para confirmar la asignación:" - ,"controls_mapping_waiting": "Esperando una tecla o botón..." - ,"controls_mapping_press": "Pulsa una tecla o un botón" - ,"status_already_present": "Ya Presente" - ,"footer_joystick": "Joystick: {0}" - ,"history_game_options_title": "Opciones del juego" - ,"history_option_download_folder": "Localizar archivo" - ,"history_option_extract_archive": "Extraer archivo" - ,"history_option_scraper": "Scraper metadatos" - ,"history_option_delete_game": "Eliminar juego" - ,"history_option_error_info": "Detalles del error" - ,"history_option_retry": "Reintentar descarga" - ,"history_option_back": "Volver" - ,"history_folder_path_label": "Ruta de destino:" - ,"history_scraper_not_implemented": "Scraper aún no implementado" - ,"history_confirm_delete": "¿Eliminar este juego del disco?" - ,"history_file_not_found": "Archivo no encontrado" - ,"history_extracting": "Extrayendo..." - ,"history_extracted": "Extraído" - ,"history_delete_success": "Juego eliminado con éxito" - ,"history_delete_error": "Error al eliminar juego: {0}" - ,"history_error_details_title": "Detalles del error" - ,"history_no_error_message": "No hay mensaje de error disponible" - ,"web_title": "Interfaz Web RGSX" - ,"web_tab_platforms": "Lista de sistemas" - ,"web_tab_downloads": "Descargas" - ,"web_tab_history": "Historial" - ,"web_tab_settings": "Configuración" - ,"web_tab_update": "Actualizar lista" - ,"web_tooltip_platforms": "Lista de sistemas" - ,"web_tooltip_downloads": "Descargas" - ,"web_tooltip_history": "Historial" - ,"web_tooltip_settings": "Configuración" - ,"web_tooltip_update": "Actualizar lista de juegos" - ,"web_search_platform": "Buscar sistemas o juegos..." - ,"web_search_game": "Buscar un juego..." - ,"web_search_results": "resultados para" - ,"web_no_results": "No se encontraron resultados" - ,"web_platforms": "Sistemas" - ,"web_games": "Juegos" - ,"web_error_search": "Error de búsqueda" - ,"web_back_platforms": "Volver a plataformas" - ,"web_back": "Volver" - ,"web_game_count": "{0} ({1} juegos)" - ,"web_download": "Descargar" - ,"web_cancel": "Cancelar" - ,"web_download_canceled": "Descarga cancelada" - ,"web_confirm_cancel": "¿Realmente desea cancelar esta descarga?" - ,"web_update_title": "Actualizando lista de juegos..." - ,"web_update_message": "Limpiando caché y recargando datos..." - ,"web_update_wait": "Esto puede tardar 10-30 segundos" - ,"web_error": "Error" - ,"web_error_unknown": "Error desconocido" - ,"web_error_update": "Error al actualizar la lista: {0}" - ,"web_error_download": "Error: {0}" - ,"web_history_clear": "Limpiar historial" - ,"web_history_cleared": "¡Historial limpiado con éxito!" - ,"web_error_clear_history": "Error al limpiar historial: {0}" - ,"web_settings_title": "Información y Configuración" - ,"web_settings_roms_folder": "Carpeta ROMs personalizada" - ,"web_settings_roms_placeholder": "Dejar vacío para predeterminado" - ,"web_settings_browse": "Explorar" - ,"web_settings_language": "Idioma" - ,"web_settings_font_scale": "Escala de fuente" - ,"web_settings_grid": "Diseño de cuadrícula" - ,"web_settings_font_family": "Familia de fuente" - ,"web_settings_music": "Música" - ,"web_settings_symlink": "Modo symlink" - ,"web_settings_source_mode": "Fuente de juegos" - ,"web_settings_custom_url": "URL personalizada" - ,"web_settings_custom_url_placeholder": "Dejar vacío para /saves/ports/rgsx/games.zip o usar una URL directa como https://ejemplo.com/juegos.zip" - ,"web_settings_save": "Guardar configuración" - ,"web_settings_saved": "¡Configuración guardada con éxito!" - ,"web_settings_saved_restart": "¡Configuración guardada con éxito!\\n\\n⚠️ Algunos ajustes requieren reiniciar el servidor:\\n- Carpeta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie el servidor web para aplicar estos cambios." - ,"web_error_save_settings": "Error al guardar configuración: {0}" - ,"web_browse_title": "Explorar directorios" - ,"web_browse_select_drive": "Seleccione una unidad..." - ,"web_browse_drives": "Unidades" - ,"web_browse_parent": "Arriba" - ,"web_browse_select": "Seleccionar esta carpeta" - ,"web_browse_cancel": "Cancelar" - ,"web_browse_empty": "No se encontraron subdirectorios" - ,"web_browse_alert_restart": "Importante: Debe GUARDAR la configuración y luego REINICIAR el servidor web para que la carpeta ROMs personalizada tenga efecto.\\n\\n📝 Pasos:\\n1. Haga clic en 'Guardar configuración' abajo\\n2. Detenga el servidor web (Ctrl+C en terminal)\\n3. Reinicie el servidor web\\n\\nRuta seleccionada: {0}" - ,"web_error_browse": "Error al explorar directorios: {0}" - ,"web_loading_platforms": "Cargando plataformas..." - ,"web_loading_games": "Cargando juegos..." - ,"web_no_platforms": "No se encontraron plataformas" - ,"web_no_downloads": "No hay descargas en curso" - ,"web_history_empty": "No hay descargas completadas" - ,"web_history_platform": "Plataforma" - ,"web_history_size": "Tamaño" - ,"web_history_status_completed": "Completado" - ,"web_history_status_error": "Error" - ,"web_settings_os": "Sistema operativo" - ,"web_settings_platforms_count": "Número de plataformas" - ,"web_settings_show_unsupported": "Mostrar plataformas no compatibles (sistema ausente en es_systems.cfg)" - ,"web_settings_allow_unknown": "Permitir extensiones desconocidas (no mostrar advertencias)" - ,"web_restart_confirm_title": "¿Reiniciar aplicación?" - ,"web_restart_confirm_message": "Los parámetros se han guardado. ¿Desea reiniciar la aplicación ahora para aplicar los cambios?" - ,"web_restart_yes": "Sí, reiniciar" - ,"web_restart_no": "No, más tarde" - ,"web_restart_success": "Reiniciando..." - ,"web_restart_error": "Error al reiniciar: {0}" - ,"web_support": "Soporte" - ,"web_support_title": "📦 Archivo de soporte generado" - ,"web_support_message": "¡Archivo de soporte creado con éxito!\\n\\n📁 Contenido:\\n• Configuración de controles\\n• Historial de descargas\\n• Configuración RGSX\\n• Registros de la aplicación\\n• Registros del servidor web\\n\\n💬 Para obtener ayuda:\\n1. Únete al Discord de RGSX\\n2. Describe tu problema\\n3. Comparte este archivo ZIP\\n\\nLa descarga comenzará..." - ,"web_support_generating": "Generando archivo de soporte..." - ,"web_support_download": "Descargar archivo de soporte" - ,"web_support_error": "Error al generar el archivo de soporte: {0}" - ,"web_tab_queue": "Cola" - ,"web_tooltip_queue": "Cola de descargas" - ,"web_queue_active_download": "⏳ Una descarga está activa" - ,"web_queue_no_active": "✓ Sin descargas activas" - ,"web_queue_title": "Cola de Descargas" - ,"web_queue_empty": "No hay elementos en la cola" - ,"web_queue_clear": "Limpiar cola" - ,"web_queue_cleared": "¡Cola limpiada con éxito!" - ,"web_confirm_remove_queue": "¿Eliminar este elemento de la cola?" - ,"web_confirm_clear_queue": "¿Limpiar toda la cola?" - ,"web_remove": "Eliminar" - ,"web_loading": "Cargando..." - ,"web_sort": "Ordenar por" - ,"web_sort_name_asc": "A-Z (Nombre)" - ,"web_sort_name_desc": "Z-A (Nombre)" - ,"web_sort_size_asc": "Tamaño +- (Menor primero)" - ,"web_sort_size_desc": "Tamaño -+ (Mayor primero)" + "popup_hide_premium_off": "Sistemas Premium visibles", + "submenu_display_font_family": "Fuente", + "popup_font_family_changed": "Fuente cambiada: {0}", + "instruction_pause_language": "Cambiar el idioma de la interfaz", + "instruction_pause_controls": "Ver esquema de controles o remapear", + "instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas", + "instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista", + "instruction_pause_settings": "Música, opción symlink y estado de claves API", + "instruction_pause_restart": "Reiniciar RGSX para recargar configuración", + "instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte", + "instruction_pause_quit": "Salir de la aplicación RGSX", + "instruction_controls_help": "Mostrar referencia completa de mando y teclado", + "instruction_controls_remap": "Cambiar asignación de botones / teclas", + "instruction_generic_back": "Volver al menú anterior", + "instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)", + "instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad", + "instruction_display_font_family": "Cambiar entre familias de fuentes disponibles", + "instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg", + "instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg", + "instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}", + "instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles", + "instruction_games_history": "Ver descargas pasadas y su estado", + "instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada", + "instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos", + "instruction_settings_music": "Activar o desactivar música de fondo", + "instruction_settings_symlink": "Alternar uso de symlinks en instalaciones", + "instruction_settings_api_keys": "Ver claves API premium detectadas", + "instruction_settings_web_service": "Activar/desactivar inicio automático del servicio web", + "settings_web_service": "Servicio Web al Inicio", + "settings_web_service_enabled": "Activado", + "settings_web_service_disabled": "Desactivado", + "settings_web_service_enabling": "Activando servicio web...", + "settings_web_service_disabling": "Desactivando servicio web...", + "settings_web_service_success_enabled": "Servicio web activado al inicio", + "settings_web_service_success_disabled": "Servicio web desactivado al inicio", + "settings_web_service_error": "Error: {0}", + "controls_desc_confirm": "Confirmar (ej. A/Cruz)", + "controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)", + "controls_desc_up": "UP ↑", + "controls_desc_down": "DOWN ↓", + "controls_desc_left": "LEFT ←", + "controls_desc_right": "RIGHT →", + "controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)", + "controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)", + "controls_desc_history": "Abrir historial (ej. Y/Triángulo)", + "controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)", + "controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)", + "controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)", + "controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)", + "controls_desc_start": "Abrir menú pausa (ej. Start)", + "controls_mapping_title": "Asignación de controles", + "controls_mapping_instruction": "Mantén para confirmar la asignación:", + "controls_mapping_waiting": "Esperando una tecla o botón...", + "controls_mapping_press": "Pulsa una tecla o un botón", + "status_already_present": "Ya Presente", + "footer_joystick": "Joystick: {0}", + "history_game_options_title": "Opciones del juego", + "history_option_download_folder": "Localizar archivo", + "history_option_extract_archive": "Extraer archivo", + "history_option_scraper": "Scraper metadatos", + "history_option_delete_game": "Eliminar juego", + "history_option_error_info": "Detalles del error", + "history_option_retry": "Reintentar descarga", + "history_option_back": "Volver", + "history_folder_path_label": "Ruta de destino:", + "history_scraper_not_implemented": "Scraper aún no implementado", + "history_confirm_delete": "¿Eliminar este juego del disco?", + "history_file_not_found": "Archivo no encontrado", + "history_extracting": "Extrayendo...", + "history_extracted": "Extraído", + "history_delete_success": "Juego eliminado con éxito", + "history_delete_error": "Error al eliminar juego: {0}", + "history_error_details_title": "Detalles del error", + "history_no_error_message": "No hay mensaje de error disponible", + "web_title": "Interfaz Web RGSX", + "web_tab_platforms": "Lista de sistemas", + "web_tab_downloads": "Descargas", + "web_tab_history": "Historial", + "web_tab_settings": "Configuración", + "web_tab_update": "Actualizar lista", + "web_tooltip_platforms": "Lista de sistemas", + "web_tooltip_downloads": "Descargas", + "web_tooltip_history": "Historial", + "web_tooltip_settings": "Configuración", + "web_tooltip_update": "Actualizar lista de juegos", + "web_search_platform": "Buscar sistemas o juegos...", + "web_search_game": "Buscar un juego...", + "web_search_results": "resultados para", + "web_no_results": "No se encontraron resultados", + "web_platforms": "Sistemas", + "web_games": "Juegos", + "web_error_search": "Error de búsqueda", + "web_back_platforms": "Volver a plataformas", + "web_back": "Volver", + "web_game_count": "{0} ({1} juegos)", + "web_download": "Descargar", + "web_cancel": "Cancelar", + "web_download_canceled": "Descarga cancelada", + "web_confirm_cancel": "¿Realmente desea cancelar esta descarga?", + "web_update_title": "Actualizando lista de juegos...", + "web_update_message": "Limpiando caché y recargando datos...", + "web_update_wait": "Esto puede tardar 10-30 segundos", + "web_error": "Error", + "web_error_unknown": "Error desconocido", + "web_error_update": "Error al actualizar la lista: {0}", + "web_error_download": "Error: {0}", + "web_history_clear": "Limpiar historial", + "web_history_cleared": "¡Historial limpiado con éxito!", + "web_error_clear_history": "Error al limpiar historial: {0}", + "web_settings_title": "Información y Configuración", + "web_settings_roms_folder": "Carpeta ROMs personalizada", + "web_settings_roms_placeholder": "Dejar vacío para predeterminado", + "web_settings_browse": "Explorar", + "web_settings_language": "Idioma", + "web_settings_font_scale": "Escala de fuente", + "web_settings_grid": "Diseño de cuadrícula", + "web_settings_font_family": "Familia de fuente", + "web_settings_music": "Música", + "web_settings_symlink": "Modo symlink", + "web_settings_source_mode": "Fuente de juegos", + "web_settings_custom_url": "URL personalizada", + "web_settings_custom_url_placeholder": "Dejar vacío para /saves/ports/rgsx/games.zip o usar una URL directa como https://ejemplo.com/juegos.zip", + "web_settings_save": "Guardar configuración", + "web_settings_saved": "¡Configuración guardada con éxito!", + "web_settings_saved_restart": "¡Configuración guardada con éxito!\\n\\n⚠️ Algunos ajustes requieren reiniciar el servidor:\\n- Carpeta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie el servidor web para aplicar estos cambios.", + "web_error_save_settings": "Error al guardar configuración: {0}", + "web_browse_title": "Explorar directorios", + "web_browse_select_drive": "Seleccione una unidad...", + "web_browse_drives": "Unidades", + "web_browse_parent": "Arriba", + "web_browse_select": "Seleccionar esta carpeta", + "web_browse_cancel": "Cancelar", + "web_browse_empty": "No se encontraron subdirectorios", + "web_browse_alert_restart": "Importante: Debe GUARDAR la configuración y luego REINICIAR el servidor web para que la carpeta ROMs personalizada tenga efecto.\\n\\n📝 Pasos:\\n1. Haga clic en 'Guardar configuración' abajo\\n2. Detenga el servidor web (Ctrl+C en terminal)\\n3. Reinicie el servidor web\\n\\nRuta seleccionada: {0}", + "web_error_browse": "Error al explorar directorios: {0}", + "web_loading_platforms": "Cargando plataformas...", + "web_loading_games": "Cargando juegos...", + "web_no_platforms": "No se encontraron plataformas", + "web_no_downloads": "No hay descargas en curso", + "web_history_empty": "No hay descargas completadas", + "web_history_platform": "Plataforma", + "web_history_size": "Tamaño", + "web_history_status_completed": "Completado", + "web_history_status_error": "Error", + "web_settings_os": "Sistema operativo", + "web_settings_platforms_count": "Número de plataformas", + "web_settings_show_unsupported": "Mostrar plataformas no compatibles (sistema ausente en es_systems.cfg)", + "web_settings_allow_unknown": "Permitir extensiones desconocidas (no mostrar advertencias)", + "web_restart_confirm_title": "¿Reiniciar aplicación?", + "web_restart_confirm_message": "Los parámetros se han guardado. ¿Desea reiniciar la aplicación ahora para aplicar los cambios?", + "web_restart_yes": "Sí, reiniciar", + "web_restart_no": "No, más tarde", + "web_restart_success": "Reiniciando...", + "web_restart_error": "Error al reiniciar: {0}", + "web_support": "Soporte", + "web_support_title": "📦 Archivo de soporte generado", + "web_support_message": "¡Archivo de soporte creado con éxito!\\n\\n📁 Contenido:\\n• Configuración de controles\\n• Historial de descargas\\n• Configuración RGSX\\n• Registros de la aplicación\\n• Registros del servidor web\\n\\n💬 Para obtener ayuda:\\n1. Únete al Discord de RGSX\\n2. Describe tu problema\\n3. Comparte este archivo ZIP\\n\\nLa descarga comenzará...", + "web_support_generating": "Generando archivo de soporte...", + "web_support_download": "Descargar archivo de soporte", + "web_support_error": "Error al generar el archivo de soporte: {0}", + "web_tab_queue": "Cola", + "web_tooltip_queue": "Cola de descargas", + "web_queue_active_download": "⏳ Una descarga está activa", + "web_queue_no_active": "✓ Sin descargas activas", + "web_queue_title": "Cola de Descargas", + "web_queue_empty": "No hay elementos en la cola", + "web_queue_clear": "Limpiar cola", + "web_queue_cleared": "¡Cola limpiada con éxito!", + "web_confirm_remove_queue": "¿Eliminar este elemento de la cola?", + "web_confirm_clear_queue": "¿Limpiar toda la cola?", + "web_remove": "Eliminar", + "web_loading": "Cargando...", + "web_sort": "Ordenar por", + "web_sort_name_asc": "A-Z (Nombre)", + "web_sort_name_desc": "Z-A (Nombre)", + "web_sort_size_asc": "Tamaño +- (Menor primero)", + "web_sort_size_desc": "Tamaño -+ (Mayor primero)", + "web_filter_region": "Región", + "web_filter_hide_non_release": "Ocultar Demos/Betas/Protos", + "web_filter_regex_mode": "Activar búsqueda Regex", + "web_filter_one_rom_per_game": "Una ROM por juego", + "web_filter_configure_priority": "Configurar orden de prioridad de regiones" } \ No newline at end of file diff --git a/ports/RGSX/languages/fr.json b/ports/RGSX/languages/fr.json index 78712b5..1d1dfde 100644 --- a/ports/RGSX/languages/fr.json +++ b/ports/RGSX/languages/fr.json @@ -141,7 +141,7 @@ "download_queued": "En file d'attente", "download_started": "Téléchargement démarré", "network_download_already_queued": "Ce téléchargement est déjà en cours", - "utils_extracted": "Extracted: {0}", + "utils_extracted": "Extrait: {0}", "utils_corrupt_zip": "Archive ZIP corrompue: {0}", "utils_permission_denied": "Permission refusée lors de l'extraction: {0}", "utils_extraction_failed": "Échec de l'extraction: {0}", @@ -353,4 +353,9 @@ ,"web_sort_name_desc": "Z-A (Nom)" ,"web_sort_size_asc": "Taille +- (Petit d'abord)" ,"web_sort_size_desc": "Taille -+ (Grand d'abord)" + ,"web_filter_region": "Région" + ,"web_filter_hide_non_release": "Masquer Démos/Betas/Protos" + ,"web_filter_regex_mode": "Activer recherche Regex" + ,"web_filter_one_rom_per_game": "Une ROM par jeu" + ,"web_filter_configure_priority": "Configurer l'ordre de priorité des régions" } \ No newline at end of file diff --git a/ports/RGSX/languages/it.json b/ports/RGSX/languages/it.json index 5742aab..f4101e8 100644 --- a/ports/RGSX/languages/it.json +++ b/ports/RGSX/languages/it.json @@ -89,7 +89,7 @@ "popup_restarting": "Riavvio...", "controls_action_clear_history": "Cancella cronologia", "controls_action_history": "Cronologia / Downloads", - "controls_action_close_history": "Chiudi Cronologia", + "controls_action_close_history": "Chiudi Cronologia", "controls_action_delete": "Elimina", "controls_action_space": "Spazio", "controls_action_start": "Aiuto / Impostazioni", @@ -128,9 +128,16 @@ "download_in_progress": "Download in corso...", "download_queued": "In coda di download", "download_started": "Download iniziato", - "accessibility_font_size": "Dimensione carattere: {0}", - "confirm_cancel_download": "Annullare il download corrente?", - "controls_help_title": "Guida ai controlli", + "network_download_already_queued": "Questo download è già in corso", + "utils_extracted": "Estratto: {0}", + "utils_corrupt_zip": "Archivio ZIP corrotto: {0}", + "utils_permission_denied": "Permesso negato durante l'estrazione: {0}", + "utils_extraction_failed": "Estrazione fallita: {0}", + "utils_unrar_unavailable": "Comando unrar non disponibile", + "utils_rar_list_failed": "Impossibile elencare i file RAR: {0}", + "symlink_option_enabled": "Opzione symlink abilitata", + "symlink_option_disabled": "Opzione symlink disabilitata", + "menu_games_source_prefix": "Sorgente giochi", "controls_category_navigation": "Navigazione", "controls_category_main_actions": "Azioni principali", "controls_category_downloads": "Download", @@ -140,9 +147,6 @@ "controls_confirm_select": "Conferma/Seleziona", "controls_cancel_back": "Annulla/Indietro", "controls_filter_search": "Filtro/Ricerca", - "symlink_option_enabled": "Opzione symlink abilitata", - "symlink_option_disabled": "Opzione symlink disabilitata", - "menu_games_source_prefix": "Sorgente giochi", "games_source_rgsx": "RGSX", "sources_mode_rgsx_select_info": "RGSX: aggiorna l'elenco dei giochi", "games_source_custom": "Personalizzato", @@ -169,181 +173,189 @@ "api_key_empty_suffix": "vuoto", "menu_hide_premium_systems": "Nascondi sistemi Premium", "popup_hide_premium_on": "Sistemi Premium nascosti", - "popup_hide_premium_off": "Sistemi Premium visibili" - ,"submenu_display_font_family": "Font" - ,"popup_font_family_changed": "Font cambiato: {0}" - ,"instruction_pause_language": "Cambiare la lingua dell'interfaccia" - ,"instruction_pause_controls": "Vedere schema controlli o avviare rimappatura" - ,"instruction_pause_display": "Configurare layout, font e visibilità sistemi" - ,"instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco" - ,"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API" - ,"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione" - ,"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto" - ,"instruction_pause_quit": "Uscire dall'applicazione RGSX" - ,"instruction_controls_help": "Mostrare riferimento completo controller & tastiera" - ,"instruction_controls_remap": "Modificare associazione pulsanti / tasti" - ,"instruction_generic_back": "Tornare al menu precedente" - ,"instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)" - ,"instruction_display_font_size": "Regolare dimensione testo per leggibilità" - ,"instruction_display_font_family": "Cambiare famiglia di font disponibile" - ,"instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg" - ,"instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg" - ,"instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}" - ,"instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili" - ,"instruction_games_history": "Elencare download passati e stato" - ,"instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata" - ,"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi" - ,"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo" - ,"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni" - ,"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate" - ,"instruction_settings_web_service": "Attivare/disattivare avvio automatico servizio web all'avvio" - ,"settings_web_service": "Servizio Web all'Avvio" - ,"settings_web_service_enabled": "Abilitato" - ,"settings_web_service_disabled": "Disabilitato" - ,"settings_web_service_enabling": "Abilitazione servizio web..." - ,"settings_web_service_disabling": "Disabilitazione servizio web..." - ,"settings_web_service_success_enabled": "Servizio web abilitato all'avvio" - ,"settings_web_service_success_disabled": "Servizio web disabilitato all'avvio" - ,"settings_web_service_error": "Errore: {0}" - ,"controls_desc_confirm": "Confermare (es. A/Croce)" - ,"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)" - ,"controls_desc_up": "UP ↑" - ,"controls_desc_down": "DOWN ↓" - ,"controls_desc_left": "LEFT ←" - ,"controls_desc_right": "RIGHT →" - ,"controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)" - ,"controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)" - ,"controls_desc_history": "Aprire cronologia (es. Y/Triangolo)" - ,"controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)" - ,"controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)" - ,"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)" - ,"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)" - ,"controls_desc_start": "Aprire menu pausa (es. Start)" - ,"controls_mapping_title": "Mappatura controlli" - ,"controls_mapping_instruction": "Tieni premuto per confermare l'associazione:" - ,"controls_mapping_waiting": "In attesa di un tasto o pulsante..." - ,"controls_mapping_press": "Premi un tasto o un pulsante" - ,"status_already_present": "Già Presente" - ,"footer_joystick": "Joystick: {0}" - ,"history_game_options_title": "Opzioni gioco" - ,"history_option_download_folder": "Localizza file" - ,"history_option_extract_archive": "Estrai archivio" - ,"history_option_scraper": "Scraper metadati" - ,"history_option_delete_game": "Elimina gioco" - ,"history_option_error_info": "Dettagli errore" - ,"history_option_retry": "Riprova download" - ,"history_option_back": "Indietro" - ,"history_folder_path_label": "Percorso destinazione:" - ,"history_scraper_not_implemented": "Scraper non ancora implementato" - ,"history_confirm_delete": "Eliminare questo gioco dal disco?" - ,"history_file_not_found": "File non trovato" - ,"history_extracting": "Estrazione in corso..." - ,"history_extracted": "Estratto" - ,"history_delete_success": "Gioco eliminato con successo" - ,"history_delete_error": "Errore durante l'eliminazione del gioco: {0}" - ,"history_error_details_title": "Dettagli errore" - ,"history_no_error_message": "Nessun messaggio di errore disponibile" - ,"web_title": "Interfaccia Web RGSX" - ,"web_tab_platforms": "Elenco sistemi" - ,"web_tab_downloads": "Download" - ,"web_tab_history": "Cronologia" - ,"web_tab_settings": "Impostazioni" - ,"web_tab_update": "Aggiorna elenco" - ,"web_tooltip_platforms": "Elenco sistemi" - ,"web_tooltip_downloads": "Download" - ,"web_tooltip_history": "Cronologia" - ,"web_tooltip_settings": "Impostazioni" - ,"web_tooltip_update": "Aggiorna elenco giochi" - ,"web_search_platform": "Cerca sistemi o giochi..." - ,"web_search_game": "Cerca un gioco..." - ,"web_search_results": "risultati per" - ,"web_no_results": "Nessun risultato trovato" - ,"web_platforms": "Sistemi" - ,"web_games": "Giochi" - ,"web_error_search": "Errore di ricerca" - ,"web_back_platforms": "Torna alle piattaforme" - ,"web_back": "Indietro" - ,"web_game_count": "{0} ({1} giochi)" - ,"web_download": "Scarica" - ,"web_cancel": "Annulla" - ,"web_download_canceled": "Download annullato" - ,"web_confirm_cancel": "Vuoi davvero annullare questo download?" - ,"web_update_title": "Aggiornamento elenco giochi..." - ,"web_update_message": "Pulizia cache e ricaricamento dati..." - ,"web_update_wait": "Potrebbe richiedere 10-30 secondi" - ,"web_error": "Errore" - ,"web_error_unknown": "Errore sconosciuto" - ,"web_error_update": "Errore durante l'aggiornamento dell'elenco: {0}" - ,"web_error_download": "Errore: {0}" - ,"web_history_clear": "Cancella cronologia" - ,"web_history_cleared": "Cronologia cancellata con successo!" - ,"web_error_clear_history": "Errore durante la cancellazione della cronologia: {0}" - ,"web_settings_title": "Info e Impostazioni" - ,"web_settings_roms_folder": "Cartella ROMs personalizzata" - ,"web_settings_roms_placeholder": "Lasciare vuoto per predefinito" - ,"web_settings_browse": "Sfoglia" - ,"web_settings_language": "Lingua" - ,"web_settings_font_scale": "Scala carattere" - ,"web_settings_grid": "Layout griglia" - ,"web_settings_font_family": "Famiglia carattere" - ,"web_settings_music": "Musica" - ,"web_settings_symlink": "Modalità symlink" - ,"web_settings_source_mode": "Fonte giochi" - ,"web_settings_custom_url": "URL personalizzato" - ,"web_settings_custom_url_placeholder": " Lasciare vuoto per /saves/ports/rgsx/games.zip o usare una URL diretta come https://esempio.com/giochi.zip" - ,"web_settings_save": "Salva impostazioni" - ,"web_settings_saved": "Impostazioni salvate con successo!" - ,"web_settings_saved_restart": "Impostazioni salvate con successo!\\n\\n⚠️ Alcune impostazioni richiedono il riavvio del server:\\n- Cartella ROMs personalizzata\\n- Lingua\\n\\nRiavviare il server web per applicare queste modifiche." - ,"web_error_save_settings": "Errore durante il salvataggio delle impostazioni: {0}" - ,"web_browse_title": "Sfoglia directory" - ,"web_browse_select_drive": "Seleziona un'unità..." - ,"web_browse_drives": "Unità" - ,"web_browse_parent": "Superiore" - ,"web_browse_select": "Seleziona questa cartella" - ,"web_browse_cancel": "Annulla" - ,"web_browse_empty": "Nessuna sottodirectory trovata" - ,"web_browse_alert_restart": "Importante: È necessario SALVARE le impostazioni e poi RIAVVIARE il server web affinché la cartella ROMs personalizzata abbia effetto.\\n\\n📝 Passaggi:\\n1. Fare clic su 'Salva impostazioni' qui sotto\\n2. Arrestare il server web (Ctrl+C nel terminale)\\n3. Riavviare il server web\\n\\nPercorso selezionato: {0}" - ,"web_error_browse": "Errore durante la navigazione delle directory: {0}" - ,"web_loading_platforms": "Caricamento piattaforme..." - ,"web_loading_games": "Caricamento giochi..." - ,"web_no_platforms": "Nessuna piattaforma trovata" - ,"web_no_downloads": "Nessun download in corso" - ,"web_history_empty": "Nessun download completato" - ,"web_history_platform": "Piattaforma" - ,"web_history_size": "Dimensione" - ,"web_history_status_completed": "Completato" - ,"web_history_status_error": "Errore" - ,"web_settings_os": "Sistema operativo" - ,"web_settings_platforms_count": "Numero di piattaforme" - ,"web_settings_show_unsupported": "Mostra piattaforme non supportate (sistema assente in es_systems.cfg)" - ,"web_settings_allow_unknown": "Consenti estensioni sconosciute (non mostrare avvisi)" - ,"web_restart_confirm_title": "Riavviare l'applicazione?" - ,"web_restart_confirm_message": "Le impostazioni sono state salvate. Vuoi riavviare l'applicazione ora per applicare le modifiche?" - ,"web_restart_yes": "Sì, riavvia" - ,"web_restart_no": "No, più tardi" - ,"web_restart_success": "Riavvio in corso..." - ,"web_restart_error": "Errore durante il riavvio: {0}" - ,"web_support": "Supporto" - ,"web_support_title": "📦 File di supporto generato" - ,"web_support_message": "File di supporto creato con successo!\\n\\n📁 Contenuto:\\n• Configurazione controlli\\n• Cronologia download\\n• Impostazioni RGSX\\n• Log dell'applicazione\\n• Log del server web\\n\\n💬 Per ottenere aiuto:\\n1. Unisciti al Discord RGSX\\n2. Descrivi il tuo problema\\n3. Condividi questo file ZIP\\n\\nIl download inizierà..." - ,"web_support_generating": "Generazione file di supporto..." - ,"web_support_download": "Scarica file di supporto" - ,"web_support_error": "Errore nella generazione del file di supporto: {0}" - ,"web_tab_queue": "Coda" - ,"web_tooltip_queue": "Coda di download" - ,"web_queue_active_download": "⏳ Un download è attivo" - ,"web_queue_no_active": "✓ Nessun download attivo" - ,"web_queue_title": "Coda di Download" - ,"web_queue_empty": "Nessun elemento in coda" - ,"web_queue_clear": "Svuota coda" - ,"web_queue_cleared": "Coda svuotata con successo!" - ,"web_confirm_remove_queue": "Rimuovere questo elemento dalla coda?" - ,"web_confirm_clear_queue": "Svuotare l'intera coda?" - ,"web_remove": "Rimuovi" - ,"web_loading": "Caricamento..." - ,"web_sort": "Ordina per" - ,"web_sort_name_asc": "A-Z (Nome)" - ,"web_sort_name_desc": "Z-A (Nome)" - ,"web_sort_size_asc": "Dimensione +- (Piccolo primo)" - ,"web_sort_size_desc": "Dimensione -+ (Grande primo)" + "popup_hide_premium_off": "Sistemi Premium visibili", + "submenu_display_font_family": "Font", + "popup_font_family_changed": "Font cambiato: {0}", + "instruction_pause_language": "Cambiare la lingua dell'interfaccia", + "instruction_pause_controls": "Vedere schema controlli o avviare rimappatura", + "instruction_pause_display": "Configurare layout, font e visibilità sistemi", + "instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco", + "instruction_pause_settings": "Musica, opzione symlink e stato chiavi API", + "instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione", + "instruction_pause_support": "Genera un file ZIP diagnostico per il supporto", + "instruction_pause_quit": "Uscire dall'applicazione RGSX", + "instruction_controls_help": "Mostrare riferimento completo controller & tastiera", + "instruction_controls_remap": "Modificare associazione pulsanti / tasti", + "instruction_generic_back": "Tornare al menu precedente", + "instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)", + "instruction_display_font_size": "Regolare dimensione testo per leggibilità", + "instruction_display_font_family": "Cambiare famiglia di font disponibile", + "instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg", + "instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg", + "instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}", + "instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili", + "instruction_games_history": "Elencare download passati e stato", + "instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata", + "instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi", + "instruction_settings_music": "Abilitare o disabilitare musica di sottofondo", + "instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni", + "instruction_settings_api_keys": "Mostrare chiavi API premium rilevate", + "instruction_settings_web_service": "Attivare/disattivare avvio automatico servizio web all'avvio", + "settings_web_service": "Servizio Web all'Avvio", + "settings_web_service_enabled": "Abilitato", + "settings_web_service_disabled": "Disabilitato", + "settings_web_service_enabling": "Abilitazione servizio web...", + "settings_web_service_disabling": "Disabilitazione servizio web...", + "settings_web_service_success_enabled": "Servizio web abilitato all'avvio", + "settings_web_service_success_disabled": "Servizio web disabilitato all'avvio", + "settings_web_service_error": "Errore: {0}", + "controls_desc_confirm": "Confermare (es. A/Croce)", + "controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)", + "controls_desc_up": "UP ↑", + "controls_desc_down": "DOWN ↓", + "controls_desc_left": "LEFT ←", + "controls_desc_right": "RIGHT →", + "controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)", + "controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)", + "controls_desc_history": "Aprire cronologia (es. Y/Triangolo)", + "controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)", + "controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)", + "controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)", + "controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)", + "controls_desc_start": "Aprire menu pausa (es. Start)", + "controls_mapping_title": "Mappatura controlli", + "controls_mapping_instruction": "Tieni premuto per confermare l'associazione:", + "controls_mapping_waiting": "In attesa di un tasto o pulsante...", + "controls_mapping_press": "Premi un tasto o un pulsante", + "status_already_present": "Già Presente", + "footer_joystick": "Joystick: {0}", + "history_game_options_title": "Opzioni gioco", + "history_option_download_folder": "Localizza file", + "history_option_extract_archive": "Estrai archivio", + "history_option_scraper": "Scraper metadati", + "history_option_delete_game": "Elimina gioco", + "history_option_error_info": "Dettagli errore", + "history_option_retry": "Riprova download", + "history_option_back": "Indietro", + "history_folder_path_label": "Percorso destinazione:", + "history_scraper_not_implemented": "Scraper non ancora implementato", + "history_confirm_delete": "Eliminare questo gioco dal disco?", + "history_file_not_found": "File non trovato", + "history_extracting": "Estrazione in corso...", + "history_extracted": "Estratto", + "history_delete_success": "Gioco eliminato con successo", + "history_delete_error": "Errore durante l'eliminazione del gioco: {0}", + "history_error_details_title": "Dettagli errore", + "history_no_error_message": "Nessun messaggio di errore disponibile", + "web_title": "Interfaccia Web RGSX", + "web_tab_platforms": "Elenco sistemi", + "web_tab_downloads": "Download", + "web_tab_history": "Cronologia", + "web_tab_settings": "Impostazioni", + "web_tab_update": "Aggiorna elenco", + "web_tooltip_platforms": "Elenco sistemi", + "web_tooltip_downloads": "Download", + "web_tooltip_history": "Cronologia", + "web_tooltip_settings": "Impostazioni", + "web_tooltip_update": "Aggiorna elenco giochi", + "web_search_platform": "Cerca sistemi o giochi...", + "web_search_game": "Cerca un gioco...", + "web_search_results": "risultati per", + "web_no_results": "Nessun risultato trovato", + "web_platforms": "Sistemi", + "web_games": "Giochi", + "web_error_search": "Errore di ricerca", + "web_back_platforms": "Torna alle piattaforme", + "web_back": "Indietro", + "web_game_count": "{0} ({1} giochi)", + "web_download": "Scarica", + "web_cancel": "Annulla", + "web_download_canceled": "Download annullato", + "web_confirm_cancel": "Vuoi davvero annullare questo download?", + "web_update_title": "Aggiornamento elenco giochi...", + "web_update_message": "Pulizia cache e ricaricamento dati...", + "web_update_wait": "Potrebbe richiedere 10-30 secondi", + "web_error": "Errore", + "web_error_unknown": "Errore sconosciuto", + "web_error_update": "Errore durante l'aggiornamento dell'elenco: {0}", + "web_error_download": "Errore: {0}", + "web_history_clear": "Cancella cronologia", + "web_history_cleared": "Cronologia cancellata con successo!", + "web_error_clear_history": "Errore durante la cancellazione della cronologia: {0}", + "web_settings_title": "Info e Impostazioni", + "web_settings_roms_folder": "Cartella ROMs personalizzata", + "web_settings_roms_placeholder": "Lasciare vuoto per predefinito", + "web_settings_browse": "Sfoglia", + "web_settings_language": "Lingua", + "web_settings_font_scale": "Scala carattere", + "web_settings_grid": "Layout griglia", + "web_settings_font_family": "Famiglia carattere", + "web_settings_music": "Musica", + "web_settings_symlink": "Modalità symlink", + "web_settings_source_mode": "Fonte giochi", + "web_settings_custom_url": "URL personalizzato", + "web_settings_custom_url_placeholder": " Lasciare vuoto per /saves/ports/rgsx/games.zip o usare una URL diretta come https://esempio.com/giochi.zip", + "web_settings_save": "Salva impostazioni", + "web_settings_saved": "Impostazioni salvate con successo!", + "web_settings_saved_restart": "Impostazioni salvate con successo!\\n\\n⚠️ Alcune impostazioni richiedono il riavvio del server:\\n- Cartella ROMs personalizzata\\n- Lingua\\n\\nRiavviare il server web per applicare queste modifiche.", + "web_error_save_settings": "Errore durante il salvataggio delle impostazioni: {0}", + "web_browse_title": "Sfoglia directory", + "web_browse_select_drive": "Seleziona un'unità...", + "web_browse_drives": "Unità", + "web_browse_parent": "Superiore", + "web_browse_select": "Seleziona questa cartella", + "web_browse_cancel": "Annulla", + "web_browse_empty": "Nessuna sottodirectory trovata", + "web_browse_alert_restart": "Importante: È necessario SALVARE le impostazioni e poi RIAVVIARE il server web affinché la cartella ROMs personalizzata abbia effetto.\\n\\n📝 Passaggi:\\n1. Fare clic su 'Salva impostazioni' qui sotto\\n2. Arrestare il server web (Ctrl+C nel terminale)\\n3. Riavviare il server web\\n\\nPercorso selezionato: {0}", + "web_error_browse": "Errore durante la navigazione delle directory: {0}", + "web_loading_platforms": "Caricamento piattaforme...", + "web_loading_games": "Caricamento giochi...", + "web_no_platforms": "Nessuna piattaforma trovata", + "web_no_downloads": "Nessun download in corso", + "web_history_empty": "Nessun download completato", + "web_history_platform": "Piattaforma", + "web_history_size": "Dimensione", + "web_history_status_completed": "Completato", + "web_history_status_error": "Errore", + "web_settings_os": "Sistema operativo", + "web_settings_platforms_count": "Numero di piattaforme", + "web_settings_show_unsupported": "Mostra piattaforme non supportate (sistema assente in es_systems.cfg)", + "web_settings_allow_unknown": "Consenti estensioni sconosciute (non mostrare avvisi)", + "web_restart_confirm_title": "Riavviare l'applicazione?", + "web_restart_confirm_message": "Le impostazioni sono state salvate. Vuoi riavviare l'applicazione ora per applicare le modifiche?", + "web_restart_yes": "Sì, riavvia", + "web_restart_no": "No, più tardi", + "web_restart_success": "Riavvio in corso...", + "web_restart_error": "Errore durante il riavvio: {0}", + "web_support": "Supporto", + "web_support_title": "📦 File di supporto generato", + "web_support_message": "File di supporto creato con successo!\\n\\n📁 Contenuto:\\n• Configurazione controlli\\n• Cronologia download\\n• Impostazioni RGSX\\n• Log dell'applicazione\\n• Log del server web\\n\\n💬 Per ottenere aiuto:\\n1. Unisciti al Discord RGSX\\n2. Descrivi il tuo problema\\n3. Condividi questo file ZIP\\n\\nIl download inizierà...", + "web_support_generating": "Generazione file di supporto...", + "web_support_download": "Scarica file di supporto", + "web_support_error": "Errore nella generazione del file di supporto: {0}", + "web_tab_queue": "Coda", + "web_tooltip_queue": "Coda di download", + "web_queue_active_download": "⏳ Un download è attivo", + "web_queue_no_active": "✓ Nessun download attivo", + "web_queue_title": "Coda di Download", + "web_queue_empty": "Nessun elemento in coda", + "web_queue_clear": "Svuota coda", + "web_queue_cleared": "Coda svuotata con successo!", + "web_confirm_remove_queue": "Rimuovere questo elemento dalla coda?", + "web_confirm_clear_queue": "Svuotare l'intera coda?", + "web_remove": "Rimuovi", + "web_loading": "Caricamento...", + "web_sort": "Ordina per", + "web_sort_name_asc": "A-Z (Nome)", + "web_sort_name_desc": "Z-A (Nome)", + "web_sort_size_asc": "Dimensione +- (Piccolo primo)", + "web_sort_size_desc": "Dimensione -+ (Grande primo)", + "accessibility_font_size": "Dimensione carattere: {0}", + "confirm_cancel_download": "Annullare il download corrente?", + "controls_help_title": "Guida ai controlli", + "web_filter_region": "Regione", + "web_filter_hide_non_release": "Nascondi Demo/Beta/Proto", + "web_filter_regex_mode": "Attiva ricerca Regex", + "web_filter_one_rom_per_game": "Una ROM per gioco", + "web_filter_configure_priority": "Configura ordine di priorità delle regioni" } \ No newline at end of file diff --git a/ports/RGSX/languages/pt.json b/ports/RGSX/languages/pt.json index c34f9b3..2b97f2a 100644 --- a/ports/RGSX/languages/pt.json +++ b/ports/RGSX/languages/pt.json @@ -89,7 +89,7 @@ "popup_restarting": "Reiniciando...", "controls_action_clear_history": "Limpar histórico", "controls_action_history": "Histórico / Downloads", - "controls_action_close_history": "Fechar Histórico", + "controls_action_close_history": "Fechar Histórico", "controls_action_delete": "Deletar", "controls_action_space": "Espaço", "controls_action_start": "Ajuda / Configurações", @@ -128,7 +128,13 @@ "download_in_progress": "Download em andamento...", "download_queued": "Na fila de download", "download_started": "Download iniciado", - "accessibility_font_size": "Tamanho da fonte: {0}", + "network_download_already_queued": "Este download já está em andamento", + "utils_extracted": "Extraído: {0}", + "utils_corrupt_zip": "Arquivo ZIP corrupto: {0}", + "utils_permission_denied": "Permissão negada durante a extração: {0}", + "utils_extraction_failed": "Falha na extração: {0}", + "utils_unrar_unavailable": "Comando unrar não disponível", + "utils_rar_list_failed": "Falha ao listar arquivos RAR: {0}", "confirm_cancel_download": "Cancelar download atual?", "controls_help_title": "Ajuda de Controles", "controls_category_navigation": "Navegação", @@ -169,181 +175,187 @@ "api_key_empty_suffix": "vazio", "menu_hide_premium_systems": "Ocultar sistemas Premium", "popup_hide_premium_on": "Sistemas Premium ocultos", - "popup_hide_premium_off": "Sistemas Premium visíveis" - ,"submenu_display_font_family": "Fonte" - ,"popup_font_family_changed": "Fonte alterada: {0}" - ,"instruction_pause_language": "Alterar o idioma da interface" - ,"instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento" - ,"instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas" - ,"instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista" - ,"instruction_pause_settings": "Música, opção symlink e status das chaves API" - ,"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração" - ,"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte" - ,"instruction_pause_quit": "Sair da aplicação RGSX" - ,"instruction_controls_help": "Mostrar referência completa de controle e teclado" - ,"instruction_controls_remap": "Modificar associação de botões / teclas" - ,"instruction_generic_back": "Voltar ao menu anterior" - ,"instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)" - ,"instruction_display_font_size": "Ajustar tamanho do texto para legibilidade" - ,"instruction_display_font_family": "Alternar entre famílias de fontes disponíveis" - ,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg" - ,"instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg" - ,"instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}" - ,"instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis" - ,"instruction_games_history": "Listar downloads anteriores e status" - ,"instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada" - ,"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos" - ,"instruction_settings_music": "Ativar ou desativar música de fundo" - ,"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações" - ,"instruction_settings_api_keys": "Ver chaves API premium detectadas" - ,"instruction_settings_web_service": "Ativar/desativar início automático do serviço web na inicialização" - ,"settings_web_service": "Serviço Web na Inicialização" - ,"settings_web_service_enabled": "Ativado" - ,"settings_web_service_disabled": "Desativado" - ,"settings_web_service_enabling": "Ativando serviço web..." - ,"settings_web_service_disabling": "Desativando serviço web..." - ,"settings_web_service_success_enabled": "Serviço web ativado na inicialização" - ,"settings_web_service_success_disabled": "Serviço web desativado na inicialização" - ,"settings_web_service_error": "Erro: {0}" - ,"controls_desc_confirm": "Confirmar (ex. A/Cruz)" - ,"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)" - ,"controls_desc_up": "UP ↑" - ,"controls_desc_down": "DOWN ↓" - ,"controls_desc_left": "LEFT ←" - ,"controls_desc_right": "RIGHT →" - ,"controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)" - ,"controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)" - ,"controls_desc_history": "Abrir histórico (ex. Y/Triângulo)" - ,"controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)" - ,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)" - ,"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)" - ,"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)" - ,"controls_desc_start": "Abrir menu pausa (ex. Start)" - ,"controls_mapping_title": "Mapeamento de controles" - ,"controls_mapping_instruction": "Mantenha para confirmar o mapeamento:" - ,"controls_mapping_waiting": "Aguardando uma tecla ou botão..." - ,"controls_mapping_press": "Pressione uma tecla ou um botão" - ,"status_already_present": "Já Presente" - ,"footer_joystick": "Joystick: {0}" - ,"history_game_options_title": "Opções do jogo" - ,"history_option_download_folder": "Localizar arquivo" - ,"history_option_extract_archive": "Extrair arquivo" - ,"history_option_scraper": "Scraper metadados" - ,"history_option_delete_game": "Excluir jogo" - ,"history_option_error_info": "Detalhes do erro" - ,"history_option_retry": "Tentar novamente" - ,"history_option_back": "Voltar" - ,"history_folder_path_label": "Caminho de destino:" - ,"history_scraper_not_implemented": "Scraper ainda não implementado" - ,"history_confirm_delete": "Excluir este jogo do disco?" - ,"history_file_not_found": "Arquivo não encontrado" - ,"history_extracting": "Extraindo..." - ,"history_extracted": "Extraído" - ,"history_delete_success": "Jogo excluído com sucesso" - ,"history_delete_error": "Erro ao excluir jogo: {0}" - ,"history_error_details_title": "Detalhes do erro" - ,"history_no_error_message": "Nenhuma mensagem de erro disponível" - ,"web_title": "Interface Web RGSX" - ,"web_tab_platforms": "Lista de sistemas" - ,"web_tab_downloads": "Downloads" - ,"web_tab_history": "Histórico" - ,"web_tab_settings": "Configurações" - ,"web_tab_update": "Atualizar lista" - ,"web_tooltip_platforms": "Lista de sistemas" - ,"web_tooltip_downloads": "Downloads" - ,"web_tooltip_history": "Histórico" - ,"web_tooltip_settings": "Configurações" - ,"web_tooltip_update": "Atualizar lista de jogos" - ,"web_search_platform": "Pesquisar sistemas ou jogos..." - ,"web_search_game": "Pesquisar um jogo..." - ,"web_search_results": "resultados para" - ,"web_no_results": "Nenhum resultado encontrado" - ,"web_platforms": "Sistemas" - ,"web_games": "Jogos" - ,"web_error_search": "Erro de pesquisa" - ,"web_back_platforms": "Voltar às plataformas" - ,"web_back": "Voltar" - ,"web_game_count": "{0} ({1} jogos)" - ,"web_download": "Baixar" - ,"web_cancel": "Cancelar" - ,"web_download_canceled": "Download cancelado" - ,"web_confirm_cancel": "Você realmente deseja cancelar este download?" - ,"web_update_title": "Atualizando lista de jogos..." - ,"web_update_message": "Limpando cache e recarregando dados..." - ,"web_update_wait": "Isso pode levar 10-30 segundos" - ,"web_error": "Erro" - ,"web_error_unknown": "Erro desconhecido" - ,"web_error_update": "Erro ao atualizar a lista: {0}" - ,"web_error_download": "Erro: {0}" - ,"web_history_clear": "Limpar histórico" - ,"web_history_cleared": "Histórico limpo com sucesso!" - ,"web_error_clear_history": "Erro ao limpar histórico: {0}" - ,"web_settings_title": "Informações e Configurações" - ,"web_settings_roms_folder": "Pasta ROMs personalizada" - ,"web_settings_roms_placeholder": "Deixar vazio para padrão" - ,"web_settings_browse": "Procurar" - ,"web_settings_language": "Idioma" - ,"web_settings_font_scale": "Escala de fonte" - ,"web_settings_grid": "Layout de grade" - ,"web_settings_font_family": "Família de fonte" - ,"web_settings_music": "Música" - ,"web_settings_symlink": "Modo symlink" - ,"web_settings_source_mode": "Fonte de jogos" - ,"web_settings_custom_url": "URL personalizada" - ,"web_settings_custom_url_placeholder": "Deixar vazio para /saves/ports/rgsx/games.zip ou usar uma URL direta como https://example.com/games.zip" - ,"web_settings_save": "Salvar configurações" - ,"web_settings_saved": "Configurações salvas com sucesso!" - ,"web_settings_saved_restart": "Configurações salvas com sucesso!\\n\\n⚠️ Algumas configurações exigem reiniciar o servidor:\\n- Pasta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie o servidor web para aplicar essas alterações." - ,"web_error_save_settings": "Erro ao salvar configurações: {0}" - ,"web_browse_title": "Procurar diretórios" - ,"web_browse_select_drive": "Selecione uma unidade..." - ,"web_browse_drives": "Unidades" - ,"web_browse_parent": "Acima" - ,"web_browse_select": "Selecionar esta pasta" - ,"web_browse_cancel": "Cancelar" - ,"web_browse_empty": "Nenhum subdiretório encontrado" - ,"web_browse_alert_restart": "Importante: Você precisa SALVAR as configurações e então REINICIAR o servidor web para que a pasta ROMs personalizada tenha efeito.\\n\\n📝 Passos:\\n1. Clique em 'Salvar configurações' abaixo\\n2. Pare o servidor web (Ctrl+C no terminal)\\n3. Reinicie o servidor web\\n\\nCaminho selecionado: {0}" - ,"web_error_browse": "Erro ao procurar diretórios: {0}" - ,"web_loading_platforms": "Carregando plataformas..." - ,"web_loading_games": "Carregando jogos..." - ,"web_no_platforms": "Nenhuma plataforma encontrada" - ,"web_no_downloads": "Nenhum download em andamento" - ,"web_history_empty": "Nenhum download concluído" - ,"web_history_platform": "Plataforma" - ,"web_history_size": "Tamanho" - ,"web_history_status_completed": "Concluído" - ,"web_history_status_error": "Erro" - ,"web_settings_os": "Sistema operacional" - ,"web_settings_platforms_count": "Número de plataformas" - ,"web_settings_show_unsupported": "Mostrar plataformas não suportadas (sistema ausente em es_systems.cfg)" - ,"web_settings_allow_unknown": "Permitir extensões desconhecidas (não mostrar avisos)" - ,"web_restart_confirm_title": "Reiniciar aplicação?" - ,"web_restart_confirm_message": "As configurações foram salvas. Deseja reiniciar a aplicação agora para aplicar as alterações?" - ,"web_restart_yes": "Sim, reiniciar" - ,"web_restart_no": "Não, mais tarde" - ,"web_restart_success": "Reiniciando..." - ,"web_restart_error": "Erro ao reiniciar: {0}" - ,"web_support": "Suporte" - ,"web_support_title": "📦 Arquivo de suporte gerado" - ,"web_support_message": "Arquivo de suporte criado com sucesso!\\n\\n📁 Conteúdo:\\n• Configuração de controles\\n• Histórico de downloads\\n• Configurações RGSX\\n• Logs da aplicação\\n• Logs do servidor web\\n\\n💬 Para obter ajuda:\\n1. Entre no Discord RGSX\\n2. Descreva seu problema\\n3. Compartilhe este arquivo ZIP\\n\\nO download vai começar..." - ,"web_support_generating": "Gerando arquivo de suporte..." - ,"web_support_download": "Baixar arquivo de suporte" - ,"web_support_error": "Erro ao gerar arquivo de suporte: {0}" - ,"web_tab_queue": "Fila" - ,"web_tooltip_queue": "Fila de downloads" - ,"web_queue_active_download": "⏳ Um download está ativo" - ,"web_queue_no_active": "✓ Sem downloads ativos" - ,"web_queue_title": "Fila de Downloads" - ,"web_queue_empty": "Nenhum item na fila" - ,"web_queue_clear": "Limpar fila" - ,"web_queue_cleared": "Fila limpa com sucesso!" - ,"web_confirm_remove_queue": "Remover este item da fila?" - ,"web_confirm_clear_queue": "Limpar toda a fila?" - ,"web_remove": "Remover" - ,"web_loading": "Carregando..." - ,"web_sort": "Ordenar por" - ,"web_sort_name_asc": "A-Z (Nome)" - ,"web_sort_name_desc": "Z-A (Nome)" - ,"web_sort_size_asc": "Tamanho +- (Menor primeiro)" - ,"web_sort_size_desc": "Tamanho -+ (Maior primeiro)" + "popup_hide_premium_off": "Sistemas Premium visíveis", + "submenu_display_font_family": "Fonte", + "popup_font_family_changed": "Fonte alterada: {0}", + "instruction_pause_language": "Alterar o idioma da interface", + "instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento", + "instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas", + "instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista", + "instruction_pause_settings": "Música, opção symlink e status das chaves API", + "instruction_pause_restart": "Reiniciar RGSX para recarregar configuração", + "instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte", + "instruction_pause_quit": "Sair da aplicação RGSX", + "instruction_controls_help": "Mostrar referência completa de controle e teclado", + "instruction_controls_remap": "Modificar associação de botões / teclas", + "instruction_generic_back": "Voltar ao menu anterior", + "instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)", + "instruction_display_font_size": "Ajustar tamanho do texto para legibilidade", + "instruction_display_font_family": "Alternar entre famílias de fontes disponíveis", + "instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg", + "instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg", + "instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}", + "instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis", + "instruction_games_history": "Listar downloads anteriores e status", + "instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada", + "instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos", + "instruction_settings_music": "Ativar ou desativar música de fundo", + "instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações", + "instruction_settings_api_keys": "Ver chaves API premium detectadas", + "instruction_settings_web_service": "Ativar/desativar início automático do serviço web na inicialização", + "settings_web_service": "Serviço Web na Inicialização", + "settings_web_service_enabled": "Ativado", + "settings_web_service_disabled": "Desativado", + "settings_web_service_enabling": "Ativando serviço web...", + "settings_web_service_disabling": "Desativando serviço web...", + "settings_web_service_success_enabled": "Serviço web ativado na inicialização", + "settings_web_service_success_disabled": "Serviço web desativado na inicialização", + "settings_web_service_error": "Erro: {0}", + "controls_desc_confirm": "Confirmar (ex. A/Cruz)", + "controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)", + "controls_desc_up": "UP ↑", + "controls_desc_down": "DOWN ↓", + "controls_desc_left": "LEFT ←", + "controls_desc_right": "RIGHT →", + "controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)", + "controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)", + "controls_desc_history": "Abrir histórico (ex. Y/Triângulo)", + "controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)", + "controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)", + "controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)", + "controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)", + "controls_desc_start": "Abrir menu pausa (ex. Start)", + "controls_mapping_title": "Mapeamento de controles", + "controls_mapping_instruction": "Mantenha para confirmar o mapeamento:", + "controls_mapping_waiting": "Aguardando uma tecla ou botão...", + "controls_mapping_press": "Pressione uma tecla ou um botão", + "status_already_present": "Já Presente", + "footer_joystick": "Joystick: {0}", + "history_game_options_title": "Opções do jogo", + "history_option_download_folder": "Localizar arquivo", + "history_option_extract_archive": "Extrair arquivo", + "history_option_scraper": "Scraper metadados", + "history_option_delete_game": "Excluir jogo", + "history_option_error_info": "Detalhes do erro", + "history_option_retry": "Tentar novamente", + "history_option_back": "Voltar", + "history_folder_path_label": "Caminho de destino:", + "history_scraper_not_implemented": "Scraper ainda não implementado", + "history_confirm_delete": "Excluir este jogo do disco?", + "history_file_not_found": "Arquivo não encontrado", + "history_extracting": "Extraindo...", + "history_extracted": "Extraído", + "history_delete_success": "Jogo excluído com sucesso", + "history_delete_error": "Erro ao excluir jogo: {0}", + "history_error_details_title": "Detalhes do erro", + "history_no_error_message": "Nenhuma mensagem de erro disponível", + "web_title": "Interface Web RGSX", + "web_tab_platforms": "Lista de sistemas", + "web_tab_downloads": "Downloads", + "web_tab_history": "Histórico", + "web_tab_settings": "Configurações", + "web_tab_update": "Atualizar lista", + "web_tooltip_platforms": "Lista de sistemas", + "web_tooltip_downloads": "Downloads", + "web_tooltip_history": "Histórico", + "web_tooltip_settings": "Configurações", + "web_tooltip_update": "Atualizar lista de jogos", + "web_search_platform": "Pesquisar sistemas ou jogos...", + "web_search_game": "Pesquisar um jogo...", + "web_search_results": "resultados para", + "web_no_results": "Nenhum resultado encontrado", + "web_platforms": "Sistemas", + "web_games": "Jogos", + "web_error_search": "Erro de pesquisa", + "web_back_platforms": "Voltar às plataformas", + "web_back": "Voltar", + "web_game_count": "{0} ({1} jogos)", + "web_download": "Baixar", + "web_cancel": "Cancelar", + "web_download_canceled": "Download cancelado", + "web_confirm_cancel": "Você realmente deseja cancelar este download?", + "web_update_title": "Atualizando lista de jogos...", + "web_update_message": "Limpando cache e recarregando dados...", + "web_update_wait": "Isso pode levar 10-30 segundos", + "web_error": "Erro", + "web_error_unknown": "Erro desconhecido", + "web_error_update": "Erro ao atualizar a lista: {0}", + "web_error_download": "Erro: {0}", + "web_history_clear": "Limpar histórico", + "web_history_cleared": "Histórico limpo com sucesso!", + "web_error_clear_history": "Erro ao limpar histórico: {0}", + "web_settings_title": "Informações e Configurações", + "web_settings_roms_folder": "Pasta ROMs personalizada", + "web_settings_roms_placeholder": "Deixar vazio para padrão", + "web_settings_browse": "Procurar", + "web_settings_language": "Idioma", + "web_settings_font_scale": "Escala de fonte", + "web_settings_grid": "Layout de grade", + "web_settings_font_family": "Família de fonte", + "web_settings_music": "Música", + "web_settings_symlink": "Modo symlink", + "web_settings_source_mode": "Fonte de jogos", + "web_settings_custom_url": "URL personalizada", + "web_settings_custom_url_placeholder": "Deixar vazio para /saves/ports/rgsx/games.zip ou usar uma URL direta como https://example.com/games.zip", + "web_settings_save": "Salvar configurações", + "web_settings_saved": "Configurações salvas com sucesso!", + "web_settings_saved_restart": "Configurações salvas com sucesso!\\n\\n⚠️ Algumas configurações exigem reiniciar o servidor:\\n- Pasta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie o servidor web para aplicar essas alterações.", + "web_error_save_settings": "Erro ao salvar configurações: {0}", + "web_browse_title": "Procurar diretórios", + "web_browse_select_drive": "Selecione uma unidade...", + "web_browse_drives": "Unidades", + "web_browse_parent": "Acima", + "web_browse_select": "Selecionar esta pasta", + "web_browse_cancel": "Cancelar", + "web_browse_empty": "Nenhum subdiretório encontrado", + "web_browse_alert_restart": "Importante: Você precisa SALVAR as configurações e então REINICIAR o servidor web para que a pasta ROMs personalizada tenha efeito.\\n\\n📝 Passos:\\n1. Clique em 'Salvar configurações' abaixo\\n2. Pare o servidor web (Ctrl+C no terminal)\\n3. Reinicie o servidor web\\n\\nCaminho selecionado: {0}", + "web_error_browse": "Erro ao procurar diretórios: {0}", + "web_loading_platforms": "Carregando plataformas...", + "web_loading_games": "Carregando jogos...", + "web_no_platforms": "Nenhuma plataforma encontrada", + "web_no_downloads": "Nenhum download em andamento", + "web_history_empty": "Nenhum download concluído", + "web_history_platform": "Plataforma", + "web_history_size": "Tamanho", + "web_history_status_completed": "Concluído", + "web_history_status_error": "Erro", + "web_settings_os": "Sistema operacional", + "web_settings_platforms_count": "Número de plataformas", + "web_settings_show_unsupported": "Mostrar plataformas não suportadas (sistema ausente em es_systems.cfg)", + "web_settings_allow_unknown": "Permitir extensões desconhecidas (não mostrar avisos)", + "web_restart_confirm_title": "Reiniciar aplicação?", + "web_restart_confirm_message": "As configurações foram salvas. Deseja reiniciar a aplicação agora para aplicar as alterações?", + "web_restart_yes": "Sim, reiniciar", + "web_restart_no": "Não, mais tarde", + "web_restart_success": "Reiniciando...", + "web_restart_error": "Erro ao reiniciar: {0}", + "web_support": "Suporte", + "web_support_title": "📦 Arquivo de suporte gerado", + "web_support_message": "Arquivo de suporte criado com sucesso!\\n\\n📁 Conteúdo:\\n• Configuração de controles\\n• Histórico de downloads\\n• Configurações RGSX\\n• Logs da aplicação\\n• Logs do servidor web\\n\\n💬 Para obter ajuda:\\n1. Entre no Discord RGSX\\n2. Descreva seu problema\\n3. Compartilhe este arquivo ZIP\\n\\nO download vai começar...", + "web_support_generating": "Gerando arquivo de suporte...", + "web_support_download": "Baixar arquivo de suporte", + "web_support_error": "Erro ao gerar arquivo de suporte: {0}", + "web_tab_queue": "Fila", + "web_tooltip_queue": "Fila de downloads", + "web_queue_active_download": "⏳ Um download está ativo", + "web_queue_no_active": "✓ Sem downloads ativos", + "web_queue_title": "Fila de Downloads", + "web_queue_empty": "Nenhum item na fila", + "web_queue_clear": "Limpar fila", + "web_queue_cleared": "Fila limpa com sucesso!", + "web_confirm_remove_queue": "Remover este item da fila?", + "web_confirm_clear_queue": "Limpar toda a fila?", + "web_remove": "Remover", + "web_loading": "Carregando...", + "web_sort": "Ordenar por", + "web_sort_name_asc": "A-Z (Nome)", + "web_sort_name_desc": "Z-A (Nome)", + "web_sort_size_asc": "Tamanho +- (Menor primeiro)", + "web_sort_size_desc": "Tamanho -+ (Maior primeiro)", + "accessibility_font_size": "Tamanho da fonte: {0}", + "web_filter_region": "Região", + "web_filter_hide_non_release": "Ocultar Demos/Betas/Protos", + "web_filter_regex_mode": "Ativar pesquisa Regex", + "web_filter_one_rom_per_game": "Uma ROM por jogo", + "web_filter_configure_priority": "Configurar ordem de prioridade das regiões" } \ No newline at end of file diff --git a/ports/RGSX/rgsx_web.py b/ports/RGSX/rgsx_web.py index 1fad117..329be65 100644 --- a/ports/RGSX/rgsx_web.py +++ b/ports/RGSX/rgsx_web.py @@ -14,6 +14,11 @@ import shutil import tempfile import socket import argparse +import copy +import hashlib +import mimetypes +from datetime import datetime, timezone +from email.utils import formatdate, parsedate_to_datetime import config from history import load_history from utils import load_sources, load_games, extract_data @@ -21,6 +26,13 @@ from network import download_rom, download_from_1fichier from pathlib import Path from rgsx_settings import get_language +try: + from watchdog.observers import Observer # type: ignore + from watchdog.events import FileSystemEventHandler # type: ignore + WATCHDOG_AVAILABLE = True +except ImportError: # pragma: no cover - optional dependency + WATCHDOG_AVAILABLE = False + # Ajouter le dossier parent au path pour imports sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -48,6 +60,182 @@ def load_translations(): # Charger les traductions globalement TRANSLATIONS = load_translations() +# Cache configuration +CACHE_TTL_SECONDS = 60 # seconds + +cache_lock = threading.RLock() + +source_cache = { + 'data': None, + 'timestamp': 0.0, + 'etag': None, + 'last_modified': None, +} + +games_cache = {} + +watchdog_observer = None +watchdog_started = False + + +def _now_utc() -> datetime: + """Return timezone-aware UTC datetime.""" + return datetime.now(timezone.utc) + + +def _httpdate(dt: datetime | None) -> str | None: + """Convert datetime to an HTTP-date string.""" + if dt is None: + return None + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + else: + dt = dt.astimezone(timezone.utc) + return formatdate(dt.timestamp(), usegmt=True) + + +def generate_etag(payload: object) -> str: + """Generate a stable ETag for JSON-serialisable payloads.""" + try: + serialized = json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(',', ':'), default=str) + except TypeError: + serialized = repr(payload) + return hashlib.md5(serialized.encode('utf-8')).hexdigest() + + +def _ensure_datetime(value: datetime | str | None) -> datetime | None: + """Return a timezone-aware datetime from mixed input.""" + if value is None: + return None + if isinstance(value, datetime): + return value if value.tzinfo else value.replace(tzinfo=timezone.utc) + try: + dt = parsedate_to_datetime(value) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + return dt + except (TypeError, ValueError): + return None + + +def invalidate_all_caches(reason: str | None = None) -> None: + """Drop all cached datasets.""" + with cache_lock: + source_cache.update({'data': None, 'timestamp': 0.0, 'etag': None, 'last_modified': None}) + games_cache.clear() + if reason and 'logger' in globals(): + logger.debug(f"Caches invalidated ({reason})") + + +def invalidate_games_cache(platform: str | None = None, reason: str | None = None) -> None: + """Invalidate either a specific platform cache or all game caches.""" + with cache_lock: + if platform is None: + games_cache.clear() + else: + games_cache.pop(platform, None) + if reason and 'logger' in globals(): + logger.debug(f"Games cache invalidated for {platform or 'ALL'} ({reason})") + + +def get_cached_sources() -> tuple[list[dict], str, datetime]: + """Return cached platforms data with ETag and last modified timestamp.""" + now = time.time() + with cache_lock: + entry_data = source_cache['data'] + if entry_data is not None and now - source_cache['timestamp'] <= CACHE_TTL_SECONDS: + return copy.deepcopy(entry_data), source_cache['etag'], source_cache['last_modified'] + + platforms = load_sources() + last_modified = _now_utc() + etag = generate_etag(platforms) + + with cache_lock: + source_cache.update({ + 'data': copy.deepcopy(platforms), + 'timestamp': now, + 'etag': etag, + 'last_modified': last_modified, + }) + + return copy.deepcopy(platforms), etag, last_modified + + +def get_cached_games(platform: str) -> tuple[list[tuple], str, datetime]: + """Return cached games list for platform with metadata.""" + now = time.time() + with cache_lock: + entry = games_cache.get(platform) + if entry and now - entry['timestamp'] <= CACHE_TTL_SECONDS: + return copy.deepcopy(entry['data']), entry['etag'], entry['last_modified'] + + games = load_games(platform) + last_modified = _now_utc() + etag = generate_etag(games) + + with cache_lock: + games_cache[platform] = { + 'data': copy.deepcopy(games), + 'timestamp': now, + 'etag': etag, + 'last_modified': last_modified, + } + + return copy.deepcopy(games), etag, last_modified + + +if WATCHDOG_AVAILABLE: + + class _CacheInvalidationHandler(FileSystemEventHandler): + """Watchdog handler to invalidate caches when files change.""" + + def on_any_event(self, event): # type: ignore[override] + if event.is_directory: + return + invalidate_all_caches(reason=f"filesystem event: {getattr(event, 'src_path', '')}") + +else: + + class _CacheInvalidationHandler: # pragma: no cover - fallback stub + def __init__(self, *_, **__): + pass + + +def start_cache_invalidation_watchdog() -> None: + """Start filesystem watcher to keep caches in sync.""" + global watchdog_observer, watchdog_started + + if watchdog_started: + return + if not WATCHDOG_AVAILABLE: + logger.info("watchdog package not available; relying on TTL cache invalidation") + return + + observer = Observer() + watched_paths = { + os.path.dirname(config.SOURCES_FILE), + config.GAMES_FOLDER, + config.ROMS_FOLDER, + } + + handler = _CacheInvalidationHandler() + + scheduled = False + + for path in watched_paths: + if path and os.path.isdir(path): + observer.schedule(handler, path=path, recursive=True) + scheduled = True + + if scheduled: + observer.daemon = True + observer.start() + watchdog_observer = observer + watchdog_started = True + logger.info("Cache invalidation watchdog started") + else: + logger.debug("No valid paths for cache watchdog; skipping watcher startup") + # Fonction d'aide pour obtenir une traduction def get_translation(key, default=None): """Obtient une traduction depuis le dictionnaire global TRANSLATIONS""" @@ -173,7 +361,7 @@ except Exception as e: # Initialiser les données au démarrage logger.info("Chargement initial des données...") try: - load_sources() # Initialise config.games_count + initial_sources = load_sources() # Initialise config.games_count logger.info(f"{len(getattr(config, 'platforms', []))} plateformes chargées") # Initialiser filter_platforms_selection depuis les settings (pour filtrer les plateformes) @@ -181,8 +369,28 @@ try: settings = load_rgsx_settings() hidden = set(settings.get("hidden_platforms", [])) if isinstance(settings, dict) else set() + if initial_sources is not None: + with cache_lock: + source_cache.update({ + 'data': copy.deepcopy(initial_sources), + 'timestamp': time.time(), + 'etag': generate_etag(initial_sources), + 'last_modified': _now_utc(), + }) + if not hasattr(config, 'filter_platforms_selection') or not config.filter_platforms_selection: - all_platform_names = sorted([p.get("platform_name", "") for p in config.platforms if p.get("platform_name")]) + all_platform_names = [] + for platform_entry in getattr(config, 'platforms', []): + if isinstance(platform_entry, str): + name = platform_entry + elif isinstance(platform_entry, dict): + name = platform_entry.get("platform_name", "") + else: + name = str(platform_entry) + name = name.strip() + if name: + all_platform_names.append(name) + all_platform_names = sorted(set(all_platform_names)) config.filter_platforms_selection = [(name, name in hidden) for name in all_platform_names] logger.info(f"Filter platforms initialisé: {len(hidden)} plateformes cachées sur {len(all_platform_names)}") @@ -195,6 +403,13 @@ except Exception as e: for handler in logging.root.handlers: handler.flush() +# Lancer le watcher de cache si disponible +try: + start_cache_invalidation_watchdog() +except Exception as watcher_error: # pragma: no cover - watcher errors shouldn't crash server + logger.warning(f"Cache watchdog startup failed: {watcher_error}") + watchdog_started = False + class RGSXHandler(BaseHTTPRequestHandler): """Handler HTTP pour les requêtes RGSX""" @@ -203,22 +418,119 @@ class RGSXHandler(BaseHTTPRequestHandler): """Override pour logger proprement (désactivé pour réduire verbosité)""" pass # Logs désactivés pour éviter la pollution des logs - def _set_headers(self, content_type='application/json', status=200): + def _set_headers(self, content_type='application/json', status=200, etag=None, last_modified=None, extra_headers=None): """Définit les headers de réponse""" self.send_response(status) self.send_header('Content-type', content_type) self.send_header('Access-Control-Allow-Origin', '*') # CORS pour dev + if etag: + self.send_header('ETag', etag) + if last_modified: + http_date = _httpdate(_ensure_datetime(last_modified)) if not isinstance(last_modified, str) else last_modified + if http_date: + self.send_header('Last-Modified', http_date) + if extra_headers: + for header, value in extra_headers.items(): + self.send_header(header, value) self.end_headers() - def _send_json(self, data, status=200): + def _send_json(self, data, status=200, etag=None, last_modified=None): """Envoie une réponse JSON""" - self._set_headers('application/json', status) + cached_dt = _ensure_datetime(last_modified) + client_etag = self.headers.get('If-None-Match') if etag else None + client_ims = self.headers.get('If-Modified-Since') if cached_dt else None + + if etag and client_etag == etag: + self._set_headers('application/json', status=304, etag=etag, last_modified=cached_dt) + return + + if cached_dt and client_ims: + try: + client_dt = parsedate_to_datetime(client_ims) + if client_dt.tzinfo is None: + client_dt = client_dt.replace(tzinfo=timezone.utc) + if client_dt >= cached_dt: + self._set_headers('application/json', status=304, etag=etag, last_modified=cached_dt) + return + except (TypeError, ValueError): + pass + + self._set_headers('application/json', status, etag=etag, last_modified=cached_dt) self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8')) - def _send_html(self, html, status=200): + def _send_html(self, html, status=200, etag=None, last_modified=None): """Envoie une réponse HTML""" - self._set_headers('text/html; charset=utf-8', status) + self._set_headers('text/html; charset=utf-8', status, etag=etag, last_modified=last_modified) self.wfile.write(html.encode('utf-8')) + + def _send_not_found(self): + """Répond avec un 404 générique.""" + self._set_headers('text/plain; charset=utf-8', status=404) + self.wfile.write(b'Not found') + + def _asset_version(self, relative_path: str) -> str: + """Retourne un identifiant de version basé sur la date de modification du fichier statique.""" + static_root = Path(__file__).resolve().parent / 'static' + asset_path = static_root / relative_path + try: + return str(int(asset_path.stat().st_mtime)) + except OSError: + return str(int(time.time())) + + def _serve_static_file(self, path: str) -> None: + """Servez un fichier statique avec gestion du cache HTTP.""" + if not path.startswith('/static/'): + self._send_not_found() + return + + relative_path = path[len('/static/'):] + safe_relative = os.path.normpath(relative_path).replace('\\', '/') + + if safe_relative.startswith('../') or safe_relative.startswith('..') or safe_relative.startswith('/'): + self._send_not_found() + return + + static_root = Path(__file__).resolve().parent / 'static' + asset_path = static_root / safe_relative + + if not asset_path.is_file(): + self._send_not_found() + return + + mime_type, _ = mimetypes.guess_type(str(asset_path)) + if not mime_type: + mime_type = 'application/octet-stream' + + stat_result = asset_path.stat() + last_modified = datetime.fromtimestamp(stat_result.st_mtime, timezone.utc) + etag = f'W/"{stat_result.st_mtime_ns}-{stat_result.st_size}"' + + cache_headers = {'Cache-Control': 'public, max-age=86400'} + + client_etag = self.headers.get('If-None-Match') + if client_etag == etag: + self._set_headers(mime_type, status=304, etag=etag, last_modified=last_modified, extra_headers=cache_headers) + return + + client_ims = self.headers.get('If-Modified-Since') + if client_ims: + try: + client_dt = parsedate_to_datetime(client_ims) + if client_dt.tzinfo is None: + client_dt = client_dt.replace(tzinfo=timezone.utc) + if client_dt >= last_modified: + self._set_headers(mime_type, status=304, etag=etag, last_modified=last_modified, extra_headers=cache_headers) + return + except (TypeError, ValueError): + pass + + data = asset_path.read_bytes() + payload_headers = { + 'Cache-Control': 'public, max-age=86400', + 'Content-Length': str(len(data)), + } + self._set_headers(mime_type, status=200, etag=etag, last_modified=last_modified, extra_headers=payload_headers) + self.wfile.write(data) def do_GET(self): """Traite les requêtes GET""" @@ -231,26 +543,30 @@ class RGSXHandler(BaseHTTPRequestHandler): # logger.info(f"GET {path}") try: + if path.startswith('/static/'): + self._serve_static_file(path) + return + # Route: Page d'accueil (avec ou sans paramètres pour navigation) if path == '/' or path == '/index.html' or path.startswith('/platform/') or path in ['/downloads', '/history', '/settings']: self._send_html(self._get_index_html()) # Route: API - Liste des plateformes elif path == '/api/platforms': - platforms = load_sources() + platforms, _, source_last_modified = get_cached_sources() # Ajouter le nombre de jeux depuis config.games_count games_count_dict = getattr(config, 'games_count', {}) - + # Filtrer les plateformes cachées selon config.filter_platforms_selection hidden_platforms = set() if hasattr(config, 'filter_platforms_selection') and config.filter_platforms_selection: hidden_platforms = {name for name, is_hidden in config.filter_platforms_selection if is_hidden} - + # Ajouter aussi les plateformes sans dossier ROM (si show_unsupported_platforms = False) from rgsx_settings import load_rgsx_settings, get_show_unsupported_platforms settings = load_rgsx_settings() show_unsupported = get_show_unsupported_platforms(settings) - + if not show_unsupported: # Masquer les plateformes dont le dossier ROM n'existe pas for platform in platforms: @@ -261,20 +577,24 @@ class RGSXHandler(BaseHTTPRequestHandler): expected_dir = os.path.join(config.ROMS_FOLDER, folder) if not os.path.isdir(expected_dir): hidden_platforms.add(platform_name) - + filtered_platforms = [] for platform in platforms: platform_name = platform.get('platform_name', '') - # Exclure les plateformes cachées - if platform_name not in hidden_platforms: - platform['games_count'] = games_count_dict.get(platform_name, 0) - filtered_platforms.append(platform) - - self._send_json({ + if platform_name in hidden_platforms: + continue + platform_copy = dict(platform) + platform_copy['games_count'] = games_count_dict.get(platform_name, 0) + filtered_platforms.append(platform_copy) + + response_payload = { 'success': True, 'count': len(filtered_platforms), 'platforms': filtered_platforms - }) + } + response_etag = generate_etag(response_payload) + + self._send_json(response_payload, etag=response_etag, last_modified=source_last_modified) # Route: API - Recherche universelle (systèmes + jeux) elif path == '/api/search': @@ -291,8 +611,8 @@ class RGSXHandler(BaseHTTPRequestHandler): }) return - # Charger toutes les plateformes - platforms = load_sources() + # Charger toutes les plateformes (avec cache) + platforms, _, source_last_modified = get_cached_sources() games_count_dict = getattr(config, 'games_count', {}) # Filtrer les plateformes cachées selon config.filter_platforms_selection @@ -318,6 +638,7 @@ class RGSXHandler(BaseHTTPRequestHandler): matching_platforms = [] matching_games = [] + latest_modified = source_last_modified # Rechercher dans les plateformes et leurs jeux for platform in platforms: @@ -342,7 +663,11 @@ class RGSXHandler(BaseHTTPRequestHandler): # Rechercher dans les jeux de cette plateforme try: - games = load_games(platform_name) + games, _, games_last_modified = get_cached_games(platform_name) + if games_last_modified and latest_modified: + latest_modified = max(latest_modified, games_last_modified) + elif games_last_modified: + latest_modified = games_last_modified for game in games: game_name = game[0] if isinstance(game, (list, tuple)) else str(game) game_name_lower = game_name.lower() @@ -357,14 +682,17 @@ class RGSXHandler(BaseHTTPRequestHandler): logger.debug(f"Erreur lors de la recherche dans {platform_name}: {e}") continue - self._send_json({ + response_payload = { 'success': True, 'search_term': search_term, 'results': { 'platforms': matching_platforms, 'games': matching_games } - }) + } + response_etag = generate_etag(response_payload) + + self._send_json(response_payload, etag=response_etag, last_modified=latest_modified) except Exception as e: logger.error(f"Erreur lors de la recherche: {e}") @@ -386,7 +714,7 @@ class RGSXHandler(BaseHTTPRequestHandler): platform_name = path.split('/api/games/')[-1] platform_name = urllib.parse.unquote(platform_name) - games = load_games(platform_name) + games, _, games_last_modified = get_cached_games(platform_name) games_formatted = [ { 'name': g[0], @@ -396,12 +724,15 @@ class RGSXHandler(BaseHTTPRequestHandler): for g in games ] - self._send_json({ + response_payload = { 'success': True, 'platform': platform_name, 'count': len(games_formatted), 'games': games_formatted - }) + } + response_etag = generate_etag(response_payload) + + self._send_json(response_payload, etag=response_etag, last_modified=games_last_modified) # Route: API - Progression des téléchargements (en cours seulement) elif path == '/api/progress': @@ -451,7 +782,7 @@ class RGSXHandler(BaseHTTPRequestHandler): # Lire depuis history.json - filtrer pour inclure en cours ET terminés history = load_history() or [] - print(f"\n[DEBUG HISTORY] history.json chargé avec {len(history)} entrées totales") + # print(f"\n[DEBUG HISTORY] history.json chargé avec {len(history)} entrées totales") # Inclure: statuts terminés + en queue + en cours included_statuses = [ @@ -465,9 +796,9 @@ class RGSXHandler(BaseHTTPRequestHandler): str(entry.get('status', '')).startswith('Try ') ] - print(f"[DEBUG HISTORY] {len(visible_history)} téléchargements (terminés + en queue + en cours) trouvés") - if visible_history: - print(f" Premier: {visible_history[0].get('game_name', '')[:50]} - Status: {visible_history[0].get('status')}") + # print(f"[DEBUG HISTORY] {len(visible_history)} téléchargements (terminés + en queue + en cours) trouvés") + # if visible_history: + # print(f" Premier: {visible_history[0].get('game_name', '')[:50]} - Status: {visible_history[0].get('status')}") # Trier par timestamp (plus récent en premier) visible_history.sort( @@ -593,8 +924,17 @@ class RGSXHandler(BaseHTTPRequestHandler): deleted.append(f'extracted: {message}') # Maintenant charger les sources + invalidate_all_caches(reason='update-cache refresh') logger.info("🔄 Chargement des plateformes...") - load_sources() + refreshed_sources = load_sources() + if refreshed_sources is not None: + with cache_lock: + source_cache.update({ + 'data': copy.deepcopy(refreshed_sources), + 'timestamp': time.time(), + 'etag': generate_etag(refreshed_sources), + 'last_modified': _now_utc(), + }) platforms_count = len(getattr(config, 'platforms', [])) logger.info(f"✅ {platforms_count} plateformes chargées") deleted.append(f'loaded: {platforms_count} platforms') @@ -737,8 +1077,8 @@ class RGSXHandler(BaseHTTPRequestHandler): }, status=400) return - # Charger les jeux de la plateforme - games = load_games(platform) + # Charger les jeux de la plateforme (cache) + games, _, _ = get_cached_games(platform) # Si game_name est fourni, chercher l'index correspondant if game_name_param and game_index is None: @@ -1494,2543 +1834,68 @@ DO NOT share this file publicly as it may contain sensitive information. def _get_index_html(self): """Retourne la page HTML d'accueil""" - return ''' + css_version = self._asset_version('css/app.css') + js_version = self._asset_version('js/app.js') + html = '''
+ +v{version}
-' + t('web_update_message') + '
' + t('web_update_wait') + '
${t('web_no_results')}
`; + } + + // Afficher les systèmes correspondants + if (platformsMatch.length > 0) { + html += `${platform.games_count} ${t('web_games')}
+❌ ${t('web_error_search')}: ${error.message}
`; + } + } + }, 300); // Attendre 300ms après la dernière frappe + } + + // Filter state: Map of region -> 'include' or 'exclude' + let regionFilters = new Map(); + + // Region priority order for "One ROM Per Game" (customizable) + let regionPriorityOrder = JSON.parse(localStorage.getItem('regionPriorityOrder')) || + ['USA', 'Canada', 'World', 'Europe', 'Japan', 'Other']; + + // Helper: Extract region(s) from game name - returns array of regions + function getGameRegions(gameName) { + const name = gameName.toUpperCase(); + const regions = []; + + // Common region patterns - check all, not just first match + // Handle both "(USA)" and "(USA, Europe)" formats + if (name.includes('USA') || name.includes('US)')) regions.push('USA'); + if (name.includes('EUROPE') || name.includes('EU)')) regions.push('Europe'); + if (name.includes('JAPAN') || name.includes('JP)') || name.includes('JPN)')) regions.push('Japan'); + if (name.includes('WORLD')) regions.push('World'); + + // Check for other regions + if (name.match(/\b(AUSTRALIA|ASIA|KOREA|BRAZIL|CHINA|RUSSIA|SCANDINAVIA|SPAIN|FRANCE|GERMANY|ITALY)\b/)) { + if (!regions.includes('Other')) regions.push('Other'); + } + + // If no region found, classify as Other + if (regions.length === 0) regions.push('Other'); + + // Debug log for multi-region games + if (regions.length > 1 && gameName.includes('Game Guru')) { + console.log('getGameRegions:', gameName, '->', regions); + } + + return regions; + } + + // Helper: Check if game is non-release version + function isNonReleaseGame(gameName) { + const name = gameName.toUpperCase(); + // Match parentheses or brackets containing these keywords + // Using [^\)] instead of .* to avoid catastrophic backtracking + const nonReleasePatterns = [ + /\([^\)]*BETA[^\)]*\)/, + /\([^\)]*DEMO[^\)]*\)/, + /\([^\)]*PROTO[^\)]*\)/, + /\([^\)]*SAMPLE[^\)]*\)/, + /\([^\)]*KIOSK[^\)]*\)/, + /\([^\)]*PREVIEW[^\)]*\)/, + /\([^\)]*TEST[^\)]*\)/, + /\([^\)]*DEBUG[^\)]*\)/, + /\([^\)]*ALPHA[^\)]*\)/, + /\([^\)]*PRE-RELEASE[^\)]*\)/, + /\([^\)]*PRERELEASE[^\)]*\)/, + /\([^\)]*UNFINISHED[^\)]*\)/, + /\([^\)]*WIP[^\)]*\)/, + /\[[^\]]*BETA[^\]]*\]/, + /\[[^\]]*DEMO[^\]]*\]/, + /\[[^\]]*TEST[^\]]*\]/ + ]; + return nonReleasePatterns.some(pattern => pattern.test(name)); + } + + // Helper: Get base game name (strip regions, versions, etc. but preserve disc numbers) + function getBaseGameName(gameName) { + let base = gameName; + + // Remove file extensions + base = base.replace(/\.(zip|7z|rar|gz|iso)$/i, ''); + + // Extract disc/disk number if present (before removing parentheses) + let discInfo = ''; + const discMatch = base.match(/\(Dis[ck]\s*(\d+)\)/i) || + base.match(/\[Dis[ck]\s*(\d+)\]/i) || + base.match(/Dis[ck]\s*(\d+)/i) || + base.match(/\(CD\s*(\d+)\)/i) || + base.match(/CD\s*(\d+)/i); + if (discMatch) { + discInfo = ` Disc ${discMatch[1]}`; + } + + // Remove parenthetical content (regions, languages, versions, etc.) + base = base.replace(/\([^)]*\)/g, ''); + base = base.replace(/\[[^\]]*\]/g, ''); + + // Normalize whitespace + base = base.replace(/\s+/g, ' ').trim(); + + // Re-append disc info + base = base + discInfo; + + return base; + } + + // Helper: Get region priority for one-rom-per-game (lower = better) + function getRegionPriority(gameName) { + const name = gameName.toUpperCase(); + + // Find the first matching region in priority order + for (let i = 0; i < regionPriorityOrder.length; i++) { + const region = regionPriorityOrder[i].toUpperCase(); + if (region === 'USA' && name.includes('USA')) return i; + if (region === 'CANADA' && name.includes('CANADA')) return i; + if (region === 'WORLD' && name.includes('WORLD')) return i; + if (region === 'EUROPE' && (name.includes('EUROPE') || name.includes('EU)'))) return i; + if (region === 'JAPAN' && (name.includes('JAPAN') || name.includes('JP)') || name.includes('JPN)'))) return i; + } + + return regionPriorityOrder.length; // Other regions (lowest priority) + } + + // Save region priority order to localStorage + function saveRegionPriorityOrder() { + localStorage.setItem('regionPriorityOrder', JSON.stringify(regionPriorityOrder)); + updateRegionPriorityDisplay(); + } + + // Update the display of current region priority order + function updateRegionPriorityDisplay() { + const display = document.getElementById('region-priority-display'); + if (display) { + display.textContent = regionPriorityOrder.join(' → '); + } + } + + // Move region up in priority (decrease index = higher priority) + function moveRegionUp(region) { + const idx = regionPriorityOrder.indexOf(region); + if (idx > 0) { + [regionPriorityOrder[idx], regionPriorityOrder[idx-1]] = + [regionPriorityOrder[idx-1], regionPriorityOrder[idx]]; + saveRegionPriorityOrder(); + renderRegionPriorityConfig(); + } + } + + // Move region down in priority (increase index = lower priority) + function moveRegionDown(region) { + const idx = regionPriorityOrder.indexOf(region); + if (idx >= 0 && idx < regionPriorityOrder.length - 1) { + [regionPriorityOrder[idx], regionPriorityOrder[idx+1]] = + [regionPriorityOrder[idx+1], regionPriorityOrder[idx]]; + saveRegionPriorityOrder(); + renderRegionPriorityConfig(); + } + } + + // Reset region priority to default + function resetRegionPriority() { + regionPriorityOrder = ['USA', 'Canada', 'World', 'Europe', 'Japan', 'Other']; + saveRegionPriorityOrder(); + renderRegionPriorityConfig(); + } + + // Render region priority configuration UI + function renderRegionPriorityConfig() { + const container = document.getElementById('region-priority-config'); + if (!container) return; + + let html = '' + t('web_no_platforms') + '
'; + return; + } + + // Construire le HTML avec les traductions + let searchPlaceholder = t('web_search_platform'); + let html = ` +${errorMsg}: ${error.message}
`; + } + } + + // Charger les jeux d'une plateforme + async function loadGames(platform, updateHistory = true) { + currentPlatform = platform; + const container = document.getElementById('platforms-content'); + container.innerHTML = '${errorMsg}: ${error.message}
+ `; + } + } + + // Retour aux plateformes avec historique + function goBackToPlatforms() { + window.history.pushState({ tab: 'platforms' }, '', '/'); + loadPlatforms(); + } + + // Télécharger un jeu + async function downloadGame(platform, gameName, gameIndex) { + const btn = event.target; + btn.disabled = true; + btn.textContent = '⏳'; + btn.title = t('web_download') + '...'; + const mode = arguments.length > 3 ? arguments[3] : 'now'; + try { + // Préparer le body de la requête + const requestBody = { platform: platform }; + if (typeof gameIndex === 'number' && gameIndex >= 0) { + requestBody.game_index = gameIndex; + } else { + requestBody.game_name = gameName; + } + requestBody.mode = mode; + const response = await fetch('/api/download', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestBody) + }); + const data = await response.json(); + if (data.success) { + btn.textContent = '✅'; + btn.title = t('web_download') + ' ✓'; + btn.style.color = '#28a745'; + + // Afficher un toast de succès (pas de redirection de page) + const toastMsg = mode === 'queue' + ? `📋 "${gameName}" ajouté à la queue` + : `⬇️ Téléchargement de "${gameName}" lancé`; + showToast(toastMsg, 'success', 3000); + + } else { + throw new Error(data.error || t('web_error_unknown')); + } + } catch (error) { + btn.textContent = '❌'; + btn.title = t('web_error'); + btn.style.color = '#dc3545'; + showToast(`Erreur: ${error.message}`, 'error', 5000); + } finally { + setTimeout(() => { + btn.disabled = false; + btn.textContent = '⬇️'; + btn.title = t('web_download'); + btn.style.color = ''; + }, 3000); + } + } + + // Annuler un téléchargement + async function cancelDownload(url, btn) { + if (!confirm(t('web_confirm_cancel'))) { + return; + } + + btn.disabled = true; + btn.textContent = '⏳'; + + try { + const response = await fetch('/api/cancel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: url }) + }); + + const data = await response.json(); + + if (data.success) { + btn.textContent = '✅'; + btn.style.color = '#28a745'; + + // Recharger la liste après un court délai + setTimeout(() => { + loadProgress(); + }, 500); + } else { + throw new Error(data.error || t('web_error_unknown')); + } + } catch (error) { + btn.textContent = '❌'; + btn.style.color = '#dc3545'; + alert(t('web_error_download', error.message)); + btn.disabled = false; + } + } + + // Charger la progression + async function loadProgress(autoRefresh = true) { + const container = document.getElementById('downloads-content'); + + // Arrêter l'ancien interval si existant + if (progressInterval) { + clearInterval(progressInterval); + progressInterval = null; + } + + try { + const response = await fetch('/api/progress'); + const data = await response.json(); + + // Mettre à jour le timestamp de dernière mise à jour + lastProgressUpdate = Date.now(); + + console.log('[DEBUG] /api/progress response:', data); + console.log('[DEBUG] downloads keys:', Object.keys(data.downloads || {})); + + if (!data.success) throw new Error(data.error); + + const downloads = Object.entries(data.downloads); + + if (downloads.length === 0) { + container.innerHTML = '' + t('web_no_downloads') + '
'; + return; + } + + container.innerHTML = downloads.map(([url, info]) => { + const percent = info.progress_percent || 0; + const downloaded = info.downloaded_size || 0; + const total = info.total_size || 0; + const status = info.status || 'En cours'; + const speed = info.speed || 0; + + // Utiliser game_name si disponible, sinon extraire de l'URL + let fileName = info.game_name || 'Téléchargement'; + if (!info.game_name) { + try { + fileName = decodeURIComponent(url.split('/').pop()); + } catch (e) { + fileName = url.split('/').pop(); + } + } + + // Afficher la plateforme si disponible + const platformInfo = info.platform ? ' (' + info.platform + ')' : ''; + + return ` +' + t('web_no_downloads') + '
'; + clearInterval(progressInterval); + progressInterval = null; + return; + } + + container.innerHTML = downloads.map(([url, info]) => { + const percent = info.progress_percent || 0; + const downloaded = info.downloaded_size || 0; + const total = info.total_size || 0; + const status = info.status || 'En cours'; + const speed = info.speed || 0; + + let fileName = info.game_name || 'Téléchargement'; + if (!info.game_name) { + try { + fileName = decodeURIComponent(url.split('/').pop()); + } catch (e) { + fileName = url.split('/').pop(); + } + } + + const platformInfo = info.platform ? ' (' + info.platform + ')' : ''; + + return ` +Erreur: ${error.message}
`; + } + } + + // Charger la file d'attente + async function loadQueue() { + const container = document.getElementById('queue-content'); + + try { + const response = await fetch('/api/queue'); + const data = await response.json(); + + if (!data.success) throw new Error(data.error); + + const queue = data.queue || []; + const isActive = data.active || false; + + let html = '' + t('web_queue_empty') + '
'; + } else { + html += '❌ ${t('web_error')}: ${error.message}
`; + } + } + + // Supprimer un élément de la queue + async function removeFromQueue(taskId, btn) { + if (!confirm(t('web_confirm_remove_queue'))) { + return; + } + + try { + const response = await fetch('/api/queue/remove', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ task_id: taskId }) + }); + const data = await response.json(); + if (data.success) { + btn.style.color = '#28a745'; + btn.textContent = '✅'; + setTimeout(() => { loadQueue(); }, 500); + } else { + alert(t('web_error') + ': ' + data.error); + } + } catch (error) { + alert(t('web_error') + ': ' + error.message); + } + } + + // Vider la queue + async function clearQueue() { + if (!confirm(t('web_confirm_clear_queue'))) { + return; + } + + try { + const response = await fetch('/api/queue/clear', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({}) + }); + const data = await response.json(); + if (data.success) { + alert(t('web_queue_cleared')); + loadQueue(); + } else { + alert(t('web_error') + ': ' + data.error); + } + } catch (error) { + alert(t('web_error') + ': ' + error.message); + } + } + + // Checker les téléchargements terminés pour afficher les toasts + async function checkCompletedDownloads() { + try { + const response = await fetch('/api/history'); + const data = await response.json(); + + if (!data.success || !data.history) return; + + // Parcourir l'historique récent pour détecter les complétions + data.history.slice(0, 10).forEach(entry => { + const gameKey = `${entry.platform}_${entry.game_name}`; + const status = entry.status || ''; + + // Si ce téléchargement n'était pas tracké et il est maintenant complété/erreur/etc + if (!trackedDownloads[gameKey]) { + if (status === 'Download_OK' || status === 'Completed') { + showToast(`✅ "${entry.game_name}" téléchargé avec succès!`, 'success', 4000); + trackedDownloads[gameKey] = 'completed'; + } else if (status === 'Erreur' || status === 'error') { + showToast(`❌ Erreur lors du téléchargement de "${entry.game_name}"`, 'error', 5000); + trackedDownloads[gameKey] = 'error'; + } else if (status === 'Already_Present') { + showToast(`ℹ️ "${entry.game_name}" était déjà présent`, 'info', 3000); + trackedDownloads[gameKey] = 'already_present'; + } else if (status === 'Canceled') { + // Ne pas afficher de toast pour les téléchargements annulés + trackedDownloads[gameKey] = 'canceled'; + } + } + }); + + // Sauvegarder dans localStorage + localStorage.setItem('trackedDownloads', JSON.stringify(trackedDownloads)); + + // Nettoyer les vieux téléchargements (garder seulement les 50 derniers) + const keys = Object.keys(trackedDownloads); + if (keys.length > 100) { + // Supprimer les 50 plus anciens + keys.slice(0, 50).forEach(key => { + delete trackedDownloads[key]; + }); + localStorage.setItem('trackedDownloads', JSON.stringify(trackedDownloads)); + } + } catch (error) { + console.error('[DEBUG] Erreur checkCompletedDownloads:', error); + } + } + + // Charger l'historique + async function loadHistory() { + const container = document.getElementById('history-content'); + container.innerHTML = '' + t('web_history_empty') + '
'; + return; + } + + // Pré-charger les traductions + const platformLabel = t('web_history_platform'); + const sizeLabel = t('web_history_size'); + const statusCompleted = t('web_history_status_completed'); + const statusError = t('web_history_status_error'); + const statusCanceled = t('history_status_canceled'); + const statusAlreadyPresent = t('status_already_present'); + const statusQueued = t('download_queued'); + const statusDownloading = t('download_in_progress'); + + container.innerHTML = data.history.map(h => { + const status = h.status || ''; + const isError = status === 'Erreur' || status === 'error'; + const isCanceled = status === 'Canceled'; + const isAlreadyPresent = status === 'Already_Present'; + const isQueued = status === 'Queued'; + const isDownloading = status === 'Downloading' || status === 'Téléchargement' || status === 'Downloading' || + status === 'Connecting' || status === 'Extracting' || status.startsWith('Try '); + const isSuccess = status === 'Download_OK' || status === 'Completed'; + + // Déterminer l'icône et la couleur + let statusIcon = '✅'; // par défaut succès + let statusColor = '#28a745'; // vert + let statusText = statusCompleted; + + if (isError) { + statusIcon = '❌'; + statusColor = '#dc3545'; // rouge + statusText = statusError; + } else if (isCanceled) { + statusIcon = '⏸️'; + statusColor = '#ffc107'; // orange + statusText = statusCanceled; + } else if (isAlreadyPresent) { + statusIcon = 'ℹ️'; + statusColor = '#17a2b8'; // bleu clair + statusText = statusAlreadyPresent; + } else if (isQueued) { + statusIcon = '📋'; + statusColor = '#6c757d'; // gris (en attente) + statusText = statusQueued; + } else if (isDownloading) { + statusIcon = '⬇️'; + statusColor = '#007bff'; // bleu (en cours) + statusText = statusDownloading; + } + + const totalMo = h.total_size ? (h.total_size / 1024 / 1024).toFixed(1) : 'N/A'; + const platform = h.platform || 'N/A'; + const timestamp = h.timestamp || 'N/A'; + + // Debug: log le timestamp pour vérifier + if (!h.timestamp) { + console.log('[DEBUG] Timestamp manquant pour:', h.game_name, 'Object:', h); + } + + return ` +${t('web_error')}: ${error.message}
`; + } + } + + // Vider l'historique + async function clearHistory() { + if (!confirm(t('web_history_clear') + '?\\n\\nThis action cannot be undone.')) { + return; + } + + try { + const response = await fetch('/api/clear-history', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const data = await response.json(); + + if (data.success) { + alert('✅ ' + t('web_history_cleared')); + loadHistory(); // Recharger l\\'historique + } else { + throw new Error(data.error || t('web_error_unknown')); + } + } catch (error) { + alert('❌ ' + t('web_error_clear_history', error.message)); + } + } + + // Charger les settings + async function loadSettings() { + const container = document.getElementById('settings-content'); + container.innerHTML = '${t('web_error')}: ${error.message}
`; + } + } + + // Sauvegarder les settings + async function saveSettings() { + try { + const settings = { + language: document.getElementById('setting-language').value, + music_enabled: document.getElementById('setting-music').checked, + accessibility: { + font_scale: parseFloat(document.getElementById('setting-font-scale').value) + }, + display: { + grid: document.getElementById('setting-grid').value, + font_family: document.getElementById('setting-font-family').value + }, + symlink: { + enabled: document.getElementById('setting-symlink').checked + }, + sources: { + mode: document.getElementById('setting-sources-mode').value, + custom_url: document.getElementById('setting-custom-url').value + }, + show_unsupported_platforms: document.getElementById('setting-show-unsupported').checked, + allow_unknown_extensions: document.getElementById('setting-allow-unknown').checked, + roms_folder: document.getElementById('setting-roms-folder').value.trim() + }; + + const response = await fetch('/api/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ settings: settings }) + }); + + const data = await response.json(); + + if (data.success) { + // Afficher le dialogue de confirmation de redémarrage + showRestartDialog(); + } else { + throw new Error(data.error || t('web_error_unknown')); + } + } catch (error) { + alert('❌ ' + t('web_error_save_settings', error.message)); + } + } + + // Afficher le dialogue de confirmation de redémarrage + function showRestartDialog() { + // Créer le dialogue modal + const modal = document.createElement('div'); + modal.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 10000;'; + + const dialog = document.createElement('div'); + dialog.style.cssText = 'background: white; padding: 30px; border-radius: 10px; max-width: 500px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);'; + + const title = document.createElement('h2'); + title.textContent = t('web_restart_confirm_title'); + title.style.cssText = 'margin: 0 0 20px 0; color: #333;'; + + const message = document.createElement('p'); + message.textContent = t('web_restart_confirm_message'); + message.style.cssText = 'margin: 0 0 30px 0; color: #666; line-height: 1.5;'; + + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = 'display: flex; gap: 10px; justify-content: flex-end;'; + + const btnNo = document.createElement('button'); + btnNo.textContent = t('web_restart_no'); + btnNo.style.cssText = 'padding: 10px 20px; background: #6c757d; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;'; + btnNo.onclick = () => { + modal.remove(); + alert('✅ ' + t('web_settings_saved')); + }; + + const btnYes = document.createElement('button'); + btnYes.textContent = t('web_restart_yes'); + btnYes.style.cssText = 'padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;'; + btnYes.onclick = async () => { + modal.remove(); + await restartApplication(); + }; + + buttonContainer.appendChild(btnNo); + buttonContainer.appendChild(btnYes); + + dialog.appendChild(title); + dialog.appendChild(message); + dialog.appendChild(buttonContainer); + modal.appendChild(dialog); + document.body.appendChild(modal); + } + + // Redémarrer l'application + async function restartApplication() { + try { + const response = await fetch('/api/restart', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const data = await response.json(); + + if (data.success) { + alert('✅ ' + t('web_restart_success')); + } else { + throw new Error(data.error || t('web_error_unknown')); + } + } catch (error) { + alert('❌ ' + t('web_restart_error', error.message)); + } + } + + // Générer un fichier ZIP de support + async function generateSupportZip() { + try { + // Afficher un message de chargement + const loadingMsg = t('web_support_generating'); + const originalButton = event ? event.target : null; + if (originalButton) { + originalButton.disabled = true; + originalButton.innerHTML = '⏳ ' + loadingMsg; + } + + // Appeler l'API pour générer le ZIP + const response = await fetch('/api/support', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || t('web_error_unknown')); + } + + // Télécharger le fichier + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + + // Extraire le nom du fichier depuis les headers + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = 'rgsx_support.zip'; + if (contentDisposition) { + const matches = /filename="?([^"]+)"?/.exec(contentDisposition); + if (matches && matches[1]) { + filename = matches[1]; + } + } + + a.download = filename; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + + // Afficher le message d'instructions + alert(t('web_support_title') + '\\n\\n' + t('web_support_message')); + + // Restaurer le bouton + if (originalButton) { + originalButton.disabled = false; + originalButton.innerHTML = '🆘 ' + t('web_support'); + } + + } catch (error) { + console.error('Erreur génération support:', error); + alert('❌ ' + t('web_support_error', error.message)); + + // Restaurer le bouton en cas d'erreur + const originalButton = event ? event.target : null; + if (originalButton) { + originalButton.disabled = false; + originalButton.innerHTML = '🆘 ' + t('web_support'); + } + } + } + + // Navigateur de répertoires pour ROMs folder + let currentBrowsePath = ''; + let browseInitialized = false; + + async function browseRomsFolder() { + try { + // Récupérer le chemin actuel de l'input SEULEMENT au premier appel + if (!browseInitialized) { + const inputValue = document.getElementById('setting-roms-folder').value.trim(); + if (inputValue) { + currentBrowsePath = inputValue; + } + browseInitialized = true; + } + + const response = await fetch(`/api/browse-directories?path=${encodeURIComponent(currentBrowsePath)}`); + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Erreur lors du listage des répertoires'); + } + + // Créer une modal pour afficher les répertoires + const modal = document.createElement('div'); + modal.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 20px;'; + + const content = document.createElement('div'); + content.style.cssText = 'background: white; border-radius: 10px; padding: 20px; max-width: 600px; width: 100%; max-height: 80vh; overflow-y: auto;'; + + // Titre avec chemin actuel + const title = document.createElement('h2'); + title.textContent = '📂 ' + t('web_browse_title'); + title.style.marginBottom = '10px'; + content.appendChild(title); + + const pathDisplay = document.createElement('div'); + pathDisplay.style.cssText = 'background: #f0f0f0; padding: 10px; border-radius: 5px; margin-bottom: 15px; word-break: break-all; font-family: monospace; font-size: 14px;'; + pathDisplay.textContent = data.current_path || t('web_browse_select_drive'); + content.appendChild(pathDisplay); + + // Boutons d'action + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = 'display: flex; gap: 10px; justify-content: flex-end;'; + + // Bouton parent - afficher si parent_path n'est pas null (même si c'est une chaîne vide pour revenir aux lecteurs) + if (data.parent_path !== null && data.parent_path !== undefined) { + const parentBtn = document.createElement('button'); + parentBtn.textContent = data.parent_path === '' ? '💾 ' + t('web_browse_drives') : '⬆️ ' + t('web_browse_parent'); + parentBtn.style.cssText = 'flex: 1; padding: 10px; background: #6c757d; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold;'; + parentBtn.onclick = () => { + currentBrowsePath = data.parent_path; + modal.remove(); + browseRomsFolder(); + }; + buttonContainer.appendChild(parentBtn); + } + + // Bouton sélectionner ce dossier + if (data.current_path) { + const selectBtn = document.createElement('button'); + selectBtn.textContent = '✅ ' + t('web_browse_select'); + selectBtn.style.cssText = 'flex: 2; padding: 10px; background: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold;'; + selectBtn.onclick = () => { + document.getElementById('setting-roms-folder').value = data.current_path; + currentBrowsePath = ''; + browseInitialized = false; + modal.remove(); + + // Afficher une alerte informant qu'il faut redémarrer + alert('⚠️ ' + t('web_browse_alert_restart', data.current_path)); + }; + buttonContainer.appendChild(selectBtn); + } + + // Bouton annuler + const cancelBtn = document.createElement('button'); + cancelBtn.textContent = '❌ ' + t('web_browse_cancel'); + cancelBtn.style.cssText = 'flex: 1; padding: 10px; background: #dc3545; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold;'; + cancelBtn.onclick = () => { + currentBrowsePath = ''; + browseInitialized = false; + modal.remove(); + }; + buttonContainer.appendChild(cancelBtn); + + content.appendChild(buttonContainer); + + // Liste des répertoires + const dirList = document.createElement('div'); + dirList.style.cssText = 'max-height: 400px; overflow-y: auto; border: 2px solid #ddd; border-radius: 5px;'; + + if (data.directories.length === 0) { + const emptyMsg = document.createElement('div'); + emptyMsg.style.cssText = 'padding: 20px; text-align: center; color: #666;'; + emptyMsg.textContent = t('web_browse_empty'); + dirList.appendChild(emptyMsg); + } else { + data.directories.forEach(dir => { + const dirItem = document.createElement('div'); + dirItem.style.cssText = 'padding: 12px; border-bottom: 1px solid #eee; cursor: pointer; display: flex; align-items: center; gap: 10px; transition: background 0.2s;'; + dirItem.onmouseover = () => dirItem.style.background = '#f0f0f0'; + dirItem.onmouseout = () => dirItem.style.background = 'white'; + + const icon = document.createElement('span'); + icon.textContent = dir.is_drive ? '💾' : '📁'; + icon.style.fontSize = '20px'; + + const name = document.createElement('span'); + name.textContent = dir.name; + name.style.flex = '1'; + + dirItem.appendChild(icon); + dirItem.appendChild(name); + + dirItem.onclick = () => { + currentBrowsePath = dir.path; + modal.remove(); + browseRomsFolder(); + }; + + dirList.appendChild(dirItem); + }); + } + + content.appendChild(dirList); + modal.appendChild(content); + document.body.appendChild(modal); + + // Fermer avec clic en dehors + modal.onclick = (e) => { + if (e.target === modal) { + currentBrowsePath = ''; + browseInitialized = false; + modal.remove(); + } + }; + + } catch (error) { + alert('❌ ' + t('web_error_browse', error.message)); + } + } + + // Initialisation au démarrage + async function init() { + await loadTranslations(); // Charger les traductions + applyTranslations(); // Appliquer les traductions à l'interface + loadPlatforms(); // Charger les plateformes + updateRegionPriorityDisplay(); // Update initial display + + // Vérifier les téléchargements complétés toutes les 2 secondes + setInterval(checkCompletedDownloads, 2000); + } + + // Lancer l'initialisation + init(); + \ No newline at end of file