diff --git a/_locales/de/messages.json b/_locales/de/messages.json index f8f046f..d615242 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1,449 +1,516 @@ { - "appName": { "message": "CinePlex" }, - "appDescription": { "message": "Sucht Plex-Server nach Inhalten und zeigt sie in der Benutzeroberfläche an" }, - "appTagline": { "message": "Filme, Serien und Musik" }, - "appLocaleCode": { "message": "de-DE" }, - "toggleNavigation": { "message": "Navigation umschalten" }, - "searchPlaceholder": { "message": "Suche nach Filmen oder Serien..." }, - "openMusicPlayer": { "message": "Musik-Player öffnen" }, - "settings": { "message": "Einstellungen" }, - "navMovies": { "message": "Filme" }, - "navSeries": { "message": "Serien" }, - "navProviders": { "message": "Anbieter" }, - "navPhotos": { "message": "Fotos" }, - "navStats": { "message": "Statistiken" }, - "navFavorites": { "message": "Favoriten" }, - "navHistory": { "message": "Verlauf" }, - "navRecommendations": { "message": "Empfehlungen" }, - "navMusic": { "message": "Musik" }, - "navM3uGenerator": { "message": "M3U-Generator" }, - "heroWelcome": { "message": "" }, - "heroSubtitle": { "message": "Entdecken Sie Tausende von Filmen und Serien." }, - "addStream": { "message": "Stream hinzufügen" }, - "moreInfo": { "message": "Mehr Info" }, - "popularMovies": { "message": "Beliebte Filme" }, - "allGenres": { "message": "Alle Genres" }, - "allYears": { "message": "Alle Jahre" }, - "sortPopular": { "message": "Am beliebtesten" }, - "sortTopRated": { "message": "Am besten bewertet" }, - "sortRecent": { "message": "Neueste" }, - "loadMore": { "message": "Mehr laden" }, - "photosBreadcrumbHome": { "message": "Alben" }, - "selectServer": { "message": "Wählen Sie einen Server" }, - "loading": { "message": "Laden..." }, - "loadingLibraries": { "message": "Lade Bibliotheken..." }, - "photosEmptyState": { "message": "Keine Alben oder Fotos gefunden." }, - "photosEmptyStateSub": { "message": "Bitte wählen Sie einen Server aus oder stellen Sie sicher, dass Sie eine Fotobibliothek in Plex haben." }, - "statsTitle": { "message": "Bibliotheksstatistiken" }, - "statsAllTokens": { "message": "Alle Token" }, - "statsAnalyzing": { "message": "Analysiere deine Bibliothek..." }, - "statsActiveTokens": { "message": "Aktive Token" }, - "statsServersFound": { "message": "Gefundene Server" }, - "statsUniqueMovies": { "message": "Einzigartige Filme" }, - "statsUniqueSeries": { "message": "Einzigartige Serien" }, - "statsUniqueArtists": { "message": "Einzigartige Künstler" }, - "statsTokenServers": { "message": "Token-Server" }, - "statsChartMoviesByGenre": { "message": "Inhalt nach Genre (Filme)" }, - "statsChartSeriesByGenre": { "message": "Inhalt nach Genre (Serien)" }, - "statsChartByDecade": { "message": "Inhalt nach Jahrzehnt" }, - "recommendationsTitle": { "message": "Empfehlungen für dich" }, - "historyTitle": { "message": "Wiedergabeverlauf" }, - "clearHistory": { "message": "Alles löschen" }, - "consoleTitle": { "message": "Plex-Scan-Konsole" }, - "footerCredit": { "message": "Eine Oberfläche für Ihr Plex-Universum." }, - "closeTrailer": { "message": "Trailer schließen" }, - "close": { "message": "Schließen" }, - "photoViewer": { "message": "Fotobetrachter" }, - "previous": { "message": "Zurück" }, - "next": { "message": "Weiter" }, - "notificationTemplateText": { "message": "Benachrichtigung" }, - "settingsTitleFull": { "message": "Einstellungen und Konfiguration" }, - "settingsTabGeneral": { "message": "Allgemein" }, - "settingsTabPlex": { "message": "Plex" }, - "settingsTabJellyfin": { "message": "Jellyfin" }, - "settingsTabPhpGen": { "message": "PHP-Generator" }, - "settingsTabData": { "message": "Daten" }, - "settingsApiServer": { "message": "API- und Server-Einstellungen" }, - "settingsTmdbApiLabel": { "message": "TMDB-API-Schlüssel (optional)" }, - "settingsTmdbApiPlaceholder": { "message": "Der Standardschlüssel wird verwendet, wenn das Feld leer gelassen wird" }, - "settingsGoogleApiLabel": { "message": "Google Gemini-API-Schlüssel (optional)" }, - "settingsGoogleApiPlaceholder": { "message": "Wird für die Nutzung des KI-Assistenten benötigt" }, - "settingsRegionLabel": { "message": "Region für die Inhaltsentdeckung" }, - "allRegions": { "message": "Alle Regionen" }, - "settingsPhpUrlLabel": { "message": "Server-URL zum Hinzufügen von Streams" }, - "settingsPhpUrlPlaceholder": { "message": "https://ihr-server.com/pfad/zum/skript.php" }, - "settingsInterface": { "message": "Benutzeroberfläche" }, - "settingsLightTheme": { "message": "Heller Modus" }, - "settingsShowHero": { "message": "Willkommensbereich 'Hero' anzeigen" }, - "settingsScanContent": { "message": "Inhalt scannen" }, - "settingsScanDesc": { "message": "Wählen Sie aus, was gescannt werden soll, und drücken Sie die Taste." }, - "settingsScanMovies": { "message": "Filme" }, - "settingsScanShows": { "message": "Serien" }, - "settingsScanArtists": { "message": "Musik" }, - "settingsScanPhotos": { "message": "Fotos" }, - "settingsSelectAll": { "message": "Alles auswählen" }, - "settingsStartScan": { "message": "Scan starten" }, - "settingsPlexTokens": { "message": "Plex-Token" }, - "settingsPlexTokensDesc": { "message": "Bearbeiten Sie die Liste der Plex-Token (JSON-Format)." }, - "settingsSaveTokens": { "message": "Token speichern" }, - "settingsJellyfinTitle": { "message": "Jellyfin-Einstellungen" }, - "settingsJellyfinDesc": { "message": "Fügen Sie Ihre Jellyfin-Serverdetails hinzu, um deren Inhalte zu scannen." }, - "jellyfinUrlLabel": { "message": "Jellyfin-Server-URL" }, - "jellyfinUserLabel": { "message": "Benutzername" }, - "jellyfinPassLabel": { "message": "Passwort" }, - "jellyfinConnectAndScan": { "message": "Verbinden und scannen" }, - "settingsPhpGenTitle": { "message": "PHP-Skript-Generator für Server" }, - "settingsPhpFileOptions": { "message": "Dateioptionen" }, - "settingsPhpSavePathLabel": { "message": "Speicherpfad auf dem Server" }, - "settingsPhpSavePathPlaceholder": { "message": "Bsp.: /var/www/html/listen (leer für denselben Ordner)" }, - "settingsPhpFilenameLabel": { "message": "Dateiname" }, - "settingsPhpFileAction": { "message": "Dateiaktion" }, - "settingsPhpActionAppend": { "message": "Am Ende der Datei anfügen (kumulativ)" }, - "settingsPhpActionOverwrite": { "message": "Datei überschreiben (von vorne beginnen)" }, - "settingsPhpSecurity": { "message": "Sicherheit (optional)" }, - "settingsPhpUseSecretKey": { "message": "Geheimschlüssel verwenden (empfohlen)" }, - "settingsPhpSecretKeyPlaceholder": { "message": "Geben Sie einen sicheren Geheimschlüssel ein" }, - "settingsPhpGeneratedCode": { "message": "Generierter Code" }, - "settingsPhpGeneratedPlaceholder": { "message": "Der generierte PHP-Code wird hier angezeigt." }, - "settingsGenerateScript": { "message": "Skript generieren" }, - "settingsCopyScript": { "message": "Skript kopieren" }, - "settingsDataManagement": { "message": "Lokale Datenbankverwaltung" }, - "settingsImportDb": { "message": "DB aus Datei importieren" }, - "settingsExportDb": { "message": "DB in Datei exportieren" }, - "settingsClearContent": { "message": "Lokale Inhaltsdaten löschen" }, - "settingsClearContentDesc": { "message": "Diese Aktion löscht Filme, Serien und Musik aus der lokalen Datenbank, hat aber keine Auswirkungen auf Ihre Favoriten oder Ihre Einstellungen." }, - "settingsClose": { "message": "Schließen" }, - "settingsSave": { "message": "Einstellungen speichern" }, - "musicSidenavTitle": { "message": "Plex-Musik" }, - "musicAllServers": { "message": "Alle Server" }, - "musicSearchArtistPlaceholder": { "message": "Suche nach einem Künstler..." }, - "musicSearchDiscographyPlaceholder": { "message": "In Diskografie suchen..." }, - "musicNothingPlaying": { "message": "Nichts wird abgespielt" }, - "musicSelectSong": { "message": "Wählen Sie ein Lied" }, - "musicToStart": { "message": "um die Wiedergabe zu starten" }, - "miniplayerDownloadSong": { "message": "Lied herunterladen" }, - "miniplayerDownloadAlbum": { "message": "M3U-Album herunterladen" }, - "miniplayerVolume": { "message": "Lautstärke" }, - "miniplayerShuffle": { "message": "Zufallswiedergabe" }, - "miniplayerEqualizer": { "message": "Equalizer" }, - "miniplayerOpenList": { "message": "Liste öffnen" }, - "eqTitle": { "message": "Grafischer Equalizer" }, - "eqPresetsLabel": { "message": "Voreinstellungen" }, - "eqPresetFlat": { "message": "Flach" }, - "eqPresetRock": { "message": "Rock" }, - "eqPresetPop": { "message": "Pop" }, - "eqPresetJazz": { "message": "Jazz" }, - "eqPresetClassical": { "message": "Klassik" }, - "eqPresetBassBoost": { "message": "Bass-Boost" }, - "eqPreampLabel": { "message": "Vorverstärker" }, - "infoModalTitle": { "message": "Informationen" }, - "infoModalFieldTitle": { "message": "Titel:" }, - "infoModalFieldArtist": { "message": "Künstler:" }, - "infoModalFieldAlbum": { "message": "Album:" }, - "infoModalFieldSong": { "message": "Lied:" }, - "infoModalFieldYear": { "message": "Jahr:" }, - "infoModalFieldGenre": { "message": "Genre:" }, - "lang_en": { "message": "Englisch" }, - "lang_es": { "message": "Spanisch" }, - "lang_fr": { "message": "Französisch" }, - "lang_de": { "message": "Deutsch" }, - "lang_it": { "message": "Italienisch" }, - "lang_pt": { "message": "Portugiesisch" }, - "essentialFeaturesNotSupported": { "message": "Ihr Browser unterstützt keine wesentlichen Funktionen." }, - "dbAccessError": { "message": "Fehler beim Zugriff auf die lokale Datenbank." }, - "dbUpdateNeeded": { "message": "Die Datenbank muss aktualisiert werden, bitte laden Sie die Seite neu." }, - "dbBlocked": { "message": "Bitte schließen Sie andere Tabs dieser Anwendung, um fortzufahren." }, - "deletingContentData": { "message": "Lokale Inhaltsdaten werden gelöscht..." }, - "noContentDataToDelete": { "message": "Keine Inhaltsdaten zum Löschen vorhanden." }, - "contentDataDeleted": { "message": "Inhaltsdaten aus IndexedDB gelöscht." }, - "errorDeletingData": { "message": "Fehler beim Löschen von Daten: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailable": { "message": "Texteditor nicht verfügbar." }, - "errorLoadingTokens": { "message": "Fehler beim Laden der Token zur Bearbeitung." }, - "errorLoadingTokensMessage": { "message": "Fehler beim Laden der Token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailableToSave": { "message": "Editor zum Speichern nicht verfügbar." }, - "invalidJsonFormat": { "message": "Ungültiges JSON-Format. Es muss { \"tokens\": [...] } sein" }, - "tokensSaved": { "message": "Token erfolgreich gespeichert." }, - "errorSavingTokens": { "message": "Fehler beim Speichern der Token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "dbNotAvailable": { "message": "IndexedDB ist nicht verfügbar." }, - "dbExported": { "message": "Datenbank erfolgreich exportiert." }, - "errorExportingDb": { "message": "Fehler beim Exportieren der Datenbank: $message$", "placeholders": { "message": { "content": "$1" } } }, - "invalidJsonFile": { "message": "Die Datei enthält kein gültiges JSON-Objekt." }, - "noDataToImport": { "message": "Die Datei enthält keine Daten für die aktuellen DB-Abschnitte." }, - "dbImported": { "message": "Datenbank erfolgreich importiert." }, - "errorImportingDb": { "message": "Fehler beim Importieren der Datenbank: $message$", "placeholders": { "message": { "content": "$1" } } }, - "updatingView": { "message": "Ansicht mit neuen Daten wird aktualisiert..." }, - "confirmClearContent": { "message": "Sind Sie sicher, dass Sie lokale Inhaltsdaten (Filme, Serien, Musik usw.) löschen möchten? Favoriten und Einstellungen werden NICHT gelöscht." }, - "trailerNotFound": { "message": "Kein Trailer für diesen Titel gefunden." }, - "confirmClearHistory": { "message": "Sind Sie sicher, dass Sie Ihren gesamten Wiedergabeverlauf löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden." }, - "historyCleared": { "message": "Wiedergabeverlauf gelöscht." }, - "historyItemDeleted": { "message": "Element aus dem Verlauf gelöscht." }, - "errorGeneratingScript": { "message": "Generieren Sie zuerst ein Skript, um es kopieren zu können." }, - "scriptCopied": { "message": "PHP-Skript in die Zwischenablage kopiert." }, - "errorCopyingScript": { "message": "Fehler beim Kopieren des Skripts." }, - "scriptGenerated": { "message": "PHP-Skript generiert." }, - "errorLoadingAlbum": { "message": "Fehler beim Laden des Albums: $message$", "placeholders": { "message": { "content": "$1" } } }, - "noPhotoServerSelected": { "message": "Fehler: Es wurde kein Fotoserver ausgewählt." }, - "loadingGenres": { "message": "Lade Genres..." }, - "errorLoadingGenres": { "message": "Fehler beim Laden" }, - "noContentFound": { "message": "Keine Ergebnisse gefunden." }, - "couldNotLoadContent": { "message": "Inhalt konnte nicht geladen werden." }, - "noFavorites": { "message": "Sie haben noch keine Favoriten." }, - "errorLoadingFavorites": { "message": "Fehler beim Laden der Favoriten." }, - "historyEmpty": { "message": "Ihr Verlauf ist leer." }, - "historyEmptySub": { "message": "Entdecken und sehen Sie sich Inhalte an, damit sie hier erscheinen." }, - "errorGeneratingRecommendations": { "message": "Fehler beim Generieren von Empfehlungen." }, - "noRecommendations": { "message": "Wir müssen Sie besser kennenlernen, um Ihnen Empfehlungen geben zu können!" }, - "errorGeneratingStats": { "message": "Fehler beim Generieren von Statistiken." }, - "noServersForToken": { "message": "Keine zugehörigen Server für dieses Token gefunden." }, - "searchingActorContent": { "message": "Suche nach Inhalten von $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, - "errorLoadingActorContent": { "message": "Inhalt für $actorName$ konnte nicht geladen werden.", "placeholders": { "actorName": { "content": "$1" } } }, - "errorAddingStream": { "message": "Fehler beim Hinzufügen von Stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, - "phpUrlNotConfigured": { "message": "Die PHP-Server-URL ist nicht konfiguriert. Bitte konfigurieren Sie sie in den Einstellungen." }, - "searchingStreams": { "message": "Suche nach Streams für \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "sendingStreams": { "message": "Sende $count$ Stream(s) an den Server...", "placeholders": { "count": { "content": "$1" } } }, - "streamAddedSuccess": { "message": "Stream(s) erfolgreich hinzugefügt." }, - "generatingM3U": { "message": "Generiere M3U für \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "m3uDownloaded": { "message": "\"$title$\" heruntergeladen.", "placeholders": { "title": { "content": "$1" } } }, - "errorGeneratingM3U": { "message": "Fehler beim Generieren von M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, - "settingsSavedSuccess": { "message": "Einstellungen erfolgreich gespeichert." }, - "errorSavingSettings": { "message": "Fehler beim Speichern der Einstellungen in der Datenbank." }, - "languageChangeReload": { "message": "Sprache geändert. Die Anwendung wird jetzt neu geladen." }, - "addedToFavorites": { "message": "Zu den Favoriten hinzugefügt." }, - "removedFromFavorites": { "message": "Aus den Favoriten entfernt." }, - "plexScanInProgress": { "message": "Plex-Scan läuft bereits." }, - "plexScanStarting": { "message": "Plex-Scan wird gestartet..." }, - "noPlexTokens": { "message": "Keine Plex-Token konfiguriert." }, - "clearingSections": { "message": "Lösche Abschnitte: $sections$", "placeholders": { "sections": { "content": "$1" } } }, - "initialScanPhaseComplete": { "message": "Erste Scanphase abgeschlossen." }, - "retryPhaseFinished": { "message": "Wiederholungsphase abgeschlossen." }, - "plexScanFinished": { "message": "Scan abgeschlossen. Inhalt wird aktualisiert..." }, - "scanCancelled": { "message": "Scan vom Benutzer abgebrochen." }, - "scanCancelledInfo": { "message": "Scan abgebrochen." }, - "errorInitializingMusicPlayer": { "message": "Fehler beim Initialisieren des Musik-Players." }, - "criticalErrorLoadingMusic": { "message": "Kritischer Fehler beim Laden der Musikdaten." }, - "errorLoadingArtists": { "message": "Fehler beim Laden der Künstler." }, - "dbUnavailableError": { "message": "Fehler: Datenbank nicht verfügbar." }, - "updatingMusicData": { "message": "Musikdaten werden aktualisiert..." }, - "musicDataUpdated": { "message": "Musikdaten aktualisiert." }, - "errorFetchingArtistSongs": { "message": "Fehler beim Abrufen der Lieder des Künstlers." }, - "errorLoadingSongs": { "message": "Fehler beim Laden der Lieder." }, - "noArtistsFound": { "message": "Keine Künstler gefunden." }, - "shuffleOn": { "message": "Zufallswiedergabe ein." }, - "shuffleOff": { "message": "Zufallswiedergabe aus." }, - "playbackError": { "message": "Wiedergabefehler" }, - "errorLabel": { "message": "Fehler" }, - "reloadingPage": { "message": "Seite wird neu geladen..." }, - "viewed": { "message": "Gesehen" }, - "local": { "message": "Lokal" }, - "topRatedSort": {"message": "Am besten bewertet"}, - "recentSort": {"message": "Neueste"}, - "popularSort": {"message": "Beliebteste"}, - "moviesSectionTitle": {"message": "Filme"}, - "seriesSectionTitle": {"message": "Serien"}, - "searchResultsFor": {"message": "Ergebnisse für \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, - "contentFrom": {"message": "Inhalt von $actor$", "placeholders": {"actor": {"content": "$1"}}}, - "explore": {"message": "Entdecken"}, - "noGenre": {"message": "Ohne Kategorie"}, - "synopsis": {"message": "Zusammenfassung"}, - "noSynopsis": {"message": "Keine Zusammenfassung verfügbar."}, - "director": {"message": "Regisseur:"}, - "writer": {"message": "Autor:"}, - "viewOnImdb": {"message": "Auf IMDb ansehen"}, - "watchTrailer": {"message": "Trailer ansehen"}, - "addToFavorites": {"message": "Zu den Favoriten hinzufügen"}, - "removeFromFavorites": {"message": "Aus den Favoriten entfernen"}, - "notAvailable": {"message": "Nicht verfügbar"}, - "mainCast": {"message": "Hauptbesetzung"}, - "seasonsAndEpisodes": {"message": "Staffeln und Episoden"}, - "similarContent": {"message": "Ähnlicher Inhalt"}, - "filmography": {"message": "Filmografie"}, - "availableOn": {"message": "Verfügbar auf"}, - "episodesCount": {"message": "$count$ Episoden", "placeholders": {"count": {"content": "$1"}}}, - "seasonsCount": {"message": "$count$ Staffeln", "placeholders": {"count": {"content": "$1"}}}, - "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, - "noTrailerFound": {"message": "Kein Trailer für diesen Titel gefunden."}, - "fatalInitError": {"message": "Fataler Initialisierungsfehler"}, - "fatalInitErrorSub": {"message": "Die Anwendung konnte nicht geladen werden."}, - "invalidStreamInfo": {"message": "Ungültige Informationen."}, - "dbUnavailableForStreams": {"message": "Lokale Datenbank nicht verfügbar."}, - "noPlexServersForStreams": {"message": "Keine Plex-Server."}, - "notFoundOnServers": {"message": "\"$query$\" auf Plex-Servern nicht gefunden.", "placeholders": {"query": {"content": "$1"}}}, - "relativeTime_justNow": { "message": "Gerade eben" }, - "relativeTime_minutesAgo": { "message": "Vor $count$ Minuten", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_hoursAgo": { "message": "Vor $count$ Stunden", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_yesterday": { "message": "Gestern" }, - "relativeTime_daysAgo": { "message": "Vor $count$ Tagen", "placeholders": { "count": { "content": "$1" } } }, - "errorLoadingDetails": { "message": "Fehler beim Laden der Details" }, - "errorLoadingLocalContent": { "message": "Fehler beim Laden des lokalen Inhalts." }, - "errorServerResponse": { "message": "Nicht erfolgreiche Serverantwort." }, - "errorPlexApi": { "message": "Plex-API-Fehler $status$.", "placeholders": { "status": { "content": "$1" } } }, - "errorParsingPlexXml": { "message": "Fehler beim Parsen von Plex-XML." }, - "untitled": { "message": "Ohne Titel" }, - "itemCount": { "message": "$count$ Elemente", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Keine Fotoserver" }, - "jellyfinScanInProgress": { "message": "Jellyfin-Scan läuft bereits." }, - "jellyfinScanning": { "message": "Scanne Jellyfin..." }, - "jellyfinMissingCredentials": { "message": "Bitte vervollständigen Sie die Jellyfin-URL und den Benutzernamen." }, - "jellyfinConnecting": { "message": "Verbinde mit Jellyfin unter: $url$", "placeholders": { "url": { "content": "$1" } } }, - "jellyfinAuthFailed": { "message": "Jellyfin-Authentifizierung fehlgeschlagen: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinAuthSuccess": { "message": "Jellyfin-Authentifizierung erfolgreich." }, - "jellyfinFetchingLibraries": { "message": "Rufe Bibliotheken ab..." }, - "jellyfinFetchFailed": { "message": "Fehler beim Abrufen der Bibliotheken: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinNoMediaLibraries": { "message": "Keine Film- oder Serienbibliotheken in Jellyfin gefunden." }, - "jellyfinLibrariesFound": { "message": "$count$ Medienbibliothek(en) gefunden.", "placeholders": { "count": { "content": "$1" } } }, - "jellyfinLibraryScanSuccess": { "message": "[Erfolg] '$libraryName' gescannt, $count$ Titel hinzugefügt.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, - "jellyfinLibraryScanFailed": { "message": "Fehler beim Scannen der Bibliothek '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, - "jellyfinScanSuccess": { "message": "Jellyfin-Scan abgeschlossen. $movies$ Filme und $series$ Serien hinzugefügt.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, - "noJellyfinCredentials": { "message": "Jellyfin-Anmeldeinformationen nicht konfiguriert." }, - "notFoundOnJellyfin": { "message": "\"$query$\" auf Jellyfin nicht gefunden.", "placeholders": { "query": { "content": "$1" } } }, - "notFoundOnAnyServer": { "message": "\"$query$\" auf keinem Server gefunden.", "placeholders": { "query": { "content": "$1" } } }, - "localOnPlex": { "message": "Auf Plex" }, - "searchOnPlex": { "message": "Auf Plex suchen" }, - "jellyfinTitle": { "message": "Jellyfin-Inhalt" }, - "noJellyfinContent": { "message": "Kein Jellyfin-Inhalt gefunden." }, - "noJellyfinContentSub": { "message": "Stellen Sie sicher, dass Sie Ihren Jellyfin-Server in den Einstellungen gescannt haben." }, - "activityViewerTitle": { "message": "Server-Aktivitätsanzeige" }, - "activitySelectServer": { "message": "Wählen Sie einen Server" }, - "activityCheckBtn": { "message": "Aktualisieren" }, - "activityNoSessions": { "message": "Keine aktiven Sitzungen auf diesem Server." }, - "activitySessionUser": { "message": "Benutzer" }, - "activitySessionDevice": { "message": "Gerät" }, - "activitySessionContent": { "message": "Inhalt" }, - "activitySessionState": { "message": "Status" }, - "activitySessionIdentifier": { "message": "Client-Identifikator" }, - "activityCopyID": { "message": "ID kopieren" }, - "activityError": { "message": "Serveraktivität konnte nicht abgerufen werden." }, - "activityCopied": { "message": "Identifikator in die Zwischenablage kopiert!" }, - "activityCopyError": { "message": "Fehler beim Kopieren des Identifikators." }, - "noProvidersFound": { "message": "Keine Anbieter gefunden." }, - "availableOnPlex": { "message": "Verfügbar auf Plex" }, - "m3uGeneratorTitle": { "message": "M3U-Listen-Generator" }, - "selectAServer": { "message": "Wählen Sie einen Server..." }, - "downloadM3u": { "message": "M3U herunterladen" }, - "m3uGenerator": { "message": "M3U-Generator" }, - "selectLibraries": { "message": "Bibliotheken auswählen" }, - "howToUse": { "message": "Anwendung" }, - "m3uInstruction1": { "message": "Wählen Sie einen Server aus der Liste." }, - "m3uInstruction2": { "message": "Wählen Sie eine oder mehrere Bibliotheken aus, die einbezogen werden sollen." }, - "m3uInstruction3": { "message": "Klicken Sie auf die Download-Schaltfläche." }, - "m3uInstruction4": { "message": "Importieren Sie die .m3u-Datei in Ihren kompatiblen Player." }, - "chatOpen": { "message": "Chat öffnen" }, - "chatTitle": { "message": "KI-Assistent" }, - "chatClose": { "message": "X" }, - "chatPlaceholder": { "message": "Geben Sie Ihre Nachricht ein..." }, - "chatSend": { "message": "➤" }, - "chatWelcome": { "message": "Willkommen! Ich bin Ihr CinePlex-Assistent. Fragen Sie mich nach Filmen, Serien oder allem, was Sie sonst noch wissen möchten." }, - "chatGoogleApiKeyMissing": { "message": "Der Google Gemini-API-Schlüssel ist nicht konfiguriert. Bitte legen Sie ihn in den Erweiterungseinstellungen fest, um den KI-Assistenten zu verwenden." }, - "chatApiInvalidResponse": { "message": "Die API hat eine ungültige Antwort zurückgegeben. Bitte versuchen Sie es erneut." }, - "chatApiError": { "message": "Fehler bei der Kommunikation mit dem KI-Assistenten" }, - "downloadAll": { "message": "Alles herunterladen" }, - "download": { "message": "Herunterladen" }, - "aiToolSearchLibraryDesc": { "message": "Durchsucht die Plex-Bibliothek des Benutzers nach Filmen oder Serien nach Titel." }, - "aiToolSearchLibraryQueryParamDesc": { "message": "Der Titel des zu suchenden Films oder der zu suchenden Serie." }, - "aiToolSearchLibraryTypeParamDesc": { "message": "Der Typ des zu suchenden Inhalts. Kann 'movie' für Filme oder 'series' für Serien sein. (Optional)." }, - "aiToolSearchLibraryResolutionParamDesc": { "message": "Die zu suchende Videoauflösung (z. B. '4k', '1080p'). (Optional)." }, - "aiToolSearchLibraryContainerParamDesc": { "message": "Das zu suchende Video-Containerformat (z. B. 'mkv', 'mp4'). (Optional)." }, - "aiToolNavigateToPageDesc": { "message": "Navigiert den Benutzer zu einer bestimmten Seite der Anwendungsoberfläche." }, - "aiToolNavigateToPagePageParamDesc": { "message": "Der Name der Seite, zu der navigiert werden soll, z. B.: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers' oder 'm3u-generator'." }, - "aiToolGetUserStatsDesc": { "message": "Ruft die Bibliotheksstatistiken des Benutzers ab und zeigt sie an, z. B. die Gesamtzahl der einzigartigen Filme, Serien und Künstler." }, - "aiToolShowItemDetailsDesc": { "message": "Zeigt die Detailseite eines bestimmten Films oder einer bestimmten Serie nach Titel und Typ an." }, - "aiToolShowItemDetailsTitleParamDesc": { "message": "Der genaue Titel des Films oder der Serie." }, - "aiToolShowItemDetailsTypeParamDesc": { "message": "Der Typ des Inhalts. Muss 'movie' oder 'series' sein." }, - "aiToolAddToPlaylistDesc": { "message": "Fügt einen Film oder eine Serie zur aktuellen Wiedergabeliste des Benutzers hinzu, um sie an einen konfigurierten PHP-Server zu streamen." }, - "aiToolAddToPlaylistTitleParamDesc": { "message": "Der Titel des hinzuzufügenden Films oder der hinzuzufügenden Serie." }, - "aiToolAddToPlaylistTypeParamDesc": { "message": "Der Typ des Inhalts. Muss 'movie' oder 'series' sein." }, - "aiToolCheckAndDownloadDesc": { "message": "Überprüft die Verfügbarkeit einer Liste von Film- oder Serientiteln auf den lokalen Servern des Benutzers und generiert und lädt, falls gefunden, eine M3U-Wiedergabelistendatei mit den gefundenen Streams herunter." }, - "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Ein Array von Film- oder Serientiteln zum Suchen und Herunterladen." }, - "aiToolCheckAndDownloadTypeParamDesc": { "message": "Der Inhaltstyp der Liste. Muss 'movie' oder 'series' sein." }, - "aiToolCheckAndDownloadFilenameParamDesc": { "message": "Der Name der herunterzuladenden M3U-Datei (z. B. 'MeineListe.m3u'). Wenn nicht angegeben, wird ein Standardname verwendet." }, - "aiToolToggleFavoriteDesc": { "message": "Fügt einen Film oder eine Serie zur Favoritenliste des Benutzers hinzu oder entfernt sie daraus." }, - "aiToolToggleFavoriteTitleParamDesc": { "message": "Der Titel des Films oder der Serie." }, - "aiToolToggleFavoriteTypeParamDesc": { "message": "Der Typ des Inhalts. Muss 'movie' oder 'series' sein." }, - "aiToolGetRecommendationsDesc": { "message": "Generiert und zeigt eine Liste von Film- oder Serienempfehlungen basierend auf dem Wiedergabeverlauf und den Favoriten des Benutzers an." }, - "aiToolApplyFiltersDesc": { "message": "Wendet Filter auf die aktuelle Ansicht von Filmen oder Serien an, um die Ergebnisse nach Typ, Genre, Jahr und Sortierreihenfolge zu verfeinern." }, - "aiToolApplyFiltersTypeParamDesc": { "message": "Der Typ des Inhalts, auf den die Filter angewendet werden sollen. Muss 'movie' oder 'series' sein." }, - "aiToolApplyFiltersGenreParamDesc": { "message": "Der Name des Genres, nach dem gefiltert werden soll (z. B. 'Action', 'Drama')." }, - "aiToolApplyFiltersYearParamDesc": { "message": "Das Erscheinungsjahr, nach dem gefiltert werden soll (z. B. '2023')." }, - "aiToolApplyFiltersSortParamDesc": { "message": "Das Sortierkriterium für die Ergebnisse. Gültige Werte: 'popularity.desc' (beliebt), 'vote_average.desc' (am besten bewertet), 'release_date.desc' (neu für Filme) oder 'first_air_date.desc' (neu für Serien)." }, - "aiToolPlayMusicByArtistDesc": { "message": "Öffnet den Musik-Player und beginnt mit der Wiedergabe von Liedern eines bestimmten Künstlers aus der Bibliothek des Benutzers." }, - "aiToolPlayMusicByArtistNameParamDesc": { "message": "Der genaue Name des Künstlers, dessen Lieder abgespielt werden sollen." }, - "aiToolClearChatHistoryDesc": { "message": "Löscht den gesamten Nachrichtenverlauf der aktuellen Konversation mit dem KI-Assistenten." }, - "aiToolDeleteDatabaseDesc": { "message": "Löscht die gesamte lokale Datenbank der Erweiterung, einschließlich gescannter Inhalte, Einstellungen und Favoriten. Diese Aktion ist irreversibel und lädt die Anwendung neu." }, - "aiToolUpdateAllTokensDesc": { "message": "Startet einen vollständigen Scan aller Plex-Server und Bibliotheken, die mit den in der Erweiterung konfigurierten Token verknüpft sind. Aktualisiert alle Filme, Serien, Künstler und Fotos." }, - "aiToolAddPlexTokenDesc": { "message": "Fügt der Konfiguration der Erweiterung einen neuen X-Plex-Token hinzu, sodass die Anwendung Inhalte von neuen Plex-Servern scannen kann." }, - "aiToolAddPlexTokenTokenParamDesc": { "message": "Die hinzuzufügende X-Plex-Token-Zeichenfolge." }, - "aiToolChangeRegionDesc": { "message": "Ändert die für die Inhaltsentdeckung in der TMDB-API verwendete Region. Dies wirkt sich auf die in den Film- und Serienabschnitten angezeigten Ergebnisse sowie auf die Streaming-Anbieter aus." }, - "aiToolChangeRegionRegionParamDesc": { "message": "Der zweibuchstabige ISO 3166-1-Ländercode für die neue Region (z. B. 'US' für die Vereinigten Staaten, 'ES' für Spanien, 'MX' für Mexiko)." }, - "aiToolClearAllFavoritesDesc": { "message": "Entfernt alle Filme und Serien, die der Benutzer als Favoriten markiert hat." }, - "aiToolClearViewingHistoryDesc": { "message": "Löscht den Wiedergabeverlauf des Benutzers von der Verlaufsseite." }, - "aiToolClearRecommendationsViewDesc": { "message": "Löscht die Empfehlungsansicht und entfernt zwischengespeicherte Empfehlungen." }, - "aiToolSearchNotFound": { "message": "'$query' in Ihrer Bibliothek nicht gefunden.", "placeholders": { "query": { "content": "$1" } } }, - "aiToolNavigateSuccess": { "message": "Zur Seite $page$ navigiert.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolNavigateError": { "message": "Fehler beim Navigieren zur Seite $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolStatsError": { "message": "Fehler beim Abrufen der Statistiken." }, - "aiToolItemNotFound": { "message": "Element '$title' nicht gefunden.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolShowItemDetailsSuccess": { "message": "Zeige Details für '$title'.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolAddToPlaylistSuccess": { "message": "'$title' zur Wiedergabeliste hinzugefügt.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteAdded": { "message": "'$title' zu den Favoriten hinzugefügt.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteRemoved": { "message": "'$title' aus den Favoriten entfernt.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolRecommendationsSuccess": { "message": "Zeige Empfehlungen an." }, - "aiToolApplyFiltersGenreNotFound": { "message": "Genre '$genre' nicht gefunden.", "placeholders": { "genre": { "content": "$1" } } }, - "aiToolApplyFiltersSuccess": { "message": "Filter erfolgreich angewendet." }, - "aiToolPlayMusicNotReady": { "message": "Der Musik-Player ist nicht bereit. Stellen Sie sicher, dass Ihre Plex-Musikbibliothek gescannt wurde." }, - "aiToolPlayMusicArtistNotFound": { "message": "Künstler '$artist_name' nicht gefunden.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicNoSongs": { "message": "Keine Lieder für '$artist_name' gefunden.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicSuccess": { "message": "Spiele Musik von '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolChatHistoryCleared": { "message": "Chatverlauf gelöscht." }, - "aiToolConfirmDeleteDatabase": { "message": "Sind Sie sicher, dass Sie die lokale Datenbank löschen möchten? Diese Aktion ist irreversibel." }, - "aiToolDeleteDatabaseCancelled": { "message": "Löschen der Datenbank abgebrochen." }, - "aiToolExecutionError": { "message": "Fehler beim Ausführen des Tools '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, - "aiToolUnknown": { "message": "Unbekanntes Tool: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, - "aiToolFavoritesCleared": { "message": "Favoriten gelöscht." }, - "aiToolFavoritesClearError": { "message": "Fehler beim Löschen der Favoriten: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolRecommendationsCleared": { "message": "Empfehlungen gelöscht." }, - "aiToolRecommendationsClearError": { "message": "Fehler beim Löschen der Empfehlungen: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleted": { "message": "Datenbank gelöscht. Die Seite wird neu geladen." }, - "aiToolDatabaseDeleteError": { "message": "Fehler beim Löschen der Datenbank: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleteBlocked": { "message": "Das Löschen der Datenbank ist blockiert. Schließen Sie andere Tabs der Anwendung." }, - "aiToolUpdateAllTokensSuccess": { "message": "Alle Token wurden erfolgreich aktualisiert." }, - "aiToolUpdateAllTokensError": { "message": "Fehler beim Aktualisieren der Token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolAddPlexTokenSuccess": { "message": "Plex-Token erfolgreich hinzugefügt." }, - "aiToolAddPlexTokenError": { "message": "Fehler beim Hinzufügen des Plex-Tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolChangeRegionSuccess": { "message": "Region auf $region$ geändert. Der Inhalt wird aktualisiert.", "placeholders": { "region": { "content": "$1" } } }, - "aiToolChangeRegionError": { "message": "Fehler beim Ändern der Region: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolViewingHistoryCleared": { "message": "Wiedergabeverlauf gelöscht." }, - "aiToolViewingHistoryClearError": { "message": "Fehler beim Löschen des Wiedergabeverlaufs: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiSystemPrompt_v3": { "message": "Sie sind ein erfahrener Film- und Serienassistent namens CinePlex. Ihre Hauptfunktion besteht darin, Benutzern bei der Entdeckung von Inhalten und der Interaktion mit ihrer Bibliothek zu helfen. Befolgen Sie diese Regeln strikt: 1. **TUN SIE NIEMALS SO**, als hätten Sie eine Aktion ausgeführt, wenn Sie kein Werkzeug dafür verwendet haben. Sagen Sie zum Beispiel nicht 'Ich habe X heruntergeladen', wenn Sie das Download-Tool nicht verwendet haben. 2. Bei Empfehlungs- oder Listenanfragen (z. B. 'Nennen Sie mir 5 Horrorfilme') verwenden Sie Ihr eigenes Wissen, um die Liste zu erstellen. Präsentieren Sie sie in nummerierter oder Aufzählungsform. Fragen Sie den Benutzer nach der Anzeige der Liste proaktiv, ob er die Verfügbarkeit auf seinen lokalen Servern überprüfen und eine M3U-Datei erstellen soll. 3. **NUR** wenn der Benutzer bestätigt, dass er die Liste überprüfen oder herunterladen möchte, verwenden Sie das Tool `check_and_download_titles_list`. Verwenden Sie es nicht ohne ausdrückliche Bestätigung. 4. Für alle anderen Aktionen wie das Navigieren, das Abrufen von Statistiken, das Suchen nach einem bestimmten Titel oder das Filtern nach Auflösung oder Container verwenden Sie die entsprechenden Tools. Seien Sie immer prägnant, freundlich und effizient." }, - "aiToolM3UNoTitlesProvided": { "message": "Bitte geben Sie eine Liste von Titeln an, um die Wiedergabeliste zu erstellen." }, - "aiToolM3UCheckingTitles": { "message": "Überprüfe die Titel auf Ihren lokalen Servern..." }, - "aiToolM3UNoLocalMatchesForDownload": { "message": "Ich habe keinen der Filme oder Serien aus der Liste auf Ihren lokalen Servern gefunden." }, - "aiToolM3UDownloadStarted": { "message": "Fertig! Ich habe $1 von $2 Titeln auf Ihren Servern gefunden und den Download der M3U-Wiedergabeliste gestartet.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, - "backToProviders": { "message": "Zurück zu den Anbietern" }, - "artistsCounterSingle": { "message": "$total$ Künstler", "placeholders": { "total": { "content": "$1" } } }, - "artistsCounterLoading": { "message": "Laden..." }, - "downloadingSong": { "message": "Starte Download von \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "songDownloaded": { "message": "\"$title$\" heruntergeladen.", "placeholders": { "title": { "content": "$1" } } }, - "errorDownloadingSong": { "message": "Fehler beim Herunterladen von \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "generatingAlbumM3U": { "message": "Generiere M3U für \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, - "albumM3UGenerated": { "message": "M3U für Album \"$artist$\" generiert.", "placeholders": { "artist": { "content": "$1" } } }, - "retyingSection": { "message": "Wiederhole Abschnitt \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "retrySuccess": { "message": "[ERFOLG] Wiederholung von \"$title$\" abgeschlossen.", "placeholders": { "title": { "content": "$1" } } }, - "retryError": { "message": "[FEHLER] Wiederholung für \"$title$\" fehlgeschlagen: $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, - "startingRetryPhase": { "message": "Starte Wiederholungsphase für $count$ Abschnitte...", "placeholders": { "count": { "content": "$1" } } }, - "tokenFoundServers": { "message": "Token $token$... hat $count$ Server gefunden.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, - "errorProcessingToken": { "message": "Fehler beim Verarbeiten des Tokens $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, - "plexScanFatalError": { "message": "FATALER FEHLER: $message$", "placeholders": { "message": { "content": "$1" } } }, - "errorDuringScan": { "message": "Fehler während des Scans: $message$", "placeholders": { "message": { "content": "$1" } } }, - "stoppingPlexScan": { "message": "Plex-Scan wird gestoppt..." }, - "invalidTokenProvided": { "message": "Ungültiges Token angegeben." }, - "tokenAlreadyExists": { "message": "Token existiert bereits." }, - "tokenAddedSuccessfully": { "message": "Token erfolgreich hinzugefügt." }, - "noStreamsFoundForSelection": { "message": "Keine Streams für die Auswahl gefunden." }, - "autoplayBlocked": { "message": "Autoplay blockiert." }, - "page": { "message": "Seite" }, - "all": { "message": "Alle" }, - "userScore": { "message": "Benutzerbewertung" }, - "duration": { "message": "Dauer" }, - "min": { "message": "Min" }, - "max": { "message": "Max" } + "appName": { "message": "CinePlex" }, + "appDescription": { "message": "Scannt Plex-Server, um Inhalte zu finden, und zeigt sie in der Benutzeroberfläche an" }, + "appTagline": { "message": "Filme, Serien und Musik" }, + "appLocaleCode": { "message": "de-DE" }, + "toggleNavigation": { "message": "Navigation umschalten" }, + "searchPlaceholder": { "message": "Suche nach Filmen oder Serien..." }, + "openMusicPlayer": { "message": "Musikplayer öffnen" }, + "settings": { "message": "Einstellungen" }, + "navMovies": { "message": "Filme" }, + "navSeries": { "message": "Serien" }, + "navProviders": { "message": "Anbieter" }, + "navPhotos": { "message": "Fotos" }, + "navStats": { "message": "Statistiken" }, + "navFavorites": { "message": "Favoriten" }, + "navHistory": { "message": "Verlauf" }, + "navRecommendations": { "message": "Empfehlungen" }, + "navMusic": { "message": "Musik" }, + "musicFeaturedPlaylists": { "message": "Empfohlene Playlists" }, + "musicRecentlyAdded": { "message": "Kürzlich hinzugefügt" }, + "navM3uGenerator": { "message": "M3U-Generator" }, + "heroWelcome": { "message": "" }, + "heroSubtitle": { "message": "Entdecke Tausende von Filmen und Serien." }, + "addStream": { "message": "Stream hinzufügen" }, + "moreInfo": { "message": "Mehr Informationen" }, + "popularMovies": { "message": "Beliebte Filme" }, + "allGenres": { "message": "Alle Genres" }, + "allYears": { "message": "Alle Jahre" }, + "sortPopular": { "message": "Am beliebtesten" }, + "sortTopRated": { "message": "Am besten bewertet" }, + "sortRecent": { "message": "Am neuesten" }, + "loadMore": { "message": "Mehr laden" }, + "photosBreadcrumbHome": { "message": "Alben" }, + "selectServer": { "message": "Wähle einen Server" }, + "loading": { "message": "Wird geladen..." }, + "loadingLibraries": { "message": "Bibliotheken werden geladen..." }, + "photosEmptyState": { "message": "Keine Alben oder Fotos gefunden." }, + "photosEmptyStateSub": { "message": "Bitte wähle einen Server aus oder stelle sicher, dass du eine Fotobibliothek in Plex hast." }, + "statsTitle": { "message": "Bibliotheksstatistiken" }, + "statsAllTokens": { "message": "Alle Tokens" }, + "statsAnalyzing": { "message": "Analysiere deine Bibliothek..." }, + "statsActiveTokens": { "message": "Aktive Tokens" }, + "statsServersFound": { "message": "Gefundene Server" }, + "statsUniqueMovies": { "message": "Einzigartige Filme" }, + "statsUniqueSeries": { "message": "Einzigartige Serien" }, + "statsUniqueArtists": { "message": "Einzigartige Künstler" }, + "statsTokenServers": { "message": "Server des Tokens" }, + "statsChartMoviesByGenre": { "message": "Inhalt nach Genre (Filme)" }, + "statsChartSeriesByGenre": { "message": "Inhalt nach Genre (Serien)" }, + "statsChartByDecade": { "message": "Inhalt nach Jahrzehnt" }, + "recommendationsTitle": { "message": "Empfehlungen für dich" }, + "historyTitle": { "message": "Wiedergabeverlauf" }, + "clearHistory": { "message": "Alles löschen" }, + "consoleTitle": { "message": "Plex-Scan-Konsole" }, + "footerCredit": { "message": "Eine Benutzeroberfläche für dein Plex-Universum." }, + "closeTrailer": { "message": "Trailer schließen" }, + "close": { "message": "Schließen" }, + "photoViewer": { "message": "Fotoanzeige" }, + "previous": { "message": "Zurück" }, + "next": { "message": "Weiter" }, + "notificationTemplateText": { "message": "Benachrichtigung" }, + "settingsTitleFull": { "message": "Einstellungen und Konfiguration" }, + "settingsTabGeneral": { "message": "Allgemein" }, + "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, + "settingsTabPhpGen": { "message": "PHP-Generator" }, + "settingsTabData": { "message": "Daten" }, + "settingsApiServer": { "message": "API- und Serverkonfiguration" }, + "settingsTmdbApiLabel": { "message": "TMDB-API-Schlüssel (Optional)" }, + "settingsTmdbApiPlaceholder": { "message": "Der Standardschlüssel wird verwendet, wenn das Feld leer gelassen wird" }, + "settingsGoogleApiLabel": { "message": "Google Gemini API-Schlüssel (Optional)" }, + "settingsGoogleApiPlaceholder": { "message": "Erforderlich, um den KI-Assistenten zu verwenden" }, + "settingsRegionLabel": { "message": "Region für die Inhaltssuche" }, + "allRegions": { "message": "Alle Regionen" }, + "settingsPhpUrlLabel": { "message": "Server-URL zum Hinzufügen von Streams" }, + "settingsPhpUrlPlaceholder": { "message": "https://dein-server.com/pfad/zum/skript.php" }, + "settingsInterface": { "message": "Benutzeroberfläche" }, + "settingsLightTheme": { "message": "Heller Modus" }, + "settingsShowHero": { "message": "Willkommensbereich 'Hero' anzeigen" }, + "settingsScanContent": { "message": "Inhaltsscan" }, + "settingsScanDesc": { "message": "Wähle aus, was gescannt werden soll, und drücke die Taste." }, + "settingsScanMovies": { "message": "Filme" }, + "settingsScanShows": { "message": "Serien" }, + "settingsScanArtists": { "message": "Musik" }, + "settingsScanPhotos": { "message": "Fotos" }, + "settingsSelectAll": { "message": "Alles auswählen" }, + "settingsStartScan": { "message": "Scan starten" }, + "settingsPlexTokens": { "message": "Plex-Tokens" }, + "settingsPlexTokensDesc": { "message": "Bearbeite die Liste der Plex-Tokens (JSON-Format)." }, + "settingsSaveTokens": { "message": "Tokens speichern" }, + "settingsJellyfinTitle": { "message": "Jellyfin-Konfiguration" }, + "settingsJellyfinDesc": { "message": "Füge die Daten deines Jellyfin-Servers hinzu, um dessen Inhalt zu scannen." }, + "jellyfinUrlLabel": { "message": "Jellyfin-Server-URL" }, + "jellyfinUserLabel": { "message": "Benutzername" }, + "jellyfinPassLabel": { "message": "Passwort" }, + "jellyfinConnectAndScan": { "message": "Verbinden und Scannen" }, + "settingsPhpGenTitle": { "message": "PHP-Skript-Generator für den Server" }, + "settingsPhpFileOptions": { "message": "Dateioptionen" }, + "settingsPhpSavePathLabel": { "message": "Speicherpfad auf dem Server" }, + "settingsPhpSavePathPlaceholder": { "message": "Bsp: /var/www/html/listen (leer für denselben Ordner)" }, + "settingsPhpFilenameLabel": { "message": "Dateiname" }, + "settingsPhpFileAction": { "message": "Aktion für die Datei" }, + "settingsPhpActionAppend": { "message": "An das Ende der Datei anhängen (kumulativ)" }, + "settingsPhpActionOverwrite": { "message": "Datei überschreiben (von vorne beginnen)" }, + "settingsPhpSecurity": { "message": "Sicherheit (Optional)" }, + "settingsPhpUseSecretKey": { "message": "Geheimschlüssel verwenden (Empfohlen)" }, + "settingsPhpSecretKeyPlaceholder": { "message": "Gib einen sicheren Geheimschlüssel ein" }, + "settingsPhpGeneratedCode": { "message": "Generierter Code" }, + "settingsPhpGeneratedPlaceholder": { "message": "Der generierte PHP-Code wird hier angezeigt." }, + "settingsGenerateScript": { "message": "Skript generieren" }, + "settingsCopyScript": { "message": "Skript kopieren" }, + "settingsDataManagement": { "message": "Verwaltung der lokalen Datenbank" }, + "settingsImportDb": { "message": "DB aus Datei importieren" }, + "settingsExportDb": { "message": "DB in Datei exportieren" }, + "settingsClearContent": { "message": "Lokale Inhaltsdaten löschen" }, + "settingsClearContentDesc": { "message": "Diese Aktion löscht Filme, Serien und Musik aus der lokalen Datenbank, hat aber keine Auswirkungen auf deine Favoriten oder Einstellungen." }, + "settingsClose": { "message": "Schließen" }, + "settingsSave": { "message": "Einstellungen speichern" }, + "musicSidenavTitle": { "message": "Plex-Musik" }, + "musicAllServers": { "message": "Alle Server" }, + "musicSearchArtistPlaceholder": { "message": "Künstler suchen..." }, + "musicSearchDiscographyPlaceholder": { "message": "In der Diskografie suchen..." }, + "musicNothingPlaying": { "message": "Nichts wird abgespielt" }, + "musicSelectSong": { "message": "Wähle ein Lied" }, + "musicToStart": { "message": "um die Wiedergabe zu starten" }, + "miniplayerDownloadSong": { "message": "Lied herunterladen" }, + "miniplayerDownloadAlbum": { "message": "M3U herunterladen" }, + "miniplayerVolume": { "message": "Lautstärke" }, + "miniplayerShuffle": { "message": "Zufallswiedergabe" }, + "miniplayerEqualizer": { "message": "Equalizer" }, + "miniplayerOpenList": { "message": "Liste öffnen" }, + "eqTitle": { "message": "Grafischer Equalizer" }, + "eqPresetsLabel": { "message": "Voreinstellungen" }, + "eqPresetFlat": { "message": "Linear" }, + "eqPresetRock": { "message": "Rock" }, + "eqPresetPop": { "message": "Pop" }, + "eqPresetJazz": { "message": "Jazz" }, + "eqPresetClassical": { "message": "Klassik" }, + "eqPresetBassBoost": { "message": "Bassverstärkung" }, + "eqPreampLabel": { "message": "Vorverstärker" }, + "infoModalTitle": { "message": "Information" }, + "infoModalFieldTitle": { "message": "Titel:" }, + "infoModalFieldArtist": { "message": "Künstler:" }, + "infoModalFieldAlbum": { "message": "Album:" }, + "infoModalFieldSong": { "message": "Lied:" }, + "infoModalFieldYear": { "message": "Jahr:" }, + "infoModalFieldGenre": { "message": "Genre:" }, + "lang_en": { "message": "Englisch" }, + "lang_es": { "message": "Spanisch" }, + "lang_fr": { "message": "Französisch" }, + "lang_de": { "message": "Deutsch" }, + "lang_it": { "message": "Italienisch" }, + "lang_pt": { "message": "Portugiesisch" }, + "essentialFeaturesNotSupported": { "message": "Dein Browser unterstützt wesentliche Funktionen nicht." }, + "dbAccessError": { "message": "Fehler beim Zugriff auf die lokale Datenbank." }, + "dbUpdateNeeded": { "message": "Die Datenbank muss aktualisiert werden, bitte lade die Seite neu." }, + "dbBlocked": { "message": "Bitte schließe andere Tabs dieser Anwendung, um fortzufahren." }, + "deletingContentData": { "message": "Lokale Inhaltsdaten werden gelöscht..." }, + "noContentDataToDelete": { "message": "Keine Inhaltsdaten zum Löschen vorhanden." }, + "contentDataDeleted": { "message": "Inhaltsdaten aus IndexedDB gelöscht." }, + "errorDeletingData": { "message": "Fehler beim Löschen der Daten: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailable": { "message": "Texteditor nicht verfügbar." }, + "errorLoadingTokens": { "message": "Fehler beim Laden der Tokens zur Bearbeitung." }, + "errorLoadingTokensMessage": { "message": "Fehler beim Laden der Tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailableToSave": { "message": "Editor zum Speichern nicht verfügbar." }, + "invalidJsonFormat": { "message": "Ungültiges JSON-Format. Es muss { \"tokens\": [...] } sein." }, + "tokensSaved": { "message": "Tokens erfolgreich gespeichert." }, + "errorSavingTokens": { "message": "Fehler beim Speichern der Tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "dbNotAvailable": { "message": "IndexedDB ist nicht verfügbar." }, + "dbExported": { "message": "Datenbank erfolgreich exportiert." }, + "errorExportingDb": { "message": "Fehler beim Exportieren der Datenbank: $message$", "placeholders": { "message": { "content": "$1" } } }, + "invalidJsonFile": { "message": "Die Datei enthält kein gültiges JSON-Objekt." }, + "noDataToImport": { "message": "Die Datei enthält keine Daten für die aktuellen DB-Abschnitte." }, + "dbImported": { "message": "Datenbank erfolgreich importiert." }, + "errorImportingDb": { "message": "Fehler beim Importieren der Datenbank: $message$", "placeholders": { "message": { "content": "$1" } } }, + "updatingView": { "message": "Ansicht wird mit neuen Daten aktualisiert..." }, + "confirmClearContent": { "message": "Bist du sicher, dass du die lokalen Inhaltsdaten (Filme, Serien, Musik usw.) löschen möchtest? Favoriten und Einstellungen werden NICHT gelöscht." }, + "trailerNotFound": { "message": "Für diesen Titel wurde kein Trailer gefunden." }, + "confirmClearHistory": { "message": "Bist du sicher, dass du deinen gesamten Wiedergabeverlauf löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden." }, + "historyCleared": { "message": "Wiedergabeverlauf gelöscht." }, + "historyItemDeleted": { "message": "Element aus dem Verlauf gelöscht." }, + "errorGeneratingScript": { "message": "Generiere zuerst ein Skript, um es kopieren zu können." }, + "scriptCopied": { "message": "PHP-Skript in die Zwischenablage kopiert." }, + "errorCopyingScript": { "message": "Fehler beim Kopieren des Skripts." }, + "scriptGenerated": { "message": "PHP-Skript generiert." }, + "errorLoadingAlbum": { "message": "Fehler beim Laden des Albums: $message$", "placeholders": { "message": { "content": "$1" } } }, + "noPhotoServerSelected": { "message": "Fehler: Es wurde kein Fotoserver ausgewählt." }, + "loadingGenres": { "message": "Genres werden geladen..." }, + "errorLoadingGenres": { "message": "Fehler beim Laden" }, + "noContentFound": { "message": "Keine Ergebnisse gefunden." }, + "couldNotLoadContent": { "message": "Inhalt konnte nicht geladen werden." }, + "noFavorites": { "message": "Du hast noch keine Favoriten." }, + "errorLoadingFavorites": { "message": "Fehler beim Laden der Favoriten." }, + "historyEmpty": { "message": "Dein Verlauf ist leer." }, + "historyEmptySub": { "message": "Entdecke und schaue Inhalte an, damit sie hier erscheinen." }, + "errorGeneratingRecommendations": { "message": "Fehler beim Generieren von Empfehlungen." }, + "noRecommendations": { "message": "Wir müssen dich besser kennenlernen, um dir Empfehlungen geben zu können!" }, + "errorGeneratingStats": { "message": "Fehler beim Generieren von Statistiken." }, + "noServersForToken": { "message": "Keine zugehörigen Server für dieses Token gefunden." }, + "searchingActorContent": { "message": "Suche nach Inhalten von $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, + "errorLoadingActorContent": { "message": "Inhalt für $actorName$ konnte nicht geladen werden.", "placeholders": { "actorName": { "content": "$1" } } }, + "errorAddingStream": { "message": "Fehler beim Hinzufügen von Stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, + "phpUrlNotConfigured": { "message": "Die PHP-Server-URL ist nicht konfiguriert. Bitte richte sie in den Einstellungen ein." }, + "searchingStreams": { "message": "Suche nach Streams für „$title$“", "placeholders": { "title": { "content": "$1" } } }, + "sendingStreams": { "message": "Sende $count$ Stream(s) an den Server...", "placeholders": { "count": { "content": "$1" } } }, + "streamAddedSuccess": { "message": "Stream(s) erfolgreich hinzugefügt." }, + "generatingM3U": { "message": "Generiere M3U für „$title$“", "placeholders": { "title": { "content": "$1" } } }, + "m3uDownloaded": { "message": "„$title$“ heruntergeladen.", "placeholders": { "title": { "content": "$1" } } }, + "errorGeneratingM3U": { "message": "Fehler beim Generieren der M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, + "settingsSavedSuccess": { "message": "Einstellungen erfolgreich gespeichert." }, + "errorSavingSettings": { "message": "Fehler beim Speichern der Einstellungen in der Datenbank." }, + "languageChangeReload": { "message": "Sprache geändert. Die Anwendung wird jetzt neu geladen." }, + "addedToFavorites": { "message": "Zu Favoriten hinzugefügt." }, + "removedFromFavorites": { "message": "Aus Favoriten entfernt." }, + "plexScanInProgress": { "message": "Der Plex-Scan läuft bereits." }, + "plexScanStarting": { "message": "Plex-Scan wird gestartet..." }, + "noPlexTokens": { "message": "Keine Plex-Tokens konfiguriert." }, + "clearingSections": { "message": "Abschnitte werden geleert: $sections$", "placeholders": { "sections": { "content": "$1" } } }, + "initialScanPhaseComplete": { "message": "Initiale Scan-Phase abgeschlossen." }, + "retryPhaseFinished": { "message": "Wiederholungsphase abgeschlossen." }, + "plexScanFinished": { "message": "Scan abgeschlossen. Inhalt wird aktualisiert..." }, + "scanCancelled": { "message": "Scan vom Benutzer abgebrochen." }, + "scanCancelledInfo": { "message": "Scan abgebrochen." }, + "errorInitializingMusicPlayer": { "message": "Fehler beim Initialisieren des Musikplayers." }, + "criticalErrorLoadingMusic": { "message": "Kritischer Fehler beim Laden der Musikdaten." }, + "errorLoadingArtists": { "message": "Fehler beim Laden der Künstler." }, + "dbUnavailableError": { "message": "Fehler: Datenbank nicht verfügbar." }, + "updatingMusicData": { "message": "Musikdaten werden aktualisiert..." }, + "musicDataUpdated": { "message": "Musikdaten aktualisiert." }, + "errorFetchingArtistSongs": { "message": "Fehler beim Abrufen der Lieder des Künstlers." }, + "errorLoadingSongs": { "message": "Fehler beim Laden der Lieder." }, + "noArtistsFound": { "message": "Keine Künstler gefunden." }, + "shuffleOn": { "message": "Zufallswiedergabe aktiviert." }, + "shuffleOff": { "message": "Zufallswiedergabe deaktiviert." }, + "playbackError": { "message": "Wiedergabefehler" }, + "errorLabel": { "message": "Fehler" }, + "reloadingPage": { "message": "Seite wird neu geladen..." }, + "viewed": { "message": "Gesehen" }, + "local": { "message": "Lokal" }, + "topRatedSort": {"message": "Am besten bewertet"}, + "recentSort": {"message": "Neueste"}, + "popularSort": {"message": "Beliebte"}, + "moviesSectionTitle": {"message": "Filme"}, + "seriesSectionTitle": {"message": "Serien"}, + "searchResultsFor": {"message": "Ergebnisse für „$query$“", "placeholders": {"query": {"content": "$1"}}}, + "contentFrom": {"message": "Inhalt von $actor$", "placeholders": {"actor": {"content": "$1"}}}, + "explore": {"message": "Entdecken"}, + "noGenre": {"message": "Ohne Kategorie"}, + "synopsis": {"message": "Inhaltsangabe"}, + "noSynopsis": {"message": "Keine Inhaltsangabe verfügbar."}, + "director": {"message": "Regisseur:"}, + "writer": {"message": "Autor:"}, + "viewOnImdb": {"message": "Auf IMDb ansehen"}, + "watchTrailer": {"message": "Trailer ansehen"}, + "addToFavorites": {"message": "Zu Favoriten hinzufügen"}, + "removeFromFavorites": {"message": "Aus Favoriten entfernen"}, + "notAvailable": {"message": "Nicht verfügbar"}, + "mainCast": {"message": "Hauptbesetzung"}, + "seasonsAndEpisodes": {"message": "Staffeln und Episoden"}, + "similarContent": {"message": "Ähnlicher Inhalt"}, + "filmography": {"message": "Filmografie"}, + "availableOn": {"message": "Verfügbar auf"}, + "episodesCount": {"message": "$count$ Episoden", "placeholders": {"count": {"content": "$1"}}}, + "seasonsCount": {"message": "$count$ Staffeln", "placeholders": {"count": {"content": "$1"}}}, + "runtimeMinutes": {"message": "$count$ Min.", "placeholders": {"count": {"content": "$1"}}}, + "noTrailerFound": {"message": "Für diesen Titel wurde kein Trailer gefunden."}, + "fatalInitError": {"message": "Fataler Initialisierungsfehler"}, + "fatalInitErrorSub": {"message": "Die Anwendung konnte nicht geladen werden."}, + "invalidStreamInfo": {"message": "Ungültige Stream-Informationen."}, + "dbUnavailableForStreams": {"message": "Lokale Datenbank nicht verfügbar."}, + "noPlexServersForStreams": {"message": "Keine Plex-Server."}, + "notFoundOnServers": {"message": "„$query$“ wurde nicht auf den Plex-Servern gefunden.", "placeholders": {"query": {"content": "$1"}}}, + "relativeTime_justNow": { "message": "Gerade eben" }, + "relativeTime_minutesAgo": { "message": "Vor $count$ Minuten", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_hoursAgo": { "message": "Vor $count$ Stunden", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_yesterday": { "message": "Gestern" }, + "relativeTime_daysAgo": { "message": "Vor $count$ Tagen", "placeholders": { "count": { "content": "$1" } } }, + "errorLoadingDetails": { "message": "Fehler beim Laden der Details" }, + "errorLoadingLocalContent": { "message": "Fehler beim Laden des lokalen Inhalts." }, + "errorServerResponse": { "message": "Nicht erfolgreiche Serverantwort." }, + "errorPlexApi": { "message": "Plex API-Fehler $status$.", "placeholders": { "status": { "content": "$1" } } }, + "errorParsingPlexXml": { "message": "Fehler beim Parsen des Plex-XML." }, + "untitled": { "message": "Ohne Titel" }, + "itemCount": { "message": "$count$ Elemente", "placeholders": { "count": { "content": "$1" } } }, + "noPhotoServers": { "message": "Keine Fotodienste" }, + "jellyfinScanInProgress": { "message": "Der Jellyfin-Scan läuft bereits." }, + "jellyfinScanning": { "message": "Scanne Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Bitte fülle die Jellyfin-URL und den Benutzernamen aus." }, + "jellyfinConnecting": { "message": "Verbinde mit Jellyfin unter: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Jellyfin-Authentifizierung fehlgeschlagen: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Jellyfin-Authentifizierung erfolgreich." }, + "jellyfinFetchingLibraries": { "message": "Bibliotheken werden abgerufen..." }, + "jellyfinFetchFailed": { "message": "Fehler beim Abrufen der Bibliotheken: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Keine Film- oder Serienbibliotheken in Jellyfin gefunden." }, + "jellyfinLibrariesFound": { "message": "$count$ Medienbibliothek(en) gefunden.", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Erfolg] '$libraryName' gescannt, $count$ Titel hinzugefügt.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Fehler beim Scannen der Bibliothek '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Jellyfin-Scan abgeschlossen. $movies$ Filme und $series$ Serien hinzugefügt.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Jellyfin-Anmeldeinformationen nicht konfiguriert." }, + "notFoundOnJellyfin": { "message": "„$query$“ wurde nicht auf Jellyfin gefunden.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnAnyServer": { "message": "„$query$“ wurde auf keinem Server gefunden.", "placeholders": {"query": {"content": "$1"}}}, + "localOnPlex": { "message": "Auf Plex" }, + "searchOnPlex": { "message": "Auf Plex suchen" }, + "jellyfinTitle": { "message": "Jellyfin-Inhalt" }, + "noJellyfinContent": { "message": "Kein Jellyfin-Inhalt gefunden." }, + "noJellyfinContentSub": { "message": "Stelle sicher, dass du deinen Jellyfin-Server in den Einstellungen gescannt hast." }, + "activityViewerTitle": { "message": "Server-Aktivitätsanzeige" }, + "activitySelectServer": { "message": "Wähle einen Server" }, + "activityCheckBtn": { "message": "Aktualisieren" }, + "activityNoSessions": { "message": "Auf diesem Server gibt es keine aktiven Sitzungen." }, + "activitySessionUser": { "message": "Benutzer" }, + "activitySessionDevice": { "message": "Gerät" }, + "activitySessionContent": { "message": "Inhalt" }, + "activitySessionState": { "message": "Status" }, + "activitySessionIdentifier": { "message": "Client-Identifikator" }, + "activityCopyID": { "message": "ID kopieren" }, + "activityError": { "message": "Serveraktivität konnte nicht abgerufen werden." }, + "activityCopied": { "message": "Identifikator in die Zwischenablage kopiert!" }, + "activityCopyError": { "message": "Fehler beim Kopieren des Identifikators." }, + "noProvidersFound": { "message": "Keine Anbieter gefunden." }, + "availableOnPlex": { "message": "Verfügbar auf Plex" }, + "m3uGeneratorTitle": { "message": "M3U-Listen-Generator" }, + "selectAServer": { "message": "Wähle einen Server..." }, + "downloadM3u": { "message": "M3U herunterladen" }, + "m3uGenerator": { "message": "M3U-Generator" }, + "selectLibraries": { "message": "Bibliotheken auswählen" }, + "howToUse": { "message": "Anwendung" }, + "m3uInstruction1": { "message": "Wähle einen Server aus der Liste." }, + "m3uInstruction2": { "message": "Wähle eine oder mehrere Bibliotheken aus, die einbezogen werden sollen." }, + "m3uInstruction3": { "message": "Klicke auf den Download-Button." }, + "m3uInstruction4": { "message": "Importiere die .m3u-Datei in deinen kompatiblen Player." }, + "chatOpen": { "message": "Chat öffnen" }, + "chatTitle": { "message": "KI-Assistent" }, + "chatClose": { "message": "X" }, + "chatPlaceholder": { "message": "Schreibe deine Nachricht..." }, + "chatSend": { "message": "➤" }, + "chatWelcome": { "message": "Willkommen! Ich bin dein CinePlex-Assistent. Frag mich nach Filmen, Serien oder allem, was du sonst wissen möchtest." }, + "chatGoogleApiKeyMissing": { "message": "Der Google Gemini API-Schlüssel ist nicht konfiguriert. Bitte richte ihn in den Erweiterungseinstellungen ein, um den KI-Assistenten zu verwenden." }, + "chatApiInvalidResponse": { "message": "Die API hat eine ungültige Antwort zurückgegeben. Bitte versuche es erneut." }, + "chatApiError": { "message": "Fehler bei der Kommunikation mit dem KI-Assistenten" }, + "downloadAll": { "message": "Alles herunterladen" }, + "download": { "message": "Herunterladen" }, + "aiToolSearchLibraryDesc": { "message": "Durchsucht die Plex-Bibliothek des Benutzers nach Filmen oder Serien nach Titel." }, + "aiToolSearchLibraryQueryParamDesc": { "message": "Der Titel des zu suchenden Films oder der zu suchenden Serie." }, + "aiToolSearchLibraryTypeParamDesc": { "message": "Der Typ des zu suchenden Inhalts. Kann 'movie' für Filme oder 'series' für Serien sein. (Optional)." }, + "aiToolSearchLibraryResolutionParamDesc": { "message": "Die zu suchende Videoauflösung (z.B. '4k', '1080p'). (Optional)." }, + "aiToolSearchLibraryContainerParamDesc": { "message": "Das zu suchende Video-Containerformat (z.B. 'mkv', 'mp4'). (Optional)." }, + "aiToolNavigateToPageDesc": { "message": "Navigiert den Benutzer zu einer bestimmten Seite der Anwendungsoberfläche." }, + "aiToolNavigateToPagePageParamDesc": { "message": "Der Name der Seite, zu der navigiert werden soll, z.B.: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator' oder 'music'." }, + "aiToolGetUserStatsDesc": { "message": "Ruft die Bibliotheksstatistiken des Benutzers ab und zeigt sie an, wie z.B. die Gesamtzahl der einzigartigen Filme, Serien und Künstler." }, + "aiToolShowItemDetailsDesc": { "message": "Zeigt die Detailseite für einen bestimmten Film oder eine bestimmte Serie anhand ihres Titels und Typs an." }, + "aiToolShowItemDetailsTitleParamDesc": { "message": "Der genaue Titel des Films oder der Serie." }, + "aiToolShowItemDetailsTypeParamDesc": { "message": "Der Inhaltstyp. Muss 'movie' oder 'series' sein." }, + "aiToolAddToPlaylistDesc": { "message": "Fügt einen Film oder eine Serie zur aktuellen Wiedergabeliste des Benutzers hinzu, um sie an einen konfigurierten PHP-Server zu streamen." }, + "aiToolAddToPlaylistTitleParamDesc": { "message": "Der Titel des hinzuzufügenden Films oder der Serie." }, + "aiToolAddToPlaylistTypeParamDesc": { "message": "Der Inhaltstyp. Muss 'movie' oder 'series' sein." }, + "aiToolDownloadSingleMovieM3UDesc": { "message": "Generiert und lädt eine M3U-Wiedergabelistendatei für einen einzelnen lokal verfügbaren Film herunter." }, + "aiToolDownloadSingleMovieM3UTitleParamDesc": { "message": "Der Titel des Films, für den die M3U generiert werden soll." }, + "aiToolDownloadSingleMovieM3UYearParamDesc": { "message": "Das Erscheinungsjahr des Films (optional, für höhere Genauigkeit)." }, + "aiToolDownloadSeriesSeasonM3UDesc": { "message": "Generiert und lädt eine M3U-Wiedergabelistendatei für eine bestimmte Staffel einer lokal verfügbaren Serie herunter." }, + "aiToolDownloadSeriesSeasonM3UTitleParamDesc": { "message": "Der Titel der Serie." }, + "aiToolDownloadSeriesSeasonM3USeasonParamDesc": { "message": "Die Nummer der herunterzuladenden Staffel." }, + "aiToolDownloadSeriesSeasonM3UYearParamDesc": { "message": "Das Erscheinungsjahr der Serie (optional)." }, + "aiToolCheckAndDownloadDesc": { "message": "Überprüft die Verfügbarkeit einer Liste von Film- oder Serientiteln auf den lokalen Servern des Benutzers und generiert und lädt, falls gefunden, eine M3U-Wiedergabelistendatei mit den gefundenen Streams herunter." }, + "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Ein Array von Film- oder Serientiteln, die gesucht und heruntergeladen werden sollen." }, + "aiToolCheckAndDownloadTypeParamDesc": { "message": "Der Inhaltstyp der Liste. Muss 'movie' oder 'series' sein." }, + "aiToolCheckAndDownloadFilenameParamDesc": { "message": "Der Name der herunterzuladenden M3U-Datei (z.B. 'MeineListe.m3u'). Wenn nicht angegeben, wird ein Standardname verwendet." }, + "aiToolToggleFavoriteDesc": { "message": "Fügt einen Film oder eine Serie zur Favoritenliste des Benutzers hinzu oder entfernt sie daraus." }, + "aiToolToggleFavoriteTitleParamDesc": { "message": "Der Titel des Films oder der Serie." }, + "aiToolToggleFavoriteTypeParamDesc": { "message": "Der Inhaltstyp. Muss 'movie' oder 'series' sein." }, + "aiToolGetRecommendationsDesc": { "message": "Generiert und zeigt eine Liste von Film- oder Serienempfehlungen basierend auf dem Wiedergabeverlauf und den Favoriten des Benutzers an." }, + "aiToolApplyFiltersDesc": { "message": "Wendet Filter auf die aktuelle Film- oder Serienansicht an, um die Ergebnisse nach Typ, Genre, Jahr und Sortierreihenfolge zu verfeinern." }, + "aiToolApplyFiltersTypeParamDesc": { "message": "Der Inhaltstyp, auf den die Filter angewendet werden sollen. Muss 'movie' oder 'series' sein." }, + "aiToolApplyFiltersGenreParamDesc": { "message": "Der Name des Genres, nach dem gefiltert werden soll (z.B. 'Action', 'Drama')." }, + "aiToolApplyFiltersYearParamDesc": { "message": "Das Erscheinungsjahr, nach dem gefiltert werden soll (z.B. '2023')." }, + "aiToolApplyFiltersSortParamDesc": { "message": "Das Sortierkriterium für die Ergebnisse. Gültige Werte: 'popularity.desc' (beliebt), 'vote_average.desc' (am besten bewertet), 'release_date.desc' (neu für Filme) oder 'first_air_date.desc' (neu für Serien)." }, + "aiToolListAvailableMusicGenresDesc": { "message": "Listet alle einzigartigen Musikgenres auf, die in der lokalen Bibliothek des Benutzers verfügbar sind." }, + "aiToolSearchMusicByGenreDesc": { "message": "Sucht nach Künstlern in der Musikbibliothek des Benutzers, die zu einem bestimmten Genre gehören." }, + "aiToolSearchMusicByGenreNameParamDesc": { "message": "Der Name des zu suchenden Musikgenres (z.B. 'Rock', 'Pop', 'Jazz')." }, + "aiToolPlayMusicByArtistDesc": { "message": "Öffnet den Musikplayer und beginnt mit der Wiedergabe von Liedern eines bestimmten Künstlers aus der Bibliothek des Benutzers." }, + "aiToolPlayMusicByArtistNameParamDesc": { "message": "Der genaue Name des Künstlers, dessen Lieder wiedergegeben werden sollen." }, + "aiToolClearChatHistoryDesc": { "message": "Löscht den gesamten Nachrichtenverlauf des aktuellen Gesprächs mit dem KI-Assistenten." }, + "aiToolDeleteDatabaseDesc": { "message": "Löscht die gesamte lokale Datenbank der Erweiterung, einschließlich gescannter Inhalte, Einstellungen und Favoriten. Diese Aktion ist unumkehrbar und lädt die Anwendung neu." }, + "aiToolUpdateAllTokensDesc": { "message": "Startet einen vollständigen Scan aller Plex-Server und -Bibliotheken, die mit den in der Erweiterung konfigurierten Tokens verknüpft sind. Aktualisiert alle Filme, Serien, Künstler und Fotos." }, + "aiToolAddPlexTokenDesc": { "message": "Fügt ein neues X-Plex-Token zur Konfiguration der Erweiterung hinzu, sodass die Anwendung Inhalte von neuen Plex-Servern scannen kann." }, + "aiToolAddPlexTokenTokenParamDesc": { "message": "Der X-Plex-Token-String, der hinzugefügt werden soll." }, + "aiToolChangeRegionDesc": { "message": "Ändert die Region, die für die Inhaltssuche in der TMDB-API verwendet wird. Dies wirkt sich auf die in den Film- und Serienbereichen angezeigten Ergebnisse sowie auf die Streaming-Anbieter aus." }, + "aiToolChangeRegionRegionParamDesc": { "message": "Der zweistellige ISO 3166-1-Ländercode für die neue Region (z.B. 'US' für Vereinigte Staaten, 'DE' für Deutschland, 'AT' für Österreich)." }, + "aiToolClearAllFavoritesDesc": { "message": "Entfernt alle Filme und Serien, die der Benutzer als Favoriten markiert hat." }, + "aiToolClearViewingHistoryDesc": { "message": "Löscht den Wiedergabeverlauf des Benutzers von der Verlaufsseite." }, + "aiToolClearRecommendationsViewDesc": { "message": "Löscht die Empfehlungsansicht und entfernt zwischengespeicherte Empfehlungen." }, + "aiToolSearchNotFound": { "message": "„$query“ wurde nicht in deiner Bibliothek gefunden.", "placeholders": { "query": { "content": "$1" } } }, + "aiToolNavigateSuccess": { "message": "Zur Seite $page$ navigiert.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolNavigateError": { "message": "Fehler beim Navigieren zur Seite $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolStatsError": { "message": "Fehler beim Abrufen der Statistiken." }, + "aiToolItemNotFound": { "message": "Element '$title' nicht gefunden.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolShowItemDetailsSuccess": { "message": "Zeige Details für '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolAddToPlaylistSuccess": { "message": "'$title' zur Wiedergabeliste hinzugefügt.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteAdded": { "message": "'$title' zu den Favoriten hinzugefügt.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteRemoved": { "message": "'$title' aus den Favoriten entfernt.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolRecommendationsSuccess": { "message": "Zeige Empfehlungen." }, + "aiToolApplyFiltersGenreNotFound": { "message": "Genre '$genre' nicht gefunden.", "placeholders": { "genre": { "content": "$1" } } }, + "aiToolApplyFiltersSuccess": { "message": "Filter erfolgreich angewendet." }, + "aiToolSearchMusicByGenreNotFound": { "message": "Ich habe keine Künstler des Genres '$genre_name' in deiner Bibliothek gefunden.", "placeholders": { "genre_name": { "content": "$1" } } }, + "aiToolPlayMusicNotReady": { "message": "Der Musikplayer ist nicht bereit. Stelle sicher, dass deine Plex-Musikbibliothek gescannt wurde." }, + "aiToolPlayMusicArtistNotFound": { "message": "Künstler '$artist_name' nicht gefunden.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicNoSongs": { "message": "Keine Lieder für '$artist_name' gefunden.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicSuccess": { "message": "Spiele Musik von '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolChatHistoryCleared": { "message": "Chatverlauf gelöscht." }, + "aiToolConfirmDeleteDatabase": { "message": "Bist du sicher, dass du die lokale Datenbank löschen möchtest? Diese Aktion ist unumkehrbar." }, + "aiToolDeleteDatabaseCancelled": { "message": "Löschen der Datenbank abgebrochen." }, + "aiToolExecutionError": { "message": "Fehler bei der Ausführung des Werkzeugs '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, + "aiToolUnknown": { "message": "Unbekanntes Werkzeug: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, + "aiToolFavoritesCleared": { "message": "Favoriten gelöscht." }, + "aiToolFavoritesClearError": { "message": "Fehler beim Löschen der Favoriten: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolRecommendationsCleared": { "message": "Empfehlungen gelöscht." }, + "aiToolRecommendationsClearError": { "message": "Fehler beim Löschen der Empfehlungen: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleted": { "message": "Datenbank gelöscht. Die Seite wird neu geladen." }, + "aiToolDatabaseDeleteError": { "message": "Fehler beim Löschen der Datenbank: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleteBlocked": { "message": "Das Löschen der Datenbank ist blockiert. Schließe andere Tabs der Anwendung." }, + "aiToolUpdateAllTokensSuccess": { "message": "Alle Tokens wurden erfolgreich aktualisiert." }, + "aiToolUpdateAllTokensError": { "message": "Fehler beim Aktualisieren der Tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolAddPlexTokenSuccess": { "message": "Plex-Token erfolgreich hinzugefügt." }, + "aiToolAddPlexTokenError": { "message": "Fehler beim Hinzufügen des Plex-Tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolChangeRegionSuccess": { "message": "Region auf $region$ geändert. Der Inhalt wird aktualisiert.", "placeholders": { "region": { "content": "$1" } } }, + "aiToolChangeRegionError": { "message": "Fehler beim Ändern der Region: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolViewingHistoryCleared": { "message": "Wiedergabeverlauf gelöscht." }, + "aiToolViewingHistoryClearError": { "message": "Fehler beim Löschen des Wiedergabeverlaufs: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSingle": { "message": "Starte den M3U-Download für '$movie_title'.", "placeholders": { "movie_title": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSeason": { "message": "Starte den M3U-Download für Staffel $1 von '$2'.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolM3UNoTitlesProvided": { "message": "Bitte gib eine Liste von Titeln an, um die Wiedergabeliste zu erstellen." }, + "aiToolM3UCheckingTitles": { "message": "Überprüfe die Titel auf deinen lokalen Servern..." }, + "aiToolM3UNoLocalMatchesForDownload": { "message": "Ich konnte keinen der Filme oder Serien aus der Liste auf deinen lokalen Servern finden." }, + "aiToolM3UDownloadStarted": { "message": "Fertig! Ich habe $1 von $2 Titeln auf deinen Servern gefunden und den Download der M3U-Wiedergabeliste gestartet.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolTrailerNotFoundSpecific": { "message": "Entschuldigung, ich konnte keinen verfügbaren Trailer für '$title' finden.", "placeholders": { "title": { "content": "$1" } } }, + "aiSystemPrompt_v4": { + "message": "Du bist ein virtueller Assistent, der in eine Chrome-Erweiterung integriert ist und mit Plex- und Jellyfin-Servern interagiert. Deine Hauptfunktion ist es, dem Benutzer beim Suchen, Verwalten, Abspielen und Herunterladen von Multimedia-Inhalten sowie bei der Verwaltung benutzerdefinierter Einstellungen zu helfen.\n\nHÖCHSTE PRIORITÄT: Immer wenn sich die Frage des Benutzers auf Multimedia-Inhalte (Filme, Serien, Musik) bezieht, MUSST du davon ausgehen, dass sie sich auf seine lokale Bibliothek bezieht. Nutze die Werkzeuge, um seine Datenbank zu durchsuchen, BEVOR du im Web suchst.\n\n🎯 Allgemeine Verhaltensregeln:\nAntworte immer klar, prägnant und direkt. Sei proaktiv und liefere alle relevanten Informationen auf einmal, um Nachfragen zu vermeiden. Wenn du beispielsweise die Verfügbarkeit einer Serie bestätigst, gib auch die Details zu den Staffeln an.\n\nVergleiche das aktuelle Datum mit den Google-Suchergebnissen, wenn du nach externen Informationen gefragt wirst, um sicherzustellen, dass sie aktuell sind.\n\nVerwende beim Aufrufen von Werkzeugen die exakten Namen der im System definierten Befehle (function.name).\n\n📦 Schlüsselfunktionen für Multimedia-Inhalte:\nUm eine M3U für einen einzelnen Film zu generieren, verwende download_single_movie_m3u.\nUm eine bestimmte Staffel einer Serie herunterzuladen, verwende download_series_season_m3u.\nFür mehrere Titel (Filme oder ganze Serien) verwende immer check_and_download_titles_list.\nUm lokale Inhalte zu suchen: search_library.\nUm in TMDB zu suchen: search_tmdb_content.\nFür Trendinhalte: get_trending_content.\nUm Details eines Titels anzuzeigen: show_item_details.\nUm zur PHP-Wiedergabeliste hinzuzufügen: add_to_playlist.\nUm die lokale Verfügbarkeit zu prüfen: check_local_availability.\nWenn eine Serie lokal verfügbar ist, gib an, wie viele Staffeln es gibt und auf welchen Servern, indem du get_local_series_seasons verwendest.\nUm Empfehlungen anzusehen: get_recommendations.\nUm Filter anzuwenden: apply_filters.\nUm Verlauf oder Favoriten anzuzeigen: view_history, view_favorites.\nUm als Favorit zu markieren: toggle_favorite.\nUm Trailer abzuspielen: play_trailer.\n\n🎵 Musikfunktionen:\nWenn der Benutzer nach allgemeinen Empfehlungen für Musikgenres fragt (z.B. 'empfiehl mir ein Genre, um mich aufzuheitern'), verwende zuerst list_available_music_genres, um zu sehen, welche Genres er hat, und basiere deine Empfehlung auf dieser Liste.\nUm alle verfügbaren Musikgenres in der Bibliothek aufzulisten: list_available_music_genres.\nUm Künstler nach Genre zu suchen: search_music_by_genre.\nUm Lieder nach Titel und/oder Künstler abzuspielen: play_song.\nUm Musik eines Künstlers abzuspielen: play_music_by_artist.\n\n🧰 Verwaltungs- und Konfigurationsfunktionen:\nUm Benutzerstatistiken zu erhalten: get_user_stats.\nUm zu bestimmten Abschnitten zu navigieren: navigate_to_page.\nUm Tokens zu aktualisieren: update_all_tokens, add_plex_token.\nUm die Inhaltsregion zu ändern: change_region.\nUm die lokale Datenbank zu exportieren oder zu importieren: export_local_database, import_local_database.\nUm die Datenbank zu löschen: delete_database.\nUm Favoriten, Verlauf oder Empfehlungen zu löschen: clear_all_favorites, clear_viewing_history, clear_recommendations_view.\nUm den Hell-/Dunkelmodus umzuschalten: toggle_light_mode.\nUm den Hero-Bereich ein- oder auszublenden: toggle_hero_section.\n\n⚠️ Zusätzliche Überlegungen:\nPriorisiere lokal verfügbare Inhalte. Verwende check_local_availability, bevor du Wiedergabe- oder Download-Optionen anzeigst.\nWenn eine Aktion fehlschlägt, informiere klar und unmissverständlich darüber.\nVermeide es, die Anfrage des Benutzers unnötig zu wiederholen, es sei denn, es hilft, die Antwort zu kontextualisieren." + }, + "backToProviders": { "message": "Zurück zu den Anbietern" }, + "artistsCounterSingle": { "message": "$total$ Künstler", "placeholders": { "total": { "content": "$1" } } }, + "artistsCounterLoading": { "message": "Wird geladen..." }, + "downloadingSong": { "message": "Starte den Download von „$title$“", "placeholders": { "title": { "content": "$1" } } }, + "songDownloaded": { "message": "„$title$“ heruntergeladen.", "placeholders": { "title": { "content": "$1" } } }, + "errorDownloadingSong": { "message": "Fehler beim Herunterladen von „$title$“", "placeholders": { "title": { "content": "$1" } } }, + "generatingAlbumM3U": { "message": "Generiere M3U für „$artist$“", "placeholders": { "artist": { "content": "$1" } } }, + "albumM3UGenerated": { "message": "M3U für das Album „$artist$“ generiert.", "placeholders": { "artist": { "content": "$1" } } }, + "retyingSection": { "message": "Wiederhole Abschnitt „$title$“", "placeholders": { "title": { "content": "$1" } } }, + "retrySuccess": { "message": "[ERFOLG] Wiederholung von „$title$“ abgeschlossen.", "placeholders": { "title": { "content": "$1" } } }, + "retryError": { "message": "[FINALER FEHLER] Wiederholung für „$title$“ fehlgeschlagen: $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, + "startingRetryPhase": { "message": "Starte Wiederholungsphase für $count$ Abschnitte...", "placeholders": { "count": { "content": "$1" } } }, + "tokenFoundServers": { "message": "Token $token$... fand $count$ Server.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, + "errorProcessingToken": { "message": "Fehler bei der Verarbeitung von Token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, + "plexScanFatalError": { "message": "FATALER FEHLER: $message$", "placeholders": { "message": { "content": "$1" } } }, + "errorDuringScan": { "message": "Fehler während des Scans: $message$", "placeholders": { "message": { "content": "$1" } } }, + "stoppingPlexScan": { "message": "Plex-Scan wird gestoppt..." }, + "invalidTokenProvided": { "message": "Ungültiges Token bereitgestellt." }, + "tokenAlreadyExists": { "message": "Das Token existiert bereits." }, + "tokenAddedSuccessfully": { "message": "Token erfolgreich hinzugefügt." }, + "noStreamsFoundForSelection": { "message": "Keine Streams für die Auswahl gefunden." }, + "autoplayBlocked": { "message": "Automatische Wiedergabe blockiert." }, + "welcomeToCinePlex": { "message": "" }, + "page": { "message": "Seite" }, + "all": { "message": "Alle" }, + "userScore": { "message": "Bewertung" }, + "duration": { "message": "Dauer" }, + "min": { "message": "Min" }, + "max": { "message": "Max" }, + "aiToolFindStreamingProvidersDesc": { "message": "Findet heraus, wo man einen Film oder eine Serie auf Streaming-Diensten ansehen kann." }, + "aiToolFindStreamingProvidersTitleParamDesc": { "message": "Der Titel des zu suchenden Films oder der Serie." }, + "aiToolFindStreamingProvidersTypeParamDesc": { "message": "Der Inhaltstyp (Film oder Serie)." }, + "aiToolFindStreamingProvidersYearParamDesc": { "message": "Das Erscheinungsjahr des Inhalts (optional)." }, + "aiToolNoStreamingProviders": { "message": "Keine Streaming-Anbieter für {title} gefunden." }, + "aiToolStreamingProvidersFound": { "message": "{title} ist auf folgenden Diensten verfügbar: {providers}." }, + "aiToolStreamingProviderError": { "message": "Fehler bei der Suche nach Streaming-Anbietern: {message}." }, + "aiToolGetLocalSeriesSeasonsDesc": { "message": "Überprüft, ob eine TV-Serie lokal verfügbar ist und gibt eine detaillierte Aufschlüsselung der verfügbaren Staffeln auf jedem Server zurück." }, + "aiToolGetLocalSeriesSeasonsTitleParamDesc": { "message": "Der Titel der zu überprüfenden Serie." }, + "aiToolGetLocalSeriesSeasonsYearParamDesc": { "message": "Das Erscheinungsjahr der Serie (optional für höhere Genauigkeit)." }, + "aiToolLocalSeriesNoSeasons": { "message": "Die Serie '$series_title' befindet sich in deiner Bibliothek, aber es wurden keine Staffeldetails gefunden.", "placeholders": { "series_title": { "content": "$1" } } }, + "artist": { "message": "Künstler" }, + "tracks": { "message": "Titel" }, + "noSongsFound": { "message": "Keine Lieder für diesen Künstler gefunden." }, + "durationMin": { "message": "Dauer (Min)" }, + "score": { "message": "Bewertung" }, + "searchGenre": { "message": "Genre suchen..." }, + "searchArtists": { "message": "Künstler suchen..." }, + "preparingMusicLibrary": { "message": "Deine Musikbibliothek wird vorbereitet..." }, + "preparingMusicLibraryDesc": { "message": "Dieser einmalige Vorgang kann einige Minuten dauern, wenn du viele Künstler hast." }, + "artistsProgress": { "message": "0 / 0 Künstler" }, + "starting": { "message": "Wird gestartet..." }, + "artistName": { "message": "Künstlername" }, + "playPause": { "message": "Wiedergabe/Pause" }, + "noLocalFilesFound": { "message": "Keine lokalen Dateien für diesen Titel gefunden." }, + "server": { "message": "Server" }, + "title": { "message": "Titel" }, + "year": { "message": "Jahr" }, + "resolution": { "message": "Auflösung" }, + "size": { "message": "Größe" }, + "container": { "message": "Container" }, + "action": { "message": "Aktion" }, + "generate": { "message": "Generieren" }, + "availableLocalFiles": { "message": "Verfügbare lokale Dateien" }, + "downloadSeason": { "message": "Staffel herunterladen" }, + "errorLoadingServersM3u": { "message": "Fehler beim Laden der Server für den M3U-Generator:" }, + "errorFetchingLibraries": { "message": "Fehler beim Abrufen der Bibliotheken." }, + "selectServerAndLibrary": { "message": "Bitte wähle einen Server und mindestens eine Bibliothek aus." }, + "generating": { "message": "Wird generiert..." }, + "errorProcessingLibrary": { "message": "Fehler bei der Verarbeitung der Bibliothek" }, + "errorProcessingLibrarySkipping": { "message": "Fehler bei der Verarbeitung der Bibliothek. Wird übersprungen." }, + "allLibrariesFailed": { "message": "Alle ausgewählten Bibliotheken konnten nicht verarbeitet werden." }, + "m3uGeneratedWithErrors": { "message": "M3U mit einigen Fehlern generiert. Einige Bibliotheken könnten fehlen." }, + "m3uDownloadedSuccess": { "message": "M3U-Wiedergabeliste erfolgreich heruntergeladen." }, + "errorGeneratingM3uFile": { "message": "Fehler beim Generieren der M3U-Datei." }, + "chatSources": { "message": "Quellen" }, + "chatUnnamedSource": { "message": "Unbenannte Quelle" }, + "googleApiFailure": { "message": "Aufruf der Google AI API fehlgeschlagen:" } } \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a242665..104b775 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1,449 +1,516 @@ { - "appName": { "message": "CinePlex" }, - "appDescription": { "message": "Scans Plex servers for content and displays it in the interface" }, - "appTagline": { "message": "Movies, Series, and Music" }, - "appLocaleCode": { "message": "en-US" }, - "toggleNavigation": { "message": "Toggle Navigation" }, - "searchPlaceholder": { "message": "Search for movies or series..." }, - "openMusicPlayer": { "message": "Open Music Player" }, - "settings": { "message": "Settings" }, - "navMovies": { "message": "Movies" }, - "navSeries": { "message": "Series" }, - "navProviders": { "message": "Providers" }, - "navPhotos": { "message": "Photos" }, - "navStats": { "message": "Statistics" }, - "navFavorites": { "message": "Favorites" }, - "navHistory": { "message": "History" }, - "navRecommendations": { "message": "Recommendations" }, - "navMusic": { "message": "Music" }, - "navM3uGenerator": { "message": "M3U Generator" }, - "heroWelcome": { "message": "" }, - "heroSubtitle": { "message": "Explore thousands of movies and series." }, - "addStream": { "message": "Add Stream" }, - "moreInfo": { "message": "More info" }, - "popularMovies": { "message": "Popular Movies" }, - "allGenres": { "message": "All genres" }, - "allYears": { "message": "All years" }, - "sortPopular": { "message": "Most popular" }, - "sortTopRated": { "message": "Top rated" }, - "sortRecent": { "message": "Most recent" }, - "loadMore": { "message": "Load more" }, - "photosBreadcrumbHome": { "message": "Albums" }, - "selectServer": { "message": "Select a server" }, - "loading": { "message": "Loading..." }, - "loadingLibraries": { "message": "Loading libraries..." }, - "photosEmptyState": { "message": "No albums or photos found." }, - "photosEmptyStateSub": { "message": "Please select a server or make sure you have a photo library in Plex." }, - "statsTitle": { "message": "Library Statistics" }, - "statsAllTokens": { "message": "All Tokens" }, - "statsAnalyzing": { "message": "Analyzing your library..." }, - "statsActiveTokens": { "message": "Active Tokens" }, - "statsServersFound": { "message": "Servers Found" }, - "statsUniqueMovies": { "message": "Unique Movies" }, - "statsUniqueSeries": { "message": "Unique Series" }, - "statsUniqueArtists": { "message": "Unique Artists" }, - "statsTokenServers": { "message": "Token Servers" }, - "statsChartMoviesByGenre": { "message": "Content by Genre (Movies)" }, - "statsChartSeriesByGenre": { "message": "Content by Genre (Series)" }, - "statsChartByDecade": { "message": "Content by Decade" }, - "recommendationsTitle": { "message": "Recommendations for you" }, - "historyTitle": { "message": "Viewing History" }, - "clearHistory": { "message": "Clear All" }, - "consoleTitle": { "message": "Plex Scan Console" }, - "footerCredit": { "message": "An interface for your Plex universe." }, - "closeTrailer": { "message": "Close trailer" }, - "close": { "message": "Close" }, - "photoViewer": { "message": "Photo Viewer" }, - "previous": { "message": "Previous" }, - "next": { "message": "Next" }, - "notificationTemplateText": { "message": "Notification" }, - "settingsTitleFull": { "message": "Settings and Configuration" }, - "settingsTabGeneral": { "message": "General" }, - "settingsTabPlex": { "message": "Plex" }, - "settingsTabJellyfin": { "message": "Jellyfin" }, - "settingsTabPhpGen": { "message": "PHP Generator" }, - "settingsTabData": { "message": "Data" }, - "settingsApiServer": { "message": "API and Server Settings" }, - "settingsTmdbApiLabel": { "message": "TMDB API Key (Optional)" }, - "settingsTmdbApiPlaceholder": { "message": "The default key will be used if left blank" }, - "settingsGoogleApiLabel": { "message": "Google Gemini API Key (Optional)" }, - "settingsGoogleApiPlaceholder": { "message": "Required to use the AI assistant" }, - "settingsRegionLabel": { "message": "Region for content discovery" }, - "allRegions": { "message": "All regions" }, - "settingsPhpUrlLabel": { "message": "Server URL for Adding Streams" }, - "settingsPhpUrlPlaceholder": { "message": "https://your-server.com/path/to/script.php" }, - "settingsInterface": { "message": "Interface" }, - "settingsLightTheme": { "message": "Light Mode" }, - "settingsShowHero": { "message": "Show 'Hero' welcome section" }, - "settingsScanContent": { "message": "Content Scan" }, - "settingsScanDesc": { "message": "Select what to scan and press the button." }, - "settingsScanMovies": { "message": "Movies" }, - "settingsScanShows": { "message": "Series" }, - "settingsScanArtists": { "message": "Music" }, - "settingsScanPhotos": { "message": "Photos" }, - "settingsSelectAll": { "message": "Select All" }, - "settingsStartScan": { "message": "Start Scan" }, - "settingsPlexTokens": { "message": "Plex Tokens" }, - "settingsPlexTokensDesc": { "message": "Edit the list of Plex tokens (JSON format)." }, - "settingsSaveTokens": { "message": "Save Tokens" }, - "settingsJellyfinTitle": { "message": "Jellyfin Settings" }, - "settingsJellyfinDesc": { "message": "Add your Jellyfin server details to scan its content." }, - "jellyfinUrlLabel": { "message": "Jellyfin Server URL" }, - "jellyfinUserLabel": { "message": "Username" }, - "jellyfinPassLabel": { "message": "Password" }, - "jellyfinConnectAndScan": { "message": "Connect and Scan" }, - "settingsPhpGenTitle": { "message": "PHP Script Generator for Server" }, - "settingsPhpFileOptions": { "message": "File Options" }, - "settingsPhpSavePathLabel": { "message": "Save Path on Server" }, - "settingsPhpSavePathPlaceholder": { "message": "Ex: /var/www/html/lists (blank for the same folder)" }, - "settingsPhpFilenameLabel": { "message": "File Name" }, - "settingsPhpFileAction": { "message": "File Action" }, - "settingsPhpActionAppend": { "message": "Append to the end of the file (cumulative)" }, - "settingsPhpActionOverwrite": { "message": "Overwrite the file (start over)" }, - "settingsPhpSecurity": { "message": "Security (Optional)" }, - "settingsPhpUseSecretKey": { "message": "Use secret key (Recommended)" }, - "settingsPhpSecretKeyPlaceholder": { "message": "Enter a secure secret key" }, - "settingsPhpGeneratedCode": { "message": "Generated Code" }, - "settingsPhpGeneratedPlaceholder": { "message": "The generated PHP code will appear here." }, - "settingsGenerateScript": { "message": "Generate Script" }, - "settingsCopyScript": { "message": "Copy Script" }, - "settingsDataManagement": { "message": "Local Database Management" }, - "settingsImportDb": { "message": "Import DB from File" }, - "settingsExportDb": { "message": "Export DB to File" }, - "settingsClearContent": { "message": "Clear Local Content Data" }, - "settingsClearContentDesc": { "message": "This action will delete movies, series, and music from the local database, but will not affect your favorites or your settings." }, - "settingsClose": { "message": "Close" }, - "settingsSave": { "message": "Save Settings" }, - "musicSidenavTitle": { "message": "Plex Music" }, - "musicAllServers": { "message": "All Servers" }, - "musicSearchArtistPlaceholder": { "message": "Search for an artist..." }, - "musicSearchDiscographyPlaceholder": { "message": "Search in discography..." }, - "musicNothingPlaying": { "message": "Nothing playing" }, - "musicSelectSong": { "message": "Select a song" }, - "musicToStart": { "message": "to start playing" }, - "miniplayerDownloadSong": { "message": "Download song" }, - "miniplayerDownloadAlbum": { "message": "Download M3U album" }, - "miniplayerVolume": { "message": "Volume" }, - "miniplayerShuffle": { "message": "Shuffle" }, - "miniplayerEqualizer": { "message": "Equalizer" }, - "miniplayerOpenList": { "message": "Open list" }, - "eqTitle": { "message": "Graphic Equalizer" }, - "eqPresetsLabel": { "message": "Presets" }, - "eqPresetFlat": { "message": "Flat" }, - "eqPresetRock": { "message": "Rock" }, - "eqPresetPop": { "message": "Pop" }, - "eqPresetJazz": { "message": "Jazz" }, - "eqPresetClassical": { "message": "Classical" }, - "eqPresetBassBoost": { "message": "Bass Boost" }, - "eqPreampLabel": { "message": "Preamp" }, - "infoModalTitle": { "message": "Information" }, - "infoModalFieldTitle": { "message": "Title:" }, - "infoModalFieldArtist": { "message": "Artist:" }, - "infoModalFieldAlbum": { "message": "Album:" }, - "infoModalFieldSong": { "message": "Song:" }, - "infoModalFieldYear": { "message": "Year:" }, - "infoModalFieldGenre": { "message": "Genre:" }, - "lang_en": { "message": "English" }, - "lang_es": { "message": "Spanish" }, - "lang_fr": { "message": "French" }, - "lang_de": { "message": "German" }, - "lang_it": { "message": "Italian" }, - "lang_pt": { "message": "Portuguese" }, - "essentialFeaturesNotSupported": { "message": "Your browser does not support essential features." }, - "dbAccessError": { "message": "Error accessing the local database." }, - "dbUpdateNeeded": { "message": "The database needs to be updated, please reload the page." }, - "dbBlocked": { "message": "Please close other tabs of this application to continue." }, - "deletingContentData": { "message": "Deleting local content data..." }, - "noContentDataToDelete": { "message": "No content data to delete." }, - "contentDataDeleted": { "message": "Content data deleted from IndexedDB." }, - "errorDeletingData": { "message": "Error deleting data: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailable": { "message": "Text editor not available." }, - "errorLoadingTokens": { "message": "Error loading tokens for editing." }, - "errorLoadingTokensMessage": { "message": "Error loading tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailableToSave": { "message": "Editor not available for saving." }, - "invalidJsonFormat": { "message": "Invalid JSON format. It must be { \"tokens\": [...] }" }, - "tokensSaved": { "message": "Tokens saved successfully." }, - "errorSavingTokens": { "message": "Error saving tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "dbNotAvailable": { "message": "IndexedDB is not available." }, - "dbExported": { "message": "Database exported successfully." }, - "errorExportingDb": { "message": "Error exporting the database: $message$", "placeholders": { "message": { "content": "$1" } } }, - "invalidJsonFile": { "message": "The file does not contain a valid JSON object." }, - "noDataToImport": { "message": "The file does not contain data for the current DB sections." }, - "dbImported": { "message": "Database imported successfully." }, - "errorImportingDb": { "message": "Error importing the database: $message$", "placeholders": { "message": { "content": "$1" } } }, - "updatingView": { "message": "Updating the view with new data..." }, - "confirmClearContent": { "message": "Are you sure you want to delete local content data (Movies, Series, Music, etc.)? Favorites and Settings will NOT be deleted." }, - "trailerNotFound": { "message": "No trailer found for this title." }, - "confirmClearHistory": { "message": "Are you sure you want to delete all your viewing history? This action cannot be undone." }, - "historyCleared": { "message": "Viewing history cleared." }, - "historyItemDeleted": { "message": "Item deleted from history." }, - "errorGeneratingScript": { "message": "First generate a script to be able to copy it." }, - "scriptCopied": { "message": "PHP script copied to clipboard." }, - "errorCopyingScript": { "message": "Error copying the script." }, - "scriptGenerated": { "message": "PHP script generated." }, - "errorLoadingAlbum": { "message": "Error loading album: $message$", "placeholders": { "message": { "content": "$1" } } }, - "noPhotoServerSelected": { "message": "Error: No photo server has been selected." }, - "loadingGenres": { "message": "Loading genres..." }, - "errorLoadingGenres": { "message": "Error loading" }, - "noContentFound": { "message": "No results found." }, - "couldNotLoadContent": { "message": "Could not load content." }, - "noFavorites": { "message": "You don't have any favorites yet." }, - "errorLoadingFavorites": { "message": "Error loading favorites." }, - "historyEmpty": { "message": "Your history is empty." }, - "historyEmptySub": { "message": "Explore and watch content for it to appear here." }, - "errorGeneratingRecommendations": { "message": "Error generating recommendations." }, - "noRecommendations": { "message": "We need to get to know you better to give you recommendations!" }, - "errorGeneratingStats": { "message": "Error generating statistics." }, - "noServersForToken": { "message": "No associated servers found for this token." }, - "searchingActorContent": { "message": "Searching for content by $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, - "errorLoadingActorContent": { "message": "Could not load content for $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, - "errorAddingStream": { "message": "Error adding stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, - "phpUrlNotConfigured": { "message": "The PHP server URL is not configured. Please configure it in Settings." }, - "searchingStreams": { "message": "Searching for streams for \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "sendingStreams": { "message": "Sending $count$ stream(s) to the server...", "placeholders": { "count": { "content": "$1" } } }, - "streamAddedSuccess": { "message": "Stream(s) added successfully." }, - "generatingM3U": { "message": "Generating M3U for \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "m3uDownloaded": { "message": "\"$title$\" downloaded.", "placeholders": { "title": { "content": "$1" } } }, - "errorGeneratingM3U": { "message": "Error generating M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, - "settingsSavedSuccess": { "message": "Settings saved successfully." }, - "errorSavingSettings": { "message": "Error saving settings to the database." }, - "languageChangeReload": { "message": "Language changed. The application will now reload." }, - "addedToFavorites": { "message": "Added to favorites." }, - "removedFromFavorites": { "message": "Removed from favorites." }, - "plexScanInProgress": { "message": "Plex scan is already in progress." }, - "plexScanStarting": { "message": "Starting Plex scan..." }, - "noPlexTokens": { "message": "No Plex tokens configured." }, - "clearingSections": { "message": "Clearing sections: $sections$", "placeholders": { "sections": { "content": "$1" } } }, - "initialScanPhaseComplete": { "message": "Initial scan phase finished." }, - "retryPhaseFinished": { "message": "Retry phase finished." }, - "plexScanFinished": { "message": "Scan finished. Updating content..." }, - "scanCancelled": { "message": "Scan cancelled by the user." }, - "scanCancelledInfo": { "message": "Scan cancelled." }, - "errorInitializingMusicPlayer": { "message": "Error initializing the music player." }, - "criticalErrorLoadingMusic": { "message": "Critical error loading music data." }, - "errorLoadingArtists": { "message": "Error loading artists." }, - "dbUnavailableError": { "message": "Error: Database not available." }, - "updatingMusicData": { "message": "Updating music data..." }, - "musicDataUpdated": { "message": "Music data updated." }, - "errorFetchingArtistSongs": { "message": "Error fetching the artist's songs." }, - "errorLoadingSongs": { "message": "Error loading songs." }, - "noArtistsFound": { "message": "No artists found." }, - "shuffleOn": { "message": "Shuffle mode on." }, - "shuffleOff": { "message": "Shuffle mode off." }, - "playbackError": { "message": "Playback error" }, - "errorLabel": { "message": "Error" }, - "reloadingPage": { "message": "Reloading the page..." }, - "viewed": { "message": "Viewed" }, - "local": { "message": "Local" }, - "topRatedSort": {"message": "Top Rated"}, - "recentSort": {"message": "Recent"}, - "popularSort": {"message": "Popular"}, - "moviesSectionTitle": {"message": "Movies"}, - "seriesSectionTitle": {"message": "Series"}, - "searchResultsFor": {"message": "Results for \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, - "contentFrom": {"message": "Content from $actor$", "placeholders": {"actor": {"content": "$1"}}}, - "explore": {"message": "Explore"}, - "noGenre": {"message": "Uncategorized"}, - "synopsis": {"message": "Synopsis"}, - "noSynopsis": {"message": "No synopsis available."}, - "director": {"message": "Director:"}, - "writer": {"message": "Writer:"}, - "viewOnImdb": {"message": "View on IMDb"}, - "watchTrailer": {"message": "Watch Trailer"}, - "addToFavorites": {"message": "Add to favorites"}, - "removeFromFavorites": {"message": "Remove from favorites"}, - "notAvailable": {"message": "Not available"}, - "mainCast": {"message": "Main Cast"}, - "seasonsAndEpisodes": {"message": "Seasons and Episodes"}, - "similarContent": {"message": "Similar Content"}, - "filmography": {"message": "Filmography"}, - "availableOn": {"message": "Available on"}, - "episodesCount": {"message": "$count$ Episodes", "placeholders": {"count": {"content": "$1"}}}, - "seasonsCount": {"message": "$count$ Seasons", "placeholders": {"count": {"content": "$1"}}}, - "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, - "noTrailerFound": {"message": "No trailer found for this title."}, - "fatalInitError": {"message": "Fatal initialization error"}, - "fatalInitErrorSub": {"message": "Could not load the application."}, - "invalidStreamInfo": {"message": "Invalid information."}, - "dbUnavailableForStreams": {"message": "Local database not available."}, - "noPlexServersForStreams": {"message": "No Plex servers."}, - "notFoundOnServers": {"message": "\"$query$\" not found on Plex servers.", "placeholders": {"query": {"content": "$1"}}}, - "relativeTime_justNow": { "message": "Just now" }, - "relativeTime_minutesAgo": { "message": "$count$ minutes ago", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_hoursAgo": { "message": "$count$ hours ago", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_yesterday": { "message": "Yesterday" }, - "relativeTime_daysAgo": { "message": "$count$ days ago", "placeholders": { "count": { "content": "$1" } } }, - "errorLoadingDetails": { "message": "Error Loading Details" }, - "errorLoadingLocalContent": { "message": "Error loading local content." }, - "errorServerResponse": { "message": "Unsuccessful server response." }, - "errorPlexApi": { "message": "Plex API error $status$.", "placeholders": { "status": { "content": "$1" } } }, - "errorParsingPlexXml": { "message": "Error parsing Plex XML." }, - "untitled": { "message": "Untitled" }, - "itemCount": { "message": "$count$ items", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "No photo servers" }, - "jellyfinScanInProgress": { "message": "Jellyfin scan is already in progress." }, - "jellyfinScanning": { "message": "Scanning Jellyfin..." }, - "jellyfinMissingCredentials": { "message": "Please complete the Jellyfin URL and username." }, - "jellyfinConnecting": { "message": "Connecting to Jellyfin at: $url$", "placeholders": { "url": { "content": "$1" } } }, - "jellyfinAuthFailed": { "message": "Jellyfin authentication failed: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinAuthSuccess": { "message": "Jellyfin authentication successful." }, - "jellyfinFetchingLibraries": { "message": "Fetching libraries..." }, - "jellyfinFetchFailed": { "message": "Error fetching libraries: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinNoMediaLibraries": { "message": "No movie or series libraries found in Jellyfin." }, - "jellyfinLibrariesFound": { "message": "$count$ media library(s) found.", "placeholders": { "count": { "content": "$1" } } }, - "jellyfinLibraryScanSuccess": { "message": "[Success] '$libraryName' scanned, $count$ titles added.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, - "jellyfinLibraryScanFailed": { "message": "Error scanning library '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, - "jellyfinScanSuccess": { "message": "Jellyfin scan completed. Added $movies$ movies and $series$ series.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, - "noJellyfinCredentials": { "message": "Jellyfin credentials not configured." }, - "notFoundOnJellyfin": { "message": "\"$query$\" not found on Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, - "notFoundOnAnyServer": { "message": "\"$query$\" not found on any server.", "placeholders": { "query": { "content": "$1" } } }, - "localOnPlex": { "message": "On Plex" }, - "searchOnPlex": { "message": "Search on Plex" }, - "jellyfinTitle": { "message": "Jellyfin Content" }, - "noJellyfinContent": { "message": "No Jellyfin content found." }, - "noJellyfinContentSub": { "message": "Make sure you have scanned your Jellyfin server in the settings." }, - "activityViewerTitle": { "message": "Server Activity Viewer" }, - "activitySelectServer": { "message": "Select a server" }, - "activityCheckBtn": { "message": "Refresh" }, - "activityNoSessions": { "message": "No active sessions on this server." }, - "activitySessionUser": { "message": "User" }, - "activitySessionDevice": { "message": "Device" }, - "activitySessionContent": { "message": "Content" }, - "activitySessionState": { "message": "State" }, - "activitySessionIdentifier": { "message": "Client Identifier" }, - "activityCopyID": { "message": "Copy ID" }, - "activityError": { "message": "Could not get server activity." }, - "activityCopied": { "message": "Identifier copied to clipboard!" }, - "activityCopyError": { "message": "Error copying the identifier." }, - "noProvidersFound": { "message": "No providers found." }, - "availableOnPlex": { "message": "Available on Plex" }, - "m3uGeneratorTitle": { "message": "M3U List Generator" }, - "selectAServer": { "message": "Select a server..." }, - "downloadM3u": { "message": "Download M3U" }, - "m3uGenerator": { "message": "M3U Generator" }, - "selectLibraries": { "message": "Select Libraries" }, - "howToUse": { "message": "How to Use" }, - "m3uInstruction1": { "message": "Choose a server from the list." }, - "m3uInstruction2": { "message": "Select one or more libraries to include." }, - "m3uInstruction3": { "message": "Click the download button." }, - "m3uInstruction4": { "message": "Import the .m3u file into your compatible player." }, - "chatOpen": { "message": "Open Chat" }, - "chatTitle": { "message": "AI Assistant" }, - "chatClose": { "message": "X" }, - "chatPlaceholder": { "message": "Type your message..." }, - "chatSend": { "message": "➤" }, - "chatWelcome": { "message": "Welcome! I'm your CinePlex assistant. Ask me about movies, series, or anything else you want to know." }, - "chatGoogleApiKeyMissing": { "message": "The Google Gemini API key is not configured. Please set it in the extension settings to use the AI assistant." }, - "chatApiInvalidResponse": { "message": "The API returned an invalid response. Please try again." }, - "chatApiError": { "message": "Error communicating with the AI assistant" }, - "downloadAll": { "message": "Download all" }, - "download": { "message": "Download" }, - "aiToolSearchLibraryDesc": { "message": "Searches the user's Plex library for movies or series by title." }, - "aiToolSearchLibraryQueryParamDesc": { "message": "The title of the movie or series to search for." }, - "aiToolSearchLibraryTypeParamDesc": { "message": "The type of content to search for. It can be 'movie' for movies or 'series' for series. (Optional)." }, - "aiToolSearchLibraryResolutionParamDesc": { "message": "The video resolution to search for (e.g., '4k', '1080p'). (Optional)." }, - "aiToolSearchLibraryContainerParamDesc": { "message": "The video container format to search for (e.g., 'mkv', 'mp4'). (Optional)." }, - "aiToolNavigateToPageDesc": { "message": "Navigates the user to a specific page of the application interface." }, - "aiToolNavigateToPagePageParamDesc": { "message": "The name of the page to navigate to, e.g.: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', or 'm3u-generator'." }, - "aiToolGetUserStatsDesc": { "message": "Gets and displays the user's library statistics, such as the total number of unique movies, series, and artists." }, - "aiToolShowItemDetailsDesc": { "message": "Displays the details page of a specific movie or series by its title and type." }, - "aiToolShowItemDetailsTitleParamDesc": { "message": "The exact title of the movie or series." }, - "aiToolShowItemDetailsTypeParamDesc": { "message": "The type of content. It must be 'movie' or 'series'." }, - "aiToolAddToPlaylistDesc": { "message": "Adds a movie or series to the user's current playlist to stream it to a configured PHP server." }, - "aiToolAddToPlaylistTitleParamDesc": { "message": "The title of the movie or series to add." }, - "aiToolAddToPlaylistTypeParamDesc": { "message": "The type of content. It must be 'movie' or 'series'." }, - "aiToolCheckAndDownloadDesc": { "message": "Checks the availability of a list of movie or series titles on the user's local servers and, if found, generates and downloads an M3U playlist file with the found streams." }, - "aiToolCheckAndDownloadTitlesParamDesc": { "message": "An array of movie or series titles to search for and download." }, - "aiToolCheckAndDownloadTypeParamDesc": { "message": "The content type of the list. It must be 'movie' or 'series'." }, - "aiToolCheckAndDownloadFilenameParamDesc": { "message": "The name of the M3U file to download (e.g., 'MyList.m3u'). If not provided, a default name will be used." }, - "aiToolToggleFavoriteDesc": { "message": "Adds or removes a movie or series from the user's favorites list." }, - "aiToolToggleFavoriteTitleParamDesc": { "message": "The title of the movie or series." }, - "aiToolToggleFavoriteTypeParamDesc": { "message": "The type of content. It must be 'movie' or 'series'." }, - "aiToolGetRecommendationsDesc": { "message": "Generates and displays a list of movie or series recommendations based on the user's viewing history and favorites." }, - "aiToolApplyFiltersDesc": { "message": "Applies filters to the current view of movies or series, allowing to refine the results by type, genre, year, and sort order." }, - "aiToolApplyFiltersTypeParamDesc": { "message": "The type of content to apply the filters to. It must be 'movie' or 'series'." }, - "aiToolApplyFiltersGenreParamDesc": { "message": "The name of the genre to filter by (e.g., 'Action', 'Drama')." }, - "aiToolApplyFiltersYearParamDesc": { "message": "The release year to filter by (e.g., '2023')." }, - "aiToolApplyFiltersSortParamDesc": { "message": "The sorting criterion for the results. Valid values: 'popularity.desc' (popular), 'vote_average.desc' (top rated), 'release_date.desc' (recent for movies) or 'first_air_date.desc' (recent for series)." }, - "aiToolPlayMusicByArtistDesc": { "message": "Opens the music player and starts playing songs by a specific artist from the user's library." }, - "aiToolPlayMusicByArtistNameParamDesc": { "message": "The exact name of the artist whose songs are to be played." }, - "aiToolClearChatHistoryDesc": { "message": "Clears all message history from the current conversation with the AI assistant." }, - "aiToolDeleteDatabaseDesc": { "message": "Deletes the entire local database of the extension, including scanned content, settings, and favorites. This action is irreversible and will reload the application." }, - "aiToolUpdateAllTokensDesc": { "message": "Initiates a full scan of all Plex servers and libraries associated with the tokens configured in the extension. Updates all movies, series, artists, and photos." }, - "aiToolAddPlexTokenDesc": { "message": "Adds a new X-Plex token to the extension's configuration, allowing the application to scan content from new Plex servers." }, - "aiToolAddPlexTokenTokenParamDesc": { "message": "The X-Plex token string to be added." }, - "aiToolChangeRegionDesc": { "message": "Changes the region used for content discovery in the TMDB API. This will affect the results shown in the movie and series sections, as well as the streaming providers." }, - "aiToolChangeRegionRegionParamDesc": { "message": "The two-letter ISO 3166-1 country code for the new region (e.g., 'US' for the United States, 'ES' for Spain, 'MX' for Mexico)." }, - "aiToolClearAllFavoritesDesc": { "message": "Removes all movies and series that the user has marked as favorites." }, - "aiToolClearViewingHistoryDesc": { "message": "Clears the user's viewing history from the history page." }, - "aiToolClearRecommendationsViewDesc": { "message": "Clears the recommendations view and removes cached recommendations." }, - "aiToolSearchNotFound": { "message": "'$query$' not found in your library.", "placeholders": { "query": { "content": "$1" } } }, - "aiToolNavigateSuccess": { "message": "Navigated to the $page$ page.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolNavigateError": { "message": "Error navigating to the $page$ page.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolStatsError": { "message": "Error getting statistics." }, - "aiToolItemNotFound": { "message": "Item '$title$' not found.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolShowItemDetailsSuccess": { "message": "Showing details for '$title$'.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolAddToPlaylistSuccess": { "message": "Added '$title$' to the playlist.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteAdded": { "message": "Added '$title$' to favorites.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteRemoved": { "message": "Removed '$title$' from favorites.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolRecommendationsSuccess": { "message": "Showing recommendations." }, - "aiToolApplyFiltersGenreNotFound": { "message": "Genre '$genre$' not found.", "placeholders": { "genre": { "content": "$1" } } }, - "aiToolApplyFiltersSuccess": { "message": "Filters applied successfully." }, - "aiToolPlayMusicNotReady": { "message": "The music player is not ready. Make sure your Plex music library has been scanned." }, - "aiToolPlayMusicArtistNotFound": { "message": "Artist '$artist_name$' not found.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicNoSongs": { "message": "No songs found for '$artist_name$'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicSuccess": { "message": "Playing music by '$artist_name$'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolChatHistoryCleared": { "message": "Chat history cleared." }, - "aiToolConfirmDeleteDatabase": { "message": "Are you sure you want to delete the local database? This action is irreversible." }, - "aiToolDeleteDatabaseCancelled": { "message": "Database deletion cancelled." }, - "aiToolExecutionError": { "message": "Error executing tool '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, - "aiToolUnknown": { "message": "Unknown tool: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, - "aiToolFavoritesCleared": { "message": "Favorites cleared." }, - "aiToolFavoritesClearError": { "message": "Error clearing favorites: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolRecommendationsCleared": { "message": "Recommendations cleared." }, - "aiToolRecommendationsClearError": { "message": "Error clearing recommendations: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleted": { "message": "Database deleted. The page will reload." }, - "aiToolDatabaseDeleteError": { "message": "Error deleting the database: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleteBlocked": { "message": "Database deletion is blocked. Close other tabs of the application." }, - "aiToolUpdateAllTokensSuccess": { "message": "All tokens have been updated successfully." }, - "aiToolUpdateAllTokensError": { "message": "Error updating tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolAddPlexTokenSuccess": { "message": "Plex token added successfully." }, - "aiToolAddPlexTokenError": { "message": "Error adding the Plex token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolChangeRegionSuccess": { "message": "Region changed to $region$. The content is being updated.", "placeholders": { "region": { "content": "$1" } } }, - "aiToolChangeRegionError": { "message": "Error changing the region: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolViewingHistoryCleared": { "message": "Viewing history cleared." }, - "aiToolViewingHistoryClearError": { "message": "Error clearing the viewing history: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiSystemPrompt_v3": { "message": "You are an expert film and series assistant called CinePlex. Your main function is to help users discover content and interact with their library. Follow these rules strictly: 1. **NEVER** pretend you have performed an action if you have not used a tool for it. For example, do not say 'I have downloaded X' if you have not used the download tool. 2. For recommendation or list requests (e.g., 'tell me 5 horror movies'), use your own knowledge to generate the list. Present it in numbered or bulleted format. After displaying the list, proactively ask the user if they want you to check for availability on their local servers and create an M3U file. 3. **ONLY** if the user confirms they want to check or download the list, use the `check_and_download_titles_list` tool. Do not use it without explicit confirmation. 4. For any other action such as navigating, getting statistics, or searching for a specific title, or filtering by resolution or container, use the appropriate tools. Always be concise, friendly, and efficient." }, - "aiToolM3UNoTitlesProvided": { "message": "Please provide a list of titles to create the playlist." }, - "aiToolM3UCheckingTitles": { "message": "Checking the titles on your local servers..." }, - "aiToolM3UNoLocalMatchesForDownload": { "message": "I haven't found any of the movies or series from the list on your local servers." }, - "aiToolM3UDownloadStarted": { "message": "Done! I found $1 of the $2 titles on your servers and have started the download of the M3U playlist.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, - "backToProviders": { "message": "Back to Providers" }, - "artistsCounterSingle": { "message": "$total$ Artist", "placeholders": { "total": { "content": "$1" } } }, - "artistsCounterLoading": { "message": "Loading..." }, - "downloadingSong": { "message": "Starting download of \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "songDownloaded": { "message": "\"$title$\" downloaded.", "placeholders": { "title": { "content": "$1" } } }, - "errorDownloadingSong": { "message": "Error downloading \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "generatingAlbumM3U": { "message": "Generating M3U for \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, - "albumM3UGenerated": { "message": "M3U for album \"$artist$\" generated.", "placeholders": { "artist": { "content": "$1" } } }, - "retyingSection": { "message": "Retrying section \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "retrySuccess": { "message": "[SUCCESS] Retry of \"$title$\" completed.", "placeholders": { "title": { "content": "$1" } } }, - "retryError": { "message": "[FINAL ERROR] Retry failed for \"$title$\": $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, - "startingRetryPhase": { "message": "Starting retry phase for $count$ sections...", "placeholders": { "count": { "content": "$1" } } }, - "tokenFoundServers": { "message": "Token $token$... found $count$ servers.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, - "errorProcessingToken": { "message": "Error processing token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, - "plexScanFatalError": { "message": "FATAL ERROR: $message$", "placeholders": { "message": { "content": "$1" } } }, - "errorDuringScan": { "message": "Error during scan: $message$", "placeholders": { "message": { "content": "$1" } } }, - "stoppingPlexScan": { "message": "Stopping Plex scan..." }, - "invalidTokenProvided": { "message": "Invalid token provided." }, - "tokenAlreadyExists": { "message": "The token already exists." }, - "tokenAddedSuccessfully": { "message": "Token added successfully." }, - "noStreamsFoundForSelection": { "message": "No streams found for the selection." }, - "autoplayBlocked": { "message": "Autoplay blocked." }, - "page": { "message": "Page" }, - "all": { "message": "All" }, - "userScore": { "message": "User Score" }, - "duration": { "message": "Duration" }, - "min": { "message": "Min" }, - "max": { "message": "Max" } + "appName": { "message": "CinePlex" }, + "appDescription": { "message": "Scans Plex servers to find content and displays it in the interface" }, + "appTagline": { "message": "Movies, Series, and Music" }, + "appLocaleCode": { "message": "en-US" }, + "toggleNavigation": { "message": "Toggle Navigation" }, + "searchPlaceholder": { "message": "Search for movies or series..." }, + "openMusicPlayer": { "message": "Open Music Player" }, + "settings": { "message": "Settings" }, + "navMovies": { "message": "Movies" }, + "navSeries": { "message": "Series" }, + "navProviders": { "message": "Providers" }, + "navPhotos": { "message": "Photos" }, + "navStats": { "message": "Statistics" }, + "navFavorites": { "message": "Favorites" }, + "navHistory": { "message": "History" }, + "navRecommendations": { "message": "Recommendations" }, + "navMusic": { "message": "Music" }, + "musicFeaturedPlaylists": { "message": "Featured Playlists" }, + "musicRecentlyAdded": { "message": "Recently Added" }, + "navM3uGenerator": { "message": "M3U Generator" }, + "heroWelcome": { "message": "" }, + "heroSubtitle": { "message": "Explore thousands of movies and series." }, + "addStream": { "message": "Add Stream" }, + "moreInfo": { "message": "More info" }, + "popularMovies": { "message": "Popular Movies" }, + "allGenres": { "message": "All genres" }, + "allYears": { "message": "All years" }, + "sortPopular": { "message": "Most popular" }, + "sortTopRated": { "message": "Top rated" }, + "sortRecent": { "message": "Most recent" }, + "loadMore": { "message": "Load more" }, + "photosBreadcrumbHome": { "message": "Albums" }, + "selectServer": { "message": "Select a server" }, + "loading": { "message": "Loading..." }, + "loadingLibraries": { "message": "Loading libraries..." }, + "photosEmptyState": { "message": "No albums or photos found." }, + "photosEmptyStateSub": { "message": "Please select a server or ensure you have a photo library in Plex." }, + "statsTitle": { "message": "Library Statistics" }, + "statsAllTokens": { "message": "All Tokens" }, + "statsAnalyzing": { "message": "Analyzing your library..." }, + "statsActiveTokens": { "message": "Active Tokens" }, + "statsServersFound": { "message": "Servers Found" }, + "statsUniqueMovies": { "message": "Unique Movies" }, + "statsUniqueSeries": { "message": "Unique Series" }, + "statsUniqueArtists": { "message": "Unique Artists" }, + "statsTokenServers": { "message": "Token's Servers" }, + "statsChartMoviesByGenre": { "message": "Content by Genre (Movies)" }, + "statsChartSeriesByGenre": { "message": "Content by Genre (Series)" }, + "statsChartByDecade": { "message": "Content by Decade" }, + "recommendationsTitle": { "message": "Recommendations for you" }, + "historyTitle": { "message": "Viewing History" }, + "clearHistory": { "message": "Clear All" }, + "consoleTitle": { "message": "Plex Scan Console" }, + "footerCredit": { "message": "An interface for your Plex universe." }, + "closeTrailer": { "message": "Close trailer" }, + "close": { "message": "Close" }, + "photoViewer": { "message": "Photo viewer" }, + "previous": { "message": "Previous" }, + "next": { "message": "Next" }, + "notificationTemplateText": { "message": "Notification" }, + "settingsTitleFull": { "message": "Settings and Configuration" }, + "settingsTabGeneral": { "message": "General" }, + "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, + "settingsTabPhpGen": { "message": "PHP Generator" }, + "settingsTabData": { "message": "Data" }, + "settingsApiServer": { "message": "API and Server Configuration" }, + "settingsTmdbApiLabel": { "message": "TMDB API Key (Optional)" }, + "settingsTmdbApiPlaceholder": { "message": "The default key will be used if left blank" }, + "settingsGoogleApiLabel": { "message": "Google Gemini API Key (Optional)" }, + "settingsGoogleApiPlaceholder": { "message": "Required to use the AI assistant" }, + "settingsRegionLabel": { "message": "Region for content discovery" }, + "allRegions": { "message": "All regions" }, + "settingsPhpUrlLabel": { "message": "Server URL for Adding Streams" }, + "settingsPhpUrlPlaceholder": { "message": "https://your-server.com/path/to/script.php" }, + "settingsInterface": { "message": "Interface" }, + "settingsLightTheme": { "message": "Light Mode" }, + "settingsShowHero": { "message": "Show 'Hero' welcome section" }, + "settingsScanContent": { "message": "Content Scan" }, + "settingsScanDesc": { "message": "Select what to scan and press the button." }, + "settingsScanMovies": { "message": "Movies" }, + "settingsScanShows": { "message": "Series" }, + "settingsScanArtists": { "message": "Music" }, + "settingsScanPhotos": { "message": "Photos" }, + "settingsSelectAll": { "message": "Select All" }, + "settingsStartScan": { "message": "Start Scan" }, + "settingsPlexTokens": { "message": "Plex Tokens" }, + "settingsPlexTokensDesc": { "message": "Edit the list of Plex tokens (JSON format)." }, + "settingsSaveTokens": { "message": "Save Tokens" }, + "settingsJellyfinTitle": { "message": "Jellyfin Configuration" }, + "settingsJellyfinDesc": { "message": "Add your Jellyfin server details to scan its content." }, + "jellyfinUrlLabel": { "message": "Jellyfin Server URL" }, + "jellyfinUserLabel": { "message": "Username" }, + "jellyfinPassLabel": { "message": "Password" }, + "jellyfinConnectAndScan": { "message": "Connect and Scan" }, + "settingsPhpGenTitle": { "message": "PHP Script Generator for Server" }, + "settingsPhpFileOptions": { "message": "File Options" }, + "settingsPhpSavePathLabel": { "message": "Save Path on Server" }, + "settingsPhpSavePathPlaceholder": { "message": "Ex: /var/www/html/lists (blank for the same folder)" }, + "settingsPhpFilenameLabel": { "message": "Filename" }, + "settingsPhpFileAction": { "message": "File Action" }, + "settingsPhpActionAppend": { "message": "Append to the end of the file (cumulative)" }, + "settingsPhpActionOverwrite": { "message": "Overwrite the file (start over)" }, + "settingsPhpSecurity": { "message": "Security (Optional)" }, + "settingsPhpUseSecretKey": { "message": "Use secret key (Recommended)" }, + "settingsPhpSecretKeyPlaceholder": { "message": "Enter a secure secret key" }, + "settingsPhpGeneratedCode": { "message": "Generated Code" }, + "settingsPhpGeneratedPlaceholder": { "message": "The generated PHP code will appear here." }, + "settingsGenerateScript": { "message": "Generate Script" }, + "settingsCopyScript": { "message": "Copy Script" }, + "settingsDataManagement": { "message": "Local Database Management" }, + "settingsImportDb": { "message": "Import DB from File" }, + "settingsExportDb": { "message": "Export DB to File" }, + "settingsClearContent": { "message": "Clear Local Content Data" }, + "settingsClearContentDesc": { "message": "This action will delete movies, series, and music from the local database, but will not affect your favorites or settings." }, + "settingsClose": { "message": "Close" }, + "settingsSave": { "message": "Save Settings" }, + "musicSidenavTitle": { "message": "Plex Music" }, + "musicAllServers": { "message": "All Servers" }, + "musicSearchArtistPlaceholder": { "message": "Search for artist..." }, + "musicSearchDiscographyPlaceholder": { "message": "Search in discography..." }, + "musicNothingPlaying": { "message": "Nothing playing" }, + "musicSelectSong": { "message": "Select a song" }, + "musicToStart": { "message": "to start playing" }, + "miniplayerDownloadSong": { "message": "Download song" }, + "miniplayerDownloadAlbum": { "message": "Download M3U" }, + "miniplayerVolume": { "message": "Volume" }, + "miniplayerShuffle": { "message": "Shuffle" }, + "miniplayerEqualizer": { "message": "Equalizer" }, + "miniplayerOpenList": { "message": "Open list" }, + "eqTitle": { "message": "Graphic Equalizer" }, + "eqPresetsLabel": { "message": "Presets" }, + "eqPresetFlat": { "message": "Flat" }, + "eqPresetRock": { "message": "Rock" }, + "eqPresetPop": { "message": "Pop" }, + "eqPresetJazz": { "message": "Jazz" }, + "eqPresetClassical": { "message": "Classical" }, + "eqPresetBassBoost": { "message": "Bass Boost" }, + "eqPreampLabel": { "message": "Preamplifier" }, + "infoModalTitle": { "message": "Information" }, + "infoModalFieldTitle": { "message": "Title:" }, + "infoModalFieldArtist": { "message": "Artist:" }, + "infoModalFieldAlbum": { "message": "Album:" }, + "infoModalFieldSong": { "message": "Song:" }, + "infoModalFieldYear": { "message": "Year:" }, + "infoModalFieldGenre": { "message": "Genre:" }, + "lang_en": { "message": "English" }, + "lang_es": { "message": "Spanish" }, + "lang_fr": { "message": "French" }, + "lang_de": { "message": "German" }, + "lang_it": { "message": "Italian" }, + "lang_pt": { "message": "Portuguese" }, + "essentialFeaturesNotSupported": { "message": "Your browser does not support essential features." }, + "dbAccessError": { "message": "Error accessing the local database." }, + "dbUpdateNeeded": { "message": "The database needs to be updated, please reload the page." }, + "dbBlocked": { "message": "Please close other tabs of this application to continue." }, + "deletingContentData": { "message": "Deleting local content data..." }, + "noContentDataToDelete": { "message": "No content data to delete." }, + "contentDataDeleted": { "message": "Content data deleted from IndexedDB." }, + "errorDeletingData": { "message": "Error deleting data: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailable": { "message": "Text editor not available." }, + "errorLoadingTokens": { "message": "Error loading tokens for editing." }, + "errorLoadingTokensMessage": { "message": "Error loading tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailableToSave": { "message": "Editor not available for saving." }, + "invalidJsonFormat": { "message": "Invalid JSON format. It must be { \"tokens\": [...] }" }, + "tokensSaved": { "message": "Tokens saved successfully." }, + "errorSavingTokens": { "message": "Error saving tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "dbNotAvailable": { "message": "IndexedDB is not available." }, + "dbExported": { "message": "Database exported successfully." }, + "errorExportingDb": { "message": "Error exporting database: $message$", "placeholders": { "message": { "content": "$1" } } }, + "invalidJsonFile": { "message": "The file does not contain a valid JSON object." }, + "noDataToImport": { "message": "The file does not contain data for the current DB sections." }, + "dbImported": { "message": "Database imported successfully." }, + "errorImportingDb": { "message": "Error importing database: $message$", "placeholders": { "message": { "content": "$1" } } }, + "updatingView": { "message": "Updating the view with new data..." }, + "confirmClearContent": { "message": "Are you sure you want to delete the local content data (Movies, Series, Music, etc.)? Favorites and Settings will NOT be deleted." }, + "trailerNotFound": { "message": "No trailer found for this title." }, + "confirmClearHistory": { "message": "Are you sure you want to delete all your viewing history? This action cannot be undone." }, + "historyCleared": { "message": "Viewing history cleared." }, + "historyItemDeleted": { "message": "Item deleted from history." }, + "errorGeneratingScript": { "message": "First generate a script to be able to copy it." }, + "scriptCopied": { "message": "PHP script copied to clipboard." }, + "errorCopyingScript": { "message": "Error copying script." }, + "scriptGenerated": { "message": "PHP script generated." }, + "errorLoadingAlbum": { "message": "Error loading album: $message$", "placeholders": { "message": { "content": "$1" } } }, + "noPhotoServerSelected": { "message": "Error: No photo server has been selected." }, + "loadingGenres": { "message": "Loading genres..." }, + "errorLoadingGenres": { "message": "Error loading" }, + "noContentFound": { "message": "No results found." }, + "couldNotLoadContent": { "message": "Could not load content." }, + "noFavorites": { "message": "You don't have any favorites yet." }, + "errorLoadingFavorites": { "message": "Error loading favorites." }, + "historyEmpty": { "message": "Your history is empty." }, + "historyEmptySub": { "message": "Explore and watch content for it to appear here." }, + "errorGeneratingRecommendations": { "message": "Error generating recommendations." }, + "noRecommendations": { "message": "We need to know you better to give you recommendations!" }, + "errorGeneratingStats": { "message": "Error generating statistics." }, + "noServersForToken": { "message": "No associated servers found for this token." }, + "searchingActorContent": { "message": "Searching for content by $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, + "errorLoadingActorContent": { "message": "Could not load content for $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, + "errorAddingStream": { "message": "Error adding stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, + "phpUrlNotConfigured": { "message": "The PHP server URL is not configured. Please set it up in Settings." }, + "searchingStreams": { "message": "Searching for streams for \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "sendingStreams": { "message": "Sending $count$ stream(s) to the server...", "placeholders": { "count": { "content": "$1" } } }, + "streamAddedSuccess": { "message": "Stream(s) added successfully." }, + "generatingM3U": { "message": "Generating M3U for \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "m3uDownloaded": { "message": "\"$title$\" downloaded.", "placeholders": { "title": { "content": "$1" } } }, + "errorGeneratingM3U": { "message": "Error generating M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, + "settingsSavedSuccess": { "message": "Settings saved successfully." }, + "errorSavingSettings": { "message": "Error saving settings to the database." }, + "languageChangeReload": { "message": "Language changed. The application will now reload." }, + "addedToFavorites": { "message": "Added to favorites." }, + "removedFromFavorites": { "message": "Removed from favorites." }, + "plexScanInProgress": { "message": "Plex scan is already in progress." }, + "plexScanStarting": { "message": "Starting Plex scan..." }, + "noPlexTokens": { "message": "No Plex tokens configured." }, + "clearingSections": { "message": "Clearing sections: $sections$", "placeholders": { "sections": { "content": "$1" } } }, + "initialScanPhaseComplete": { "message": "Initial scan phase completed." }, + "retryPhaseFinished": { "message": "Retry phase finished." }, + "plexScanFinished": { "message": "Scan finished. Updating content..." }, + "scanCancelled": { "message": "Scan canceled by the user." }, + "scanCancelledInfo": { "message": "Scan canceled." }, + "errorInitializingMusicPlayer": { "message": "Error initializing music player." }, + "criticalErrorLoadingMusic": { "message": "Critical error loading music data." }, + "errorLoadingArtists": { "message": "Error loading artists." }, + "dbUnavailableError": { "message": "Error: Database not available." }, + "updatingMusicData": { "message": "Updating music data..." }, + "musicDataUpdated": { "message": "Music data updated." }, + "errorFetchingArtistSongs": { "message": "Error fetching artist's songs." }, + "errorLoadingSongs": { "message": "Error loading songs." }, + "noArtistsFound": { "message": "No artists found." }, + "shuffleOn": { "message": "Shuffle mode on." }, + "shuffleOff": { "message": "Shuffle mode off." }, + "playbackError": { "message": "Playback error" }, + "errorLabel": { "message": "Error" }, + "reloadingPage": { "message": "Reloading page..." }, + "viewed": { "message": "Viewed" }, + "local": { "message": "Local" }, + "topRatedSort": {"message": "Top Rated"}, + "recentSort": {"message": "Recent"}, + "popularSort": {"message": "Popular"}, + "moviesSectionTitle": {"message": "Movies"}, + "seriesSectionTitle": {"message": "Series"}, + "searchResultsFor": {"message": "Results for \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, + "contentFrom": {"message": "Content from $actor$", "placeholders": {"actor": {"content": "$1"}}}, + "explore": {"message": "Explore"}, + "noGenre": {"message": "Uncategorized"}, + "synopsis": {"message": "Synopsis"}, + "noSynopsis": {"message": "No synopsis available."}, + "director": {"message": "Director:"}, + "writer": {"message": "Writer:"}, + "viewOnImdb": {"message": "View on IMDb"}, + "watchTrailer": {"message": "Watch Trailer"}, + "addToFavorites": {"message": "Add to favorites"}, + "removeFromFavorites": {"message": "Remove from favorites"}, + "notAvailable": {"message": "Not available"}, + "mainCast": {"message": "Main Cast"}, + "seasonsAndEpisodes": {"message": "Seasons and Episodes"}, + "similarContent": {"message": "Similar Content"}, + "filmography": {"message": "Filmography"}, + "availableOn": {"message": "Available on"}, + "episodesCount": {"message": "$count$ Episodes", "placeholders": {"count": {"content": "$1"}}}, + "seasonsCount": {"message": "$count$ Seasons", "placeholders": {"count": {"content": "$1"}}}, + "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, + "noTrailerFound": {"message": "No trailer found for this title."}, + "fatalInitError": {"message": "Fatal initialization error"}, + "fatalInitErrorSub": {"message": "Could not load the application."}, + "invalidStreamInfo": {"message": "Invalid stream info."}, + "dbUnavailableForStreams": {"message": "Local database not available."}, + "noPlexServersForStreams": {"message": "No Plex servers."}, + "notFoundOnServers": {"message": "\"$query$\" not found on Plex servers.", "placeholders": {"query": {"content": "$1"}}}, + "relativeTime_justNow": { "message": "Just now" }, + "relativeTime_minutesAgo": { "message": "$count$ minutes ago", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_hoursAgo": { "message": "$count$ hours ago", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_yesterday": { "message": "Yesterday" }, + "relativeTime_daysAgo": { "message": "$count$ days ago", "placeholders": { "count": { "content": "$1" } } }, + "errorLoadingDetails": { "message": "Error Loading Details" }, + "errorLoadingLocalContent": { "message": "Error loading local content." }, + "errorServerResponse": { "message": "Unsuccessful server response." }, + "errorPlexApi": { "message": "Plex API error $status$.", "placeholders": { "status": { "content": "$1" } } }, + "errorParsingPlexXml": { "message": "Error parsing Plex XML." }, + "untitled": { "message": "Untitled" }, + "itemCount": { "message": "$count$ items", "placeholders": { "count": { "content": "$1" } } }, + "noPhotoServers": { "message": "No photo servers" }, + "jellyfinScanInProgress": { "message": "Jellyfin scan is already in progress." }, + "jellyfinScanning": { "message": "Scanning Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Please fill in the Jellyfin URL and username." }, + "jellyfinConnecting": { "message": "Connecting to Jellyfin at: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Jellyfin authentication failed: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Jellyfin authentication successful." }, + "jellyfinFetchingLibraries": { "message": "Fetching libraries..." }, + "jellyfinFetchFailed": { "message": "Error fetching libraries: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "No movie or series libraries found in Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ media library(s) found.", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Success] '$libraryName' scanned, $count$ titles added.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Error scanning library '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Jellyfin scan completed. Added $movies$ movies and $series$ series.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Jellyfin credentials not configured." }, + "notFoundOnJellyfin": { "message": "\"$query$\" not found on Jellyfin.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnAnyServer": { "message": "\"$query$\" not found on any server.", "placeholders": {"query": {"content": "$1"}}}, + "localOnPlex": { "message": "On Plex" }, + "searchOnPlex": { "message": "Search on Plex" }, + "jellyfinTitle": { "message": "Jellyfin Content" }, + "noJellyfinContent": { "message": "No Jellyfin content found." }, + "noJellyfinContentSub": { "message": "Make sure you have scanned your Jellyfin server in the settings." }, + "activityViewerTitle": { "message": "Server Activity Viewer" }, + "activitySelectServer": { "message": "Select a server" }, + "activityCheckBtn": { "message": "Refresh" }, + "activityNoSessions": { "message": "There are no active sessions on this server." }, + "activitySessionUser": { "message": "User" }, + "activitySessionDevice": { "message": "Device" }, + "activitySessionContent": { "message": "Content" }, + "activitySessionState": { "message": "State" }, + "activitySessionIdentifier": { "message": "Client Identifier" }, + "activityCopyID": { "message": "Copy ID" }, + "activityError": { "message": "Could not get server activity." }, + "activityCopied": { "message": "Identifier copied to clipboard!" }, + "activityCopyError": { "message": "Error copying identifier." }, + "noProvidersFound": { "message": "No providers found." }, + "availableOnPlex": { "message": "Available on Plex" }, + "m3uGeneratorTitle": { "message": "M3U List Generator" }, + "selectAServer": { "message": "Select a server..." }, + "downloadM3u": { "message": "Download M3U" }, + "m3uGenerator": { "message": "M3U Generator" }, + "selectLibraries": { "message": "Select Libraries" }, + "howToUse": { "message": "How to Use" }, + "m3uInstruction1": { "message": "Choose a server from the list." }, + "m3uInstruction2": { "message": "Select one or more libraries to include." }, + "m3uInstruction3": { "message": "Click the download button." }, + "m3uInstruction4": { "message": "Import the .m3u file into your compatible player." }, + "chatOpen": { "message": "Open Chat" }, + "chatTitle": { "message": "AI Assistant" }, + "chatClose": { "message": "X" }, + "chatPlaceholder": { "message": "Type your message..." }, + "chatSend": { "message": "➤" }, + "chatWelcome": { "message": "Welcome! I'm your CinePlex assistant. Ask me about movies, series, or anything else you'd like to know." }, + "chatGoogleApiKeyMissing": { "message": "The Google Gemini API key is not configured. Please set it up in the extension settings to use the AI assistant." }, + "chatApiInvalidResponse": { "message": "The API returned an invalid response. Please try again." }, + "chatApiError": { "message": "Error communicating with the AI assistant" }, + "downloadAll": { "message": "Download all" }, + "download": { "message": "Download" }, + "aiToolSearchLibraryDesc": { "message": "Searches the user's Plex library for movies or series by title." }, + "aiToolSearchLibraryQueryParamDesc": { "message": "The title of the movie or series to search for." }, + "aiToolSearchLibraryTypeParamDesc": { "message": "The type of content to search for. Can be 'movie' or 'series'. (Optional)." }, + "aiToolSearchLibraryResolutionParamDesc": { "message": "The video resolution to search for (e.g., '4k', '1080p'). (Optional)." }, + "aiToolSearchLibraryContainerParamDesc": { "message": "The video container format to search for (e.g., 'mkv', 'mp4'). (Optional)." }, + "aiToolNavigateToPageDesc": { "message": "Navigates the user to a specific page within the application's interface." }, + "aiToolNavigateToPagePageParamDesc": { "message": "The name of the page to navigate to, e.g., 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator', or 'music'." }, + "aiToolGetUserStatsDesc": { "message": "Gets and displays the user's library statistics, such as the total number of unique movies, series, and artists." }, + "aiToolShowItemDetailsDesc": { "message": "Displays the details page for a specific movie or series by its title and type." }, + "aiToolShowItemDetailsTitleParamDesc": { "message": "The exact title of the movie or series." }, + "aiToolShowItemDetailsTypeParamDesc": { "message": "The type of content. Must be 'movie' or 'series'." }, + "aiToolAddToPlaylistDesc": { "message": "Adds a movie or series to the user's current playlist to be streamed to a configured PHP server." }, + "aiToolAddToPlaylistTitleParamDesc": { "message": "The title of the movie or series to add." }, + "aiToolAddToPlaylistTypeParamDesc": { "message": "The type of content. Must be 'movie' or 'series'." }, + "aiToolDownloadSingleMovieM3UDesc": { "message": "Generates and downloads an M3U playlist file for a single locally available movie." }, + "aiToolDownloadSingleMovieM3UTitleParamDesc": { "message": "The title of the movie for which the M3U will be generated." }, + "aiToolDownloadSingleMovieM3UYearParamDesc": { "message": "The release year of the movie (optional, for better accuracy)." }, + "aiToolDownloadSeriesSeasonM3UDesc": { "message": "Generates and downloads an M3U playlist file for a specific season of a locally available series." }, + "aiToolDownloadSeriesSeasonM3UTitleParamDesc": { "message": "The title of the series." }, + "aiToolDownloadSeriesSeasonM3USeasonParamDesc": { "message": "The season number to download." }, + "aiToolDownloadSeriesSeasonM3UYearParamDesc": { "message": "The release year of the series (optional)." }, + "aiToolCheckAndDownloadDesc": { "message": "Checks the availability of a list of movie or series titles on the user's local servers and, if found, generates and downloads an M3U playlist file with the found streams." }, + "aiToolCheckAndDownloadTitlesParamDesc": { "message": "An array of movie or series titles to search for and download." }, + "aiToolCheckAndDownloadTypeParamDesc": { "message": "The content type of the list. Must be 'movie' or 'series'." }, + "aiToolCheckAndDownloadFilenameParamDesc": { "message": "The name of the M3U file to download (e.g., 'MyList.m3u'). If not provided, a default name will be used." }, + "aiToolToggleFavoriteDesc": { "message": "Adds or removes a movie or series from the user's favorites list." }, + "aiToolToggleFavoriteTitleParamDesc": { "message": "The title of the movie or series." }, + "aiToolToggleFavoriteTypeParamDesc": { "message": "The type of content. Must be 'movie' or 'series'." }, + "aiToolGetRecommendationsDesc": { "message": "Generates and displays a list of movie or series recommendations based on the user's viewing history and favorites." }, + "aiToolApplyFiltersDesc": { "message": "Applies filters to the current movie or series view, allowing results to be refined by type, genre, year, and sort order." }, + "aiToolApplyFiltersTypeParamDesc": { "message": "The content type to apply filters to. Must be 'movie' or 'series'." }, + "aiToolApplyFiltersGenreParamDesc": { "message": "The name of the genre to filter by (e.g., 'Action', 'Drama')." }, + "aiToolApplyFiltersYearParamDesc": { "message": "The release year to filter by (e.g., '2023')." }, + "aiToolApplyFiltersSortParamDesc": { "message": "The sorting criteria for the results. Valid values: 'popularity.desc' (popular), 'vote_average.desc' (top rated), 'release_date.desc' (recent for movies), or 'first_air_date.desc' (recent for series)." }, + "aiToolListAvailableMusicGenresDesc": { "message": "Lists all unique music genres available in the user's local library." }, + "aiToolSearchMusicByGenreDesc": { "message": "Searches for artists in the user's music library that belong to a specific genre." }, + "aiToolSearchMusicByGenreNameParamDesc": { "message": "The name of the music genre to search for (e.g., 'Rock', 'Pop', 'Jazz')." }, + "aiToolPlayMusicByArtistDesc": { "message": "Opens the music player and starts playing songs by a specific artist from the user's library." }, + "aiToolPlayMusicByArtistNameParamDesc": { "message": "The exact name of the artist whose songs are to be played." }, + "aiToolClearChatHistoryDesc": { "message": "Clears the entire message history of the current conversation with the AI assistant." }, + "aiToolDeleteDatabaseDesc": { "message": "Deletes the entire local database of the extension, including scanned content, settings, and favorites. This action is irreversible and will reload the application." }, + "aiToolUpdateAllTokensDesc": { "message": "Initiates a full scan of all Plex servers and libraries associated with the configured tokens in the extension. Updates all movies, series, artists, and photos." }, + "aiToolAddPlexTokenDesc": { "message": "Adds a new X-Plex token to the extension's configuration, allowing the application to scan content from new Plex servers." }, + "aiToolAddPlexTokenTokenParamDesc": { "message": "The X-Plex token string to be added." }, + "aiToolChangeRegionDesc": { "message": "Changes the region used for content discovery in the TMDB API. This will affect the results displayed in the movies and series sections, as well as the streaming providers." }, + "aiToolChangeRegionRegionParamDesc": { "message": "The two-letter ISO 3166-1 country code for the new region (e.g., 'US' for United States, 'ES' for Spain, 'MX' for Mexico)." }, + "aiToolClearAllFavoritesDesc": { "message": "Removes all movies and series that the user has marked as favorites." }, + "aiToolClearViewingHistoryDesc": { "message": "Clears the user's viewing history from the history page." }, + "aiToolClearRecommendationsViewDesc": { "message": "Clears the recommendations view and removes cached recommendations." }, + "aiToolSearchNotFound": { "message": "Could not find '$query' in your library.", "placeholders": { "query": { "content": "$1" } } }, + "aiToolNavigateSuccess": { "message": "Navigated to the $page$ page.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolNavigateError": { "message": "Error navigating to the $page$ page.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolStatsError": { "message": "Error getting statistics." }, + "aiToolItemNotFound": { "message": "Item '$title' not found.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolShowItemDetailsSuccess": { "message": "Showing details for '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolAddToPlaylistSuccess": { "message": "Added '$title' to the playlist.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteAdded": { "message": "Added '$title' to favorites.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteRemoved": { "message": "Removed '$title' from favorites.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolRecommendationsSuccess": { "message": "Showing recommendations." }, + "aiToolApplyFiltersGenreNotFound": { "message": "Genre '$genre' not found.", "placeholders": { "genre": { "content": "$1" } } }, + "aiToolApplyFiltersSuccess": { "message": "Filters applied successfully." }, + "aiToolSearchMusicByGenreNotFound": { "message": "I couldn't find artists of the genre '$genre_name' in your library.", "placeholders": { "genre_name": { "content": "$1" } } }, + "aiToolPlayMusicNotReady": { "message": "The music player is not ready. Make sure your Plex music library has been scanned." }, + "aiToolPlayMusicArtistNotFound": { "message": "Artist '$artist_name' not found.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicNoSongs": { "message": "No songs found for '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicSuccess": { "message": "Playing music by '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolChatHistoryCleared": { "message": "Chat history cleared." }, + "aiToolConfirmDeleteDatabase": { "message": "Are you sure you want to delete the local database? This action is irreversible." }, + "aiToolDeleteDatabaseCancelled": { "message": "Database deletion cancelled." }, + "aiToolExecutionError": { "message": "Error executing tool '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, + "aiToolUnknown": { "message": "Unknown tool: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, + "aiToolFavoritesCleared": { "message": "Favorites cleared." }, + "aiToolFavoritesClearError": { "message": "Error clearing favorites: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolRecommendationsCleared": { "message": "Recommendations cleared." }, + "aiToolRecommendationsClearError": { "message": "Error clearing recommendations: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleted": { "message": "Database deleted. The page will now reload." }, + "aiToolDatabaseDeleteError": { "message": "Error deleting database: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleteBlocked": { "message": "Database deletion is blocked. Please close other application tabs." }, + "aiToolUpdateAllTokensSuccess": { "message": "All tokens have been updated successfully." }, + "aiToolUpdateAllTokensError": { "message": "Error updating tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolAddPlexTokenSuccess": { "message": "Plex token added successfully." }, + "aiToolAddPlexTokenError": { "message": "Error adding Plex token: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolChangeRegionSuccess": { "message": "Region changed to $region$. Content is being updated.", "placeholders": { "region": { "content": "$1" } } }, + "aiToolChangeRegionError": { "message": "Error changing region: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolViewingHistoryCleared": { "message": "Viewing history cleared." }, + "aiToolViewingHistoryClearError": { "message": "Error clearing viewing history: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSingle": { "message": "Starting M3U download for '$movie_title'.", "placeholders": { "movie_title": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSeason": { "message": "Starting M3U download for season $1 of '$2'.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolM3UNoTitlesProvided": { "message": "Please provide a list of titles to create the playlist." }, + "aiToolM3UCheckingTitles": { "message": "Checking titles on your local servers..." }, + "aiToolM3UNoLocalMatchesForDownload": { "message": "I couldn't find any of the movies or series from the list on your local servers." }, + "aiToolM3UDownloadStarted": { "message": "Done! I found $1 of the $2 titles on your servers and have started downloading the M3U playlist.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolTrailerNotFoundSpecific": { "message": "Sorry, I couldn't find an available trailer for '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiSystemPrompt_v4": { + "message": "You are a virtual assistant integrated into a Chrome extension that interacts with Plex and Jellyfin servers. Your main function is to help the user search, manage, play, and download multimedia content, as well as manage custom settings.\n\nTOP PRIORITY: Whenever the user's question refers to multimedia content (movies, series, music), you MUST assume it refers to their local library. Use the tools to search their database BEFORE searching the web.\n\n🎯 General behavior rules:\nAlways respond clearly, concisely, and directly. Be proactive and provide all relevant information at once to avoid follow-up questions. For example, when confirming the availability of a series, include the season details.\n\nCompare the current date with Google search results when asked for external information to ensure it is up-to-date.\n\nUse the exact names of the commands defined in the system (function.name) when calling tools.\n\n📦 Key functions for multimedia content:\nTo generate an M3U for a single movie, use download_single_movie_m3u.\nTo download a specific season of a series, use download_series_season_m3u.\nFor multiple titles (movies or full series), always use check_and_download_titles_list.\nTo search local content: search_library.\nTo search TMDB: search_tmdb_content.\nFor trending content: get_trending_content.\nTo show details of a title: show_item_details.\nTo add to the PHP playlist: add_to_playlist.\nTo check local availability: check_local_availability.\nIf a series is available locally, report how many seasons there are and on which servers using get_local_series_seasons.\nTo see recommendations: get_recommendations.\nTo apply filters: apply_filters.\nTo view history or favorites: view_history, view_favorites.\nTo mark as favorite: toggle_favorite.\nTo play trailer: play_trailer.\n\n🎵 Music functions:\nIf the user asks for general music genre recommendations (e.g., 'recommend a genre to cheer me up'), first use list_available_music_genres to see what genres they have and base your recommendation on that list.\nTo list all available music genres in the library: list_available_music_genres.\nTo search for artists by genre: search_music_by_genre.\nTo play songs by title and/or artist: play_song.\nTo play music by an artist: play_music_by_artist.\n\n🧰 Management and configuration functions:\nTo get user statistics: get_user_stats.\nTo navigate to specific sections: navigate_to_page.\nTo update tokens: update_all_tokens, add_plex_token.\nTo change content region: change_region.\nTo export or import the local database: export_local_database, import_local_database.\nTo delete the database: delete_database.\nTo clear favorites, history, or recommendations: clear_all_favorites, clear_viewing_history, clear_recommendations_view.\nTo toggle light/dark mode: toggle_light_mode.\nTo show or hide the hero section: toggle_hero_section.\n\n⚠️ Additional considerations:\nPrioritize locally available content. Use check_local_availability before showing playback or download options.\nIf an action fails, report it clearly and bluntly.\nAvoid unnecessarily repeating the user's request, unless it helps to contextualize the response." + }, + "backToProviders": { "message": "Back to Providers" }, + "artistsCounterSingle": { "message": "$total$ Artist", "placeholders": { "total": { "content": "$1" } } }, + "artistsCounterLoading": { "message": "Loading..." }, + "downloadingSong": { "message": "Starting download of \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "songDownloaded": { "message": "\"$title$\" downloaded.", "placeholders": { "title": { "content": "$1" } } }, + "errorDownloadingSong": { "message": "Error downloading \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "generatingAlbumM3U": { "message": "Generating M3U for \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, + "albumM3UGenerated": { "message": "M3U for album \"$artist$\" generated.", "placeholders": { "artist": { "content": "$1" } } }, + "retyingSection": { "message": "Retrying section \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "retrySuccess": { "message": "[SUCCESS] Retry of \"$title$\" completed.", "placeholders": { "title": { "content": "$1" } } }, + "retryError": { "message": "[FINAL ERROR] Retry for \"$title$\" failed: $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, + "startingRetryPhase": { "message": "Starting retry phase for $count$ sections...", "placeholders": { "count": { "content": "$1" } } }, + "tokenFoundServers": { "message": "Token $token$... found $count$ servers.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, + "errorProcessingToken": { "message": "Error processing token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, + "plexScanFatalError": { "message": "FATAL ERROR: $message$", "placeholders": { "message": { "content": "$1" } } }, + "errorDuringScan": { "message": "Error during scan: $message$", "placeholders": { "message": { "content": "$1" } } }, + "stoppingPlexScan": { "message": "Stopping Plex scan..." }, + "invalidTokenProvided": { "message": "Invalid token provided." }, + "tokenAlreadyExists": { "message": "Token already exists." }, + "tokenAddedSuccessfully": { "message": "Token added successfully." }, + "noStreamsFoundForSelection": { "message": "No streams found for the selection." }, + "autoplayBlocked": { "message": "Autoplay blocked." }, + "welcomeToCinePlex": { "message": "" }, + "page": { "message": "Page" }, + "all": { "message": "All" }, + "userScore": { "message": "Score" }, + "duration": { "message": "Duration" }, + "min": { "message": "Min" }, + "max": { "message": "Max" }, + "aiToolFindStreamingProvidersDesc": { "message": "Finds where to watch a movie or series on streaming services." }, + "aiToolFindStreamingProvidersTitleParamDesc": { "message": "The title of the movie or series to search for." }, + "aiToolFindStreamingProvidersTypeParamDesc": { "message": "The content type (movie or series)." }, + "aiToolFindStreamingProvidersYearParamDesc": { "message": "The release year of the content (optional)." }, + "aiToolNoStreamingProviders": { "message": "No streaming providers found for {title}." }, + "aiToolStreamingProvidersFound": { "message": "{title} is available on the following services: {providers}." }, + "aiToolStreamingProviderError": { "message": "Error searching for streaming providers: {message}." }, + "aiToolGetLocalSeriesSeasonsDesc": { "message": "Checks if a TV series is available locally and returns a detailed breakdown of the available seasons on each server." }, + "aiToolGetLocalSeriesSeasonsTitleParamDesc": { "message": "The title of the series to check." }, + "aiToolGetLocalSeriesSeasonsYearParamDesc": { "message": "The release year of the series (optional for greater accuracy)." }, + "aiToolLocalSeriesNoSeasons": { "message": "The series '$series_title' is in your library, but no season details were found.", "placeholders": { "series_title": { "content": "$1" } } }, + "artist": { "message": "Artist" }, + "tracks": { "message": "tracks" }, + "noSongsFound": { "message": "No songs found for this artist." }, + "durationMin": { "message": "Duration (Min)" }, + "score": { "message": "Score" }, + "searchGenre": { "message": "Search genre..." }, + "searchArtists": { "message": "Search artists..." }, + "preparingMusicLibrary": { "message": "Preparing your music library..." }, + "preparingMusicLibraryDesc": { "message": "This one-time process may take a few minutes if you have many artists." }, + "artistsProgress": { "message": "0 / 0 artists" }, + "starting": { "message": "Starting..." }, + "artistName": { "message": "Artist Name" }, + "playPause": { "message": "Play/Pause" }, + "noLocalFilesFound": { "message": "No local files found for this title." }, + "server": { "message": "Server" }, + "title": { "message": "Title" }, + "year": { "message": "Year" }, + "resolution": { "message": "Resolution" }, + "size": { "message": "Size" }, + "container": { "message": "Container" }, + "action": { "message": "Action" }, + "generate": { "message": "Generate" }, + "availableLocalFiles": { "message": "Available Local Files" }, + "downloadSeason": { "message": "Download Season" }, + "errorLoadingServersM3u": { "message": "Error loading servers for the M3U generator:" }, + "errorFetchingLibraries": { "message": "Error fetching libraries." }, + "selectServerAndLibrary": { "message": "Please select a server and at least one library." }, + "generating": { "message": "Generating..." }, + "errorProcessingLibrary": { "message": "Error processing library" }, + "errorProcessingLibrarySkipping": { "message": "Error processing library. Skipping." }, + "allLibrariesFailed": { "message": "All selected libraries failed to process." }, + "m3uGeneratedWithErrors": { "message": "M3U generated with some errors. Some libraries may be missing." }, + "m3uDownloadedSuccess": { "message": "M3U playlist downloaded successfully." }, + "errorGeneratingM3uFile": { "message": "Error generating the M3U file." }, + "chatSources": { "message": "Sources" }, + "chatUnnamedSource": { "message": "Unnamed source" }, + "googleApiFailure": { "message": "Google AI API call failed:" } } \ No newline at end of file diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 82b2e0d..8684db6 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -1,450 +1,516 @@ { - "appName": { "message": "CinePlex" }, - "appDescription": { "message": "Escanea servidores de Plex para encontrar contenido y lo muestra en la interfaz" }, - "appTagline": { "message": "Películas, Series y Música" }, - "appLocaleCode": { "message": "es-ES" }, - "toggleNavigation": { "message": "Alternar Navegación" }, - "searchPlaceholder": { "message": "Buscar películas o series..." }, - "openMusicPlayer": { "message": "Abrir Reproductor de Música" }, - "settings": { "message": "Ajustes" }, - "navMovies": { "message": "Películas" }, - "navSeries": { "message": "Series" }, - "navProviders": { "message": "Proveedores" }, - "navPhotos": { "message": "Fotos" }, - "navStats": { "message": "Estadísticas" }, - "navFavorites": { "message": "Favoritos" }, - "navHistory": { "message": "Historial" }, - "navRecommendations": { "message": "Recomendaciones" }, - "navMusic": { "message": "Música" }, - "navM3uGenerator": { "message": "Generador M3U" }, - "heroWelcome": { "message": "" }, - "heroSubtitle": { "message": "Explora miles de películas y series." }, - "addStream": { "message": "Añadir Stream" }, - "moreInfo": { "message": "Más información" }, - "popularMovies": { "message": "Películas Populares" }, - "allGenres": { "message": "Todos los géneros" }, - "allYears": { "message": "Todos los años" }, - "sortPopular": { "message": "Más populares" }, - "sortTopRated": { "message": "Mejor valoradas" }, - "sortRecent": { "message": "Más recientes" }, - "loadMore": { "message": "Cargar más" }, - "photosBreadcrumbHome": { "message": "Álbumes" }, - "selectServer": { "message": "Selecciona un servidor" }, - "loading": { "message": "Cargando..." }, - "loadingLibraries": { "message": "Cargando bibliotecas..." }, - "photosEmptyState": { "message": "No se encontraron álbumes ni fotos." }, - "photosEmptyStateSub": { "message": "Por favor, selecciona un servidor o asegúrate de tener una biblioteca de fotos en Plex." }, - "statsTitle": { "message": "Estadísticas de la Biblioteca" }, - "statsAllTokens": { "message": "Todos los Tokens" }, - "statsAnalyzing": { "message": "Analizando tu biblioteca..." }, - "statsActiveTokens": { "message": "Tokens Activos" }, - "statsServersFound": { "message": "Servidores Encontrados" }, - "statsUniqueMovies": { "message": "Películas Únicas" }, - "statsUniqueSeries": { "message": "Series Únicas" }, - "statsUniqueArtists": { "message": "Artistas Únicos" }, - "statsTokenServers": { "message": "Servidores del Token" }, - "statsChartMoviesByGenre": { "message": "Contenido por Género (Películas)" }, - "statsChartSeriesByGenre": { "message": "Contenido por Género (Series)" }, - "statsChartByDecade": { "message": "Contenido por Década" }, - "recommendationsTitle": { "message": "Recomendaciones para ti" }, - "historyTitle": { "message": "Historial de Visualización" }, - "clearHistory": { "message": "Borrar Todo" }, - "consoleTitle": { "message": "Consola de Escaneo Plex" }, - "footerCredit": { "message": "Una interfaz para tu universo Plex." }, - "closeTrailer": { "message": "Cerrar tráiler" }, - "close": { "message": "Cerrar" }, - "photoViewer": { "message": "Visor de fotos" }, - "previous": { "message": "Anterior" }, - "next": { "message": "Siguiente" }, - "notificationTemplateText": { "message": "Notificación" }, - "settingsTitleFull": { "message": "Ajustes y Configuración" }, - "settingsTabGeneral": { "message": "General" }, - "settingsTabPlex": { "message": "Plex" }, - "settingsTabJellyfin": { "message": "Jellyfin" }, - "settingsTabPhpGen": { "message": "Generador PHP" }, - "settingsTabData": { "message": "Datos" }, - "settingsApiServer": { "message": "Configuración de API y Servidor" }, - "settingsTmdbApiLabel": { "message": "Clave de API de TMDB (Opcional)" }, - "settingsTmdbApiPlaceholder": { "message": "Se usará la clave por defecto si se deja en blanco" }, - "settingsGoogleApiLabel": { "message": "Clave de API de Google Gemini (Opcional)" }, - "settingsGoogleApiPlaceholder": { "message": "Necesaria para usar el asistente de IA" }, - "settingsRegionLabel": { "message": "Región para descubrimiento de contenido" }, - "allRegions": { "message": "Todas las regiones" }, - "settingsPhpUrlLabel": { "message": "URL del Servidor para Añadir Streams" }, - "settingsPhpUrlPlaceholder": { "message": "https://tu-servidor.com/ruta/al/script.php" }, - "settingsInterface": { "message": "Interfaz" }, - "settingsLightTheme": { "message": "Modo Claro" }, - "settingsShowHero": { "message": "Mostrar sección de bienvenida 'Hero'" }, - "settingsScanContent": { "message": "Escaneo de Contenido" }, - "settingsScanDesc": { "message": "Selecciona qué escanear y pulsa el botón." }, - "settingsScanMovies": { "message": "Películas" }, - "settingsScanShows": { "message": "Series" }, - "settingsScanArtists": { "message": "Música" }, - "settingsScanPhotos": { "message": "Fotos" }, - "settingsSelectAll": { "message": "Seleccionar Todo" }, - "settingsStartScan": { "message": "Iniciar Escaneo" }, - "settingsPlexTokens": { "message": "Tokens de Plex" }, - "settingsPlexTokensDesc": { "message": "Edita la lista de tokens de Plex (formato JSON)." }, - "settingsSaveTokens": { "message": "Guardar Tokens" }, - "settingsJellyfinTitle": { "message": "Configuración de Jellyfin" }, - "settingsJellyfinDesc": { "message": "Añade los datos de tu servidor Jellyfin para escanear su contenido." }, - "jellyfinUrlLabel": { "message": "URL del Servidor Jellyfin" }, - "jellyfinUserLabel": { "message": "Nombre de Usuario" }, - "jellyfinPassLabel": { "message": "Contraseña" }, - "jellyfinConnectAndScan": { "message": "Conectar y Escanear" }, - "settingsPhpGenTitle": { "message": "Generador de Script PHP para el Servidor" }, - "settingsPhpFileOptions": { "message": "Opciones del Archivo" }, - "settingsPhpSavePathLabel": { "message": "Ruta de Guardado en el Servidor" }, - "settingsPhpSavePathPlaceholder": { "message": "Ej: /var/www/html/listas (en blanco para la misma carpeta)" }, - "settingsPhpFilenameLabel": { "message": "Nombre del Archivo" }, - "settingsPhpFileAction": { "message": "Acción sobre el Archivo" }, - "settingsPhpActionAppend": { "message": "Añadir al final del archivo (acumulativo)" }, - "settingsPhpActionOverwrite": { "message": "Sobrescribir el archivo (empezar de nuevo)" }, - "settingsPhpSecurity": { "message": "Seguridad (Opcional)" }, - "settingsPhpUseSecretKey": { "message": "Usar clave secreta (Recomendado)" }, - "settingsPhpSecretKeyPlaceholder": { "message": "Introduce una clave secreta segura" }, - "settingsPhpGeneratedCode": { "message": "Código Generado" }, - "settingsPhpGeneratedPlaceholder": { "message": "El código PHP generado aparecerá aquí." }, - "settingsGenerateScript": { "message": "Generar Script" }, - "settingsCopyScript": { "message": "Copiar Script" }, - "settingsDataManagement": { "message": "Gestión de la Base de Datos Local" }, - "settingsImportDb": { "message": "Importar BD desde Archivo" }, - "settingsExportDb": { "message": "Exportar BD a Archivo" }, - "settingsClearContent": { "message": "Borrar Datos de Contenido Local" }, - "settingsClearContentDesc": { "message": "Esta acción borrará películas, series y música de la base de datos local, pero no afectará a tus favoritos ni a tus ajustes." }, - "settingsClose": { "message": "Cerrar" }, - "settingsSave": { "message": "Guardar Ajustes" }, - "musicSidenavTitle": { "message": "Música de Plex" }, - "musicAllServers": { "message": "Todos los Servidores" }, - "musicSearchArtistPlaceholder": { "message": "Buscar artista..." }, - "musicSearchDiscographyPlaceholder": { "message": "Buscar en la discografía..." }, - "musicNothingPlaying": { "message": "Nada en reproducción" }, - "musicSelectSong": { "message": "Selecciona una canción" }, - "musicToStart": { "message": "para empezar a reproducir" }, - "miniplayerDownloadSong": { "message": "Descargar canción" }, - "miniplayerDownloadAlbum": { "message": "Descargar álbum M3U" }, - "miniplayerVolume": { "message": "Volumen" }, - "miniplayerShuffle": { "message": "Aleatorio" }, - "miniplayerEqualizer": { "message": "Ecualizador" }, - "miniplayerOpenList": { "message": "Abrir lista" }, - "eqTitle": { "message": "Ecualizador Gráfico" }, - "eqPresetsLabel": { "message": "Preajustes" }, - "eqPresetFlat": { "message": "Plano" }, - "eqPresetRock": { "message": "Rock" }, - "eqPresetPop": { "message": "Pop" }, - "eqPresetJazz": { "message": "Jazz" }, - "eqPresetClassical": { "message": "Clásica" }, - "eqPresetBassBoost": { "message": "Refuerzo de Graves" }, - "eqPreampLabel": { "message": "Preamplificador" }, - "infoModalTitle": { "message": "Información" }, - "infoModalFieldTitle": { "message": "Título:" }, - "infoModalFieldArtist": { "message": "Artista:" }, - "infoModalFieldAlbum": { "message": "Álbum:" }, - "infoModalFieldSong": { "message": "Canción:" }, - "infoModalFieldYear": { "message": "Año:" }, - "infoModalFieldGenre": { "message": "Género:" }, - "lang_en": { "message": "Inglés" }, - "lang_es": { "message": "Español" }, - "lang_fr": { "message": "Francés" }, - "lang_de": { "message": "Alemán" }, - "lang_it": { "message": "Italiano" }, - "lang_pt": { "message": "Portugués" }, - "essentialFeaturesNotSupported": { "message": "Tu navegador no soporta funciones esenciales." }, - "dbAccessError": { "message": "Error al acceder a la base de datos local." }, - "dbUpdateNeeded": { "message": "La base de datos necesita actualizarse, por favor recarga la página." }, - "dbBlocked": { "message": "Por favor, cierra otras pestañas de esta aplicación para continuar." }, - "deletingContentData": { "message": "Borrando datos de contenido locales..." }, - "noContentDataToDelete": { "message": "No hay datos de contenido que borrar." }, - "contentDataDeleted": { "message": "Datos de contenido borrados de IndexedDB." }, - "errorDeletingData": { "message": "Error al borrar datos: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailable": { "message": "Editor de texto no disponible." }, - "errorLoadingTokens": { "message": "Error al cargar tokens para editar." }, - "errorLoadingTokensMessage": { "message": "Error al cargar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailableToSave": { "message": "Editor no disponible para guardar." }, - "invalidJsonFormat": { "message": "Formato JSON inválido. Debe ser { \"tokens\": [...] }" }, - "tokensSaved": { "message": "Tokens guardados correctamente." }, - "errorSavingTokens": { "message": "Error al guardar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "dbNotAvailable": { "message": "IndexedDB no está disponible." }, - "dbExported": { "message": "Base de datos exportada con éxito." }, - "errorExportingDb": { "message": "Error al exportar la base de datos: $message$", "placeholders": { "message": { "content": "$1" } } }, - "invalidJsonFile": { "message": "El archivo no contiene un objeto JSON válido." }, - "noDataToImport": { "message": "El archivo no contiene datos para las secciones de la BD actual." }, - "dbImported": { "message": "Base de datos importada correctamente." }, - "errorImportingDb": { "message": "Error al importar la base de datos: $message$", "placeholders": { "message": { "content": "$1" } } }, - "updatingView": { "message": "Actualizando la vista con los nuevos datos..." }, - "confirmClearContent": { "message": "¿Estás seguro de que deseas borrar los datos de contenido locales (Películas, Series, Música, etc.)? Los Favoritos y Ajustes NO se borrarán." }, - "trailerNotFound": { "message": "No se encontró tráiler para este título." }, - "confirmClearHistory": { "message": "¿Estás seguro de que deseas borrar todo tu historial de visualización? Esta acción no se puede rehacer." }, - "historyCleared": { "message": "Historial de visualización borrado." }, - "historyItemDeleted": { "message": "Elemento borrado del historial." }, - "errorGeneratingScript": { "message": "Primero genera un script para poder copiarlo." }, - "scriptCopied": { "message": "Script PHP copiado al portapapeles." }, - "errorCopyingScript": { "message": "Error al copiar el script." }, - "scriptGenerated": { "message": "Script PHP generado." }, - "errorLoadingAlbum": { "message": "Error al cargar álbum: $message$", "placeholders": { "message": { "content": "$1" } } }, - "noPhotoServerSelected": { "message": "Error: No se ha seleccionado un servidor de fotos." }, - "loadingGenres": { "message": "Cargando géneros..." }, - "errorLoadingGenres": { "message": "Error al cargar" }, - "noContentFound": { "message": "No se encontraron resultados." }, - "couldNotLoadContent": { "message": "No se pudo cargar el contenido." }, - "noFavorites": { "message": "Aún no tienes favoritos." }, - "errorLoadingFavorites": { "message": "Error al cargar favoritos." }, - "historyEmpty": { "message": "Tu historial está vacío." }, - "historyEmptySub": { "message": "Explora y mira contenido para que aparezca aquí." }, - "errorGeneratingRecommendations": { "message": "Error al generar recomendaciones." }, - "noRecommendations": { "message": "¡Necesitamos conocerte mejor para darte recomendaciones!" }, - "errorGeneratingStats": { "message": "Error al generar estadísticas." }, - "noServersForToken": { "message": "No se encontraron servidores asociados para este token." }, - "searchingActorContent": { "message": "Buscando contenido de $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, - "errorLoadingActorContent": { "message": "No se pudo cargar el contenido para $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, - "errorAddingStream": { "message": "Error al añadir stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, - "phpUrlNotConfigured": { "message": "La URL del servidor PHP no está configurada. Por favor, configúrala en Ajustes." }, - "searchingStreams": { "message": "Buscando streams para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "sendingStreams": { "message": "Enviando $count$ stream(s) al servidor...", "placeholders": { "count": { "content": "$1" } } }, - "streamAddedSuccess": { "message": "Stream(s) añadido(s) con éxito." }, - "generatingM3U": { "message": "Generando M3U para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "m3uDownloaded": { "message": "\"$title$\" descargado.", "placeholders": { "title": { "content": "$1" } } }, - "errorGeneratingM3U": { "message": "Error al generar M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, - "settingsSavedSuccess": { "message": "Ajustes guardados correctamente." }, - "errorSavingSettings": { "message": "Error al guardar los ajustes en la base de datos." }, - "languageChangeReload": { "message": "Idioma cambiado. La aplicación se recargará ahora." }, - "addedToFavorites": { "message": "Añadido a favoritos." }, - "removedFromFavorites": { "message": "Eliminado de favoritos." }, - "plexScanInProgress": { "message": "El escaneo Plex ya está en curso." }, - "plexScanStarting": { "message": "Iniciando escaneo Plex..." }, - "noPlexTokens": { "message": "No hay tokens de Plex configurados." }, - "clearingSections": { "message": "Limpiando secciones: $sections$", "placeholders": { "sections": { "content": "$1" } } }, - "initialScanPhaseComplete": { "message": "Fase de escaneo inicial finalizada." }, - "retryPhaseFinished": { "message": "Fase de reintentos finalizada." }, - "plexScanFinished": { "message": "Escaneo finalizado. Actualizando contenido..." }, - "scanCancelled": { "message": "Escaneo cancelado por el usuario." }, - "scanCancelledInfo": { "message": "Escaneo cancelado." }, - "errorInitializingMusicPlayer": { "message": "Error inicializando el reproductor de música." }, - "criticalErrorLoadingMusic": { "message": "Error crítico al cargar datos de música." }, - "errorLoadingArtists": { "message": "Error al cargar artistas." }, - "dbUnavailableError": { "message": "Error: Base de datos no disponible." }, - "updatingMusicData": { "message": "Actualizando datos de música..." }, - "musicDataUpdated": { "message": "Datos de música actualizados." }, - "errorFetchingArtistSongs": { "message": "Error al obtener las canciones del artista." }, - "errorLoadingSongs": { "message": "Error cargando canciones." }, - "noArtistsFound": { "message": "No se encontraron artistas." }, - "shuffleOn": { "message": "Modo aleatorio activado." }, - "shuffleOff": { "message": "Modo aleatorio desactivado." }, - "playbackError": { "message": "Error de reproducción" }, - "errorLabel": { "message": "Error" }, - "reloadingPage": { "message": "Recargando la página..." }, - "viewed": { "message": "Visto" }, - "local": { "message": "Local" }, - "topRatedSort": {"message": "Mejor Valoradas"}, - "recentSort": {"message": "Recientes"}, - "popularSort": {"message": "Populares"}, - "moviesSectionTitle": {"message": "Películas"}, - "seriesSectionTitle": {"message": "Series"}, - "searchResultsFor": {"message": "Resultados para \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, - "contentFrom": {"message": "Contenido de $actor$", "placeholders": {"actor": {"content": "$1"}}}, - "explore": {"message": "Explorar"}, - "noGenre": {"message": "Sin categoría"}, - "synopsis": {"message": "Sinopsis"}, - "noSynopsis": {"message": "No hay sinopsis disponible."}, - "director": {"message": "Director:"}, - "writer": {"message": "Escritor:"}, - "viewOnImdb": {"message": "Ver en IMDb"}, - "watchTrailer": {"message": "Ver Tráiler"}, - "addToFavorites": {"message": "Añadir a favoritos"}, - "removeFromFavorites": {"message": "Quitar de favoritos"}, - "notAvailable": {"message": "No disponible"}, - "mainCast": {"message": "Reparto Principal"}, - "seasonsAndEpisodes": {"message": "Temporadas y Episodios"}, - "similarContent": {"message": "Contenido Similar"}, - "filmography": {"message": "Filmografía"}, - "availableOn": {"message": "Disponible en"}, - "episodesCount": {"message": "$count$ Episodios", "placeholders": {"count": {"content": "$1"}}}, - "seasonsCount": {"message": "$count$ Temporadas", "placeholders": {"count": {"content": "$1"}}}, - "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, - "noTrailerFound": {"message": "No se encontró tráiler para este título."}, - "fatalInitError": {"message": "Error fatal de inicialización"}, - "fatalInitErrorSub": {"message": "No se pudo cargar la aplicación."}, - "invalidStreamInfo": {"message": "Información inválida."}, - "dbUnavailableForStreams": {"message": "Base de datos local no disponible."}, - "noPlexServersForStreams": {"message": "No hay servidores Plex."}, - "notFoundOnServers": {"message": "No se encontró \"$query$\" en los servidores de Plex.", "placeholders": {"query": {"content": "$1"}}}, - "relativeTime_justNow": { "message": "Ahora mismo" }, - "relativeTime_minutesAgo": { "message": "Hace $count$ minutos", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_hoursAgo": { "message": "Hace $count$ horas", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_yesterday": { "message": "Ayer" }, - "relativeTime_daysAgo": { "message": "Hace $count$ días", "placeholders": { "count": { "content": "$1" } } }, - "errorLoadingDetails": { "message": "Error al Cargar los Detalles" }, - "errorLoadingLocalContent": { "message": "Error al cargar el contenido local." }, - "errorServerResponse": { "message": "Respuesta no exitosa del servidor." }, - "errorPlexApi": { "message": "Error $status$ de la API de Plex.", "placeholders": { "status": { "content": "$1" } } }, - "errorParsingPlexXml": { "message": "Error al analizar el XML de Plex." }, - "untitled": { "message": "Sin título" }, - "itemCount": { "message": "$count$ elementos", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "No hay servidores de fotos" }, - "jellyfinScanInProgress": { "message": "El escaneo Jellyfin ya está en curso." }, - "jellyfinScanning": { "message": "Escaneando Jellyfin..." }, - "jellyfinMissingCredentials": { "message": "Por favor, completa la URL y el usuario de Jellyfin." }, - "jellyfinConnecting": { "message": "Conectando a Jellyfin en: $url$", "placeholders": { "url": { "content": "$1" } } }, - "jellyfinAuthFailed": { "message": "Autenticación Jellyfin fallida: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinAuthSuccess": { "message": "Autenticación Jellyfin exitosa." }, - "jellyfinFetchingLibraries": { "message": "Obteniendo bibliotecas..." }, - "jellyfinFetchFailed": { "message": "Error al obtener bibliotecas: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinNoMediaLibraries": { "message": "No se encontraron bibliotecas de películas o series en Jellyfin." }, - "jellyfinLibrariesFound": { "message": "$count$ biblioteca(s) de medios encontrada(s).", "placeholders": { "count": { "content": "$1" } } }, - "jellyfinLibraryScanSuccess": { "message": "[Éxito] '$libraryName escaneada, $count$ títulos añadidos.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, - "jellyfinLibraryScanFailed": { "message": "Error al escanear la biblioteca '$libraryName.", "placeholders": { "libraryName": { "content": "$1" } } }, - "jellyfinScanSuccess": { "message": "Escaneo Jellyfin completado. Añadidas $movies$ películas y $series$ series.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, - "noJellyfinCredentials": { "message": "Credenciales de Jellyfin no configuradas." }, - "notFoundOnJellyfin": { "message": "No se encontró \"$query$\" en Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, - "notFoundOnAnyServer": { "message": "No se encontró \"$query$\" en ningún servidor.", "placeholders": { "query": { "content": "$1" } } }, - "localOnPlex": { "message": "En Plex" }, - "searchOnPlex": { "message": "Buscar en Plex" }, - "jellyfinTitle": { "message": "Contenido de Jellyfin" }, - "noJellyfinContent": { "message": "No se encontró contenido de Jellyfin." }, - "noJellyfinContentSub": { "message": "Asegúrate de haber escaneado tu servidor Jellyfin en los ajustes." }, - "activityViewerTitle": { "message": "Visor de Actividad del Servidor" }, - "activitySelectServer": { "message": "Selecciona un servidor" }, - "activityCheckBtn": { "message": "Actualizar" }, - "activityNoSessions": { "message": "No hay sesiones activas en este servidor." }, - "activitySessionUser": { "message": "Usuario" }, - "activitySessionDevice": { "message": "Dispositivo" }, - "activitySessionContent": { "message": "Contenido" }, - "activitySessionState": { "message": "Estado" }, - "activitySessionIdentifier": { "message": "Identificador del Cliente" }, - "activityCopyID": { "message": "Copiar ID" }, - "activityError": { "message": "No se pudo obtener la actividad del servidor." }, - "activityCopied": { "message": "¡Identificador copiado al portapapeles!" }, - "activityCopyError": { "message": "Error al copiar el identificador." }, - "noProvidersFound": { "message": "No se encontraron proveedores." }, - "availableOnPlex": { "message": "Disponible en Plex" }, - "m3uGeneratorTitle": { "message": "Generador de Listas M3U" }, - "selectAServer": { "message": "Selecciona un servidor..." }, - "downloadM3u": { "message": "Descargar M3U" }, - "m3uGenerator": { "message": "Generador M3U" }, - "selectLibraries": { "message": "Seleccionar Bibliotecas" }, - "howToUse": { "message": "Cómo Usar" }, - "m3uInstruction1": { "message": "Elige un servidor de la lista." }, - "m3uInstruction2": { "message": "Selecciona una o más bibliotecas para incluir." }, - "m3uInstruction3": { "message": "Haz clic en el botón de descarga." }, - "m3uInstruction4": { "message": "Importa el archivo .m3u en tu reproductor compatible." }, - "chatOpen": { "message": "Abrir Chat" }, - "chatTitle": { "message": "Asistente IA" }, - "chatClose": { "message": "X" }, - "chatPlaceholder": { "message": "Escribe tu mensaje..." }, - "chatSend": { "message": "➤" }, - "chatWelcome": { "message": "¡Bienvenido! Soy tu asistente de CinePlex. Pregúntame sobre películas, series o cualquier otra cosa que quieras saber." }, - "chatGoogleApiKeyMissing": { "message": "La clave de la API de Google Gemini no está configurada. Por favor, configúrala en los ajustes de la extensión para usar el asistente de IA." }, - "chatApiInvalidResponse": { "message": "La API ha devuelto una respuesta no válida. Por favor, inténtalo de nuevo." }, - "chatApiError": { "message": "Error al comunicarse con el asistente de IA" }, - "downloadAll": { "message": "Descargar todo" }, - "download": { "message": "Descargar" }, - "aiToolSearchLibraryDesc": { "message": "Busca en la biblioteca de Plex del usuario películas o series por título." }, - "aiToolSearchLibraryQueryParamDesc": { "message": "El título de la película o serie a buscar." }, - "aiToolSearchLibraryTypeParamDesc": { "message": "El tipo de contenido a buscar. Puede ser 'movie' para películas o 'series' para series. (Opcional)." }, - "aiToolSearchLibraryResolutionParamDesc": { "message": "La resolución del video a buscar (por ejemplo, '4k', '1080p'). (Opcional)." }, - "aiToolSearchLibraryContainerParamDesc": { "message": "El formato contenedor del video a buscar (por ejemplo, 'mkv', 'mp4'). (Opcional)." }, - "aiToolNavigateToPageDesc": { "message": "Navega al usuario a una página específica de la interfaz de la aplicación." }, - "aiToolNavigateToPagePageParamDesc": { "message": "El nombre de la página a la que navegar, por ejemplo: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', o 'm3u-generator'." }, - "aiToolGetUserStatsDesc": { "message": "Obtiene y muestra las estadísticas de la biblioteca del usuario, como el número total de películas, series y artistas únicos." }, - "aiToolShowItemDetailsDesc": { "message": "Muestra la página de detalles de una película o serie específica por su título y tipo." }, - "aiToolShowItemDetailsTitleParamDesc": { "message": "El título exacto de la película o serie." }, - "aiToolShowItemDetailsTypeParamDesc": { "message": "El tipo de contenido. Debe ser 'movie' o 'series'." }, - "aiToolAddToPlaylistDesc": { "message": "Añade una película o serie a la lista de reproducción actual del usuario para transmitirla a un servidor PHP configurado." }, - "aiToolAddToPlaylistTitleParamDesc": { "message": "El título de la película o serie a añadir." }, - "aiToolAddToPlaylistTypeParamDesc": { "message": "El tipo de contenido. Debe ser 'movie' o 'series'." }, - "aiToolCheckAndDownloadDesc": { "message": "Comprueba la disponibilidad de una lista de títulos de películas o series en los servidores locales del usuario y, si se encuentran, genera y descarga un archivo de lista de reproducción M3U con los streams encontrados." }, - "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Una matriz de títulos de películas o series para buscar y descargar." }, - "aiToolCheckAndDownloadTypeParamDesc": { "message": "El tipo de contenido de la lista. Debe ser 'movie' o 'series'." }, - "aiToolCheckAndDownloadFilenameParamDesc": { "message": "El nombre del archivo M3U a descargar (por ejemplo, 'MiLista.m3u'). Si no se proporciona, se usará un nombre por defecto." }, - "aiToolToggleFavoriteDesc": { "message": "Añade o quita una película o serie de la lista de favoritos del usuario." }, - "aiToolToggleFavoriteTitleParamDesc": { "message": "El título de la película o serie." }, - "aiToolToggleFavoriteTypeParamDesc": { "message": "El tipo de contenido. Debe ser 'movie' o 'series'." }, - "aiToolGetRecommendationsDesc": { "message": "Genera y muestra una lista de recomendaciones de películas o series basadas en el historial de visualización y los favoritos del usuario." }, - "aiToolApplyFiltersDesc": { "message": "Aplica filtros a la vista actual de películas o series, permitiendo refinar los resultados por tipo, género, año y orden de clasificación." }, - "aiToolApplyFiltersTypeParamDesc": { "message": "El tipo de contenido al que aplicar los filtros. Debe ser 'movie' o 'series'." }, - "aiToolApplyFiltersGenreParamDesc": { "message": "El nombre del género por el que filtrar (por ejemplo, 'Acción', 'Drama')." }, - "aiToolApplyFiltersYearParamDesc": { "message": "El año de lanzamiento por el que filtrar (por ejemplo, '2023')." }, - "aiToolApplyFiltersSortParamDesc": { "message": "El criterio de ordenación para los resultados. Valores válidos: 'popularity.desc' (populares), 'vote_average.desc' (mejor valoradas), 'release_date.desc' (recientes para películas) o 'first_air_date.desc' (recientes para series)." }, - "aiToolPlayMusicByArtistDesc": { "message": "Abre el reproductor de música y comienza a reproducir canciones de un artista específico de la biblioteca del usuario." }, - "aiToolPlayMusicByArtistNameParamDesc": { "message": "El nombre exacto del artista cuyas canciones se desean reproducir." }, - "aiToolClearChatHistoryDesc": { "message": "Borra todo el historial de mensajes de la conversación actual con el asistente de IA." }, - "aiToolDeleteDatabaseDesc": { "message": "Elimina toda la base de datos local de la extensión, incluyendo el contenido escaneado, los ajustes y los favoritos. Esta acción es irreversible y recargará la aplicación." }, - "aiToolUpdateAllTokensDesc": { "message": "Inicia un escaneo completo de todos los servidores y bibliotecas de Plex asociados con los tokens configurados en la extensión. Actualiza todas las películas, series, artistas y fotos." }, - "aiToolAddPlexTokenDesc": { "message": "Añade un nuevo token X-Plex a la configuración de la extensión, permitiendo que la aplicación escanee contenido de nuevos servidores Plex." }, - "aiToolAddPlexTokenTokenParamDesc": { "message": "La cadena del token X-Plex que se desea añadir." }, - "aiToolChangeRegionDesc": { "message": "Cambia la región utilizada para el descubrimiento de contenido en la API de TMDB. Esto afectará a los resultados mostrados en las secciones de películas y series, así como a los proveedores de streaming." }, - "aiToolChangeRegionRegionParamDesc": { "message": "El código de país ISO 3166-1 de dos letras para la nueva región (por ejemplo, 'US' para Estados Unidos, 'ES' para España, 'MX' para México)." }, - "aiToolClearAllFavoritesDesc": { "message": "Elimina todas las películas y series que el usuario ha marcado como favoritas." }, - "aiToolClearViewingHistoryDesc": { "message": "Borra el historial de visualización del usuario de la página de historial." }, - "aiToolClearRecommendationsViewDesc": { "message": "Limpia la vista de recomendaciones y elimina las recomendaciones almacenadas en caché." }, - "aiToolSearchNotFound": { "message": "No se encontró '$query en tu biblioteca.", "placeholders": { "query": { "content": "$1" } } }, - "aiToolNavigateSuccess": { "message": "Navegado a la página de $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolNavigateError": { "message": "Error al navegar a la página de $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolStatsError": { "message": "Error al obtener estadísticas." }, - "aiToolItemNotFound": { "message": "No se encontró el elemento '$title.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolShowItemDetailsSuccess": { "message": "Mostrando detalles de '$title.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolAddToPlaylistSuccess": { "message": "Añadido '$title a la lista de reproducción.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteAdded": { "message": "Añadido '$title a favoritos.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteRemoved": { "message": "Eliminado '$title de favoritos.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolRecommendationsSuccess": { "message": "Mostrando recomendaciones." }, - "aiToolApplyFiltersGenreNotFound": { "message": "Género '$genre no encontrado.", "placeholders": { "genre": { "content": "$1" } } }, - "aiToolApplyFiltersSuccess": { "message": "Filtros aplicados correctamente." }, - "aiToolPlayMusicNotReady": { "message": "El reproductor de música no está listo. Asegúrate de que tu biblioteca de música de Plex haya sido escaneada." }, - "aiToolPlayMusicArtistNotFound": { "message": "Artista '$artist_name no encontrado.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicNoSongs": { "message": "No se encontraron canciones para '$artist_name.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicSuccess": { "message": "Reproduciendo música de '$artist_name.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolChatHistoryCleared": { "message": "Historial de chat borrado." }, - "aiToolConfirmDeleteDatabase": { "message": "¿Estás seguro de que quieres eliminar la base de datos local? Esta acción es irreversible." }, - "aiToolDeleteDatabaseCancelled": { "message": "Eliminación de la base de datos cancelada." }, - "aiToolExecutionError": { "message": "Error al ejecutar la herramienta '$toolName: $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, - "aiToolUnknown": { "message": "Herramienta desconocida: '$toolName.", "placeholders": { "toolName": { "content": "$1" } } }, - "aiToolFavoritesCleared": { "message": "Favoritos eliminados." }, - "aiToolFavoritesClearError": { "message": "Error al eliminar los favoritos: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolRecommendationsCleared": { "message": "Recomendaciones eliminadas." }, - "aiToolRecommendationsClearError": { "message": "Error al eliminar las recomendaciones: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleted": { "message": "Base de datos eliminada. La página se recargará." }, - "aiToolDatabaseDeleteError": { "message": "Error al eliminar la base de datos: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleteBlocked": { "message": "La eliminación de la base de datos está bloqueada. Cierra otras pestañas de la aplicación." }, - "aiToolUpdateAllTokensSuccess": { "message": "Todos los tokens se han actualizado correctamente." }, - "aiToolUpdateAllTokensError": { "message": "Error al actualizar los tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolAddPlexTokenSuccess": { "message": "Token de Plex añadido correctamente." }, - "aiToolAddPlexTokenError": { "message": "Error al añadir el token de Plex: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolChangeRegionSuccess": { "message": "Región cambiada a $region$. El contenido se está actualizando.", "placeholders": { "region": { "content": "$1" } } }, - "aiToolChangeRegionError": { "message": "Error al cambiar la región: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolViewingHistoryCleared": { "message": "Historial de visualización borrado." }, - "aiToolViewingHistoryClearError": { "message": "Error al borrar el historial de visualización: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiSystemPrompt_v3": { "message": "Eres un asistente experto en cine y series llamado CinePlex. Tu función principal es ayudar a los usuarios a descubrir contenido y a interactuar con su biblioteca. Sigue estas reglas rigurosamente: 1. **NUNCA** inventes que has realizado una acción si no has usado una herramienta para ello. Por ejemplo, no digas 'he descargado X' si no has usado la herramienta de descarga. 2. Para peticiones de recomendaciones o listas (ej. 'dime 5 películas de terror'), usa tu propio conocimiento para generar la lista. Preséntala en formato numerado o con viñetas. Después de mostrar la lista, pregunta proactivamente al usuario si quiere que compruebes la disponibilidad en sus servidores locales y crees un archivo M3U. 3. **SOLO** si el usuario confirma que quiere comprobar o descargar la lista, utiliza la herramienta `check_and_download_titles_list`. No la uses sin confirmación explícita. 4. Para cualquier otra acción como navegar, obtener estadísticas o buscar un título específico, o filtrar por resolución o contenedor, usa las herramientas apropiadas. Sé siempre conciso, amigable y eficiente." }, - "aiToolM3UNoTitlesProvided": { "message": "Por favor, proporciona una lista de títulos para crear la lista de reproducción." }, - "aiToolM3UCheckingTitles": { "message": "Comprobando los títulos en tus servidores locales..." }, - "aiToolM3UNoLocalMatchesForDownload": { "message": "No he encontrado ninguna de las películas o series de la lista en tus servidores locales." }, - "aiToolM3UDownloadStarted": { "message": "¡Hecho! He encontrado $1 de los $2 títulos en tus servidores y he iniciado la descarga de la lista de reproducción M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, - "backToProviders": { "message": "Volver a Proveedores" }, - "artistsCounterSingle": { "message": "$total$ Artista", "placeholders": { "total": { "content": "$1" } } }, - "artistsCounterLoading": { "message": "Cargando..." }, - "downloadingSong": { "message": "Iniciando descarga de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "songDownloaded": { "message": "\"$title$\" descargado.", "placeholders": { "title": { "content": "$1" } } }, - "errorDownloadingSong": { "message": "Error al descargar \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "generatingAlbumM3U": { "message": "Generando M3U para \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, - "albumM3UGenerated": { "message": "M3U para el álbum \"$artist$\" generado.", "placeholders": { "artist": { "content": "$1" } } }, - "retyingSection": { "message": "Reintentando sección \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "retrySuccess": { "message": "[ÉXITO] Reintento de \"$title$\" completado.", "placeholders": { "title": { "content": "$1" } } }, - "retryError": { "message": "[ERROR FINAL] Falló el reintento para \"$title$\": $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, - "startingRetryPhase": { "message": "Iniciando fase de reintentos para $count$ secciones...", "placeholders": { "count": { "content": "$1" } } }, - "tokenFoundServers": { "message": "Token $token$... encontró $count$ servidores.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, - "errorProcessingToken": { "message": "Error procesando token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, - "plexScanFatalError": { "message": "ERROR FATAL: $message$", "placeholders": { "message": { "content": "$1" } } }, - "errorDuringScan": { "message": "Error durante el escaneo: $message$", "placeholders": { "message": { "content": "$1" } } }, - "stoppingPlexScan": { "message": "Deteniendo escaneo Plex..." }, - "invalidTokenProvided": { "message": "Token inválido proporcionado." }, - "tokenAlreadyExists": { "message": "El token ya existe." }, - "tokenAddedSuccessfully": { "message": "Token añadido correctamente." }, - "noStreamsFoundForSelection": { "message": "No se encontraron streams para la selección." }, - "autoplayBlocked": { "message": "Reproducción automática bloqueada." }, - "welcomeToCinePlex": { "message": "" }, - "page": { "message": "Página" }, - "all": { "message": "Todo" }, - "userScore": { "message": "Puntuación" }, - "duration": { "message": "Duración" }, - "min": { "message": "Mín" }, - "max": { "message": "Máx" } + "appName": { "message": "CinePlex" }, + "appDescription": { "message": "Escanea servidores de Plex para encontrar contenido y lo muestra en la interfaz" }, + "appTagline": { "message": "Películas, Series y Música" }, + "appLocaleCode": { "message": "es-ES" }, + "toggleNavigation": { "message": "Alternar Navegación" }, + "searchPlaceholder": { "message": "Buscar películas o series..." }, + "openMusicPlayer": { "message": "Abrir Reproductor de Música" }, + "settings": { "message": "Ajustes" }, + "navMovies": { "message": "Películas" }, + "navSeries": { "message": "Series" }, + "navProviders": { "message": "Proveedores" }, + "navPhotos": { "message": "Fotos" }, + "navStats": { "message": "Estadísticas" }, + "navFavorites": { "message": "Favoritos" }, + "navHistory": { "message": "Historial" }, + "navRecommendations": { "message": "Recomendaciones" }, + "navMusic": { "message": "Música" }, + "musicFeaturedPlaylists": { "message": "Playlists Destacadas" }, + "musicRecentlyAdded": { "message": "Añadido Recientemente" }, + "navM3uGenerator": { "message": "Generador M3U" }, + "heroWelcome": { "message": "" }, + "heroSubtitle": { "message": "Explora miles de películas y series." }, + "addStream": { "message": "Añadir Stream" }, + "moreInfo": { "message": "Más información" }, + "popularMovies": { "message": "Películas Populares" }, + "allGenres": { "message": "Todos los géneros" }, + "allYears": { "message": "Todos los años" }, + "sortPopular": { "message": "Más populares" }, + "sortTopRated": { "message": "Mejor valoradas" }, + "sortRecent": { "message": "Más recientes" }, + "loadMore": { "message": "Cargar más" }, + "photosBreadcrumbHome": { "message": "Álbumes" }, + "selectServer": { "message": "Selecciona un servidor" }, + "loading": { "message": "Cargando..." }, + "loadingLibraries": { "message": "Cargando bibliotecas..." }, + "photosEmptyState": { "message": "No se encontraron álbumes ni fotos." }, + "photosEmptyStateSub": { "message": "Por favor, selecciona un servidor o asegúrate de tener una biblioteca de fotos en Plex." }, + "statsTitle": { "message": "Estadísticas de la Biblioteca" }, + "statsAllTokens": { "message": "Todos los Tokens" }, + "statsAnalyzing": { "message": "Analizando tu biblioteca..." }, + "statsActiveTokens": { "message": "Tokens Activos" }, + "statsServersFound": { "message": "Servidores Encontrados" }, + "statsUniqueMovies": { "message": "Películas Únicas" }, + "statsUniqueSeries": { "message": "Series Únicas" }, + "statsUniqueArtists": { "message": "Artistas Únicos" }, + "statsTokenServers": { "message": "Servidores del Token" }, + "statsChartMoviesByGenre": { "message": "Contenido por Género (Películas)" }, + "statsChartSeriesByGenre": { "message": "Contenido por Género (Series)" }, + "statsChartByDecade": { "message": "Contenido por Década" }, + "recommendationsTitle": { "message": "Recomendaciones para ti" }, + "historyTitle": { "message": "Historial de Visualización" }, + "clearHistory": { "message": "Borrar Todo" }, + "consoleTitle": { "message": "Consola de Escaneo Plex" }, + "footerCredit": { "message": "Una interfaz para tu universo Plex." }, + "closeTrailer": { "message": "Cerrar tráiler" }, + "close": { "message": "Cerrar" }, + "photoViewer": { "message": "Visor de fotos" }, + "previous": { "message": "Anterior" }, + "next": { "message": "Siguiente" }, + "notificationTemplateText": { "message": "Notificación" }, + "settingsTitleFull": { "message": "Ajustes y Configuración" }, + "settingsTabGeneral": { "message": "General" }, + "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, + "settingsTabPhpGen": { "message": "Generador PHP" }, + "settingsTabData": { "message": "Datos" }, + "settingsApiServer": { "message": "Configuración de API y Servidor" }, + "settingsTmdbApiLabel": { "message": "Clave de API de TMDB (Opcional)" }, + "settingsTmdbApiPlaceholder": { "message": "Se usará la clave por defecto si se deja en blanco" }, + "settingsGoogleApiLabel": { "message": "Clave de API de Google Gemini (Opcional)" }, + "settingsGoogleApiPlaceholder": { "message": "Necesaria para usar el asistente de IA" }, + "settingsRegionLabel": { "message": "Región para descubrimiento de contenido" }, + "allRegions": { "message": "Todas las regiones" }, + "settingsPhpUrlLabel": { "message": "URL del Servidor para Añadir Streams" }, + "settingsPhpUrlPlaceholder": { "message": "https://tu-servidor.com/ruta/al/script.php" }, + "settingsInterface": { "message": "Interfaz" }, + "settingsLightTheme": { "message": "Modo Claro" }, + "settingsShowHero": { "message": "Mostrar sección de bienvenida 'Hero'" }, + "settingsScanContent": { "message": "Escaneo de Contenido" }, + "settingsScanDesc": { "message": "Selecciona qué escanear y pulsa el botón." }, + "settingsScanMovies": { "message": "Películas" }, + "settingsScanShows": { "message": "Series" }, + "settingsScanArtists": { "message": "Música" }, + "settingsScanPhotos": { "message": "Fotos" }, + "settingsSelectAll": { "message": "Seleccionar Todo" }, + "settingsStartScan": { "message": "Iniciar Escaneo" }, + "settingsPlexTokens": { "message": "Tokens de Plex" }, + "settingsPlexTokensDesc": { "message": "Edita la lista de tokens de Plex (formato JSON)." }, + "settingsSaveTokens": { "message": "Guardar Tokens" }, + "settingsJellyfinTitle": { "message": "Configuración de Jellyfin" }, + "settingsJellyfinDesc": { "message": "Añade los datos de tu servidor Jellyfin para escanear su contenido." }, + "jellyfinUrlLabel": { "message": "URL del Servidor Jellyfin" }, + "jellyfinUserLabel": { "message": "Nombre de Usuario" }, + "jellyfinPassLabel": { "message": "Contraseña" }, + "jellyfinConnectAndScan": { "message": "Conectar y Escanear" }, + "settingsPhpGenTitle": { "message": "Generador de Script PHP para el Servidor" }, + "settingsPhpFileOptions": { "message": "Opciones del Archivo" }, + "settingsPhpSavePathLabel": { "message": "Ruta de Guardado en el Servidor" }, + "settingsPhpSavePathPlaceholder": { "message": "Ej: /var/www/html/listas (en blanco para la misma carpeta)" }, + "settingsPhpFilenameLabel": { "message": "Nombre del Archivo" }, + "settingsPhpFileAction": { "message": "Acción sobre el Archivo" }, + "settingsPhpActionAppend": { "message": "Añadir al final del archivo (acumulativo)" }, + "settingsPhpActionOverwrite": { "message": "Sobrescribir el archivo (empezar de nuevo)" }, + "settingsPhpSecurity": { "message": "Seguridad (Opcional)" }, + "settingsPhpUseSecretKey": { "message": "Usar clave secreta (Recomendado)" }, + "settingsPhpSecretKeyPlaceholder": { "message": "Introduce una clave secreta segura" }, + "settingsPhpGeneratedCode": { "message": "Código Generado" }, + "settingsPhpGeneratedPlaceholder": { "message": "El código PHP generado aparecerá aquí." }, + "settingsGenerateScript": { "message": "Generar Script" }, + "settingsCopyScript": { "message": "Copiar Script" }, + "settingsDataManagement": { "message": "Gestión de la Base de Datos Local" }, + "settingsImportDb": { "message": "Importar BD desde Archivo" }, + "settingsExportDb": { "message": "Exportar BD a Archivo" }, + "settingsClearContent": { "message": "Borrar Datos de Contenido Local" }, + "settingsClearContentDesc": { "message": "Esta acción borrará películas, series y música de la base de datos local, pero no afectará a tus favoritos ni a tus ajustes." }, + "settingsClose": { "message": "Cerrar" }, + "settingsSave": { "message": "Guardar Ajustes" }, + "musicSidenavTitle": { "message": "Música de Plex" }, + "musicAllServers": { "message": "Todos los Servidores" }, + "musicSearchArtistPlaceholder": { "message": "Buscar artista..." }, + "musicSearchDiscographyPlaceholder": { "message": "Buscar en la discografía..." }, + "musicNothingPlaying": { "message": "Nada en reproducción" }, + "musicSelectSong": { "message": "Selecciona una canción" }, + "musicToStart": { "message": "para empezar a reproducir" }, + "miniplayerDownloadSong": { "message": "Descargar canción" }, + "miniplayerDownloadAlbum": { "message": "Descargar M3U" }, + "miniplayerVolume": { "message": "Volumen" }, + "miniplayerShuffle": { "message": "Aleatorio" }, + "miniplayerEqualizer": { "message": "Ecualizador" }, + "miniplayerOpenList": { "message": "Abrir lista" }, + "eqTitle": { "message": "Ecualizador Gráfico" }, + "eqPresetsLabel": { "message": "Preajustes" }, + "eqPresetFlat": { "message": "Plano" }, + "eqPresetRock": { "message": "Rock" }, + "eqPresetPop": { "message": "Pop" }, + "eqPresetJazz": { "message": "Jazz" }, + "eqPresetClassical": { "message": "Clásica" }, + "eqPresetBassBoost": { "message": "Refuerzo de Graves" }, + "eqPreampLabel": { "message": "Preamplificador" }, + "infoModalTitle": { "message": "Información" }, + "infoModalFieldTitle": { "message": "Título:" }, + "infoModalFieldArtist": { "message": "Artista:" }, + "infoModalFieldAlbum": { "message": "Álbum:" }, + "infoModalFieldSong": { "message": "Canción:" }, + "infoModalFieldYear": { "message": "Año:" }, + "infoModalFieldGenre": { "message": "Género:" }, + "lang_en": { "message": "Inglés" }, + "lang_es": { "message": "Español" }, + "lang_fr": { "message": "Francés" }, + "lang_de": { "message": "Alemán" }, + "lang_it": { "message": "Italiano" }, + "lang_pt": { "message": "Portugués" }, + "essentialFeaturesNotSupported": { "message": "Tu navegador no soporta funciones esenciales." }, + "dbAccessError": { "message": "Error al acceder a la base de datos local." }, + "dbUpdateNeeded": { "message": "La base de datos necesita actualizarse, por favor recarga la página." }, + "dbBlocked": { "message": "Por favor, cierra otras pestañas de esta aplicación para continuar." }, + "deletingContentData": { "message": "Borrando datos de contenido locales..." }, + "noContentDataToDelete": { "message": "No hay datos de contenido que borrar." }, + "contentDataDeleted": { "message": "Datos de contenido borrados de IndexedDB." }, + "errorDeletingData": { "message": "Error al borrar datos: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailable": { "message": "Editor de texto no disponible." }, + "errorLoadingTokens": { "message": "Error al cargar tokens para editar." }, + "errorLoadingTokensMessage": { "message": "Error al cargar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailableToSave": { "message": "Editor no disponible para guardar." }, + "invalidJsonFormat": { "message": "Formato JSON inválido. Debe ser { \"tokens\": [...] }" }, + "tokensSaved": { "message": "Tokens guardados correctamente." }, + "errorSavingTokens": { "message": "Error al guardar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "dbNotAvailable": { "message": "IndexedDB no está disponible." }, + "dbExported": { "message": "Base de datos exportada con éxito." }, + "errorExportingDb": { "message": "Error al exportar la base de datos: $message$", "placeholders": { "message": { "content": "$1" } } }, + "invalidJsonFile": { "message": "El archivo no contiene un objeto JSON válido." }, + "noDataToImport": { "message": "El archivo no contiene datos para las secciones de la BD actual." }, + "dbImported": { "message": "Base de datos importada correctamente." }, + "errorImportingDb": { "message": "Error al importar la base de datos: $message$", "placeholders": { "message": { "content": "$1" } } }, + "updatingView": { "message": "Actualizando la vista con los nuevos datos..." }, + "confirmClearContent": { "message": "¿Estás seguro de que deseas borrar los datos de contenido locales (Películas, Series, Música, etc.)? Los Favoritos y Ajustes NO se borrarán." }, + "trailerNotFound": { "message": "No se encontró tráiler para este título." }, + "confirmClearHistory": { "message": "¿Estás seguro de que deseas borrar todo tu historial de visualización? Esta acción no se puede rehacer." }, + "historyCleared": { "message": "Historial de visualización borrado." }, + "historyItemDeleted": { "message": "Elemento borrado del historial." }, + "errorGeneratingScript": { "message": "Primero genera un script para poder copiarlo." }, + "scriptCopied": { "message": "Script PHP copiado al portapapeles." }, + "errorCopyingScript": { "message": "Error al copiar el script." }, + "scriptGenerated": { "message": "Script PHP generado." }, + "errorLoadingAlbum": { "message": "Error al cargar álbum: $message$", "placeholders": { "message": { "content": "$1" } } }, + "noPhotoServerSelected": { "message": "Error: No se ha seleccionado un servidor de fotos." }, + "loadingGenres": { "message": "Cargando géneros..." }, + "errorLoadingGenres": { "message": "Error al cargar" }, + "noContentFound": { "message": "No se encontraron resultados." }, + "couldNotLoadContent": { "message": "No se pudo cargar el contenido." }, + "noFavorites": { "message": "Aún no tienes favoritos." }, + "errorLoadingFavorites": { "message": "Error al cargar favoritos." }, + "historyEmpty": { "message": "Tu historial está vacío." }, + "historyEmptySub": { "message": "Explora y mira contenido para que aparezca aquí." }, + "errorGeneratingRecommendations": { "message": "Error al generar recomendaciones." }, + "noRecommendations": { "message": "¡Necesitamos conocerte mejor para darte recomendaciones!" }, + "errorGeneratingStats": { "message": "Error al generar estadísticas." }, + "noServersForToken": { "message": "No se encontraron servidores asociados para este token." }, + "searchingActorContent": { "message": "Buscando contenido de $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, + "errorLoadingActorContent": { "message": "No se pudo cargar el contenido para $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, + "errorAddingStream": { "message": "Error al añadir stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, + "phpUrlNotConfigured": { "message": "La URL del servidor PHP no está configurada. Por favor, configúrala en Ajustes." }, + "searchingStreams": { "message": "Buscando streams para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "sendingStreams": { "message": "Enviando $count$ stream(s) al servidor...", "placeholders": { "count": { "content": "$1" } } }, + "streamAddedSuccess": { "message": "Stream(s) añadido(s) con éxito." }, + "generatingM3U": { "message": "Generando M3U para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "m3uDownloaded": { "message": "\"$title$\" descargado.", "placeholders": { "title": { "content": "$1" } } }, + "errorGeneratingM3U": { "message": "Error al generar M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, + "settingsSavedSuccess": { "message": "Ajustes guardados correctamente." }, + "errorSavingSettings": { "message": "Error al guardar los ajustes en la base de datos." }, + "languageChangeReload": { "message": "Idioma cambiado. La aplicación se recargará ahora." }, + "addedToFavorites": { "message": "Añadido a favoritos." }, + "removedFromFavorites": { "message": "Eliminado de favoritos." }, + "plexScanInProgress": { "message": "El escaneo Plex ya está en curso." }, + "plexScanStarting": { "message": "Iniciando escaneo Plex..." }, + "noPlexTokens": { "message": "No hay tokens de Plex configurados." }, + "clearingSections": { "message": "Limpiando secciones: $sections$", "placeholders": { "sections": { "content": "$1" } } }, + "initialScanPhaseComplete": { "message": "Fase de escaneo inicial finalizada." }, + "retryPhaseFinished": { "message": "Fase de reintentos finalizada." }, + "plexScanFinished": { "message": "Escaneo finalizado. Actualizando contenido..." }, + "scanCancelled": { "message": "Escaneo cancelado por el usuario." }, + "scanCancelledInfo": { "message": "Escaneo cancelado." }, + "errorInitializingMusicPlayer": { "message": "Error inicializando el reproductor de música." }, + "criticalErrorLoadingMusic": { "message": "Error crítico al cargar datos de música." }, + "errorLoadingArtists": { "message": "Error al cargar artistas." }, + "dbUnavailableError": { "message": "Error: Base de datos no disponible." }, + "updatingMusicData": { "message": "Actualizando datos de música..." }, + "musicDataUpdated": { "message": "Datos de música actualizados." }, + "errorFetchingArtistSongs": { "message": "Error al obtener las canciones del artista." }, + "errorLoadingSongs": { "message": "Error cargando canciones." }, + "noArtistsFound": { "message": "No se encontraron artistas." }, + "shuffleOn": { "message": "Modo aleatorio activado." }, + "shuffleOff": { "message": "Modo aleatorio desactivado." }, + "playbackError": { "message": "Error de reproducción" }, + "errorLabel": { "message": "Error" }, + "reloadingPage": { "message": "Recargando la página..." }, + "viewed": { "message": "Visto" }, + "local": { "message": "Local" }, + "topRatedSort": {"message": "Mejor Valoradas"}, + "recentSort": {"message": "Recientes"}, + "popularSort": {"message": "Populares"}, + "moviesSectionTitle": {"message": "Películas"}, + "seriesSectionTitle": {"message": "Series"}, + "searchResultsFor": {"message": "Resultados para \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, + "contentFrom": {"message": "Contenido de $actor$", "placeholders": {"actor": {"content": "$1"}}}, + "explore": {"message": "Explorar"}, + "noGenre": {"message": "Sin categoría"}, + "synopsis": {"message": "Sinopsis"}, + "noSynopsis": {"message": "No hay sinopsis disponible."}, + "director": {"message": "Director:"}, + "writer": {"message": "Escritor:"}, + "viewOnImdb": {"message": "Ver en IMDb"}, + "watchTrailer": {"message": "Tráiler"}, + "addToFavorites": {"message": "Añadir favoritos"}, + "removeFromFavorites": {"message": "Quitar de favoritos"}, + "notAvailable": {"message": "No disponible"}, + "mainCast": {"message": "Reparto Principal"}, + "seasonsAndEpisodes": {"message": "Temporadas y Episodios"}, + "similarContent": {"message": "Contenido Similar"}, + "filmography": {"message": "Filmografía"}, + "availableOn": {"message": "Disponible en"}, + "episodesCount": {"message": "$count$ Episodios", "placeholders": {"count": {"content": "$1"}}}, + "seasonsCount": {"message": "$count$ Temporadas", "placeholders": {"count": {"content": "$1"}}}, + "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, + "noTrailerFound": {"message": "No se encontró tráiler para este título."}, + "fatalInitError": {"message": "Error fatal de inicialización"}, + "fatalInitErrorSub": {"message": "No se pudo cargar la aplicación."}, + "invalidStreamInfo": {"message": "Información inválida."}, + "dbUnavailableForStreams": {"message": "Base de datos local no disponible."}, + "noPlexServersForStreams": {"message": "No hay servidores Plex."}, + "notFoundOnServers": {"message": "No se encontró \"$query$\" en los servidores de Plex.", "placeholders": {"query": {"content": "$1"}}}, + "relativeTime_justNow": { "message": "Ahora mismo" }, + "relativeTime_minutesAgo": { "message": "Hace $count$ minutos", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_hoursAgo": { "message": "Hace $count$ horas", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_yesterday": { "message": "Ayer" }, + "relativeTime_daysAgo": { "message": "Hace $count$ días", "placeholders": { "count": { "content": "$1" } } }, + "errorLoadingDetails": { "message": "Error al Cargar los Detalles" }, + "errorLoadingLocalContent": { "message": "Error al cargar el contenido local." }, + "errorServerResponse": { "message": "Respuesta no exitosa del servidor." }, + "errorPlexApi": { "message": "Error $status$ de la API de Plex.", "placeholders": { "status": { "content": "$1" } } }, + "errorParsingPlexXml": { "message": "Error al analizar el XML de Plex." }, + "untitled": { "message": "Sin título" }, + "itemCount": { "message": "$count$ elementos", "placeholders": { "count": { "content": "$1" } } }, + "noPhotoServers": { "message": "No hay servidores de fotos" }, + "jellyfinScanInProgress": { "message": "El escaneo Jellyfin ya está en curso." }, + "jellyfinScanning": { "message": "Escaneando Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Por favor, completa la URL y el usuario de Jellyfin." }, + "jellyfinConnecting": { "message": "Conectando a Jellyfin en: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Autenticación Jellyfin fallida: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Autenticación Jellyfin exitosa." }, + "jellyfinFetchingLibraries": { "message": "Obteniendo bibliotecas..." }, + "jellyfinFetchFailed": { "message": "Error al obtener bibliotecas: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "No se encontraron bibliotecas de películas o series en Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ biblioteca(s) de medios encontrada(s).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Éxito] '$libraryName escaneada, $count$ títulos añadidos.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Error al escanear la biblioteca '$libraryName.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Escaneo Jellyfin completado. Añadidas $movies$ películas y $series$ series.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Credenciales de Jellyfin no configuradas." }, + "notFoundOnJellyfin": { "message": "No se encontró \"$query$\" en Jellyfin.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnAnyServer": { "message": "No se encontró \"$query$\" en ningún servidor.", "placeholders": {"query": {"content": "$1"}}}, + "localOnPlex": { "message": "En Plex" }, + "searchOnPlex": { "message": "Buscar en Plex" }, + "jellyfinTitle": { "message": "Contenido de Jellyfin" }, + "noJellyfinContent": { "message": "No se encontró contenido de Jellyfin." }, + "noJellyfinContentSub": { "message": "Asegúrate de haber escaneado tu servidor Jellyfin en los ajustes." }, + "activityViewerTitle": { "message": "Visor de Actividad del Servidor" }, + "activitySelectServer": { "message": "Selecciona un servidor" }, + "activityCheckBtn": { "message": "Actualizar" }, + "activityNoSessions": { "message": "No hay sesiones activas en este servidor." }, + "activitySessionUser": { "message": "Usuario" }, + "activitySessionDevice": { "message": "Dispositivo" }, + "activitySessionContent": { "message": "Contenido" }, + "activitySessionState": { "message": "Estado" }, + "activitySessionIdentifier": { "message": "Identificador del Cliente" }, + "activityCopyID": { "message": "Copiar ID" }, + "activityError": { "message": "No se pudo obtener la actividad del servidor." }, + "activityCopied": { "message": "¡Identificador copiado al portapapeles!" }, + "activityCopyError": { "message": "Error al copiar el identificador." }, + "noProvidersFound": { "message": "No se encontraron proveedores." }, + "availableOnPlex": { "message": "Disponible en Plex" }, + "m3uGeneratorTitle": { "message": "Generador de Listas M3U" }, + "selectAServer": { "message": "Selecciona un servidor..." }, + "downloadM3u": { "message": "Descargar M3U" }, + "m3uGenerator": { "message": "Generador M3U" }, + "selectLibraries": { "message": "Seleccionar Bibliotecas" }, + "howToUse": { "message": "Cómo Usar" }, + "m3uInstruction1": { "message": "Elige un servidor de la lista." }, + "m3uInstruction2": { "message": "Selecciona una o más bibliotecas para incluir." }, + "m3uInstruction3": { "message": "Haz clic en el botón de descarga." }, + "m3uInstruction4": { "message": "Importa el archivo .m3u en tu reproductor compatible." }, + "chatOpen": { "message": "Abrir Chat" }, + "chatTitle": { "message": "Asistente IA" }, + "chatClose": { "message": "X" }, + "chatPlaceholder": { "message": "Escribe tu mensaje..." }, + "chatSend": { "message": "➤" }, + "chatWelcome": { "message": "¡Bienvenido! Soy tu asistente de CinePlex. Pregúntame sobre películas, series o cualquier otra cosa que quieras saber." }, + "chatGoogleApiKeyMissing": { "message": "La clave de la API de Google Gemini no está configurada. Por favor, configúrala en los ajustes de la extensión para usar el asistente de IA." }, + "chatApiInvalidResponse": { "message": "La API ha devuelto una respuesta no válida. Por favor, inténtalo de nuevo." }, + "chatApiError": { "message": "Error al comunicarse con el asistente de IA" }, + "downloadAll": { "message": "Descargar todo" }, + "download": { "message": "Descargar" }, + "aiToolSearchLibraryDesc": { "message": "Busca en la biblioteca de Plex del usuario películas o series por título." }, + "aiToolSearchLibraryQueryParamDesc": { "message": "El título de la película o serie a buscar." }, + "aiToolSearchLibraryTypeParamDesc": { "message": "El tipo de contenido a buscar. Puede ser 'movie' para películas o 'series' para series. (Opcional)." }, + "aiToolSearchLibraryResolutionParamDesc": { "message": "La resolución del video a buscar (por ejemplo, '4k', '1080p'). (Opcional)." }, + "aiToolSearchLibraryContainerParamDesc": { "message": "El formato contenedor del video a buscar (por ejemplo, 'mkv', 'mp4'). (Opcional)." }, + "aiToolNavigateToPageDesc": { "message": "Navega al usuario a una página específica de la interfaz de la aplicación." }, + "aiToolNavigateToPagePageParamDesc": { "message": "El nombre de la página a la que navegar, por ejemplo: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator' o 'music'." }, + "aiToolGetUserStatsDesc": { "message": "Obtiene y muestra las estadísticas de la biblioteca del usuario, como el número total de películas, series y artistas únicos." }, + "aiToolShowItemDetailsDesc": { "message": "Muestra la página de detalles de una película o serie específica por su título y tipo." }, + "aiToolShowItemDetailsTitleParamDesc": { "message": "El título exacto de la película o serie." }, + "aiToolShowItemDetailsTypeParamDesc": { "message": "El tipo de contenido. Debe ser 'movie' o 'series'." }, + "aiToolAddToPlaylistDesc": { "message": "Añade una película o serie a la lista de reproducción actual del usuario para transmitirla a un servidor PHP configurado." }, + "aiToolAddToPlaylistTitleParamDesc": { "message": "El título de la película o serie a añadir." }, + "aiToolAddToPlaylistTypeParamDesc": { "message": "El tipo de contenido. Debe ser 'movie' o 'series'." }, + "aiToolDownloadSingleMovieM3UDesc": { "message": "Genera y descarga un archivo de lista de reproducción M3U para una única película disponible localmente." }, + "aiToolDownloadSingleMovieM3UTitleParamDesc": { "message": "El título de la película para la que se generará el M3U." }, + "aiToolDownloadSingleMovieM3UYearParamDesc": { "message": "El año de lanzamiento de la película (opcional, para mayor precisión)." }, + "aiToolDownloadSeriesSeasonM3UDesc": { "message": "Genera y descarga un archivo de lista de reproducción M3U para una temporada específica de una serie disponible localmente." }, + "aiToolDownloadSeriesSeasonM3UTitleParamDesc": { "message": "El título de la serie." }, + "aiToolDownloadSeriesSeasonM3USeasonParamDesc": { "message": "El número de la temporada a descargar." }, + "aiToolDownloadSeriesSeasonM3UYearParamDesc": { "message": "El año de lanzamiento de la serie (opcional)." }, + "aiToolCheckAndDownloadDesc": { "message": "Comprueba la disponibilidad de una lista de títulos de películas o series en los servidores locales del usuario y, si se encuentran, genera y descarga un archivo de lista de reproducción M3U con los streams encontrados." }, + "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Una matriz de títulos de películas o series para buscar y descargar." }, + "aiToolCheckAndDownloadTypeParamDesc": { "message": "El tipo de contenido de la lista. Debe ser 'movie' o 'series'." }, + "aiToolCheckAndDownloadFilenameParamDesc": { "message": "El nombre del archivo M3U a descargar (por ejemplo, 'MiLista.m3u'). Si no se proporciona, se usará un nombre por defecto." }, + "aiToolToggleFavoriteDesc": { "message": "Añade o quita una película o serie de la lista de favoritos del usuario." }, + "aiToolToggleFavoriteTitleParamDesc": { "message": "El título de la película o serie." }, + "aiToolToggleFavoriteTypeParamDesc": { "message": "El tipo de contenido. Debe ser 'movie' o 'series'." }, + "aiToolGetRecommendationsDesc": { "message": "Genera y muestra una lista de recomendaciones de películas o series basadas en el historial de visualización y los favoritos del usuario." }, + "aiToolApplyFiltersDesc": { "message": "Aplica filtros a la vista actual de películas o series, permitiendo refinar los resultados por tipo, género, año y orden de clasificación." }, + "aiToolApplyFiltersTypeParamDesc": { "message": "El tipo de contenido al que aplicar los filtros. Debe ser 'movie' o 'series'." }, + "aiToolApplyFiltersGenreParamDesc": { "message": "El nombre del género por el que filtrar (por ejemplo, 'Acción', 'Drama')." }, + "aiToolApplyFiltersYearParamDesc": { "message": "El año de lanzamiento por el que filtrar (por ejemplo, '2023')." }, + "aiToolApplyFiltersSortParamDesc": { "message": "El criterio de ordenación para los resultados. Valores válidos: 'popularity.desc' (populares), 'vote_average.desc' (mejor valoradas), 'release_date.desc' (recientes para películas) o 'first_air_date.desc' (recientes para series)." }, + "aiToolListAvailableMusicGenresDesc": { "message": "Lista todos los géneros musicales únicos disponibles en la biblioteca local del usuario." }, + "aiToolSearchMusicByGenreDesc": { "message": "Busca artistas en la biblioteca musical del usuario que pertenezcan a un género específico." }, + "aiToolSearchMusicByGenreNameParamDesc": { "message": "El nombre del género musical a buscar (ej. 'Rock', 'Pop', 'Jazz')." }, + "aiToolPlayMusicByArtistDesc": { "message": "Abre el reproductor de música y comienza a reproducir canciones de un artista específico de la biblioteca del usuario." }, + "aiToolPlayMusicByArtistNameParamDesc": { "message": "El nombre exacto del artista cuyas canciones se desean reproducir." }, + "aiToolClearChatHistoryDesc": { "message": "Borra todo el historial de mensajes de la conversación actual con el asistente de IA." }, + "aiToolDeleteDatabaseDesc": { "message": "Elimina toda la base de datos local de la extensión, incluyendo el contenido escaneado, los ajustes y los favoritos. Esta acción es irreversible y recargará la aplicación." }, + "aiToolUpdateAllTokensDesc": { "message": "Inicia un escaneo completo de todos los servidores y bibliotecas de Plex asociados con los tokens configurados en la extensión. Actualiza todas las películas, series, artistas y fotos." }, + "aiToolAddPlexTokenDesc": { "message": "Añade un nuevo token X-Plex a la configuración de la extensión, permitiendo que la aplicación escanee contenido de nuevos servidores Plex." }, + "aiToolAddPlexTokenTokenParamDesc": { "message": "La cadena del token X-Plex que se desea añadir." }, + "aiToolChangeRegionDesc": { "message": "Cambia la región utilizada para el descubrimiento de contenido en la API de TMDB. Esto afectará a los resultados mostrados en las secciones de películas y series, así como a los proveedores de streaming." }, + "aiToolChangeRegionRegionParamDesc": { "message": "El código de país ISO 3166-1 de dos letras para la nueva región (por ejemplo, 'US' para Estados Unidos, 'ES' para España, 'MX' para México)." }, + "aiToolClearAllFavoritesDesc": { "message": "Elimina todas las películas y series que el usuario ha marcado como favoritas." }, + "aiToolClearViewingHistoryDesc": { "message": "Borra el historial de visualización del usuario de la página de historial." }, + "aiToolClearRecommendationsViewDesc": { "message": "Limpia la vista de recomendaciones y elimina las recomendaciones almacenadas en caché." }, + "aiToolSearchNotFound": { "message": "No se encontró '$query en tu biblioteca.", "placeholders": { "query": { "content": "$1" } } }, + "aiToolNavigateSuccess": { "message": "Navegado a la página de $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolNavigateError": { "message": "Error al navegar a la página de $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolStatsError": { "message": "Error al obtener estadísticas." }, + "aiToolItemNotFound": { "message": "No se encontró el elemento '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolShowItemDetailsSuccess": { "message": "Mostrando detalles de '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolAddToPlaylistSuccess": { "message": "Añadido '$title' a la lista de reproducción.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteAdded": { "message": "Añadido '$title' a favoritos.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteRemoved": { "message": "Eliminado '$title' de favoritos.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolRecommendationsSuccess": { "message": "Mostrando recomendaciones." }, + "aiToolApplyFiltersGenreNotFound": { "message": "Género '$genre' no encontrado.", "placeholders": { "genre": { "content": "$1" } } }, + "aiToolApplyFiltersSuccess": { "message": "Filtros aplicados correctamente." }, + "aiToolSearchMusicByGenreNotFound": { "message": "No encontré artistas del género '$genre_name' en tu biblioteca.", "placeholders": { "genre_name": { "content": "$1" } } }, + "aiToolPlayMusicNotReady": { "message": "El reproductor de música no está listo. Asegúrate de que tu biblioteca de música de Plex haya sido escaneada." }, + "aiToolPlayMusicArtistNotFound": { "message": "Artista '$artist_name' no encontrado.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicNoSongs": { "message": "No se encontraron canciones para '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicSuccess": { "message": "Reproduciendo música de '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolChatHistoryCleared": { "message": "Historial de chat borrado." }, + "aiToolConfirmDeleteDatabase": { "message": "¿Estás seguro de que quieres eliminar la base de datos local? Esta acción es irreversible." }, + "aiToolDeleteDatabaseCancelled": { "message": "Eliminación de la base de datos cancelada." }, + "aiToolExecutionError": { "message": "Error al ejecutar la herramienta '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, + "aiToolUnknown": { "message": "Herramienta desconocida: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, + "aiToolFavoritesCleared": { "message": "Favoritos eliminados." }, + "aiToolFavoritesClearError": { "message": "Error al eliminar los favoritos: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolRecommendationsCleared": { "message": "Recomendaciones eliminadas." }, + "aiToolRecommendationsClearError": { "message": "Error al eliminar las recomendaciones: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleted": { "message": "Base de datos eliminada. La página se recargará." }, + "aiToolDatabaseDeleteError": { "message": "Error al eliminar la base de datos: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleteBlocked": { "message": "La eliminación de la base de datos está bloqueada. Cierra otras pestañas de la aplicación." }, + "aiToolUpdateAllTokensSuccess": { "message": "Todos los tokens se han actualizado correctamente." }, + "aiToolUpdateAllTokensError": { "message": "Error al actualizar los tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolAddPlexTokenSuccess": { "message": "Token de Plex añadido correctamente." }, + "aiToolAddPlexTokenError": { "message": "Error al añadir el token de Plex: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolChangeRegionSuccess": { "message": "Región cambiada a $region$. El contenido se está actualizando.", "placeholders": { "region": { "content": "$1" } } }, + "aiToolChangeRegionError": { "message": "Error al cambiar la región: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolViewingHistoryCleared": { "message": "Historial de visualización borrado." }, + "aiToolViewingHistoryClearError": { "message": "Error al borrar el historial de visualización: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSingle": { "message": "Iniciando la descarga del M3U para '$movie_title'.", "placeholders": { "movie_title": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSeason": { "message": "Iniciando la descarga del M3U para la temporada $1 de '$2'.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolM3UNoTitlesProvided": { "message": "Por favor, proporciona una lista de títulos para crear la lista de reproducción." }, + "aiToolM3UCheckingTitles": { "message": "Comprobando los títulos en tus servidores locales..." }, + "aiToolM3UNoLocalMatchesForDownload": { "message": "No he encontrado ninguna de las películas o series de la lista en tus servidores locales." }, + "aiToolM3UDownloadStarted": { "message": "¡Hecho! He encontrado $1 de los $2 títulos en tus servidores y he iniciado la descarga de la lista de reproducción M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolTrailerNotFoundSpecific": { "message": "Lo siento, no pude encontrar un tráiler disponible para '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiSystemPrompt_v4": { + "message": "Eres un asistente virtual integrado en una extensión de Chrome que interactúa con servidores Plex y Jellyfin. Tu función principal es ayudar al usuario a buscar, gestionar, reproducir y descargar contenido multimedia, así como administrar ajustes personalizados.\n\nPRIORIDAD MÁXIMA: Siempre que la pregunta del usuario se refiera a contenido multimedia (películas, series, música), DEBES asumir que se refiere a su biblioteca local. Utiliza las herramientas para buscar en su base de datos ANTES de buscar en la web.\n\n🎯 Reglas generales de comportamiento:\nResponde siempre de forma clara, concisa y directa. Sé proactivo y proporciona toda la información relevante de una vez para evitar preguntas de seguimiento. Por ejemplo, al confirmar la disponibilidad de una serie, incluye los detalles de las temporadas.\n\nCompara la fecha actual con los resultados de búsqueda de Google cuando se te pida información externa para garantizar que esté actualizada.\n\nUsa los nombres exactos de los comandos definidos en el sistema (function.name) al llamar herramientas.\n\n📦 Funciones clave para contenido multimedia:\nPara generar un M3U para una única película, usa download_single_movie_m3u.\nPara descargar una temporada específica de una serie, usa download_series_season_m3u.\nPara múltiples títulos (películas o series completas), usa siempre check_and_download_titles_list.\nPara buscar contenido local: search_library.\nPara buscar en TMDB: search_tmdb_content.\nPara contenido en tendencia: get_trending_content.\nPara mostrar detalles de un título: show_item_details.\nPara añadir a la lista de reproducción PHP: add_to_playlist.\nPara comprobar disponibilidad local: check_local_availability.\nSi una serie está disponible localmente, informa de cuántas temporadas hay y en qué servidores usando get_local_series_seasons.\nPara ver recomendaciones: get_recommendations.\nPara aplicar filtros: apply_filters.\nPara ver historial o favoritos: view_history, view_favorites.\nPara marcar como favorito: toggle_favorite.\nPara reproducir tráiler: play_trailer.\n\n🎵 Funciones musicales:\nSi el usuario pide recomendaciones de géneros musicales de forma general (ej. 'recomiéndame un género para animarme'), primero usa list_available_music_genres para ver qué géneros tiene y basa tu recomendación en esa lista.\nPara listar todos los géneros musicales disponibles en la biblioteca: list_available_music_genres.\nPara buscar artistas por género: search_music_by_genre.\nPara reproducir canciones por título y/o artista: play_song.\nPara reproducir música de un artista: play_music_by_artist.\n\n🧰 Funciones de gestión y configuración:\nPara obtener estadísticas del usuario: get_user_stats.\nPara navegar a secciones específicas: navigate_to_page.\nPara actualizar tokens: update_all_tokens, add_plex_token.\nPara cambiar la región de contenido: change_region.\nPara exportar o importar la base de datos local: export_local_database, import_local_database.\nPara borrar la base de datos: delete_database.\nPara limpiar favoritos, historial o recomendaciones: clear_all_favorites, clear_viewing_history, clear_recommendations_view.\nPara cambiar el modo claro/oscuro: toggle_light_mode.\nPara mostrar u ocultar la sección de héroe: toggle_hero_section.\n\n⚠️ Consideraciones adicionales:\nPrioriza contenido disponible localmente. Usa check_local_availability antes de mostrar opciones de reproducción o descarga.\nSi una acción falla, informa de manera clara y sin rodeos.\nEvita repetir innecesariamente la solicitud del usuario, salvo que ayude a contextualizar la respuesta." + }, + "backToProviders": { "message": "Volver a Proveedores" }, + "artistsCounterSingle": { "message": "$total$ Artista", "placeholders": { "total": { "content": "$1" } } }, + "artistsCounterLoading": { "message": "Cargando..." }, + "downloadingSong": { "message": "Iniciando descarga de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "songDownloaded": { "message": "\"$title$\" descargado.", "placeholders": { "title": { "content": "$1" } } }, + "errorDownloadingSong": { "message": "Error al descargar \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "generatingAlbumM3U": { "message": "Generando M3U para \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, + "albumM3UGenerated": { "message": "M3U para el álbum \"$artist$\" generado.", "placeholders": { "artist": { "content": "$1" } } }, + "retyingSection": { "message": "Reintentando sección \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "retrySuccess": { "message": "[ÉXITO] Reintento de \"$title$\" completado.", "placeholders": { "title": { "content": "$1" } } }, + "retryError": { "message": "[ERROR FINAL] Falló el reintento para \"$title$\": $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, + "startingRetryPhase": { "message": "Iniciando fase de reintentos para $count$ secciones...", "placeholders": { "count": { "content": "$1" } } }, + "tokenFoundServers": { "message": "Token $token$... encontró $count$ servidores.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, + "errorProcessingToken": { "message": "Error procesando token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, + "plexScanFatalError": { "message": "ERROR FATAL: $message$", "placeholders": { "message": { "content": "$1" } } }, + "errorDuringScan": { "message": "Error durante el escaneo: $message$", "placeholders": { "message": { "content": "$1" } } }, + "stoppingPlexScan": { "message": "Deteniendo escaneo Plex..." }, + "invalidTokenProvided": { "message": "Token inválido proporcionado." }, + "tokenAlreadyExists": { "message": "El token ya existe." }, + "tokenAddedSuccessfully": { "message": "Token añadido correctamente." }, + "noStreamsFoundForSelection": { "message": "No se encontraron streams para la selección." }, + "autoplayBlocked": { "message": "Reproducción automática bloqueada." }, + "welcomeToCinePlex": { "message": "" }, + "page": { "message": "Página" }, + "all": { "message": "Todo" }, + "userScore": { "message": "Puntuación" }, + "duration": { "message": "Duración" }, + "min": { "message": "Mín" }, + "max": { "message": "Máx" }, + "aiToolFindStreamingProvidersDesc": { "message": "Busca dónde ver una película o serie en servicios de streaming." }, + "aiToolFindStreamingProvidersTitleParamDesc": { "message": "El título de la película o serie a buscar." }, + "aiToolFindStreamingProvidersTypeParamDesc": { "message": "El tipo de contenido (película o serie)." }, + "aiToolFindStreamingProvidersYearParamDesc": { "message": "El año de lanzamiento del contenido (opcional)." }, + "aiToolNoStreamingProviders": { "message": "No se encontraron proveedores de streaming para {title}." }, + "aiToolStreamingProvidersFound": { "message": "{title} está disponible en los siguientes servicios: {providers}." }, + "aiToolStreamingProviderError": { "message": "Error al buscar proveedores de streaming: {message}." }, + "aiToolGetLocalSeriesSeasonsDesc": { "message": "Comprueba si una serie de TV está disponible localmente y devuelve un desglose detallado de las temporadas disponibles en cada servidor." }, + "aiToolGetLocalSeriesSeasonsTitleParamDesc": { "message": "El título de la serie a comprobar." }, + "aiToolGetLocalSeriesSeasonsYearParamDesc": { "message": "El año de lanzamiento de la serie (opcional para mayor precisión)." }, + "aiToolLocalSeriesNoSeasons": { "message": "La serie '$series_title' está en tu biblioteca, pero no se encontraron detalles de las temporadas.", "placeholders": { "series_title": { "content": "$1" } } }, + "artist": { "message": "Artista" }, + "tracks": { "message": "pistas" }, + "noSongsFound": { "message": "No se encontraron canciones para este artista." }, + "durationMin": { "message": "Duración (Min)" }, + "score": { "message": "Puntuación" }, + "searchGenre": { "message": "Buscar género..." }, + "searchArtists": { "message": "Buscar artistas..." }, + "preparingMusicLibrary": { "message": "Preparando tu biblioteca musical..." }, + "preparingMusicLibraryDesc": { "message": "Este proceso único puede tardar unos minutos si tienes muchos artistas." }, + "artistsProgress": { "message": "0 / 0 artistas" }, + "starting": { "message": "Iniciando..." }, + "artistName": { "message": "Nombre del Artista" }, + "playPause": { "message": "Reproducir/Pausar" }, + "noLocalFilesFound": { "message": "No se encontraron archivos locales para este título." }, + "server": { "message": "Servidor" }, + "title": { "message": "Título" }, + "year": { "message": "Año" }, + "resolution": { "message": "Resolución" }, + "size": { "message": "Tamaño" }, + "container": { "message": "Contenedor" }, + "action": { "message": "Acción" }, + "generate": { "message": "Generar" }, + "availableLocalFiles": { "message": "Archivos Locales Disponibles" }, + "downloadSeason": { "message": "Descargar Temporada" }, + "errorLoadingServersM3u": { "message": "Error al cargar los servidores para el generador M3U:" }, + "errorFetchingLibraries": { "message": "Error al obtener las bibliotecas." }, + "selectServerAndLibrary": { "message": "Por favor, selecciona un servidor y al menos una biblioteca." }, + "generating": { "message": "Generando..." }, + "errorProcessingLibrary": { "message": "Error al procesar la biblioteca" }, + "errorProcessingLibrarySkipping": { "message": "Error al procesar la biblioteca. Omitiendo." }, + "allLibrariesFailed": { "message": "Todas las bibliotecas seleccionadas fallaron al procesar." }, + "m3uGeneratedWithErrors": { "message": "M3U generado con algunos errores. Algunas bibliotecas pueden faltar." }, + "m3uDownloadedSuccess": { "message": "Lista de reproducción M3U descargada con éxito." }, + "errorGeneratingM3uFile": { "message": "Error al generar el archivo M3U." }, + "chatSources": { "message": "Fuentes" }, + "chatUnnamedSource": { "message": "Fuente sin nombre" }, + "googleApiFailure": { "message": "Fallo en la llamada a la API de Google AI:" } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 03c3ed0..ccba920 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -1,449 +1,516 @@ { - "appName": { "message": "CinePlex" }, - "appDescription": { "message": "Scanne les serveurs Plex à la recherche de contenu et l'affiche dans l'interface" }, - "appTagline": { "message": "Films, Séries et Musique" }, - "appLocaleCode": { "message": "fr-FR" }, - "toggleNavigation": { "message": "Basculer la navigation" }, - "searchPlaceholder": { "message": "Rechercher des films ou des séries..." }, - "openMusicPlayer": { "message": "Ouvrir le lecteur de musique" }, - "settings": { "message": "Paramètres" }, - "navMovies": { "message": "Films" }, - "navSeries": { "message": "Séries" }, - "navProviders": { "message": "Fournisseurs" }, - "navPhotos": { "message": "Photos" }, - "navStats": { "message": "Statistiques" }, - "navFavorites": { "message": "Favoris" }, - "navHistory": { "message": "Historique" }, - "navRecommendations": { "message": "Recommandations" }, - "navMusic": { "message": "Musique" }, - "navM3uGenerator": { "message": "Générateur M3U" }, - "heroWelcome": { "message": "" }, - "heroSubtitle": { "message": "Explorez des milliers de films et de séries." }, - "addStream": { "message": "Ajouter un flux" }, - "moreInfo": { "message": "Plus d'infos" }, - "popularMovies": { "message": "Films populaires" }, - "allGenres": { "message": "Tous les genres" }, - "allYears": { "message": "Toutes les années" }, - "sortPopular": { "message": "Les plus populaires" }, - "sortTopRated": { "message": "Les mieux notés" }, - "sortRecent": { "message": "Les plus récents" }, - "loadMore": { "message": "Charger plus" }, - "photosBreadcrumbHome": { "message": "Albums" }, - "selectServer": { "message": "Sélectionnez un serveur" }, - "loading": { "message": "Chargement..." }, - "loadingLibraries": { "message": "Chargement des bibliothèques..." }, - "photosEmptyState": { "message": "Aucun album ou photo trouvé." }, - "photosEmptyStateSub": { "message": "Veuillez sélectionner un serveur ou vous assurer que vous disposez d'une photothèque dans Plex." }, - "statsTitle": { "message": "Statistiques de la bibliothèque" }, - "statsAllTokens": { "message": "Tous les jetons" }, - "statsAnalyzing": { "message": "Analyse de votre bibliothèque..." }, - "statsActiveTokens": { "message": "Jetons actifs" }, - "statsServersFound": { "message": "Serveurs trouvés" }, - "statsUniqueMovies": { "message": "Films uniques" }, - "statsUniqueSeries": { "message": "Séries uniques" }, - "statsUniqueArtists": { "message": "Artistes uniques" }, - "statsTokenServers": { "message": "Serveurs de jetons" }, - "statsChartMoviesByGenre": { "message": "Contenu par genre (Films)" }, - "statsChartSeriesByGenre": { "message": "Contenu par genre (Séries)" }, - "statsChartByDecade": { "message": "Contenu par décennie" }, - "recommendationsTitle": { "message": "Recommandations pour vous" }, - "historyTitle": { "message": "Historique de visionnage" }, - "clearHistory": { "message": "Tout effacer" }, - "consoleTitle": { "message": "Console d'analyse Plex" }, - "footerCredit": { "message": "Une interface pour votre univers Plex." }, - "closeTrailer": { "message": "Fermer la bande-annonce" }, - "close": { "message": "Fermer" }, - "photoViewer": { "message": "Visionneuse de photos" }, - "previous": { "message": "Précédent" }, - "next": { "message": "Suivant" }, - "notificationTemplateText": { "message": "Notification" }, - "settingsTitleFull": { "message": "Paramètres et configuration" }, - "settingsTabGeneral": { "message": "Général" }, - "settingsTabPlex": { "message": "Plex" }, - "settingsTabJellyfin": { "message": "Jellyfin" }, - "settingsTabPhpGen": { "message": "Générateur PHP" }, - "settingsTabData": { "message": "Données" }, - "settingsApiServer": { "message": "Paramètres API et serveur" }, - "settingsTmdbApiLabel": { "message": "Clé API TMDB (facultatif)" }, - "settingsTmdbApiPlaceholder": { "message": "La clé par défaut sera utilisée si ce champ est laissé vide" }, - "settingsGoogleApiLabel": { "message": "Clé API Google Gemini (facultatif)" }, - "settingsGoogleApiPlaceholder": { "message": "Requis pour utiliser l'assistant IA" }, - "settingsRegionLabel": { "message": "Région pour la découverte de contenu" }, - "allRegions": { "message": "Toutes les régions" }, - "settingsPhpUrlLabel": { "message": "URL du serveur pour l'ajout de flux" }, - "settingsPhpUrlPlaceholder": { "message": "https://votre-serveur.com/chemin/vers/script.php" }, - "settingsInterface": { "message": "Interface" }, - "settingsLightTheme": { "message": "Mode clair" }, - "settingsShowHero": { "message": "Afficher la section d'accueil 'Hero'" }, - "settingsScanContent": { "message": "Analyse du contenu" }, - "settingsScanDesc": { "message": "Sélectionnez les éléments à analyser et appuyez sur le bouton." }, - "settingsScanMovies": { "message": "Films" }, - "settingsScanShows": { "message": "Séries" }, - "settingsScanArtists": { "message": "Musique" }, - "settingsScanPhotos": { "message": "Photos" }, - "settingsSelectAll": { "message": "Tout sélectionner" }, - "settingsStartScan": { "message": "Démarrer l'analyse" }, - "settingsPlexTokens": { "message": "Jetons Plex" }, - "settingsPlexTokensDesc": { "message": "Modifiez la liste des jetons Plex (format JSON)." }, - "settingsSaveTokens": { "message": "Enregistrer les jetons" }, - "settingsJellyfinTitle": { "message": "Paramètres Jellyfin" }, - "settingsJellyfinDesc": { "message": "Ajoutez les détails de votre serveur Jellyfin pour analyser son contenu." }, - "jellyfinUrlLabel": { "message": "URL du serveur Jellyfin" }, - "jellyfinUserLabel": { "message": "Nom d'utilisateur" }, - "jellyfinPassLabel": { "message": "Mot de passe" }, - "jellyfinConnectAndScan": { "message": "Connecter et analyser" }, - "settingsPhpGenTitle": { "message": "Générateur de script PHP pour serveur" }, - "settingsPhpFileOptions": { "message": "Options de fichier" }, - "settingsPhpSavePathLabel": { "message": "Chemin d'enregistrement sur le serveur" }, - "settingsPhpSavePathPlaceholder": { "message": "Ex: /var/www/html/listes (vide pour le même dossier)" }, - "settingsPhpFilenameLabel": { "message": "Nom de fichier" }, - "settingsPhpFileAction": { "message": "Action sur le fichier" }, - "settingsPhpActionAppend": { "message": "Ajouter à la fin du fichier (cumulatif)" }, - "settingsPhpActionOverwrite": { "message": "Écraser le fichier (repartir de zéro)" }, - "settingsPhpSecurity": { "message": "Sécurité (facultatif)" }, - "settingsPhpUseSecretKey": { "message": "Utiliser une clé secrète (recommandé)" }, - "settingsPhpSecretKeyPlaceholder": { "message": "Saisissez une clé secrète sécurisée" }, - "settingsPhpGeneratedCode": { "message": "Code généré" }, - "settingsPhpGeneratedPlaceholder": { "message": "Le code PHP généré apparaîtra ici." }, - "settingsGenerateScript": { "message": "Générer le script" }, - "settingsCopyScript": { "message": "Copier le script" }, - "settingsDataManagement": { "message": "Gestion de la base de données locale" }, - "settingsImportDb": { "message": "Importer la base de données depuis un fichier" }, - "settingsExportDb": { "message": "Exporter la base de données vers un fichier" }, - "settingsClearContent": { "message": "Effacer les données de contenu local" }, - "settingsClearContentDesc": { "message": "Cette action supprimera les films, les séries et la musique de la base de données locale, mais n'affectera pas vos favoris ni vos paramètres." }, - "settingsClose": { "message": "Fermer" }, - "settingsSave": { "message": "Enregistrer les paramètres" }, - "musicSidenavTitle": { "message": "Musique Plex" }, - "musicAllServers": { "message": "Tous les serveurs" }, - "musicSearchArtistPlaceholder": { "message": "Rechercher un artiste..." }, - "musicSearchDiscographyPlaceholder": { "message": "Rechercher dans la discographie..." }, - "musicNothingPlaying": { "message": "Aucune lecture en cours" }, - "musicSelectSong": { "message": "Sélectionnez une chanson" }, - "musicToStart": { "message": "pour démarrer la lecture" }, - "miniplayerDownloadSong": { "message": "Télécharger la chanson" }, - "miniplayerDownloadAlbum": { "message": "Télécharger l'album M3U" }, - "miniplayerVolume": { "message": "Volume" }, - "miniplayerShuffle": { "message": "Aléatoire" }, - "miniplayerEqualizer": { "message": "Égaliseur" }, - "miniplayerOpenList": { "message": "Ouvrir la liste" }, - "eqTitle": { "message": "Égaliseur graphique" }, - "eqPresetsLabel": { "message": "Préréglages" }, - "eqPresetFlat": { "message": "Plat" }, - "eqPresetRock": { "message": "Rock" }, - "eqPresetPop": { "message": "Pop" }, - "eqPresetJazz": { "message": "Jazz" }, - "eqPresetClassical": { "message": "Classique" }, - "eqPresetBassBoost": { "message": "Amplification des basses" }, - "eqPreampLabel": { "message": "Préampli" }, - "infoModalTitle": { "message": "Informations" }, - "infoModalFieldTitle": { "message": "Titre :" }, - "infoModalFieldArtist": { "message": "Artiste :" }, - "infoModalFieldAlbum": { "message": "Album :" }, - "infoModalFieldSong": { "message": "Chanson :" }, - "infoModalFieldYear": { "message": "Année :" }, - "infoModalFieldGenre": { "message": "Genre :" }, - "lang_en": { "message": "Anglais" }, - "lang_es": { "message": "Espagnol" }, - "lang_fr": { "message": "Français" }, - "lang_de": { "message": "Allemand" }, - "lang_it": { "message": "Italien" }, - "lang_pt": { "message": "Portugais" }, - "essentialFeaturesNotSupported": { "message": "Votre navigateur ne prend pas en charge les fonctionnalités essentielles." }, - "dbAccessError": { "message": "Erreur d'accès à la base de données locale." }, - "dbUpdateNeeded": { "message": "La base de données doit être mise à jour, veuillez recharger la page." }, - "dbBlocked": { "message": "Veuillez fermer les autres onglets de cette application pour continuer." }, - "deletingContentData": { "message": "Suppression des données de contenu local..." }, - "noContentDataToDelete": { "message": "Aucune donnée de contenu à supprimer." }, - "contentDataDeleted": { "message": "Données de contenu supprimées d'IndexedDB." }, - "errorDeletingData": { "message": "Erreur lors de la suppression des données : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailable": { "message": "Éditeur de texte non disponible." }, - "errorLoadingTokens": { "message": "Erreur lors du chargement des jetons pour modification." }, - "errorLoadingTokensMessage": { "message": "Erreur lors du chargement des jetons : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailableToSave": { "message": "Éditeur non disponible pour l'enregistrement." }, - "invalidJsonFormat": { "message": "Format JSON non valide. Il doit être { \"tokens\": [...] }" }, - "tokensSaved": { "message": "Jetons enregistrés avec succès." }, - "errorSavingTokens": { "message": "Erreur lors de l'enregistrement des jetons : $message$", "placeholders": { "message": { "content": "$1" } } }, - "dbNotAvailable": { "message": "IndexedDB n'est pas disponible." }, - "dbExported": { "message": "Base de données exportée avec succès." }, - "errorExportingDb": { "message": "Erreur lors de l'exportation de la base de données : $message$", "placeholders": { "message": { "content": "$1" } } }, - "invalidJsonFile": { "message": "Le fichier ne contient pas d'objet JSON valide." }, - "noDataToImport": { "message": "Le fichier ne contient aucune donnée pour les sections actuelles de la base de données." }, - "dbImported": { "message": "Base de données importée avec succès." }, - "errorImportingDb": { "message": "Erreur lors de l'importation de la base de données : $message$", "placeholders": { "message": { "content": "$1" } } }, - "updatingView": { "message": "Mise à jour de la vue avec les nouvelles données..." }, - "confirmClearContent": { "message": "Êtes-vous sûr de vouloir supprimer les données de contenu local (films, séries, musique, etc.) ? Les favoris et les paramètres ne seront PAS supprimés." }, - "trailerNotFound": { "message": "Aucune bande-annonce trouvée pour ce titre." }, - "confirmClearHistory": { "message": "Êtes-vous sûr de vouloir effacer tout votre historique de visionnage ? Cette action est irréversible." }, - "historyCleared": { "message": "Historique de visionnage effacé." }, - "historyItemDeleted": { "message": "Élément supprimé de l'historique." }, - "errorGeneratingScript": { "message": "Générez d'abord un script pour pouvoir le copier." }, - "scriptCopied": { "message": "Script PHP copié dans le presse-papiers." }, - "errorCopyingScript": { "message": "Erreur lors de la copie du script." }, - "scriptGenerated": { "message": "Script PHP généré." }, - "errorLoadingAlbum": { "message": "Erreur lors du chargement de l'album : $message$", "placeholders": { "message": { "content": "$1" } } }, - "noPhotoServerSelected": { "message": "Erreur : Aucun serveur photo n'a été sélectionné." }, - "loadingGenres": { "message": "Chargement des genres..." }, - "errorLoadingGenres": { "message": "Erreur de chargement" }, - "noContentFound": { "message": "Aucun résultat trouvé." }, - "couldNotLoadContent": { "message": "Impossible de charger le contenu." }, - "noFavorites": { "message": "Vous n'avez pas encore de favoris." }, - "errorLoadingFavorites": { "message": "Erreur lors du chargement des favoris." }, - "historyEmpty": { "message": "Votre historique est vide." }, - "historyEmptySub": { "message": "Explorez et regardez du contenu pour qu'il apparaisse ici." }, - "errorGeneratingRecommendations": { "message": "Erreur lors de la génération des recommandations." }, - "noRecommendations": { "message": "Nous avons besoin de mieux vous connaître pour vous faire des recommandations !" }, - "errorGeneratingStats": { "message": "Erreur lors de la génération des statistiques." }, - "noServersForToken": { "message": "Aucun serveur associé trouvé pour ce jeton." }, - "searchingActorContent": { "message": "Recherche de contenu de $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, - "errorLoadingActorContent": { "message": "Impossible de charger le contenu pour $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, - "errorAddingStream": { "message": "Erreur lors de l'ajout de flux : $message$", "placeholders": { "message": { "content": "$1" } } }, - "phpUrlNotConfigured": { "message": "L'URL du serveur PHP n'est pas configurée. Veuillez la configurer dans les paramètres." }, - "searchingStreams": { "message": "Recherche de flux pour \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "sendingStreams": { "message": "Envoi de $count$ flux au serveur...", "placeholders": { "count": { "content": "$1" } } }, - "streamAddedSuccess": { "message": "Flux ajouté(s) avec succès." }, - "generatingM3U": { "message": "Génération de M3U pour \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "m3uDownloaded": { "message": "\"$title$\" téléchargé.", "placeholders": { "title": { "content": "$1" } } }, - "errorGeneratingM3U": { "message": "Erreur lors de la génération de M3U : $message$", "placeholders": { "message": { "content": "$1" } } }, - "settingsSavedSuccess": { "message": "Paramètres enregistrés avec succès." }, - "errorSavingSettings": { "message": "Erreur lors de l'enregistrement des paramètres dans la base de données." }, - "languageChangeReload": { "message": "Langue modifiée. L'application va maintenant se recharger." }, - "addedToFavorites": { "message": "Ajouté aux favoris." }, - "removedFromFavorites": { "message": "Supprimé des favoris." }, - "plexScanInProgress": { "message": "L'analyse Plex est déjà en cours." }, - "plexScanStarting": { "message": "Démarrage de l'analyse Plex..." }, - "noPlexTokens": { "message": "Aucun jeton Plex configuré." }, - "clearingSections": { "message": "Effacement des sections : $sections$", "placeholders": { "sections": { "content": "$1" } } }, - "initialScanPhaseComplete": { "message": "Phase d'analyse initiale terminée." }, - "retryPhaseFinished": { "message": "Phase de nouvelle tentative terminée." }, - "plexScanFinished": { "message": "Analyse terminée. Mise à jour du contenu..." }, - "scanCancelled": { "message": "Analyse annulée par l'utilisateur." }, - "scanCancelledInfo": { "message": "Analyse annulée." }, - "errorInitializingMusicPlayer": { "message": "Erreur lors de l'initialisation du lecteur de musique." }, - "criticalErrorLoadingMusic": { "message": "Erreur critique lors du chargement des données musicales." }, - "errorLoadingArtists": { "message": "Erreur lors du chargement des artistes." }, - "dbUnavailableError": { "message": "Erreur : Base de données non disponible." }, - "updatingMusicData": { "message": "Mise à jour des données musicales..." }, - "musicDataUpdated": { "message": "Données musicales mises à jour." }, - "errorFetchingArtistSongs": { "message": "Erreur lors de la récupération des chansons de l'artiste." }, - "errorLoadingSongs": { "message": "Erreur lors du chargement des chansons." }, - "noArtistsFound": { "message": "Aucun artiste trouvé." }, - "shuffleOn": { "message": "Mode aléatoire activé." }, - "shuffleOff": { "message": "Mode aléatoire désactivé." }, - "playbackError": { "message": "Erreur de lecture" }, - "errorLabel": { "message": "Erreur" }, - "reloadingPage": { "message": "Rechargement de la page..." }, - "viewed": { "message": "Vu" }, - "local": { "message": "Local" }, - "topRatedSort": {"message": "Les mieux notés"}, - "recentSort": {"message": "Récents"}, - "popularSort": {"message": "Populaires"}, - "moviesSectionTitle": {"message": "Films"}, - "seriesSectionTitle": {"message": "Séries"}, - "searchResultsFor": {"message": "Résultats pour \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, - "contentFrom": {"message": "Contenu de $actor$", "placeholders": {"actor": {"content": "$1"}}}, - "explore": {"message": "Explorer"}, - "noGenre": {"message": "Non classé"}, - "synopsis": {"message": "Synopsis"}, - "noSynopsis": {"message": "Aucun synopsis disponible."}, - "director": {"message": "Réalisateur :"}, - "writer": {"message": "Scénariste :"}, - "viewOnImdb": {"message": "Voir sur IMDb"}, - "watchTrailer": {"message": "Regarder la bande-annonce"}, - "addToFavorites": {"message": "Ajouter aux favoris"}, - "removeFromFavorites": {"message": "Retirer des favoris"}, - "notAvailable": {"message": "Non disponible"}, - "mainCast": {"message": "Distribution principale"}, - "seasonsAndEpisodes": {"message": "Saisons et épisodes"}, - "similarContent": {"message": "Contenu similaire"}, - "filmography": {"message": "Filmographie"}, - "availableOn": {"message": "Disponible sur"}, - "episodesCount": {"message": "$count$ épisodes", "placeholders": {"count": {"content": "$1"}}}, - "seasonsCount": {"message": "$count$ saisons", "placeholders": {"count": {"content": "$1"}}}, - "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, - "noTrailerFound": {"message": "Aucune bande-annonce trouvée pour ce titre."}, - "fatalInitError": {"message": "Erreur d'initialisation fatale"}, - "fatalInitErrorSub": {"message": "Impossible de charger l'application."}, - "invalidStreamInfo": {"message": "Informations non valides."}, - "dbUnavailableForStreams": {"message": "Base de données locale non disponible."}, - "noPlexServersForStreams": {"message": "Aucun serveur Plex."}, - "notFoundOnServers": {"message": "\"$query$\" introuvable sur les serveurs Plex.", "placeholders": {"query": {"content": "$1"}}}, - "relativeTime_justNow": { "message": "À l'instant" }, - "relativeTime_minutesAgo": { "message": "Il y a $count$ minutes", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_hoursAgo": { "message": "Il y a $count$ heures", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_yesterday": { "message": "Hier" }, - "relativeTime_daysAgo": { "message": "Il y a $count$ jours", "placeholders": { "count": { "content": "$1" } } }, - "errorLoadingDetails": { "message": "Erreur lors du chargement des détails" }, - "errorLoadingLocalContent": { "message": "Erreur lors du chargement du contenu local." }, - "errorServerResponse": { "message": "Réponse du serveur infructueuse." }, - "errorPlexApi": { "message": "Erreur $status$ de l'API Plex.", "placeholders": { "status": { "content": "$1" } } }, - "errorParsingPlexXml": { "message": "Erreur lors de l'analyse du XML Plex." }, - "untitled": { "message": "Sans titre" }, - "itemCount": { "message": "$count$ éléments", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Aucun serveur photo" }, - "jellyfinScanInProgress": { "message": "L'analyse Jellyfin est déjà en cours." }, - "jellyfinScanning": { "message": "Analyse de Jellyfin..." }, - "jellyfinMissingCredentials": { "message": "Veuillez compléter l'URL et le nom d'utilisateur de Jellyfin." }, - "jellyfinConnecting": { "message": "Connexion à Jellyfin à : $url$", "placeholders": { "url": { "content": "$1" } } }, - "jellyfinAuthFailed": { "message": "Échec de l'authentification Jellyfin : $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinAuthSuccess": { "message": "Authentification Jellyfin réussie." }, - "jellyfinFetchingLibraries": { "message": "Récupération des bibliothèques..." }, - "jellyfinFetchFailed": { "message": "Erreur lors de la récupération des bibliothèques : $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinNoMediaLibraries": { "message": "Aucune bibliothèque de films ou de séries trouvée dans Jellyfin." }, - "jellyfinLibrariesFound": { "message": "$count$ bibliothèque(s) multimédia(s) trouvée(s).", "placeholders": { "count": { "content": "$1" } } }, - "jellyfinLibraryScanSuccess": { "message": "[Succès] '$libraryName' analysée, $count$ titres ajoutés.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, - "jellyfinLibraryScanFailed": { "message": "Erreur lors de l'analyse de la bibliothèque '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, - "jellyfinScanSuccess": { "message": "Analyse Jellyfin terminée. $movies$ films et $series$ séries ajoutés.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, - "noJellyfinCredentials": { "message": "Informations d'identification Jellyfin non configurées." }, - "notFoundOnJellyfin": { "message": "\"$query$\" introuvable sur Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, - "notFoundOnAnyServer": { "message": "\"$query$\" introuvable sur aucun serveur.", "placeholders": { "query": { "content": "$1" } } }, - "localOnPlex": { "message": "Sur Plex" }, - "searchOnPlex": { "message": "Rechercher sur Plex" }, - "jellyfinTitle": { "message": "Contenu Jellyfin" }, - "noJellyfinContent": { "message": "Aucun contenu Jellyfin trouvé." }, - "noJellyfinContentSub": { "message": "Assurez-vous d'avoir analysé votre serveur Jellyfin dans les paramètres." }, - "activityViewerTitle": { "message": "Visionneuse d'activité du serveur" }, - "activitySelectServer": { "message": "Sélectionnez un serveur" }, - "activityCheckBtn": { "message": "Actualiser" }, - "activityNoSessions": { "message": "Aucune session active sur ce serveur." }, - "activitySessionUser": { "message": "Utilisateur" }, - "activitySessionDevice": { "message": "Appareil" }, - "activitySessionContent": { "message": "Contenu" }, - "activitySessionState": { "message": "État" }, - "activitySessionIdentifier": { "message": "Identifiant du client" }, - "activityCopyID": { "message": "Copier l'ID" }, - "activityError": { "message": "Impossible d'obtenir l'activité du serveur." }, - "activityCopied": { "message": "Identifiant copié dans le presse-papiers !" }, - "activityCopyError": { "message": "Erreur lors de la copie de l'identifiant." }, - "noProvidersFound": { "message": "Aucun fournisseur trouvé." }, - "availableOnPlex": { "message": "Disponible sur Plex" }, - "m3uGeneratorTitle": { "message": "Générateur de listes M3U" }, - "selectAServer": { "message": "Sélectionnez un serveur..." }, - "downloadM3u": { "message": "Télécharger M3U" }, - "m3uGenerator": { "message": "Générateur M3U" }, - "selectLibraries": { "message": "Sélectionner les bibliothèques" }, - "howToUse": { "message": "Comment utiliser" }, - "m3uInstruction1": { "message": "Choisissez un serveur dans la liste." }, - "m3uInstruction2": { "message": "Sélectionnez une ou plusieurs bibliothèques à inclure." }, - "m3uInstruction3": { "message": "Cliquez sur le bouton de téléchargement." }, - "m3uInstruction4": { "message": "Importez le fichier .m3u dans votre lecteur compatible." }, - "chatOpen": { "message": "Ouvrir le chat" }, - "chatTitle": { "message": "Assistant IA" }, - "chatClose": { "message": "X" }, - "chatPlaceholder": { "message": "Saisissez votre message..." }, - "chatSend": { "message": "➤" }, - "chatWelcome": { "message": "Bienvenue ! Je suis votre assistant CinePlex. Posez-moi des questions sur les films, les séries ou tout ce que vous voulez savoir." }, - "chatGoogleApiKeyMissing": { "message": "La clé API Google Gemini n'est pas configurée. Veuillez la définir dans les paramètres de l'extension pour utiliser l'assistant IA." }, - "chatApiInvalidResponse": { "message": "L'API a renvoyé une réponse non valide. Veuillez réessayer." }, - "chatApiError": { "message": "Erreur de communication avec l'assistant IA" }, - "downloadAll": { "message": "Tout télécharger" }, - "download": { "message": "Télécharger" }, - "aiToolSearchLibraryDesc": { "message": "Recherche dans la bibliothèque Plex de l'utilisateur des films ou des séries par titre." }, - "aiToolSearchLibraryQueryParamDesc": { "message": "Le titre du film ou de la série à rechercher." }, - "aiToolSearchLibraryTypeParamDesc": { "message": "Le type de contenu à rechercher. Peut être 'movie' pour les films ou 'series' pour les séries. (Facultatif)." }, - "aiToolSearchLibraryResolutionParamDesc": { "message": "La résolution vidéo à rechercher (par exemple, '4k', '1080p'). (Facultatif)." }, - "aiToolSearchLibraryContainerParamDesc": { "message": "Le format de conteneur vidéo à rechercher (par exemple, 'mkv', 'mp4'). (Facultatif)." }, - "aiToolNavigateToPageDesc": { "message": "Dirige l'utilisateur vers une page spécifique de l'interface de l'application." }, - "aiToolNavigateToPagePageParamDesc": { "message": "Le nom de la page vers laquelle naviguer, par exemple : 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers' ou 'm3u-generator'." }, - "aiToolGetUserStatsDesc": { "message": "Récupère et affiche les statistiques de la bibliothèque de l'utilisateur, telles que le nombre total de films, de séries et d'artistes uniques." }, - "aiToolShowItemDetailsDesc": { "message": "Affiche la page de détails d'un film ou d'une série spécifique par son titre et son type." }, - "aiToolShowItemDetailsTitleParamDesc": { "message": "Le titre exact du film ou de la série." }, - "aiToolShowItemDetailsTypeParamDesc": { "message": "Le type de contenu. Doit être 'movie' ou 'series'." }, - "aiToolAddToPlaylistDesc": { "message": "Ajoute un film ou une série à la liste de lecture actuelle de l'utilisateur pour le diffuser sur un serveur PHP configuré." }, - "aiToolAddToPlaylistTitleParamDesc": { "message": "Le titre du film ou de la série à ajouter." }, - "aiToolAddToPlaylistTypeParamDesc": { "message": "Le type de contenu. Doit être 'movie' ou 'series'." }, - "aiToolCheckAndDownloadDesc": { "message": "Vérifie la disponibilité d'une liste de titres de films ou de séries sur les serveurs locaux de l'utilisateur et, si trouvés, génère et télécharge un fichier de liste de lecture M3U avec les flux trouvés." }, - "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Un tableau de titres de films ou de séries à rechercher et à télécharger." }, - "aiToolCheckAndDownloadTypeParamDesc": { "message": "Le type de contenu de la liste. Doit être 'movie' ou 'series'." }, - "aiToolCheckAndDownloadFilenameParamDesc": { "message": "Le nom du fichier M3U à télécharger (par exemple, 'MaListe.m3u'). Si aucun nom n'est fourni, un nom par défaut sera utilisé." }, - "aiToolToggleFavoriteDesc": { "message": "Ajoute ou supprime un film ou une série de la liste des favoris de l'utilisateur." }, - "aiToolToggleFavoriteTitleParamDesc": { "message": "Le titre du film ou de la série." }, - "aiToolToggleFavoriteTypeParamDesc": { "message": "Le type de contenu. Doit être 'movie' ou 'series'." }, - "aiToolGetRecommendationsDesc": { "message": "Génère et affiche une liste de recommandations de films ou de séries basées sur l'historique de visionnage et les favoris de l'utilisateur." }, - "aiToolApplyFiltersDesc": { "message": "Applique des filtres à la vue actuelle des films ou des séries, permettant d'affiner les résultats par type, genre, année et ordre de tri." }, - "aiToolApplyFiltersTypeParamDesc": { "message": "Le type de contenu auquel appliquer les filtres. Doit être 'movie' ou 'series'." }, - "aiToolApplyFiltersGenreParamDesc": { "message": "Le nom du genre par lequel filtrer (par exemple, 'Action', 'Drame')." }, - "aiToolApplyFiltersYearParamDesc": { "message": "L'année de sortie par laquelle filtrer (par exemple, '2023')." }, - "aiToolApplyFiltersSortParamDesc": { "message": "Le critère de tri pour les résultats. Valeurs valides : 'popularity.desc' (populaires), 'vote_average.desc' (mieux notés), 'release_date.desc' (récents pour les films) ou 'first_air_date.desc' (récents pour les séries)." }, - "aiToolPlayMusicByArtistDesc": { "message": "Ouvre le lecteur de musique et commence à jouer les chansons d'un artiste spécifique de la bibliothèque de l'utilisateur." }, - "aiToolPlayMusicByArtistNameParamDesc": { "message": "Le nom exact de l'artiste dont les chansons doivent être jouées." }, - "aiToolClearChatHistoryDesc": { "message": "Efface tout l'historique des messages de la conversation en cours avec l'assistant IA." }, - "aiToolDeleteDatabaseDesc": { "message": "Supprime l'intégralité de la base de données locale de l'extension, y compris le contenu analysé, les paramètres et les favoris. Cette action est irréversible et rechargera l'application." }, - "aiToolUpdateAllTokensDesc": { "message": "Lance une analyse complète de tous les serveurs et bibliothèques Plex associés aux jetons configurés dans l'extension. Met à jour tous les films, séries, artistes et photos." }, - "aiToolAddPlexTokenDesc": { "message": "Ajoute un nouveau jeton X-Plex à la configuration de l'extension, permettant à l'application d'analyser le contenu de nouveaux serveurs Plex." }, - "aiToolAddPlexTokenTokenParamDesc": { "message": "La chaîne de jeton X-Plex à ajouter." }, - "aiToolChangeRegionDesc": { "message": "Modifie la région utilisée pour la découverte de contenu dans l'API TMDB. Cela affectera les résultats affichés dans les sections des films et des séries, ainsi que les fournisseurs de streaming." }, - "aiToolChangeRegionRegionParamDesc": { "message": "Le code de pays ISO 3166-1 à deux lettres pour la nouvelle région (par exemple, 'US' pour les États-Unis, 'ES' pour l'Espagne, 'MX' pour le Mexique)." }, - "aiToolClearAllFavoritesDesc": { "message": "Supprime tous les films et séries que l'utilisateur a marqués comme favoris." }, - "aiToolClearViewingHistoryDesc": { "message": "Efface l'historique de visionnage de l'utilisateur de la page d'historique." }, - "aiToolClearRecommendationsViewDesc": { "message": "Efface la vue des recommandations et supprime les recommandations mises en cache." }, - "aiToolSearchNotFound": { "message": "'$query' introuvable dans votre bibliothèque.", "placeholders": { "query": { "content": "$1" } } }, - "aiToolNavigateSuccess": { "message": "Navigation vers la page $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolNavigateError": { "message": "Erreur lors de la navigation vers la page $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolStatsError": { "message": "Erreur lors de l'obtention des statistiques." }, - "aiToolItemNotFound": { "message": "Élément '$title' introuvable.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolShowItemDetailsSuccess": { "message": "Affichage des détails de '$title'.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolAddToPlaylistSuccess": { "message": "'$title' ajouté à la liste de lecture.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteAdded": { "message": "'$title' ajouté aux favoris.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteRemoved": { "message": "'$title' supprimé des favoris.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolRecommendationsSuccess": { "message": "Affichage des recommandations." }, - "aiToolApplyFiltersGenreNotFound": { "message": "Genre '$genre' introuvable.", "placeholders": { "genre": { "content": "$1" } } }, - "aiToolApplyFiltersSuccess": { "message": "Filtres appliqués avec succès." }, - "aiToolPlayMusicNotReady": { "message": "Le lecteur de musique n'est pas prêt. Assurez-vous que votre bibliothèque musicale Plex a été analysée." }, - "aiToolPlayMusicArtistNotFound": { "message": "Artiste '$artist_name' introuvable.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicNoSongs": { "message": "Aucune chanson trouvée pour '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicSuccess": { "message": "Lecture de la musique de '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolChatHistoryCleared": { "message": "Historique du chat effacé." }, - "aiToolConfirmDeleteDatabase": { "message": "Êtes-vous sûr de vouloir supprimer la base de données locale ? Cette action est irréversible." }, - "aiToolDeleteDatabaseCancelled": { "message": "Suppression de la base de données annulée." }, - "aiToolExecutionError": { "message": "Erreur lors de l'exécution de l'outil '$toolName' : $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, - "aiToolUnknown": { "message": "Outil inconnu : '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, - "aiToolFavoritesCleared": { "message": "Favoris effacés." }, - "aiToolFavoritesClearError": { "message": "Erreur lors de l'effacement des favoris : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolRecommendationsCleared": { "message": "Recommandations effacées." }, - "aiToolRecommendationsClearError": { "message": "Erreur lors de l'effacement des recommandations : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleted": { "message": "Base de données supprimée. La page va être rechargée." }, - "aiToolDatabaseDeleteError": { "message": "Erreur lors de la suppression de la base de données : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleteBlocked": { "message": "La suppression de la base de données est bloquée. Fermez les autres onglets de l'application." }, - "aiToolUpdateAllTokensSuccess": { "message": "Tous les jetons ont été mis à jour avec succès." }, - "aiToolUpdateAllTokensError": { "message": "Erreur lors de la mise à jour des jetons : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolAddPlexTokenSuccess": { "message": "Jeton Plex ajouté avec succès." }, - "aiToolAddPlexTokenError": { "message": "Erreur lors de l'ajout du jeton Plex : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolChangeRegionSuccess": { "message": "Région modifiée en $region$. Le contenu est en cours de mise à jour.", "placeholders": { "region": { "content": "$1" } } }, - "aiToolChangeRegionError": { "message": "Erreur lors du changement de région : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolViewingHistoryCleared": { "message": "Historique de visionnage effacé." }, - "aiToolViewingHistoryClearError": { "message": "Erreur lors de l'effacement de l'historique de visionnage : $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiSystemPrompt_v3": { "message": "Vous êtes un assistant expert en films et séries appelé CinePlex. Votre fonction principale est d'aider les utilisateurs à découvrir du contenu et à interagir avec leur bibliothèque. Suivez rigoureusement ces règles : 1. **N'INVENTEZ JAMAIS** avoir effectué une action si vous n'avez pas utilisé d'outil pour cela. Par exemple, ne dites pas 'J'ai téléchargé X' si vous n'avez pas utilisé l'outil de téléchargement. 2. Pour les demandes de recommandations ou de listes (par exemple, 'donnez-moi 5 films d'horreur'), utilisez vos propres connaissances pour générer la liste. Présentez-la sous forme de liste numérotée ou à puces. Après avoir affiché la liste, demandez de manière proactive à l'utilisateur s'il souhaite que vous vérifiiez la disponibilité sur ses serveurs locaux et que vous créiez un fichier M3U. 3. **UNIQUEMENT** si l'utilisateur confirme qu'il souhaite vérifier ou télécharger la liste, utilisez l'outil `check_and_download_titles_list`. Ne l'utilisez pas sans confirmation explicite. 4. Pour toute autre action telle que la navigation, l'obtention de statistiques, la recherche d'un titre spécifique ou le filtrage par résolution ou conteneur, utilisez les outils appropriés. Soyez toujours concis, amical et efficace." }, - "aiToolM3UNoTitlesProvided": { "message": "Veuillez fournir une liste de titres pour créer la liste de lecture." }, - "aiToolM3UCheckingTitles": { "message": "Vérification des titres sur vos serveurs locaux..." }, - "aiToolM3UNoLocalMatchesForDownload": { "message": "Je n'ai trouvé aucun des films ou séries de la liste sur vos serveveurs locaux." }, - "aiToolM3UDownloadStarted": { "message": "Terminé ! J'ai trouvé $1 des $2 titres sur vos serveurs et j'ai lancé le téléchargement de la liste de lecture M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, - "backToProviders": { "message": "Retour aux fournisseurs" }, - "artistsCounterSingle": { "message": "$total$ artiste", "placeholders": { "total": { "content": "$1" } } }, - "artistsCounterLoading": { "message": "Chargement..." }, - "downloadingSong": { "message": "Début du téléchargement de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "songDownloaded": { "message": "\"$title$\" téléchargé.", "placeholders": { "title": { "content": "$1" } } }, - "errorDownloadingSong": { "message": "Erreur lors du téléchargement de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "generatingAlbumM3U": { "message": "Génération de M3U pour \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, - "albumM3UGenerated": { "message": "M3U pour l'album \"$artist$\" généré.", "placeholders": { "artist": { "content": "$1" } } }, - "retyingSection": { "message": "Nouvelle tentative de la section \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "retrySuccess": { "message": "[SUCCÈS] Nouvelle tentative pour \"$title$\" terminée.", "placeholders": { "title": { "content": "$1" } } }, - "retryError": { "message": "[ERREUR FINALE] Échec de la nouvelle tentative pour \"$title$\" : $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, - "startingRetryPhase": { "message": "Démarrage de la phase de nouvelle tentative pour $count$ sections...", "placeholders": { "count": { "content": "$1" } } }, - "tokenFoundServers": { "message": "Jeton $token$... a trouvé $count$ serveurs.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, - "errorProcessingToken": { "message": "Erreur lors du traitement du jeton $token$... : $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, - "plexScanFatalError": { "message": "ERREUR FATALE : $message$", "placeholders": { "message": { "content": "$1" } } }, - "errorDuringScan": { "message": "Erreur pendant l'analyse : $message$", "placeholders": { "message": { "content": "$1" } } }, - "stoppingPlexScan": { "message": "Arrêt de l'analyse Plex..." }, - "invalidTokenProvided": { "message": "Jeton non valide fourni." }, - "tokenAlreadyExists": { "message": "Le jeton existe déjà." }, - "tokenAddedSuccessfully": { "message": "Jeton ajouté avec succès." }, - "noStreamsFoundForSelection": { "message": "Aucun flux trouvé pour la sélection." }, - "autoplayBlocked": { "message": "Lecture automatique bloquée." }, - "page": { "message": "Page" }, - "all": { "message": "Tous" }, - "userScore": { "message": "Score des utilisateurs" }, - "duration": { "message": "Durée" }, - "min": { "message": "Min" }, - "max": { "message": "Max" } + "appName": { "message": "CinePlex" }, + "appDescription": { "message": "Analyse les serveurs Plex pour trouver du contenu et l'affiche dans l'interface" }, + "appTagline": { "message": "Films, Séries et Musique" }, + "appLocaleCode": { "message": "fr-FR" }, + "toggleNavigation": { "message": "Basculer la Navigation" }, + "searchPlaceholder": { "message": "Rechercher des films ou des séries..." }, + "openMusicPlayer": { "message": "Ouvrir le Lecteur de Musique" }, + "settings": { "message": "Paramètres" }, + "navMovies": { "message": "Films" }, + "navSeries": { "message": "Séries" }, + "navProviders": { "message": "Fournisseurs" }, + "navPhotos": { "message": "Photos" }, + "navStats": { "message": "Statistiques" }, + "navFavorites": { "message": "Favoris" }, + "navHistory": { "message": "Historique" }, + "navRecommendations": { "message": "Recommandations" }, + "navMusic": { "message": "Musique" }, + "musicFeaturedPlaylists": { "message": "Playlists à la une" }, + "musicRecentlyAdded": { "message": "Ajouté Récemment" }, + "navM3uGenerator": { "message": "Générateur M3U" }, + "heroWelcome": { "message": "" }, + "heroSubtitle": { "message": "Explorez des milliers de films et de séries." }, + "addStream": { "message": "Ajouter un Stream" }, + "moreInfo": { "message": "Plus d'informations" }, + "popularMovies": { "message": "Films Populaires" }, + "allGenres": { "message": "Tous les genres" }, + "allYears": { "message": "Toutes les années" }, + "sortPopular": { "message": "Les plus populaires" }, + "sortTopRated": { "message": "Les mieux notés" }, + "sortRecent": { "message": "Les plus récents" }, + "loadMore": { "message": "Charger plus" }, + "photosBreadcrumbHome": { "message": "Albums" }, + "selectServer": { "message": "Sélectionnez un serveur" }, + "loading": { "message": "Chargement..." }, + "loadingLibraries": { "message": "Chargement des bibliothèques..." }, + "photosEmptyState": { "message": "Aucun album ni photo trouvé." }, + "photosEmptyStateSub": { "message": "Veuillez sélectionner un serveur ou vous assurer d'avoir une bibliothèque de photos dans Plex." }, + "statsTitle": { "message": "Statistiques de la Bibliothèque" }, + "statsAllTokens": { "message": "Tous les Tokens" }, + "statsAnalyzing": { "message": "Analyse de votre bibliothèque..." }, + "statsActiveTokens": { "message": "Tokens Actifs" }, + "statsServersFound": { "message": "Serveurs Trouvés" }, + "statsUniqueMovies": { "message": "Films Uniques" }, + "statsUniqueSeries": { "message": "Séries Uniques" }, + "statsUniqueArtists": { "message": "Artistes Uniques" }, + "statsTokenServers": { "message": "Serveurs du Token" }, + "statsChartMoviesByGenre": { "message": "Contenu par Genre (Films)" }, + "statsChartSeriesByGenre": { "message": "Contenu par Genre (Séries)" }, + "statsChartByDecade": { "message": "Contenu par Décennie" }, + "recommendationsTitle": { "message": "Recommandations pour vous" }, + "historyTitle": { "message": "Historique de Visionnage" }, + "clearHistory": { "message": "Tout effacer" }, + "consoleTitle": { "message": "Console d'Analyse Plex" }, + "footerCredit": { "message": "Une interface pour votre univers Plex." }, + "closeTrailer": { "message": "Fermer la bande-annonce" }, + "close": { "message": "Fermer" }, + "photoViewer": { "message": "Visionneuse de photos" }, + "previous": { "message": "Précédent" }, + "next": { "message": "Suivant" }, + "notificationTemplateText": { "message": "Notification" }, + "settingsTitleFull": { "message": "Paramètres et Configuration" }, + "settingsTabGeneral": { "message": "Général" }, + "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, + "settingsTabPhpGen": { "message": "Générateur PHP" }, + "settingsTabData": { "message": "Données" }, + "settingsApiServer": { "message": "Configuration API et Serveur" }, + "settingsTmdbApiLabel": { "message": "Clé API TMDB (Optionnel)" }, + "settingsTmdbApiPlaceholder": { "message": "La clé par défaut sera utilisée si laissée vide" }, + "settingsGoogleApiLabel": { "message": "Clé API Google Gemini (Optionnel)" }, + "settingsGoogleApiPlaceholder": { "message": "Nécessaire pour utiliser l'assistant IA" }, + "settingsRegionLabel": { "message": "Région pour la découverte de contenu" }, + "allRegions": { "message": "Toutes les régions" }, + "settingsPhpUrlLabel": { "message": "URL du Serveur pour l'Ajout de Streams" }, + "settingsPhpUrlPlaceholder": { "message": "https://votre-serveur.com/chemin/vers/script.php" }, + "settingsInterface": { "message": "Interface" }, + "settingsLightTheme": { "message": "Mode Clair" }, + "settingsShowHero": { "message": "Afficher la section de bienvenue 'Hero'" }, + "settingsScanContent": { "message": "Analyse de Contenu" }, + "settingsScanDesc": { "message": "Sélectionnez ce que vous voulez analyser et appuyez sur le bouton." }, + "settingsScanMovies": { "message": "Films" }, + "settingsScanShows": { "message": "Séries" }, + "settingsScanArtists": { "message": "Musique" }, + "settingsScanPhotos": { "message": "Photos" }, + "settingsSelectAll": { "message": "Tout Sélectionner" }, + "settingsStartScan": { "message": "Démarrer l'Analyse" }, + "settingsPlexTokens": { "message": "Tokens Plex" }, + "settingsPlexTokensDesc": { "message": "Modifiez la liste des tokens Plex (format JSON)." }, + "settingsSaveTokens": { "message": "Enregistrer les Tokens" }, + "settingsJellyfinTitle": { "message": "Configuration de Jellyfin" }, + "settingsJellyfinDesc": { "message": "Ajoutez les informations de votre serveur Jellyfin pour analyser son contenu." }, + "jellyfinUrlLabel": { "message": "URL du Serveur Jellyfin" }, + "jellyfinUserLabel": { "message": "Nom d'utilisateur" }, + "jellyfinPassLabel": { "message": "Mot de passe" }, + "jellyfinConnectAndScan": { "message": "Connecter et Analyser" }, + "settingsPhpGenTitle": { "message": "Générateur de Script PHP pour le Serveur" }, + "settingsPhpFileOptions": { "message": "Options du Fichier" }, + "settingsPhpSavePathLabel": { "message": "Chemin de Sauvegarde sur le Serveur" }, + "settingsPhpSavePathPlaceholder": { "message": "Ex: /var/www/html/listes (vide pour le même dossier)" }, + "settingsPhpFilenameLabel": { "message": "Nom du Fichier" }, + "settingsPhpFileAction": { "message": "Action sur le Fichier" }, + "settingsPhpActionAppend": { "message": "Ajouter à la fin du fichier (cumulatif)" }, + "settingsPhpActionOverwrite": { "message": "Écraser le fichier (recommencer)" }, + "settingsPhpSecurity": { "message": "Sécurité (Optionnel)" }, + "settingsPhpUseSecretKey": { "message": "Utiliser une clé secrète (Recommandé)" }, + "settingsPhpSecretKeyPlaceholder": { "message": "Entrez une clé secrète sécurisée" }, + "settingsPhpGeneratedCode": { "message": "Code Généré" }, + "settingsPhpGeneratedPlaceholder": { "message": "Le code PHP généré apparaîtra ici." }, + "settingsGenerateScript": { "message": "Générer le Script" }, + "settingsCopyScript": { "message": "Copier le Script" }, + "settingsDataManagement": { "message": "Gestion de la Base de Données Locale" }, + "settingsImportDb": { "message": "Importer la BD depuis un Fichier" }, + "settingsExportDb": { "message": "Exporter la BD vers un Fichier" }, + "settingsClearContent": { "message": "Effacer les Données de Contenu Locales" }, + "settingsClearContentDesc": { "message": "Cette action effacera les films, séries et musiques de la base de données locale, mais n'affectera pas vos favoris ni vos paramètres." }, + "settingsClose": { "message": "Fermer" }, + "settingsSave": { "message": "Enregistrer les Paramètres" }, + "musicSidenavTitle": { "message": "Musique de Plex" }, + "musicAllServers": { "message": "Tous les Serveurs" }, + "musicSearchArtistPlaceholder": { "message": "Rechercher un artiste..." }, + "musicSearchDiscographyPlaceholder": { "message": "Rechercher dans la discographie..." }, + "musicNothingPlaying": { "message": "Rien en cours de lecture" }, + "musicSelectSong": { "message": "Sélectionnez une chanson" }, + "musicToStart": { "message": "pour commencer la lecture" }, + "miniplayerDownloadSong": { "message": "Télécharger la chanson" }, + "miniplayerDownloadAlbum": { "message": "Télécharger M3U" }, + "miniplayerVolume": { "message": "Volume" }, + "miniplayerShuffle": { "message": "Aléatoire" }, + "miniplayerEqualizer": { "message": "Égaliseur" }, + "miniplayerOpenList": { "message": "Ouvrir la liste" }, + "eqTitle": { "message": "Égaliseur Graphique" }, + "eqPresetsLabel": { "message": "Préréglages" }, + "eqPresetFlat": { "message": "Plat" }, + "eqPresetRock": { "message": "Rock" }, + "eqPresetPop": { "message": "Pop" }, + "eqPresetJazz": { "message": "Jazz" }, + "eqPresetClassical": { "message": "Classique" }, + "eqPresetBassBoost": { "message": "Renforcement des Basses" }, + "eqPreampLabel": { "message": "Préamplificateur" }, + "infoModalTitle": { "message": "Information" }, + "infoModalFieldTitle": { "message": "Titre :" }, + "infoModalFieldArtist": { "message": "Artiste :" }, + "infoModalFieldAlbum": { "message": "Album :" }, + "infoModalFieldSong": { "message": "Chanson :" }, + "infoModalFieldYear": { "message": "Année :" }, + "infoModalFieldGenre": { "message": "Genre :" }, + "lang_en": { "message": "Anglais" }, + "lang_es": { "message": "Espagnol" }, + "lang_fr": { "message": "Français" }, + "lang_de": { "message": "Allemand" }, + "lang_it": { "message": "Italien" }, + "lang_pt": { "message": "Portugais" }, + "essentialFeaturesNotSupported": { "message": "Votre navigateur ne prend pas en charge les fonctionnalités essentielles." }, + "dbAccessError": { "message": "Erreur d'accès à la base de données locale." }, + "dbUpdateNeeded": { "message": "La base de données doit être mise à jour, veuillez recharger la page." }, + "dbBlocked": { "message": "Veuillez fermer les autres onglets de cette application pour continuer." }, + "deletingContentData": { "message": "Suppression des données de contenu locales..." }, + "noContentDataToDelete": { "message": "Aucune donnée de contenu à supprimer." }, + "contentDataDeleted": { "message": "Données de contenu supprimées d'IndexedDB." }, + "errorDeletingData": { "message": "Erreur lors de la suppression des données : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailable": { "message": "Éditeur de texte non disponible." }, + "errorLoadingTokens": { "message": "Erreur lors du chargement des tokens pour l'édition." }, + "errorLoadingTokensMessage": { "message": "Erreur lors du chargement des tokens : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailableToSave": { "message": "Éditeur non disponible pour l'enregistrement." }, + "invalidJsonFormat": { "message": "Format JSON invalide. Il doit être { \"tokens\": [...] }" }, + "tokensSaved": { "message": "Tokens enregistrés avec succès." }, + "errorSavingTokens": { "message": "Erreur lors de l'enregistrement des tokens : $message$", "placeholders": { "message": { "content": "$1" } } }, + "dbNotAvailable": { "message": "IndexedDB n'est pas disponible." }, + "dbExported": { "message": "Base de données exportée avec succès." }, + "errorExportingDb": { "message": "Erreur lors de l'exportation de la base de données : $message$", "placeholders": { "message": { "content": "$1" } } }, + "invalidJsonFile": { "message": "Le fichier ne contient pas d'objet JSON valide." }, + "noDataToImport": { "message": "Le fichier ne contient pas de données pour les sections actuelles de la BD." }, + "dbImported": { "message": "Base de données importée avec succès." }, + "errorImportingDb": { "message": "Erreur lors de l'importation de la base de données : $message$", "placeholders": { "message": { "content": "$1" } } }, + "updatingView": { "message": "Mise à jour de la vue avec les nouvelles données..." }, + "confirmClearContent": { "message": "Êtes-vous sûr de vouloir supprimer les données de contenu locales (Films, Séries, Musique, etc.) ? Les Favoris et les Paramètres ne seront PAS supprimés." }, + "trailerNotFound": { "message": "Aucune bande-annonce trouvée pour ce titre." }, + "confirmClearHistory": { "message": "Êtes-vous sûr de vouloir supprimer tout votre historique de visionnage ? Cette action est irréversible." }, + "historyCleared": { "message": "Historique de visionnage effacé." }, + "historyItemDeleted": { "message": "Élément supprimé de l'historique." }, + "errorGeneratingScript": { "message": "Générez d'abord un script pour pouvoir le copier." }, + "scriptCopied": { "message": "Script PHP copié dans le presse-papiers." }, + "errorCopyingScript": { "message": "Erreur lors de la copie du script." }, + "scriptGenerated": { "message": "Script PHP généré." }, + "errorLoadingAlbum": { "message": "Erreur lors du chargement de l'album : $message$", "placeholders": { "message": { "content": "$1" } } }, + "noPhotoServerSelected": { "message": "Erreur : Aucun serveur de photos n'a été sélectionné." }, + "loadingGenres": { "message": "Chargement des genres..." }, + "errorLoadingGenres": { "message": "Erreur de chargement" }, + "noContentFound": { "message": "Aucun résultat trouvé." }, + "couldNotLoadContent": { "message": "Impossible de charger le contenu." }, + "noFavorites": { "message": "Vous n'avez pas encore de favoris." }, + "errorLoadingFavorites": { "message": "Erreur lors du chargement des favoris." }, + "historyEmpty": { "message": "Votre historique est vide." }, + "historyEmptySub": { "message": "Explorez et regardez du contenu pour qu'il apparaisse ici." }, + "errorGeneratingRecommendations": { "message": "Erreur lors de la génération des recommandations." }, + "noRecommendations": { "message": "Nous avons besoin de mieux vous connaître pour vous faire des recommandations !" }, + "errorGeneratingStats": { "message": "Erreur lors de la génération des statistiques." }, + "noServersForToken": { "message": "Aucun serveur associé trouvé pour ce token." }, + "searchingActorContent": { "message": "Recherche de contenu de $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, + "errorLoadingActorContent": { "message": "Impossible de charger le contenu pour $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, + "errorAddingStream": { "message": "Erreur lors de l'ajout du ou des streams : $message$", "placeholders": { "message": { "content": "$1" } } }, + "phpUrlNotConfigured": { "message": "L'URL du serveur PHP n'est pas configurée. Veuillez la configurer dans les Paramètres." }, + "searchingStreams": { "message": "Recherche de streams pour \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "sendingStreams": { "message": "Envoi de $count$ stream(s) au serveur...", "placeholders": { "count": { "content": "$1" } } }, + "streamAddedSuccess": { "message": "Stream(s) ajouté(s) avec succès." }, + "generatingM3U": { "message": "Génération du M3U pour \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "m3uDownloaded": { "message": "\"$title$\" téléchargé.", "placeholders": { "title": { "content": "$1" } } }, + "errorGeneratingM3U": { "message": "Erreur lors de la génération du M3U : $message$", "placeholders": { "message": { "content": "$1" } } }, + "settingsSavedSuccess": { "message": "Paramètres enregistrés avec succès." }, + "errorSavingSettings": { "message": "Erreur lors de l'enregistrement des paramètres dans la base de données." }, + "languageChangeReload": { "message": "Langue modifiée. L'application va maintenant se recharger." }, + "addedToFavorites": { "message": "Ajouté aux favoris." }, + "removedFromFavorites": { "message": "Retiré des favoris." }, + "plexScanInProgress": { "message": "L'analyse Plex est déjà en cours." }, + "plexScanStarting": { "message": "Démarrage de l'analyse Plex..." }, + "noPlexTokens": { "message": "Aucun token Plex configuré." }, + "clearingSections": { "message": "Nettoyage des sections : $sections$", "placeholders": { "sections": { "content": "$1" } } }, + "initialScanPhaseComplete": { "message": "Phase d'analyse initiale terminée." }, + "retryPhaseFinished": { "message": "Phase de nouvelle tentative terminée." }, + "plexScanFinished": { "message": "Analyse terminée. Mise à jour du contenu..." }, + "scanCancelled": { "message": "Analyse annulée par l'utilisateur." }, + "scanCancelledInfo": { "message": "Analyse annulée." }, + "errorInitializingMusicPlayer": { "message": "Erreur lors de l'initialisation du lecteur de musique." }, + "criticalErrorLoadingMusic": { "message": "Erreur critique lors du chargement des données musicales." }, + "errorLoadingArtists": { "message": "Erreur lors du chargement des artistes." }, + "dbUnavailableError": { "message": "Erreur : Base de données non disponible." }, + "updatingMusicData": { "message": "Mise à jour des données musicales..." }, + "musicDataUpdated": { "message": "Données musicales mises à jour." }, + "errorFetchingArtistSongs": { "message": "Erreur lors de la récupération des chansons de l'artiste." }, + "errorLoadingSongs": { "message": "Erreur lors du chargement des chansons." }, + "noArtistsFound": { "message": "Aucun artiste trouvé." }, + "shuffleOn": { "message": "Mode aléatoire activé." }, + "shuffleOff": { "message": "Mode aléatoire désactivé." }, + "playbackError": { "message": "Erreur de lecture" }, + "errorLabel": { "message": "Erreur" }, + "reloadingPage": { "message": "Rechargement de la page..." }, + "viewed": { "message": "Vu" }, + "local": { "message": "Local" }, + "topRatedSort": {"message": "Mieux notés"}, + "recentSort": {"message": "Récents"}, + "popularSort": {"message": "Populaires"}, + "moviesSectionTitle": {"message": "Films"}, + "seriesSectionTitle": {"message": "Séries"}, + "searchResultsFor": {"message": "Résultats pour \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, + "contentFrom": {"message": "Contenu de $actor$", "placeholders": {"actor": {"content": "$1"}}}, + "explore": {"message": "Explorer"}, + "noGenre": {"message": "Sans catégorie"}, + "synopsis": {"message": "Synopsis"}, + "noSynopsis": {"message": "Aucun synopsis disponible."}, + "director": {"message": "Réalisateur :"}, + "writer": {"message": "Scénariste :"}, + "viewOnImdb": {"message": "Voir sur IMDb"}, + "watchTrailer": {"message": "Bande-annonce"}, + "addToFavorites": {"message": "Ajouter aux favoris"}, + "removeFromFavorites": {"message": "Retirer des favoris"}, + "notAvailable": {"message": "Non disponible"}, + "mainCast": {"message": "Distribution Principale"}, + "seasonsAndEpisodes": {"message": "Saisons et Épisodes"}, + "similarContent": {"message": "Contenu Similaire"}, + "filmography": {"message": "Filmographie"}, + "availableOn": {"message": "Disponible sur"}, + "episodesCount": {"message": "$count$ Épisodes", "placeholders": {"count": {"content": "$1"}}}, + "seasonsCount": {"message": "$count$ Saisons", "placeholders": {"count": {"content": "$1"}}}, + "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, + "noTrailerFound": {"message": "Aucune bande-annonce trouvée pour ce titre."}, + "fatalInitError": {"message": "Erreur fatale d'initialisation"}, + "fatalInitErrorSub": {"message": "Impossible de charger l'application."}, + "invalidStreamInfo": {"message": "Informations de stream invalides."}, + "dbUnavailableForStreams": {"message": "Base de données locale non disponible."}, + "noPlexServersForStreams": {"message": "Aucun serveur Plex."}, + "notFoundOnServers": {"message": "\"$query$\" non trouvé sur les serveurs Plex.", "placeholders": {"query": {"content": "$1"}}}, + "relativeTime_justNow": { "message": "À l'instant" }, + "relativeTime_minutesAgo": { "message": "Il y a $count$ minutes", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_hoursAgo": { "message": "Il y a $count$ heures", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_yesterday": { "message": "Hier" }, + "relativeTime_daysAgo": { "message": "Il y a $count$ jours", "placeholders": { "count": { "content": "$1" } } }, + "errorLoadingDetails": { "message": "Erreur de Chargement des Détails" }, + "errorLoadingLocalContent": { "message": "Erreur lors du chargement du contenu local." }, + "errorServerResponse": { "message": "Réponse du serveur non réussie." }, + "errorPlexApi": { "message": "Erreur $status$ de l'API Plex.", "placeholders": { "status": { "content": "$1" } } }, + "errorParsingPlexXml": { "message": "Erreur lors de l'analyse du XML de Plex." }, + "untitled": { "message": "Sans titre" }, + "itemCount": { "message": "$count$ éléments", "placeholders": { "count": { "content": "$1" } } }, + "noPhotoServers": { "message": "Aucun serveur de photos" }, + "jellyfinScanInProgress": { "message": "L'analyse Jellyfin est déjà en cours." }, + "jellyfinScanning": { "message": "Analyse de Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Veuillez compléter l'URL et le nom d'utilisateur de Jellyfin." }, + "jellyfinConnecting": { "message": "Connexion à Jellyfin sur : $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Authentification Jellyfin échouée : $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Authentification Jellyfin réussie." }, + "jellyfinFetchingLibraries": { "message": "Récupération des bibliothèques..." }, + "jellyfinFetchFailed": { "message": "Erreur lors de la récupération des bibliothèques : $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Aucune bibliothèque de films ou de séries trouvée dans Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ bibliothèque(s) de médias trouvée(s).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Succès] '$libraryName' analysée, $count$ titres ajoutés.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Erreur lors de l'analyse de la bibliothèque '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Analyse Jellyfin terminée. $movies$ films et $series$ séries ajoutés.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Identifiants Jellyfin non configurés." }, + "notFoundOnJellyfin": { "message": "\"$query$\" non trouvé sur Jellyfin.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnAnyServer": { "message": "\"$query$\" non trouvé sur aucun serveur.", "placeholders": {"query": {"content": "$1"}}}, + "localOnPlex": { "message": "Sur Plex" }, + "searchOnPlex": { "message": "Chercher sur Plex" }, + "jellyfinTitle": { "message": "Contenu de Jellyfin" }, + "noJellyfinContent": { "message": "Aucun contenu Jellyfin trouvé." }, + "noJellyfinContentSub": { "message": "Assurez-vous d'avoir analysé votre serveur Jellyfin dans les paramètres." }, + "activityViewerTitle": { "message": "Visualiseur d'Activité du Serveur" }, + "activitySelectServer": { "message": "Sélectionnez un serveur" }, + "activityCheckBtn": { "message": "Actualiser" }, + "activityNoSessions": { "message": "Aucune session active sur ce serveur." }, + "activitySessionUser": { "message": "Utilisateur" }, + "activitySessionDevice": { "message": "Appareil" }, + "activitySessionContent": { "message": "Contenu" }, + "activitySessionState": { "message": "État" }, + "activitySessionIdentifier": { "message": "Identifiant du Client" }, + "activityCopyID": { "message": "Copier l'ID" }, + "activityError": { "message": "Impossible d'obtenir l'activité du serveur." }, + "activityCopied": { "message": "Identifiant copié dans le presse-papiers !" }, + "activityCopyError": { "message": "Erreur lors de la copie de l'identifiant." }, + "noProvidersFound": { "message": "Aucun fournisseur trouvé." }, + "availableOnPlex": { "message": "Disponible sur Plex" }, + "m3uGeneratorTitle": { "message": "Générateur de Listes M3U" }, + "selectAServer": { "message": "Sélectionnez un serveur..." }, + "downloadM3u": { "message": "Télécharger M3U" }, + "m3uGenerator": { "message": "Générateur M3U" }, + "selectLibraries": { "message": "Sélectionner les Bibliothèques" }, + "howToUse": { "message": "Comment Utiliser" }, + "m3uInstruction1": { "message": "Choisissez un serveur dans la liste." }, + "m3uInstruction2": { "message": "Sélectionnez une ou plusieurs bibliothèques à inclure." }, + "m3uInstruction3": { "message": "Cliquez sur le bouton de téléchargement." }, + "m3uInstruction4": { "message": "Importez le fichier .m3u dans votre lecteur compatible." }, + "chatOpen": { "message": "Ouvrir le Chat" }, + "chatTitle": { "message": "Assistant IA" }, + "chatClose": { "message": "X" }, + "chatPlaceholder": { "message": "Écrivez votre message..." }, + "chatSend": { "message": "➤" }, + "chatWelcome": { "message": "Bienvenue ! Je suis votre assistant CinePlex. Posez-moi des questions sur les films, les séries ou tout ce que vous voulez savoir." }, + "chatGoogleApiKeyMissing": { "message": "La clé de l'API Google Gemini n'est pas configurée. Veuillez la configurer dans les paramètres de l'extension pour utiliser l'assistant IA." }, + "chatApiInvalidResponse": { "message": "L'API a renvoyé une réponse invalide. Veuillez réessayer." }, + "chatApiError": { "message": "Erreur de communication avec l'assistant IA" }, + "downloadAll": { "message": "Tout télécharger" }, + "download": { "message": "Télécharger" }, + "aiToolSearchLibraryDesc": { "message": "Recherche dans la bibliothèque Plex de l'utilisateur des films ou des séries par titre." }, + "aiToolSearchLibraryQueryParamDesc": { "message": "Le titre du film ou de la série à rechercher." }, + "aiToolSearchLibraryTypeParamDesc": { "message": "Le type de contenu à rechercher. Peut être 'movie' pour les films ou 'series' pour les séries. (Optionnel)." }, + "aiToolSearchLibraryResolutionParamDesc": { "message": "La résolution vidéo à rechercher (par ex., '4k', '1080p'). (Optionnel)." }, + "aiToolSearchLibraryContainerParamDesc": { "message": "Le format de conteneur vidéo à rechercher (par ex., 'mkv', 'mp4'). (Optionnel)." }, + "aiToolNavigateToPageDesc": { "message": "Navigue l'utilisateur vers une page spécifique de l'interface de l'application." }, + "aiToolNavigateToPagePageParamDesc": { "message": "Le nom de la page vers laquelle naviguer, par ex. : 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator' ou 'music'." }, + "aiToolGetUserStatsDesc": { "message": "Obtient et affiche les statistiques de la bibliothèque de l'utilisateur, comme le nombre total de films, séries et artistes uniques." }, + "aiToolShowItemDetailsDesc": { "message": "Affiche la page de détails d'un film ou d'une série spécifique par son titre et son type." }, + "aiToolShowItemDetailsTitleParamDesc": { "message": "Le titre exact du film ou de la série." }, + "aiToolShowItemDetailsTypeParamDesc": { "message": "Le type de contenu. Doit être 'movie' ou 'series'." }, + "aiToolAddToPlaylistDesc": { "message": "Ajoute un film ou une série à la liste de lecture actuelle de l'utilisateur pour la diffuser sur un serveur PHP configuré." }, + "aiToolAddToPlaylistTitleParamDesc": { "message": "Le titre du film ou de la série à ajouter." }, + "aiToolAddToPlaylistTypeParamDesc": { "message": "Le type de contenu. Doit être 'movie' ou 'series'." }, + "aiToolDownloadSingleMovieM3UDesc": { "message": "Génère et télécharge un fichier de liste de lecture M3U pour un seul film disponible localement." }, + "aiToolDownloadSingleMovieM3UTitleParamDesc": { "message": "Le titre du film pour lequel le M3U sera généré." }, + "aiToolDownloadSingleMovieM3UYearParamDesc": { "message": "L'année de sortie du film (optionnel, pour plus de précision)." }, + "aiToolDownloadSeriesSeasonM3UDesc": { "message": "Génère et télécharge un fichier de liste de lecture M3U pour une saison spécifique d'une série disponible localement." }, + "aiToolDownloadSeriesSeasonM3UTitleParamDesc": { "message": "Le titre de la série." }, + "aiToolDownloadSeriesSeasonM3USeasonParamDesc": { "message": "Le numéro de la saison à télécharger." }, + "aiToolDownloadSeriesSeasonM3UYearParamDesc": { "message": "L'année de sortie de la série (optionnel)." }, + "aiToolCheckAndDownloadDesc": { "message": "Vérifie la disponibilité d'une liste de titres de films ou de séries sur les serveurs locaux de l'utilisateur et, s'ils sont trouvés, génère et télécharge un fichier de liste de lecture M3U avec les flux trouvés." }, + "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Un tableau de titres de films ou de séries à rechercher et à télécharger." }, + "aiToolCheckAndDownloadTypeParamDesc": { "message": "Le type de contenu de la liste. Doit être 'movie' ou 'series'." }, + "aiToolCheckAndDownloadFilenameParamDesc": { "message": "Le nom du fichier M3U à télécharger (par ex., 'MaListe.m3u'). S'il n'est pas fourni, un nom par défaut sera utilisé." }, + "aiToolToggleFavoriteDesc": { "message": "Ajoute ou supprime un film ou une série de la liste des favoris de l'utilisateur." }, + "aiToolToggleFavoriteTitleParamDesc": { "message": "Le titre du film ou de la série." }, + "aiToolToggleFavoriteTypeParamDesc": { "message": "Le type de contenu. Doit être 'movie' ou 'series'." }, + "aiToolGetRecommendationsDesc": { "message": "Génère et affiche une liste de recommandations de films ou de séries basées sur l'historique de visionnage et les favoris de l'utilisateur." }, + "aiToolApplyFiltersDesc": { "message": "Applique des filtres à la vue actuelle des films ou des séries, permettant d'affiner les résultats par type, genre, année et ordre de tri." }, + "aiToolApplyFiltersTypeParamDesc": { "message": "Le type de contenu auquel appliquer les filtres. Doit être 'movie' ou 'series'." }, + "aiToolApplyFiltersGenreParamDesc": { "message": "Le nom du genre par lequel filtrer (par ex., 'Action', 'Drame')." }, + "aiToolApplyFiltersYearParamDesc": { "message": "L'année de sortie par laquelle filtrer (par ex., '2023')." }, + "aiToolApplyFiltersSortParamDesc": { "message": "Le critère de tri pour les résultats. Valeurs valides : 'popularity.desc' (populaires), 'vote_average.desc' (mieux notés), 'release_date.desc' (récents pour les films) ou 'first_air_date.desc' (récents pour les séries)." }, + "aiToolListAvailableMusicGenresDesc": { "message": "Liste tous les genres musicaux uniques disponibles dans la bibliothèque locale de l'utilisateur." }, + "aiToolSearchMusicByGenreDesc": { "message": "Recherche des artistes dans la bibliothèque musicale de l'utilisateur appartenant à un genre spécifique." }, + "aiToolSearchMusicByGenreNameParamDesc": { "message": "Le nom du genre musical à rechercher (ex. 'Rock', 'Pop', 'Jazz')." }, + "aiToolPlayMusicByArtistDesc": { "message": "Ouvre le lecteur de musique et commence à jouer les chansons d'un artiste spécifique de la bibliothèque de l'utilisateur." }, + "aiToolPlayMusicByArtistNameParamDesc": { "message": "Le nom exact de l'artiste dont les chansons doivent être jouées." }, + "aiToolClearChatHistoryDesc": { "message": "Efface tout l'historique des messages de la conversation actuelle avec l'assistant IA." }, + "aiToolDeleteDatabaseDesc": { "message": "Supprime toute la base de données locale de l'extension, y compris le contenu analysé, les paramètres et les favoris. Cette action est irréversible et rechargera l'application." }, + "aiToolUpdateAllTokensDesc": { "message": "Lance une analyse complète de tous les serveurs et bibliothèques Plex associés aux tokens configurés dans l'extension. Met à jour tous les films, séries, artistes et photos." }, + "aiToolAddPlexTokenDesc": { "message": "Ajoute un nouveau token X-Plex à la configuration de l'extension, permettant à l'application d'analyser le contenu de nouveaux serveurs Plex." }, + "aiToolAddPlexTokenTokenParamDesc": { "message": "La chaîne du token X-Plex à ajouter." }, + "aiToolChangeRegionDesc": { "message": "Modifie la région utilisée pour la découverte de contenu dans l'API de TMDB. Cela affectera les résultats affichés dans les sections des films et des séries, ainsi que les fournisseurs de streaming." }, + "aiToolChangeRegionRegionParamDesc": { "message": "Le code de pays ISO 3166-1 à deux lettres pour la nouvelle région (par ex., 'US' pour les États-Unis, 'FR' pour la France, 'CA' pour le Canada)." }, + "aiToolClearAllFavoritesDesc": { "message": "Supprime tous les films et séries que l'utilisateur a marqués comme favoris." }, + "aiToolClearViewingHistoryDesc": { "message": "Efface l'historique de visionnage de l'utilisateur de la page d'historique." }, + "aiToolClearRecommendationsViewDesc": { "message": "Nettoie la vue des recommandations et supprime les recommandations mises en cache." }, + "aiToolSearchNotFound": { "message": "Impossible de trouver '$query' dans votre bibliothèque.", "placeholders": { "query": { "content": "$1" } } }, + "aiToolNavigateSuccess": { "message": "Navigué vers la page $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolNavigateError": { "message": "Erreur de navigation vers la page $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolStatsError": { "message": "Erreur lors de l'obtention des statistiques." }, + "aiToolItemNotFound": { "message": "Élément '$title' non trouvé.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolShowItemDetailsSuccess": { "message": "Affichage des détails de '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolAddToPlaylistSuccess": { "message": "Ajouté '$title' à la liste de lecture.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteAdded": { "message": "Ajouté '$title' aux favoris.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteRemoved": { "message": "Supprimé '$title' des favoris.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolRecommendationsSuccess": { "message": "Affichage des recommandations." }, + "aiToolApplyFiltersGenreNotFound": { "message": "Genre '$genre' non trouvé.", "placeholders": { "genre": { "content": "$1" } } }, + "aiToolApplyFiltersSuccess": { "message": "Filtres appliqués avec succès." }, + "aiToolSearchMusicByGenreNotFound": { "message": "Je n'ai pas trouvé d'artistes du genre '$genre_name' dans votre bibliothèque.", "placeholders": { "genre_name": { "content": "$1" } } }, + "aiToolPlayMusicNotReady": { "message": "Le lecteur de musique n'est pas prêt. Assurez-vous que votre bibliothèque musicale Plex a été analysée." }, + "aiToolPlayMusicArtistNotFound": { "message": "Artiste '$artist_name' non trouvé.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicNoSongs": { "message": "Aucune chanson trouvée pour '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicSuccess": { "message": "Lecture de la musique de '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolChatHistoryCleared": { "message": "Historique du chat effacé." }, + "aiToolConfirmDeleteDatabase": { "message": "Êtes-vous sûr de vouloir supprimer la base de données locale ? Cette action est irréversible." }, + "aiToolDeleteDatabaseCancelled": { "message": "Suppression de la base de données annulée." }, + "aiToolExecutionError": { "message": "Erreur lors de l'exécution de l'outil '$toolName' : $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, + "aiToolUnknown": { "message": "Outil inconnu : '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, + "aiToolFavoritesCleared": { "message": "Favoris supprimés." }, + "aiToolFavoritesClearError": { "message": "Erreur lors de la suppression des favoris : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolRecommendationsCleared": { "message": "Recommandations supprimées." }, + "aiToolRecommendationsClearError": { "message": "Erreur lors de la suppression des recommandations : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleted": { "message": "Base de données supprimée. La page va se recharger." }, + "aiToolDatabaseDeleteError": { "message": "Erreur lors de la suppression de la base de données : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleteBlocked": { "message": "La suppression de la base de données est bloquée. Veuillez fermer les autres onglets de l'application." }, + "aiToolUpdateAllTokensSuccess": { "message": "Tous les tokens ont été mis à jour avec succès." }, + "aiToolUpdateAllTokensError": { "message": "Erreur lors de la mise à jour des tokens : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolAddPlexTokenSuccess": { "message": "Token Plex ajouté avec succès." }, + "aiToolAddPlexTokenError": { "message": "Erreur lors de l'ajout du token Plex : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolChangeRegionSuccess": { "message": "Région changée en $region$. Le contenu est en cours de mise à jour.", "placeholders": { "region": { "content": "$1" } } }, + "aiToolChangeRegionError": { "message": "Erreur lors du changement de région : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolViewingHistoryCleared": { "message": "Historique de visionnage effacé." }, + "aiToolViewingHistoryClearError": { "message": "Erreur lors de l'effacement de l'historique de visionnage : $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSingle": { "message": "Démarrage du téléchargement du M3U pour '$movie_title'.", "placeholders": { "movie_title": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSeason": { "message": "Démarrage du téléchargement du M3U pour la saison $1 de '$2'.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolM3UNoTitlesProvided": { "message": "Veuillez fournir une liste de titres pour créer la liste de lecture." }, + "aiToolM3UCheckingTitles": { "message": "Vérification des titres sur vos serveurs locaux..." }, + "aiToolM3UNoLocalMatchesForDownload": { "message": "Je n'ai trouvé aucun des films ou séries de la liste sur vos serveurs locaux." }, + "aiToolM3UDownloadStarted": { "message": "Terminé ! J'ai trouvé $1 des $2 titres sur vos serveurs et j'ai commencé le téléchargement de la liste de lecture M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolTrailerNotFoundSpecific": { "message": "Désolé, je n'ai pas pu trouver de bande-annonce disponible pour '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiSystemPrompt_v4": { + "message": "Vous êtes un assistant virtuel intégré à une extension Chrome qui interagit avec les serveurs Plex et Jellyfin. Votre fonction principale est d'aider l'utilisateur à rechercher, gérer, lire et télécharger du contenu multimédia, ainsi qu'à gérer des paramètres personnalisés.\n\nPRIORITÉ MAXIMALE : Chaque fois que la question de l'utilisateur concerne du contenu multimédia (films, séries, musique), vous DEVEZ supposer qu'elle se réfère à sa bibliothèque locale. Utilisez les outils pour rechercher dans sa base de données AVANT de chercher sur le web.\n\n🎯 Règles générales de comportement :\nRépondez toujours de manière claire, concise et directe. Soyez proactif et fournissez toutes les informations pertinentes en une seule fois pour éviter les questions de suivi. Par exemple, en confirmant la disponibilité d'une série, incluez les détails des saisons.\n\nComparez la date actuelle avec les résultats de recherche Google lorsque l'on vous demande des informations externes pour garantir qu'elles sont à jour.\n\nUtilisez les noms exacts des commandes définies dans le système (function.name) lors de l'appel d'outils.\n\n📦 Fonctions clés pour le contenu multimédia :\nPour générer un M3U pour un seul film, utilisez download_single_movie_m3u.\nPour télécharger une saison spécifique d'une série, utilisez download_series_season_m3u.\nPour plusieurs titres (films ou séries complètes), utilisez toujours check_and_download_titles_list.\nPour rechercher du contenu local : search_library.\nPour rechercher sur TMDB : search_tmdb_content.\nPour le contenu tendance : get_trending_content.\nPour afficher les détails d'un titre : show_item_details.\nPour ajouter à la liste de lecture PHP : add_to_playlist.\nPour vérifier la disponibilité locale : check_local_availability.\nSi une série est disponible localement, indiquez combien de saisons il y a et sur quels serveurs en utilisant get_local_series_seasons.\nPour voir les recommandations : get_recommendations.\nPour appliquer des filtres : apply_filters.\nPour voir l'historique ou les favoris : view_history, view_favorites.\nPour marquer comme favori : toggle_favorite.\nPour lire la bande-annonce : play_trailer.\n\n🎵 Fonctions musicales :\nSi l'utilisateur demande des recommandations de genres musicaux de manière générale (ex. 'recommande-moi un genre pour me remonter le moral'), utilisez d'abord list_available_music_genres pour voir quels genres il possède et basez votre recommandation sur cette liste.\nPour lister tous les genres musicaux disponibles dans la bibliothèque : list_available_music_genres.\nPour rechercher des artistes par genre : search_music_by_genre.\nPour lire des chansons par titre et/ou artiste : play_song.\nPour lire la musique d'un artiste : play_music_by_artist.\n\n🧰 Fonctions de gestion et de configuration :\nPour obtenir les statistiques de l'utilisateur : get_user_stats.\nPour naviguer vers des sections spécifiques : navigate_to_page.\nPour mettre à jour les tokens : update_all_tokens, add_plex_token.\nPour changer la région du contenu : change_region.\nPour exporter ou importer la base de données locale : export_local_database, import_local_database.\nPour supprimer la base de données : delete_database.\nPour effacer les favoris, l'historique ou les recommandations : clear_all_favorites, clear_viewing_history, clear_recommendations_view.\nPour basculer le mode clair/sombre : toggle_light_mode.\nPour afficher ou masquer la section héros : toggle_hero_section.\n\n⚠️ Considérations supplémentaires :\nDonnez la priorité au contenu disponible localement. Utilisez check_local_availability avant d'afficher les options de lecture ou de téléchargement.\nSi une action échoue, signalez-le de manière claire et directe.\nÉvitez de répéter inutilement la demande de l'utilisateur, sauf si cela aide à contextualiser la réponse." + }, + "backToProviders": { "message": "Retour aux Fournisseurs" }, + "artistsCounterSingle": { "message": "$total$ Artiste", "placeholders": { "total": { "content": "$1" } } }, + "artistsCounterLoading": { "message": "Chargement..." }, + "downloadingSong": { "message": "Début du téléchargement de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "songDownloaded": { "message": "\"$title$\" téléchargé.", "placeholders": { "title": { "content": "$1" } } }, + "errorDownloadingSong": { "message": "Erreur lors du téléchargement de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "generatingAlbumM3U": { "message": "Génération du M3U pour \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, + "albumM3UGenerated": { "message": "M3U pour l'album \"$artist$\" généré.", "placeholders": { "artist": { "content": "$1" } } }, + "retyingSection": { "message": "Nouvelle tentative pour la section \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "retrySuccess": { "message": "[SUCCÈS] Nouvelle tentative pour \"$title$\" terminée.", "placeholders": { "title": { "content": "$1" } } }, + "retryError": { "message": "[ERREUR FINALE] Échec de la nouvelle tentative pour \"$title$\" : $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, + "startingRetryPhase": { "message": "Démarrage de la phase de nouvelle tentative pour $count$ sections...", "placeholders": { "count": { "content": "$1" } } }, + "tokenFoundServers": { "message": "Token $token$... a trouvé $count$ serveurs.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, + "errorProcessingToken": { "message": "Erreur de traitement du token $token$... : $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, + "plexScanFatalError": { "message": "ERREUR FATALE : $message$", "placeholders": { "message": { "content": "$1" } } }, + "errorDuringScan": { "message": "Erreur pendant l'analyse : $message$", "placeholders": { "message": { "content": "$1" } } }, + "stoppingPlexScan": { "message": "Arrêt de l'analyse Plex..." }, + "invalidTokenProvided": { "message": "Token fourni invalide." }, + "tokenAlreadyExists": { "message": "Le token existe déjà." }, + "tokenAddedSuccessfully": { "message": "Token ajouté avec succès." }, + "noStreamsFoundForSelection": { "message": "Aucun stream trouvé pour la sélection." }, + "autoplayBlocked": { "message": "Lecture automatique bloquée." }, + "welcomeToCinePlex": { "message": "" }, + "page": { "message": "Page" }, + "all": { "message": "Tout" }, + "userScore": { "message": "Score" }, + "duration": { "message": "Durée" }, + "min": { "message": "Min" }, + "max": { "message": "Max" }, + "aiToolFindStreamingProvidersDesc": { "message": "Trouve où regarder un film ou une série sur les services de streaming." }, + "aiToolFindStreamingProvidersTitleParamDesc": { "message": "Le titre du film ou de la série à rechercher." }, + "aiToolFindStreamingProvidersTypeParamDesc": { "message": "Le type de contenu (film ou série)." }, + "aiToolFindStreamingProvidersYearParamDesc": { "message": "L'année de sortie du contenu (optionnel)." }, + "aiToolNoStreamingProviders": { "message": "Aucun fournisseur de streaming trouvé pour {title}." }, + "aiToolStreamingProvidersFound": { "message": "{title} est disponible sur les services suivants : {providers}." }, + "aiToolStreamingProviderError": { "message": "Erreur lors de la recherche des fournisseurs de streaming : {message}." }, + "aiToolGetLocalSeriesSeasonsDesc": { "message": "Vérifie si une série télévisée est disponible localement et renvoie une ventilation détaillée des saisons disponibles sur chaque serveur." }, + "aiToolGetLocalSeriesSeasonsTitleParamDesc": { "message": "Le titre de la série à vérifier." }, + "aiToolGetLocalSeriesSeasonsYearParamDesc": { "message": "L'année de sortie de la série (optionnel pour une plus grande précision)." }, + "aiToolLocalSeriesNoSeasons": { "message": "La série '$series_title' est dans votre bibliothèque, mais aucun détail de saison n'a été trouvé.", "placeholders": { "series_title": { "content": "$1" } } }, + "artist": { "message": "Artiste" }, + "tracks": { "message": "pistes" }, + "noSongsFound": { "message": "Aucune chanson trouvée pour cet artiste." }, + "durationMin": { "message": "Durée (Min)" }, + "score": { "message": "Score" }, + "searchGenre": { "message": "Rechercher un genre..." }, + "searchArtists": { "message": "Rechercher des artistes..." }, + "preparingMusicLibrary": { "message": "Préparation de votre bibliothèque musicale..." }, + "preparingMusicLibraryDesc": { "message": "Ce processus unique peut prendre quelques minutes si vous avez de nombreux artistes." }, + "artistsProgress": { "message": "0 / 0 artistes" }, + "starting": { "message": "Démarrage..." }, + "artistName": { "message": "Nom de l'Artiste" }, + "playPause": { "message": "Lecture/Pause" }, + "noLocalFilesFound": { "message": "Aucun fichier local trouvé pour ce titre." }, + "server": { "message": "Serveur" }, + "title": { "message": "Titre" }, + "year": { "message": "Année" }, + "resolution": { "message": "Résolution" }, + "size": { "message": "Taille" }, + "container": { "message": "Conteneur" }, + "action": { "message": "Action" }, + "generate": { "message": "Générer" }, + "availableLocalFiles": { "message": "Fichiers Locaux Disponibles" }, + "downloadSeason": { "message": "Télécharger la Saison" }, + "errorLoadingServersM3u": { "message": "Erreur lors du chargement des serveurs pour le générateur M3U :" }, + "errorFetchingLibraries": { "message": "Erreur lors de la récupération des bibliothèques." }, + "selectServerAndLibrary": { "message": "Veuillez sélectionner un serveur et au moins une bibliothèque." }, + "generating": { "message": "Génération..." }, + "errorProcessingLibrary": { "message": "Erreur lors du traitement de la bibliothèque" }, + "errorProcessingLibrarySkipping": { "message": "Erreur lors du traitement de la bibliothèque. Ignorer." }, + "allLibrariesFailed": { "message": "Toutes les bibliothèques sélectionnées n'ont pas pu être traitées." }, + "m3uGeneratedWithErrors": { "message": "M3U généré avec quelques erreurs. Certaines bibliothèques peuvent manquer." }, + "m3uDownloadedSuccess": { "message": "Liste de lecture M3U téléchargée avec succès." }, + "errorGeneratingM3uFile": { "message": "Erreur lors de la génération du fichier M3U." }, + "chatSources": { "message": "Sources" }, + "chatUnnamedSource": { "message": "Source sans nom" }, + "googleApiFailure": { "message": "Échec de l'appel à l'API Google AI :" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index eb06da2..1f3f0f2 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -1,449 +1,516 @@ { - "appName": { "message": "CinePlex" }, - "appDescription": { "message": "Scansiona i server Plex alla ricerca di contenuti e li visualizza nell'interfaccia" }, - "appTagline": { "message": "Film, Serie e Musica" }, - "appLocaleCode": { "message": "it-IT" }, - "toggleNavigation": { "message": "Attiva/disattiva la navigazione" }, - "searchPlaceholder": { "message": "Cerca film o serie..." }, - "openMusicPlayer": { "message": "Apri il lettore musicale" }, - "settings": { "message": "Impostazioni" }, - "navMovies": { "message": "Film" }, - "navSeries": { "message": "Serie" }, - "navProviders": { "message": "Provider" }, - "navPhotos": { "message": "Foto" }, - "navStats": { "message": "Statistiche" }, - "navFavorites": { "message": "Preferiti" }, - "navHistory": { "message": "Cronologia" }, - "navRecommendations": { "message": "Raccomandazioni" }, - "navMusic": { "message": "Musica" }, - "navM3uGenerator": { "message": "Generatore M3U" }, - "heroWelcome": { "message": "" }, - "heroSubtitle": { "message": "Esplora migliaia di film e serie." }, - "addStream": { "message": "Aggiungi streaming" }, - "moreInfo": { "message": "Più informazioni" }, - "popularMovies": { "message": "Film popolari" }, - "allGenres": { "message": "Tutti i generi" }, - "allYears": { "message": "Tutti gli anni" }, - "sortPopular": { "message": "I più popolari" }, - "sortTopRated": { "message": "I più votati" }, - "sortRecent": { "message": "I più recenti" }, - "loadMore": { "message": "Carica altro" }, - "photosBreadcrumbHome": { "message": "Album" }, - "selectServer": { "message": "Seleziona un server" }, - "loading": { "message": "Caricamento..." }, - "loadingLibraries": { "message": "Caricamento delle librerie..." }, - "photosEmptyState": { "message": "Nessun album o foto trovati." }, - "photosEmptyStateSub": { "message": "Seleziona un server o assicurati di avere una libreria di foto in Plex." }, - "statsTitle": { "message": "Statistiche della libreria" }, - "statsAllTokens": { "message": "Tutti i token" }, - "statsAnalyzing": { "message": "Analisi della tua libreria..." }, - "statsActiveTokens": { "message": "Token attivi" }, - "statsServersFound": { "message": "Server trovati" }, - "statsUniqueMovies": { "message": "Film unici" }, - "statsUniqueSeries": { "message": "Serie uniche" }, - "statsUniqueArtists": { "message": "Artisti unici" }, - "statsTokenServers": { "message": "Server token" }, - "statsChartMoviesByGenre": { "message": "Contenuti per genere (Film)" }, - "statsChartSeriesByGenre": { "message": "Contenuti per genere (Serie)" }, - "statsChartByDecade": { "message": "Contenuti per decennio" }, - "recommendationsTitle": { "message": "Raccomandazioni per te" }, - "historyTitle": { "message": "Cronologia visualizzazioni" }, - "clearHistory": { "message": "Cancella tutto" }, - "consoleTitle": { "message": "Console di scansione Plex" }, - "footerCredit": { "message": "Un'interfaccia per il tuo universo Plex." }, - "closeTrailer": { "message": "Chiudi trailer" }, - "close": { "message": "Chiudi" }, - "photoViewer": { "message": "Visualizzatore di foto" }, - "previous": { "message": "Precedente" }, - "next": { "message": "Successivo" }, - "notificationTemplateText": { "message": "Notifica" }, - "settingsTitleFull": { "message": "Impostazioni e configurazione" }, - "settingsTabGeneral": { "message": "Generale" }, - "settingsTabPlex": { "message": "Plex" }, - "settingsTabJellyfin": { "message": "Jellyfin" }, - "settingsTabPhpGen": { "message": "Generatore PHP" }, - "settingsTabData": { "message": "Dati" }, - "settingsApiServer": { "message": "Impostazioni API e server" }, - "settingsTmdbApiLabel": { "message": "Chiave API TMDB (facoltativa)" }, - "settingsTmdbApiPlaceholder": { "message": "La chiave predefinita verrà utilizzata se lasciata vuota" }, - "settingsGoogleApiLabel": { "message": "Chiave API Google Gemini (facoltativa)" }, - "settingsGoogleApiPlaceholder": { "message": "Necessaria per utilizzare l'assistente AI" }, - "settingsRegionLabel": { "message": "Regione per la scoperta di contenuti" }, - "allRegions": { "message": "Tutte le regioni" }, - "settingsPhpUrlLabel": { "message": "URL del server per l'aggiunta di streaming" }, - "settingsPhpUrlPlaceholder": { "message": "https://tuo-server.com/percorso/dello/script.php" }, - "settingsInterface": { "message": "Interfaccia" }, - "settingsLightTheme": { "message": "Modalità chiara" }, - "settingsShowHero": { "message": "Mostra la sezione di benvenuto 'Hero'" }, - "settingsScanContent": { "message": "Scansione dei contenuti" }, - "settingsScanDesc": { "message": "Seleziona cosa scansionare e premi il pulsante." }, - "settingsScanMovies": { "message": "Film" }, - "settingsScanShows": { "message": "Serie" }, - "settingsScanArtists": { "message": "Musica" }, - "settingsScanPhotos": { "message": "Foto" }, - "settingsSelectAll": { "message": "Seleziona tutto" }, - "settingsStartScan": { "message": "Avvia scansione" }, - "settingsPlexTokens": { "message": "Token Plex" }, - "settingsPlexTokensDesc": { "message": "Modifica l'elenco dei token Plex (formato JSON)." }, - "settingsSaveTokens": { "message": "Salva token" }, - "settingsJellyfinTitle": { "message": "Impostazioni Jellyfin" }, - "settingsJellyfinDesc": { "message": "Aggiungi i dettagli del tuo server Jellyfin per scansionarne il contenuto." }, - "jellyfinUrlLabel": { "message": "URL del server Jellyfin" }, - "jellyfinUserLabel": { "message": "Nome utente" }, - "jellyfinPassLabel": { "message": "Password" }, - "jellyfinConnectAndScan": { "message": "Connetti e scansiona" }, - "settingsPhpGenTitle": { "message": "Generatore di script PHP per server" }, - "settingsPhpFileOptions": { "message": "Opzioni file" }, - "settingsPhpSavePathLabel": { "message": "Percorso di salvataggio sul server" }, - "settingsPhpSavePathPlaceholder": { "message": "Es: /var/www/html/liste (vuoto per la stessa cartella)" }, - "settingsPhpFilenameLabel": { "message": "Nome file" }, - "settingsPhpFileAction": { "message": "Azione file" }, - "settingsPhpActionAppend": { "message": "Aggiungi alla fine del file (cumulativo)" }, - "settingsPhpActionOverwrite": { "message": "Sovrascrivi il file (ricomincia da capo)" }, - "settingsPhpSecurity": { "message": "Sicurezza (facoltativa)" }, - "settingsPhpUseSecretKey": { "message": "Usa chiave segreta (consigliato)" }, - "settingsPhpSecretKeyPlaceholder": { "message": "Inserisci una chiave segreta sicura" }, - "settingsPhpGeneratedCode": { "message": "Codice generato" }, - "settingsPhpGeneratedPlaceholder": { "message": "Il codice PHP generato apparirà qui." }, - "settingsGenerateScript": { "message": "Genera script" }, - "settingsCopyScript": { "message": "Copia script" }, - "settingsDataManagement": { "message": "Gestione del database locale" }, - "settingsImportDb": { "message": "Importa DB da file" }, - "settingsExportDb": { "message": "Esporta DB su file" }, - "settingsClearContent": { "message": "Cancella i dati dei contenuti locali" }, - "settingsClearContentDesc": { "message": "Questa azione eliminerà film, serie e musica dal database locale, ma non influirà sui preferiti o sulle impostazioni." }, - "settingsClose": { "message": "Chiudi" }, - "settingsSave": { "message": "Salva impostazioni" }, - "musicSidenavTitle": { "message": "Musica Plex" }, - "musicAllServers": { "message": "Tutti i server" }, - "musicSearchArtistPlaceholder": { "message": "Cerca un artista..." }, - "musicSearchDiscographyPlaceholder": { "message": "Cerca nella discografia..." }, - "musicNothingPlaying": { "message": "Nessuna riproduzione in corso" }, - "musicSelectSong": { "message": "Seleziona un brano" }, - "musicToStart": { "message": "per avviare la riproduzione" }, - "miniplayerDownloadSong": { "message": "Scarica brano" }, - "miniplayerDownloadAlbum": { "message": "Scarica album M3U" }, - "miniplayerVolume": { "message": "Volume" }, - "miniplayerShuffle": { "message": "Casuale" }, - "miniplayerEqualizer": { "message": "Equalizzatore" }, - "miniplayerOpenList": { "message": "Apri elenco" }, - "eqTitle": { "message": "Equalizzatore grafico" }, - "eqPresetsLabel": { "message": "Preimpostazioni" }, - "eqPresetFlat": { "message": "Piatto" }, - "eqPresetRock": { "message": "Rock" }, - "eqPresetPop": { "message": "Pop" }, - "eqPresetJazz": { "message": "Jazz" }, - "eqPresetClassical": { "message": "Classica" }, - "eqPresetBassBoost": { "message": "Aumento dei bassi" }, - "eqPreampLabel": { "message": "Preamplificatore" }, - "infoModalTitle": { "message": "Informazioni" }, - "infoModalFieldTitle": { "message": "Titolo:" }, - "infoModalFieldArtist": { "message": "Artista:" }, - "infoModalFieldAlbum": { "message": "Album:" }, - "infoModalFieldSong": { "message": "Brano:" }, - "infoModalFieldYear": { "message": "Anno:" }, - "infoModalFieldGenre": { "message": "Genere:" }, - "lang_en": { "message": "Inglese" }, - "lang_es": { "message": "Spagnolo" }, - "lang_fr": { "message": "Francese" }, - "lang_de": { "message": "Tedesco" }, - "lang_it": { "message": "Italiano" }, - "lang_pt": { "message": "Portoghese" }, - "essentialFeaturesNotSupported": { "message": "Il tuo browser non supporta le funzionalità essenziali." }, - "dbAccessError": { "message": "Errore di accesso al database locale." }, - "dbUpdateNeeded": { "message": "Il database deve essere aggiornato, ricarica la pagina." }, - "dbBlocked": { "message": "Chiudi le altre schede di questa applicazione per continuare." }, - "deletingContentData": { "message": "Eliminazione dei dati dei contenuti locali..." }, - "noContentDataToDelete": { "message": "Nessun dato di contenuto da eliminare." }, - "contentDataDeleted": { "message": "Dati dei contenuti eliminati da IndexedDB." }, - "errorDeletingData": { "message": "Errore durante l'eliminazione dei dati: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailable": { "message": "Editor di testo non disponibile." }, - "errorLoadingTokens": { "message": "Errore durante il caricamento dei token per la modifica." }, - "errorLoadingTokensMessage": { "message": "Errore durante il caricamento dei token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailableToSave": { "message": "Editor non disponibile per il salvataggio." }, - "invalidJsonFormat": { "message": "Formato JSON non valido. Deve essere { \"tokens\": [...] }" }, - "tokensSaved": { "message": "Token salvati correttamente." }, - "errorSavingTokens": { "message": "Errore durante il salvataggio dei token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "dbNotAvailable": { "message": "IndexedDB non è disponibile." }, - "dbExported": { "message": "Database esportato correttamente." }, - "errorExportingDb": { "message": "Errore durante l'esportazione del database: $message$", "placeholders": { "message": { "content": "$1" } } }, - "invalidJsonFile": { "message": "Il file non contiene un oggetto JSON valido." }, - "noDataToImport": { "message": "Il file non contiene dati per le sezioni correnti del database." }, - "dbImported": { "message": "Database importato correttamente." }, - "errorImportingDb": { "message": "Errore durante l'importazione del database: $message$", "placeholders": { "message": { "content": "$1" } } }, - "updatingView": { "message": "Aggiornamento della vista con i nuovi dati..." }, - "confirmClearContent": { "message": "Sei sicuro di voler eliminare i dati dei contenuti locali (film, serie, musica, ecc.)? I preferiti e le impostazioni NON verranno eliminati." }, - "trailerNotFound": { "message": "Nessun trailer trovato per questo titolo." }, - "confirmClearHistory": { "message": "Sei sicuro di voler cancellare tutta la cronologia delle visualizzazioni? Questa azione non può essere annullata." }, - "historyCleared": { "message": "Cronologia visualizzazioni cancellata." }, - "historyItemDeleted": { "message": "Elemento eliminato dalla cronologia." }, - "errorGeneratingScript": { "message": "Genera prima uno script per poterlo copiare." }, - "scriptCopied": { "message": "Script PHP copiato negli appunti." }, - "errorCopyingScript": { "message": "Errore durante la copia dello script." }, - "scriptGenerated": { "message": "Script PHP generato." }, - "errorLoadingAlbum": { "message": "Errore durante il caricamento dell'album: $message$", "placeholders": { "message": { "content": "$1" } } }, - "noPhotoServerSelected": { "message": "Errore: non è stato selezionato alcun server di foto." }, - "loadingGenres": { "message": "Caricamento dei generi..." }, - "errorLoadingGenres": { "message": "Errore durante il caricamento" }, - "noContentFound": { "message": "Nessun risultato trovato." }, - "couldNotLoadContent": { "message": "Impossibile caricare il contenuto." }, - "noFavorites": { "message": "Non hai ancora preferiti." }, - "errorLoadingFavorites": { "message": "Errore durante il caricamento dei preferiti." }, - "historyEmpty": { "message": "La tua cronologia è vuota." }, - "historyEmptySub": { "message": "Esplora e guarda i contenuti perché appaiano qui." }, - "errorGeneratingRecommendations": { "message": "Errore durante la generazione delle raccomandazioni." }, - "noRecommendations": { "message": "Dobbiamo conoscerti meglio per darti consigli!" }, - "errorGeneratingStats": { "message": "Errore durante la generazione delle statistiche." }, - "noServersForToken": { "message": "Nessun server associato trovato per questo token." }, - "searchingActorContent": { "message": "Ricerca di contenuti di $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, - "errorLoadingActorContent": { "message": "Impossibile caricare i contenuti per $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, - "errorAddingStream": { "message": "Errore durante l'aggiunta di streaming: $message$", "placeholders": { "message": { "content": "$1" } } }, - "phpUrlNotConfigured": { "message": "L'URL del server PHP non è configurato. Configuralo nelle Impostazioni." }, - "searchingStreams": { "message": "Ricerca di streaming per \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "sendingStreams": { "message": "Invio di $count$ streaming al server...", "placeholders": { "count": { "content": "$1" } } }, - "streamAddedSuccess": { "message": "Streaming aggiunto/i con successo." }, - "generatingM3U": { "message": "Generazione di M3U per \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "m3uDownloaded": { "message": "\"$title$\" scaricato.", "placeholders": { "title": { "content": "$1" } } }, - "errorGeneratingM3U": { "message": "Errore durante la generazione di M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, - "settingsSavedSuccess": { "message": "Impostazioni salvate correttamente." }, - "errorSavingSettings": { "message": "Errore durante il salvataggio delle impostazioni nel database." }, - "languageChangeReload": { "message": "Lingua modificata. L'applicazione verrà ora ricaricata." }, - "addedToFavorites": { "message": "Aggiunto ai preferiti." }, - "removedFromFavorites": { "message": "Rimosso dai preferiti." }, - "plexScanInProgress": { "message": "La scansione di Plex è già in corso." }, - "plexScanStarting": { "message": "Avvio della scansione di Plex..." }, - "noPlexTokens": { "message": "Nessun token Plex configurato." }, - "clearingSections": { "message": "Cancellazione delle sezioni: $sections$", "placeholders": { "sections": { "content": "$1" } } }, - "initialScanPhaseComplete": { "message": "Fase di scansione iniziale completata." }, - "retryPhaseFinished": { "message": "Fase di tentativi ripetuti terminata." }, - "plexScanFinished": { "message": "Scansione terminata. Aggiornamento dei contenuti..." }, - "scanCancelled": { "message": "Scansione annullata dall'utente." }, - "scanCancelledInfo": { "message": "Scansione annullata." }, - "errorInitializingMusicPlayer": { "message": "Errore durante l'inizializzazione del lettore musicale." }, - "criticalErrorLoadingMusic": { "message": "Errore critico durante il caricamento dei dati musicali." }, - "errorLoadingArtists": { "message": "Errore durante il caricamento degli artisti." }, - "dbUnavailableError": { "message": "Errore: database non disponibile." }, - "updatingMusicData": { "message": "Aggiornamento dei dati musicali..." }, - "musicDataUpdated": { "message": "Dati musicali aggiornati." }, - "errorFetchingArtistSongs": { "message": "Errore durante il recupero dei brani dell'artista." }, - "errorLoadingSongs": { "message": "Errore durante il caricamento dei brani." }, - "noArtistsFound": { "message": "Nessun artista trovato." }, - "shuffleOn": { "message": "Modalità casuale attivata." }, - "shuffleOff": { "message": "Modalità casuale disattivata." }, - "playbackError": { "message": "Errore di riproduzione" }, - "errorLabel": { "message": "Errore" }, - "reloadingPage": { "message": "Ricaricamento della pagina..." }, - "viewed": { "message": "Visto" }, - "local": { "message": "Locale" }, - "topRatedSort": {"message": "I più votati"}, - "recentSort": {"message": "Recenti"}, - "popularSort": {"message": "Popolari"}, - "moviesSectionTitle": {"message": "Film"}, - "seriesSectionTitle": {"message": "Serie"}, - "searchResultsFor": {"message": "Risultati per \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, - "contentFrom": {"message": "Contenuti di $actor$", "placeholders": {"actor": {"content": "$1"}}}, - "explore": {"message": "Esplora"}, - "noGenre": {"message": "Senza categoria"}, - "synopsis": {"message": "Sinossi"}, - "noSynopsis": {"message": "Nessuna sinossi disponibile."}, - "director": {"message": "Regista:"}, - "writer": {"message": "Sceneggiatore:"}, - "viewOnImdb": {"message": "Vedi su IMDb"}, - "watchTrailer": {"message": "Guarda il trailer"}, - "addToFavorites": {"message": "Aggiungi ai preferiti"}, - "removeFromFavorites": {"message": "Rimuovi dai preferiti"}, - "notAvailable": {"message": "Non disponibile"}, - "mainCast": {"message": "Cast principale"}, - "seasonsAndEpisodes": {"message": "Stagioni ed episodi"}, - "similarContent": {"message": "Contenuti simili"}, - "filmography": {"message": "Filmografia"}, - "availableOn": {"message": "Disponibile su"}, - "episodesCount": {"message": "$count$ episodi", "placeholders": {"count": {"content": "$1"}}}, - "seasonsCount": {"message": "$count$ stagioni", "placeholders": {"count": {"content": "$1"}}}, - "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, - "noTrailerFound": {"message": "Nessun trailer trovato per questo titolo."}, - "fatalInitError": {"message": "Errore fatale di inizializzazione"}, - "fatalInitErrorSub": {"message": "Impossibile caricare l'applicazione."}, - "invalidStreamInfo": {"message": "Informazioni non valide."}, - "dbUnavailableForStreams": {"message": "Database locale non disponibile."}, - "noPlexServersForStreams": {"message": "Nessun server Plex."}, - "notFoundOnServers": {"message": "\"$query$\" non trovato sui server Plex.", "placeholders": {"query": {"content": "$1"}}}, - "relativeTime_justNow": { "message": "Poco fa" }, - "relativeTime_minutesAgo": { "message": "$count$ minuti fa", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_hoursAgo": { "message": "$count$ ore fa", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_yesterday": { "message": "Ieri" }, - "relativeTime_daysAgo": { "message": "$count$ giorni fa", "placeholders": { "count": { "content": "$1" } } }, - "errorLoadingDetails": { "message": "Errore durante il caricamento dei dettagli" }, - "errorLoadingLocalContent": { "message": "Errore durante il caricamento del contenuto locale." }, - "errorServerResponse": { "message": "Risposta del server non riuscita." }, - "errorPlexApi": { "message": "Errore $status$ dell'API Plex.", "placeholders": { "status": { "content": "$1" } } }, - "errorParsingPlexXml": { "message": "Errore durante l'analisi dell'XML di Plex." }, - "untitled": { "message": "Senza titolo" }, - "itemCount": { "message": "$count$ elementi", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Nessun server di foto" }, - "jellyfinScanInProgress": { "message": "La scansione di Jellyfin è già in corso." }, - "jellyfinScanning": { "message": "Scansione di Jellyfin..." }, - "jellyfinMissingCredentials": { "message": "Completa l'URL e il nome utente di Jellyfin." }, - "jellyfinConnecting": { "message": "Connessione a Jellyfin in corso: $url$", "placeholders": { "url": { "content": "$1" } } }, - "jellyfinAuthFailed": { "message": "Autenticazione Jellyfin non riuscita: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinAuthSuccess": { "message": "Autenticazione Jellyfin riuscita." }, - "jellyfinFetchingLibraries": { "message": "Recupero delle librerie..." }, - "jellyfinFetchFailed": { "message": "Errore durante il recupero delle librerie: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinNoMediaLibraries": { "message": "Nessuna libreria di film o serie trovata in Jellyfin." }, - "jellyfinLibrariesFound": { "message": "$count$ libreria/e multimediale/i trovata/e.", "placeholders": { "count": { "content": "$1" } } }, - "jellyfinLibraryScanSuccess": { "message": "[Successo] '$libraryName' scansionata, $count$ titoli aggiunti.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, - "jellyfinLibraryScanFailed": { "message": "Errore durante la scansione della libreria '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, - "jellyfinScanSuccess": { "message": "Scansione Jellyfin completata. Aggiunti $movies$ film e $series$ serie.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, - "noJellyfinCredentials": { "message": "Credenziali Jellyfin non configurate." }, - "notFoundOnJellyfin": { "message": "\"$query$\" non trovato su Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, - "notFoundOnAnyServer": { "message": "\"$query$\" non trovato su nessun server.", "placeholders": { "query": { "content": "$1" } } }, - "localOnPlex": { "message": "Su Plex" }, - "searchOnPlex": { "message": "Cerca su Plex" }, - "jellyfinTitle": { "message": "Contenuti Jellyfin" }, - "noJellyfinContent": { "message": "Nessun contenuto Jellyfin trovato." }, - "noJellyfinContentSub": { "message": "Assicurati di aver scansionato il tuo server Jellyfin nelle impostazioni." }, - "activityViewerTitle": { "message": "Visualizzatore attività del server" }, - "activitySelectServer": { "message": "Seleziona un server" }, - "activityCheckBtn": { "message": "Aggiorna" }, - "activityNoSessions": { "message": "Nessuna sessione attiva su questo server." }, - "activitySessionUser": { "message": "Utente" }, - "activitySessionDevice": { "message": "Dispositivo" }, - "activitySessionContent": { "message": "Contenuto" }, - "activitySessionState": { "message": "Stato" }, - "activitySessionIdentifier": { "message": "Identificatore del client" }, - "activityCopyID": { "message": "Copia ID" }, - "activityError": { "message": "Impossibile ottenere l'attività del server." }, - "activityCopied": { "message": "Identificatore copiato negli appunti!" }, - "activityCopyError": { "message": "Errore durante la copia dell'identificatore." }, - "noProvidersFound": { "message": "Nessun provider trovato." }, - "availableOnPlex": { "message": "Disponibile su Plex" }, - "m3uGeneratorTitle": { "message": "Generatore di elenchi M3U" }, - "selectAServer": { "message": "Seleziona un server..." }, - "downloadM3u": { "message": "Scarica M3U" }, - "m3uGenerator": { "message": "Generatore M3U" }, - "selectLibraries": { "message": "Seleziona librerie" }, - "howToUse": { "message": "Come usare" }, - "m3uInstruction1": { "message": "Scegli un server dall'elenco." }, - "m3uInstruction2": { "message": "Seleziona una o più librerie da includere." }, - "m3uInstruction3": { "message": "Fai clic sul pulsante di download." }, - "m3uInstruction4": { "message": "Importa il file .m3u nel tuo lettore compatibile." }, - "chatOpen": { "message": "Apri chat" }, - "chatTitle": { "message": "Assistente AI" }, - "chatClose": { "message": "X" }, - "chatPlaceholder": { "message": "Scrivi il tuo messaggio..." }, - "chatSend": { "message": "➤" }, - "chatWelcome": { "message": "Benvenuto! Sono il tuo assistente CinePlex. Chiedimi di film, serie o qualsiasi altra cosa tu voglia sapere." }, - "chatGoogleApiKeyMissing": { "message": "La chiave API di Google Gemini non è configurata. Impostala nelle impostazioni dell'estensione per utilizzare l'assistente AI." }, - "chatApiInvalidResponse": { "message": "L'API ha restituito una risposta non valida. Riprova." }, - "chatApiError": { "message": "Errore di comunicazione con l'assistente AI" }, - "downloadAll": { "message": "Scarica tutto" }, - "download": { "message": "Scarica" }, - "aiToolSearchLibraryDesc": { "message": "Cerca nella libreria Plex dell'utente film o serie per titolo." }, - "aiToolSearchLibraryQueryParamDesc": { "message": "Il titolo del film o della serie da cercare." }, - "aiToolSearchLibraryTypeParamDesc": { "message": "Il tipo di contenuto da cercare. Può essere 'movie' per i film o 'series' per le serie. (Facoltativo)." }, - "aiToolSearchLibraryResolutionParamDesc": { "message": "La risoluzione video da cercare (ad es. '4k', '1080p'). (Facoltativo)." }, - "aiToolSearchLibraryContainerParamDesc": { "message": "Il formato del contenitore video da cercare (ad es. 'mkv', 'mp4'). (Facoltativo)." }, - "aiToolNavigateToPageDesc": { "message": "Indirizza l'utente a una pagina specifica dell'interfaccia dell'applicazione." }, - "aiToolNavigateToPagePageParamDesc": { "message": "Il nome della pagina a cui navigare, ad es.: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers' o 'm3u-generator'." }, - "aiToolGetUserStatsDesc": { "message": "Recupera e visualizza le statistiche della libreria dell'utente, come il numero totale di film, serie e artisti unici." }, - "aiToolShowItemDetailsDesc": { "message": "Visualizza la pagina dei dettagli di un film o di una serie specifica in base al titolo e al tipo." }, - "aiToolShowItemDetailsTitleParamDesc": { "message": "Il titolo esatto del film o della serie." }, - "aiToolShowItemDetailsTypeParamDesc": { "message": "Il tipo di contenuto. Deve essere 'movie' o 'series'." }, - "aiToolAddToPlaylistDesc": { "message": "Aggiunge un film o una serie alla playlist corrente dell'utente per lo streaming su un server PHP configurato." }, - "aiToolAddToPlaylistTitleParamDesc": { "message": "Il titolo del film o della serie da aggiungere." }, - "aiToolAddToPlaylistTypeParamDesc": { "message": "Il tipo di contenuto. Deve essere 'movie' o 'series'." }, - "aiToolCheckAndDownloadDesc": { "message": "Controlla la disponibilità di un elenco di titoli di film o serie sui server locali dell'utente e, se trovati, genera e scarica un file di playlist M3U con gli streaming trovati." }, - "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Un array di titoli di film o serie da cercare e scaricare." }, - "aiToolCheckAndDownloadTypeParamDesc": { "message": "Il tipo di contenuto dell'elenco. Deve essere 'movie' o 'series'." }, - "aiToolCheckAndDownloadFilenameParamDesc": { "message": "Il nome del file M3U da scaricare (ad es. 'MiaLista.m3u'). Se non fornito, verrà utilizzato un nome predefinito." }, - "aiToolToggleFavoriteDesc": { "message": "Aggiunge o rimuove un film o una serie dall'elenco dei preferiti dell'utente." }, - "aiToolToggleFavoriteTitleParamDesc": { "message": "Il titolo del film o della serie." }, - "aiToolToggleFavoriteTypeParamDesc": { "message": "Il tipo di contenuto. Deve essere 'movie' o 'series'." }, - "aiToolGetRecommendationsDesc": { "message": "Genera e visualizza un elenco di consigli di film o serie basati sulla cronologia di visualizzazione e sui preferiti dell'utente." }, - "aiToolApplyFiltersDesc": { "message": "Applica filtri alla visualizzazione corrente di film o serie, consentendo di affinare i risultati per tipo, genere, anno e ordine di ordinamento." }, - "aiToolApplyFiltersTypeParamDesc": { "message": "Il tipo di contenuto a cui applicare i filtri. Deve essere 'movie' o 'series'." }, - "aiToolApplyFiltersGenreParamDesc": { "message": "Il nome del genere per cui filtrare (ad es. 'Azione', 'Drammatico')." }, - "aiToolApplyFiltersYearParamDesc": { "message": "L'anno di uscita per cui filtrare (ad es. '2023')." }, - "aiToolApplyFiltersSortParamDesc": { "message": "Il criterio di ordinamento per i risultati. Valori validi: 'popularity.desc' (popolari), 'vote_average.desc' (più votati), 'release_date.desc' (recenti per i film) o 'first_air_date.desc' (recenti per le serie)." }, - "aiToolPlayMusicByArtistDesc": { "message": "Apre il lettore musicale e avvia la riproduzione dei brani di un artista specifico dalla libreria dell'utente." }, - "aiToolPlayMusicByArtistNameParamDesc": { "message": "Il nome esatto dell'artista di cui si desidera riprodurre i brani." }, - "aiToolClearChatHistoryDesc": { "message": "Cancella tutta la cronologia dei messaggi della conversazione corrente con l'assistente AI." }, - "aiToolDeleteDatabaseDesc": { "message": "Elimina l'intero database locale dell'estensione, inclusi i contenuti scansionati, le impostazioni e i preferiti. Questa azione è irreversibile e ricaricherà l'applicazione." }, - "aiToolUpdateAllTokensDesc": { "message": "Avvia una scansione completa di tutti i server e le librerie Plex associati ai token configurati nell'estensione. Aggiorna tutti i film, le serie, gli artisti e le foto." }, - "aiToolAddPlexTokenDesc": { "message": "Aggiunge un nuovo token X-Plex alla configurazione dell'estensione, consentendo all'applicazione di scansionare i contenuti di nuovi server Plex." }, - "aiToolAddPlexTokenTokenParamDesc": { "message": "La stringa del token X-Plex da aggiungere." }, - "aiToolChangeRegionDesc": { "message": "Modifica la regione utilizzata per la scoperta di contenuti nell'API TMDB. Ciò influirà sui risultati visualizzati nelle sezioni di film e serie, nonché sui provider di streaming." }, - "aiToolChangeRegionRegionParamDesc": { "message": "Il codice paese ISO 3166-1 a due lettere per la nuova regione (ad es. 'US' per gli Stati Uniti, 'ES' per la Spagna, 'MX' per il Messico)." }, - "aiToolClearAllFavoritesDesc": { "message": "Rimuove tutti i film e le serie che l'utente ha contrassegnato come preferiti." }, - "aiToolClearViewingHistoryDesc": { "message": "Cancella la cronologia di visualizzazione dell'utente dalla pagina della cronologia." }, - "aiToolClearRecommendationsViewDesc": { "message": "Svuota la vista dei consigli e rimuove i consigli memorizzati nella cache." }, - "aiToolSearchNotFound": { "message": "'$query' non trovato nella tua libreria.", "placeholders": { "query": { "content": "$1" } } }, - "aiToolNavigateSuccess": { "message": "Navigato alla pagina $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolNavigateError": { "message": "Errore durante la navigazione alla pagina $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolStatsError": { "message": "Errore nel recupero delle statistiche." }, - "aiToolItemNotFound": { "message": "Elemento '$title' non trovato.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolShowItemDetailsSuccess": { "message": "Visualizzazione dei dettagli di '$title'.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolAddToPlaylistSuccess": { "message": "Aggiunto '$title' alla playlist.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteAdded": { "message": "Aggiunto '$title' ai preferiti.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteRemoved": { "message": "Rimosso '$title' dai preferiti.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolRecommendationsSuccess": { "message": "Visualizzazione dei consigli." }, - "aiToolApplyFiltersGenreNotFound": { "message": "Genere '$genre' non trovato.", "placeholders": { "genre": { "content": "$1" } } }, - "aiToolApplyFiltersSuccess": { "message": "Filtri applicati correttamente." }, - "aiToolPlayMusicNotReady": { "message": "Il lettore musicale non è pronto. Assicurati che la tua libreria musicale di Plex sia stata scansionata." }, - "aiToolPlayMusicArtistNotFound": { "message": "Artista '$artist_name' non trovato.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicNoSongs": { "message": "Nessun brano trovato per '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicSuccess": { "message": "Riproduzione di musica di '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolChatHistoryCleared": { "message": "Cronologia della chat cancellata." }, - "aiToolConfirmDeleteDatabase": { "message": "Sei sicuro di voler eliminare il database locale? Questa azione è irreversibile." }, - "aiToolDeleteDatabaseCancelled": { "message": "Eliminazione del database annullata." }, - "aiToolExecutionError": { "message": "Errore durante l'esecuzione dello strumento '$toolName$': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, - "aiToolUnknown": { "message": "Strumento sconosciuto: '$toolName$'.", "placeholders": { "toolName": { "content": "$1" } } }, - "aiToolFavoritesCleared": { "message": "Preferiti cancellati." }, - "aiToolFavoritesClearError": { "message": "Errore durante la cancellazione dei preferiti: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolRecommendationsCleared": { "message": "Consigli cancellati." }, - "aiToolRecommendationsClearError": { "message": "Errore durante la cancellazione dei consigli: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleted": { "message": "Database eliminato. La pagina verrà ricaricata." }, - "aiToolDatabaseDeleteError": { "message": "Errore durante l'eliminazione del database: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleteBlocked": { "message": "L'eliminazione del database è bloccata. Chiudi le altre schede dell'applicazione." }, - "aiToolUpdateAllTokensSuccess": { "message": "Tutti i token sono stati aggiornati correttamente." }, - "aiToolUpdateAllTokensError": { "message": "Errore durante l'aggiornamento dei token: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolAddPlexTokenSuccess": { "message": "Token Plex aggiunto correttamente." }, - "aiToolAddPlexTokenError": { "message": "Errore durante l'aggiunta del token Plex: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolChangeRegionSuccess": { "message": "Regione modificata in $region$. Il contenuto è in fase di aggiornamento.", "placeholders": { "region": { "content": "$1" } } }, - "aiToolChangeRegionError": { "message": "Errore durante la modifica della regione: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolViewingHistoryCleared": { "message": "Cronologia visualizzazioni cancellata." }, - "aiToolViewingHistoryClearError": { "message": "Errore durante la cancellazione della cronologia di visualizzazione: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiSystemPrompt_v3": { "message": "Sei un assistente esperto di film e serie chiamato CinePlex. La tua funzione principale è aiutare gli utenti a scoprire contenuti e a interagire con la loro libreria. Segui rigorosamente queste regole: 1. **NON FINGERE MAI** di aver eseguito un'azione se non hai utilizzato uno strumento per farlo. Ad esempio, non dire 'Ho scaricato X' se non hai utilizzato lo strumento di download. 2. Per le richieste di consigli o elenchi (ad es. 'dimmi 5 film dell'orrore'), usa le tue conoscenze per generare l'elenco. Presentalo in formato numerato o puntato. Dopo aver visualizzato l'elenco, chiedi proattivamente all'utente se desidera che tu verifichi la disponibilità sui suoi server locali e crei un file M3U. 3. **SOLO** se l'utente conferma di voler controllare o scaricare l'elenco, utilizza lo strumento `check_and_download_titles_list`. Non utilizzarlo senza una conferma esplicita. 4. Per qualsiasi altra azione come la navigazione, l'ottenimento di statistiche, la ricerca di un titolo specifico o il filtraggio per risoluzione o contenitore, utilizza gli strumenti appropriati. Sii sempre conciso, amichevole ed efficiente." }, - "aiToolM3UNoTitlesProvided": { "message": "Fornisci un elenco di titoli per creare la playlist." }, - "aiToolM3UCheckingTitles": { "message": "Controllo dei titoli sui tuoi server locali..." }, - "aiToolM3UNoLocalMatchesForDownload": { "message": "Non ho trovato nessuno dei film o delle serie dell'elenco sui tuoi server locali." }, - "aiToolM3UDownloadStarted": { "message": "Fatto! Ho trovato $1 dei $2 titoli sui tuoi server e ho avviato il download della playlist M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, - "backToProviders": { "message": "Torna ai provider" }, - "artistsCounterSingle": { "message": "$total$ artista", "placeholders": { "total": { "content": "$1" } } }, - "artistsCounterLoading": { "message": "Caricamento..." }, - "downloadingSong": { "message": "Avvio del download di \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "songDownloaded": { "message": "\"$title$\" scaricato.", "placeholders": { "title": { "content": "$1" } } }, - "errorDownloadingSong": { "message": "Errore durante il download di \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "generatingAlbumM3U": { "message": "Generazione di M3U per \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, - "albumM3UGenerated": { "message": "M3U per l'album \"$artist$\" generato.", "placeholders": { "artist": { "content": "$1" } } }, - "retyingSection": { "message": "Nuovo tentativo per la sezione \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "retrySuccess": { "message": "[SUCCESSO] Nuovo tentativo per \"$title$\" completato.", "placeholders": { "title": { "content": "$1" } } }, - "retryError": { "message": "[ERRORE FINALE] Tentativo fallito per \"$title$\": $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, - "startingRetryPhase": { "message": "Avvio della fase di tentativi ripetuti per $count$ sezioni...", "placeholders": { "count": { "content": "$1" } } }, - "tokenFoundServers": { "message": "Token $token$... ha trovato $count$ server.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, - "errorProcessingToken": { "message": "Errore durante l'elaborazione del token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, - "plexScanFatalError": { "message": "ERRORE FATALE: $message$", "placeholders": { "message": { "content": "$1" } } }, - "errorDuringScan": { "message": "Errore durante la scansione: $message$", "placeholders": { "message": { "content": "$1" } } }, - "stoppingPlexScan": { "message": "Arresto della scansione di Plex..." }, - "invalidTokenProvided": { "message": "Token non valido fornito." }, - "tokenAlreadyExists": { "message": "Il token esiste già." }, - "tokenAddedSuccessfully": { "message": "Token aggiunto correttamente." }, - "noStreamsFoundForSelection": { "message": "Nessuno streaming trovato per la selezione." }, - "autoplayBlocked": { "message": "Riproduzione automatica bloccata." }, - "page": { "message": "Pagina" }, - "all": { "message": "Tutti" }, - "userScore": { "message": "Punteggio degli utenti" }, - "duration": { "message": "Durata" }, - "min": { "message": "Min" }, - "max": { "message": "Max" } + "appName": { "message": "CinePlex" }, + "appDescription": { "message": "Scansiona i server Plex per trovare contenuti e li visualizza nell'interfaccia" }, + "appTagline": { "message": "Film, Serie e Musica" }, + "appLocaleCode": { "message": "it-IT" }, + "toggleNavigation": { "message": "Attiva/Disattiva Navigazione" }, + "searchPlaceholder": { "message": "Cerca film o serie..." }, + "openMusicPlayer": { "message": "Apri Lettore Musicale" }, + "settings": { "message": "Impostazioni" }, + "navMovies": { "message": "Film" }, + "navSeries": { "message": "Serie" }, + "navProviders": { "message": "Fornitori" }, + "navPhotos": { "message": "Foto" }, + "navStats": { "message": "Statistiche" }, + "navFavorites": { "message": "Preferiti" }, + "navHistory": { "message": "Cronologia" }, + "navRecommendations": { "message": "Raccomandazioni" }, + "navMusic": { "message": "Musica" }, + "musicFeaturedPlaylists": { "message": "Playlist in Evidenza" }, + "musicRecentlyAdded": { "message": "Aggiunti di Recente" }, + "navM3uGenerator": { "message": "Generatore M3U" }, + "heroWelcome": { "message": "" }, + "heroSubtitle": { "message": "Esplora migliaia di film e serie." }, + "addStream": { "message": "Aggiungi Stream" }, + "moreInfo": { "message": "Maggiori informazioni" }, + "popularMovies": { "message": "Film Popolari" }, + "allGenres": { "message": "Tutti i generi" }, + "allYears": { "message": "Tutti gli anni" }, + "sortPopular": { "message": "Più popolari" }, + "sortTopRated": { "message": "I più votati" }, + "sortRecent": { "message": "I più recenti" }, + "loadMore": { "message": "Carica altro" }, + "photosBreadcrumbHome": { "message": "Album" }, + "selectServer": { "message": "Seleziona un server" }, + "loading": { "message": "Caricamento in corso..." }, + "loadingLibraries": { "message": "Caricamento librerie..." }, + "photosEmptyState": { "message": "Nessun album o foto trovati." }, + "photosEmptyStateSub": { "message": "Seleziona un server o assicurati di avere una libreria di foto in Plex." }, + "statsTitle": { "message": "Statistiche della Libreria" }, + "statsAllTokens": { "message": "Tutti i Token" }, + "statsAnalyzing": { "message": "Analisi della tua libreria in corso..." }, + "statsActiveTokens": { "message": "Token Attivi" }, + "statsServersFound": { "message": "Server Trovati" }, + "statsUniqueMovies": { "message": "Film Unici" }, + "statsUniqueSeries": { "message": "Serie Uniche" }, + "statsUniqueArtists": { "message": "Artisti Unici" }, + "statsTokenServers": { "message": "Server del Token" }, + "statsChartMoviesByGenre": { "message": "Contenuti per Genere (Film)" }, + "statsChartSeriesByGenre": { "message": "Contenuti per Genere (Serie)" }, + "statsChartByDecade": { "message": "Contenuti per Decennio" }, + "recommendationsTitle": { "message": "Raccomandazioni per te" }, + "historyTitle": { "message": "Cronologia di Visione" }, + "clearHistory": { "message": "Cancella Tutto" }, + "consoleTitle": { "message": "Console di Scansione Plex" }, + "footerCredit": { "message": "Un'interfaccia per il tuo universo Plex." }, + "closeTrailer": { "message": "Chiudi trailer" }, + "close": { "message": "Chiudi" }, + "photoViewer": { "message": "Visualizzatore di foto" }, + "previous": { "message": "Precedente" }, + "next": { "message": "Successivo" }, + "notificationTemplateText": { "message": "Notifica" }, + "settingsTitleFull": { "message": "Impostazioni e Configurazione" }, + "settingsTabGeneral": { "message": "Generale" }, + "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, + "settingsTabPhpGen": { "message": "Generatore PHP" }, + "settingsTabData": { "message": "Dati" }, + "settingsApiServer": { "message": "Configurazione API e Server" }, + "settingsTmdbApiLabel": { "message": "Chiave API di TMDB (Opzionale)" }, + "settingsTmdbApiPlaceholder": { "message": "Verrà utilizzata la chiave predefinita se lasciato vuoto" }, + "settingsGoogleApiLabel": { "message": "Chiave API di Google Gemini (Opzionale)" }, + "settingsGoogleApiPlaceholder": { "message": "Necessaria per utilizzare l'assistente AI" }, + "settingsRegionLabel": { "message": "Regione per la scoperta di contenuti" }, + "allRegions": { "message": "Tutte le regioni" }, + "settingsPhpUrlLabel": { "message": "URL del Server per Aggiungere Stream" }, + "settingsPhpUrlPlaceholder": { "message": "https://tuo-server.com/percorso/dello/script.php" }, + "settingsInterface": { "message": "Interfaccia" }, + "settingsLightTheme": { "message": "Modalità Chiara" }, + "settingsShowHero": { "message": "Mostra sezione di benvenuto 'Hero'" }, + "settingsScanContent": { "message": "Scansione Contenuti" }, + "settingsScanDesc": { "message": "Seleziona cosa scansionare e premi il pulsante." }, + "settingsScanMovies": { "message": "Film" }, + "settingsScanShows": { "message": "Serie" }, + "settingsScanArtists": { "message": "Musica" }, + "settingsScanPhotos": { "message": "Foto" }, + "settingsSelectAll": { "message": "Seleziona Tutto" }, + "settingsStartScan": { "message": "Avvia Scansione" }, + "settingsPlexTokens": { "message": "Token di Plex" }, + "settingsPlexTokensDesc": { "message": "Modifica l'elenco dei token di Plex (formato JSON)." }, + "settingsSaveTokens": { "message": "Salva Token" }, + "settingsJellyfinTitle": { "message": "Configurazione di Jellyfin" }, + "settingsJellyfinDesc": { "message": "Aggiungi i dati del tuo server Jellyfin per scansionarne il contenuto." }, + "jellyfinUrlLabel": { "message": "URL del Server Jellyfin" }, + "jellyfinUserLabel": { "message": "Nome Utente" }, + "jellyfinPassLabel": { "message": "Password" }, + "jellyfinConnectAndScan": { "message": "Connetti e Scansiona" }, + "settingsPhpGenTitle": { "message": "Generatore di Script PHP per il Server" }, + "settingsPhpFileOptions": { "message": "Opzioni del File" }, + "settingsPhpSavePathLabel": { "message": "Percorso di Salvataggio sul Server" }, + "settingsPhpSavePathPlaceholder": { "message": "Es: /var/www/html/liste (vuoto per la stessa cartella)" }, + "settingsPhpFilenameLabel": { "message": "Nome del File" }, + "settingsPhpFileAction": { "message": "Azione sul File" }, + "settingsPhpActionAppend": { "message": "Aggiungi in fondo al file (cumulativo)" }, + "settingsPhpActionOverwrite": { "message": "Sovrascrivi il file (ricomincia da capo)" }, + "settingsPhpSecurity": { "message": "Sicurezza (Opzionale)" }, + "settingsPhpUseSecretKey": { "message": "Usa chiave segreta (Consigliato)" }, + "settingsPhpSecretKeyPlaceholder": { "message": "Inserisci una chiave segreta sicura" }, + "settingsPhpGeneratedCode": { "message": "Codice Generato" }, + "settingsPhpGeneratedPlaceholder": { "message": "Il codice PHP generato apparirà qui." }, + "settingsGenerateScript": { "message": "Genera Script" }, + "settingsCopyScript": { "message": "Copia Script" }, + "settingsDataManagement": { "message": "Gestione del Database Locale" }, + "settingsImportDb": { "message": "Importa DB da File" }, + "settingsExportDb": { "message": "Esporta DB su File" }, + "settingsClearContent": { "message": "Cancella Dati Contenuti Locali" }, + "settingsClearContentDesc": { "message": "Questa azione cancellerà film, serie e musica dal database locale, ma non influenzerà i tuoi preferiti né le tue impostazioni." }, + "settingsClose": { "message": "Chiudi" }, + "settingsSave": { "message": "Salva Impostazioni" }, + "musicSidenavTitle": { "message": "Musica di Plex" }, + "musicAllServers": { "message": "Tutti i Server" }, + "musicSearchArtistPlaceholder": { "message": "Cerca artista..." }, + "musicSearchDiscographyPlaceholder": { "message": "Cerca nella discografia..." }, + "musicNothingPlaying": { "message": "Nessuna riproduzione in corso" }, + "musicSelectSong": { "message": "Seleziona una canzone" }, + "musicToStart": { "message": "per iniziare la riproduzione" }, + "miniplayerDownloadSong": { "message": "Scarica canzone" }, + "miniplayerDownloadAlbum": { "message": "Scarica M3U" }, + "miniplayerVolume": { "message": "Volume" }, + "miniplayerShuffle": { "message": "Casuale" }, + "miniplayerEqualizer": { "message": "Equalizzatore" }, + "miniplayerOpenList": { "message": "Apri lista" }, + "eqTitle": { "message": "Equalizzatore Grafico" }, + "eqPresetsLabel": { "message": "Preimpostazioni" }, + "eqPresetFlat": { "message": "Piatto" }, + "eqPresetRock": { "message": "Rock" }, + "eqPresetPop": { "message": "Pop" }, + "eqPresetJazz": { "message": "Jazz" }, + "eqPresetClassical": { "message": "Classica" }, + "eqPresetBassBoost": { "message": "Potenziamento Bassi" }, + "eqPreampLabel": { "message": "Preamplificatore" }, + "infoModalTitle": { "message": "Informazioni" }, + "infoModalFieldTitle": { "message": "Titolo:" }, + "infoModalFieldArtist": { "message": "Artista:" }, + "infoModalFieldAlbum": { "message": "Album:" }, + "infoModalFieldSong": { "message": "Canzone:" }, + "infoModalFieldYear": { "message": "Anno:" }, + "infoModalFieldGenre": { "message": "Genere:" }, + "lang_en": { "message": "Inglese" }, + "lang_es": { "message": "Spagnolo" }, + "lang_fr": { "message": "Francese" }, + "lang_de": { "message": "Tedesco" }, + "lang_it": { "message": "Italiano" }, + "lang_pt": { "message": "Portoghese" }, + "essentialFeaturesNotSupported": { "message": "Il tuo browser non supporta funzionalità essenziali." }, + "dbAccessError": { "message": "Errore di accesso al database locale." }, + "dbUpdateNeeded": { "message": "Il database deve essere aggiornato, per favore ricarica la pagina." }, + "dbBlocked": { "message": "Per favore, chiudi le altre schede di questa applicazione per continuare." }, + "deletingContentData": { "message": "Cancellazione dei dati dei contenuti locali in corso..." }, + "noContentDataToDelete": { "message": "Nessun dato di contenuto da cancellare." }, + "contentDataDeleted": { "message": "Dati dei contenuti cancellati da IndexedDB." }, + "errorDeletingData": { "message": "Errore durante la cancellazione dei dati: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailable": { "message": "Editor di testo non disponibile." }, + "errorLoadingTokens": { "message": "Errore durante il caricamento dei token per la modifica." }, + "errorLoadingTokensMessage": { "message": "Errore durante il caricamento dei token: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailableToSave": { "message": "Editor non disponibile per il salvataggio." }, + "invalidJsonFormat": { "message": "Formato JSON non valido. Deve essere { \"tokens\": [...] }" }, + "tokensSaved": { "message": "Token salvati correttamente." }, + "errorSavingTokens": { "message": "Errore durante il salvataggio dei token: $message$", "placeholders": { "message": { "content": "$1" } } }, + "dbNotAvailable": { "message": "IndexedDB non è disponibile." }, + "dbExported": { "message": "Database esportato con successo." }, + "errorExportingDb": { "message": "Errore durante l'esportazione del database: $message$", "placeholders": { "message": { "content": "$1" } } }, + "invalidJsonFile": { "message": "Il file non contiene un oggetto JSON valido." }, + "noDataToImport": { "message": "Il file non contiene dati per le sezioni del DB corrente." }, + "dbImported": { "message": "Database importato correttamente." }, + "errorImportingDb": { "message": "Errore durante l'importazione del database: $message$", "placeholders": { "message": { "content": "$1" } } }, + "updatingView": { "message": "Aggiornamento della vista con i nuovi dati in corso..." }, + "confirmClearContent": { "message": "Sei sicuro di voler cancellare i dati dei contenuti locali (Film, Serie, Musica, ecc.)? I Preferiti e le Impostazioni NON verranno cancellati." }, + "trailerNotFound": { "message": "Nessun trailer trovato per questo titolo." }, + "confirmClearHistory": { "message": "Sei sicuro di voler cancellare tutta la tua cronologia di visione? Questa azione non può essere annullata." }, + "historyCleared": { "message": "Cronologia di visione cancellata." }, + "historyItemDeleted": { "message": "Elemento cancellato dalla cronologia." }, + "errorGeneratingScript": { "message": "Prima genera uno script per poterlo copiare." }, + "scriptCopied": { "message": "Script PHP copiato negli appunti." }, + "errorCopyingScript": { "message": "Errore durante la copia dello script." }, + "scriptGenerated": { "message": "Script PHP generato." }, + "errorLoadingAlbum": { "message": "Errore durante il caricamento dell'album: $message$", "placeholders": { "message": { "content": "$1" } } }, + "noPhotoServerSelected": { "message": "Errore: Nessun server di foto selezionato." }, + "loadingGenres": { "message": "Caricamento generi in corso..." }, + "errorLoadingGenres": { "message": "Errore di caricamento" }, + "noContentFound": { "message": "Nessun risultato trovato." }, + "couldNotLoadContent": { "message": "Impossibile caricare il contenuto." }, + "noFavorites": { "message": "Non hai ancora preferiti." }, + "errorLoadingFavorites": { "message": "Errore durante il caricamento dei preferiti." }, + "historyEmpty": { "message": "La tua cronologia è vuota." }, + "historyEmptySub": { "message": "Esplora e guarda contenuti affinché appaiano qui." }, + "errorGeneratingRecommendations": { "message": "Errore durante la generazione delle raccomandazioni." }, + "noRecommendations": { "message": "Dobbiamo conoscerti meglio per darti delle raccomandazioni!" }, + "errorGeneratingStats": { "message": "Errore durante la generazione delle statistiche." }, + "noServersForToken": { "message": "Nessun server associato trovato per questo token." }, + "searchingActorContent": { "message": "Ricerca di contenuti di $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, + "errorLoadingActorContent": { "message": "Impossibile caricare i contenuti per $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, + "errorAddingStream": { "message": "Errore durante l'aggiunta di stream: $message$", "placeholders": { "message": { "content": "$1" } } }, + "phpUrlNotConfigured": { "message": "L'URL del server PHP non è configurato. Per favore, configuralo nelle Impostazioni." }, + "searchingStreams": { "message": "Ricerca di stream per \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "sendingStreams": { "message": "Invio di $count$ stream al server in corso...", "placeholders": { "count": { "content": "$1" } } }, + "streamAddedSuccess": { "message": "Stream aggiunti con successo." }, + "generatingM3U": { "message": "Generazione M3U per \"$title$\" in corso", "placeholders": { "title": { "content": "$1" } } }, + "m3uDownloaded": { "message": "\"$title$\" scaricato.", "placeholders": { "title": { "content": "$1" } } }, + "errorGeneratingM3U": { "message": "Errore durante la generazione di M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, + "settingsSavedSuccess": { "message": "Impostazioni salvate correttamente." }, + "errorSavingSettings": { "message": "Errore durante il salvataggio delle impostazioni nel database." }, + "languageChangeReload": { "message": "Lingua cambiata. L'applicazione verrà ricaricata ora." }, + "addedToFavorites": { "message": "Aggiunto ai preferiti." }, + "removedFromFavorites": { "message": "Rimosso dai preferiti." }, + "plexScanInProgress": { "message": "La scansione Plex è già in corso." }, + "plexScanStarting": { "message": "Avvio scansione Plex in corso..." }, + "noPlexTokens": { "message": "Nessun token di Plex configurato." }, + "clearingSections": { "message": "Pulizia sezioni: $sections$", "placeholders": { "sections": { "content": "$1" } } }, + "initialScanPhaseComplete": { "message": "Fase di scansione iniziale completata." }, + "retryPhaseFinished": { "message": "Fase di ritentativi completata." }, + "plexScanFinished": { "message": "Scansione completata. Aggiornamento dei contenuti in corso..." }, + "scanCancelled": { "message": "Scansione annullata dall'utente." }, + "scanCancelledInfo": { "message": "Scansione annullata." }, + "errorInitializingMusicPlayer": { "message": "Errore durante l'inizializzazione del lettore musicale." }, + "criticalErrorLoadingMusic": { "message": "Errore critico durante il caricamento dei dati musicali." }, + "errorLoadingArtists": { "message": "Errore durante il caricamento degli artisti." }, + "dbUnavailableError": { "message": "Errore: Database non disponibile." }, + "updatingMusicData": { "message": "Aggiornamento dati musicali in corso..." }, + "musicDataUpdated": { "message": "Dati musicali aggiornati." }, + "errorFetchingArtistSongs": { "message": "Errore durante il recupero delle canzoni dell'artista." }, + "errorLoadingSongs": { "message": "Errore durante il caricamento delle canzoni." }, + "noArtistsFound": { "message": "Nessun artista trovato." }, + "shuffleOn": { "message": "Modalità casuale attivata." }, + "shuffleOff": { "message": "Modalità casuale disattivata." }, + "playbackError": { "message": "Errore di riproduzione" }, + "errorLabel": { "message": "Errore" }, + "reloadingPage": { "message": "Ricaricamento pagina in corso..." }, + "viewed": { "message": "Visto" }, + "local": { "message": "Locale" }, + "topRatedSort": {"message": "I più votati"}, + "recentSort": {"message": "Recenti"}, + "popularSort": {"message": "Popolari"}, + "moviesSectionTitle": {"message": "Film"}, + "seriesSectionTitle": {"message": "Serie"}, + "searchResultsFor": {"message": "Risultati per \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, + "contentFrom": {"message": "Contenuti di $actor$", "placeholders": {"actor": {"content": "$1"}}}, + "explore": {"message": "Esplora"}, + "noGenre": {"message": "Senza categoria"}, + "synopsis": {"message": "Sinossi"}, + "noSynopsis": {"message": "Nessuna sinossi disponibile."}, + "director": {"message": "Regista:"}, + "writer": {"message": "Sceneggiatore:"}, + "viewOnImdb": {"message": "Vedi su IMDb"}, + "watchTrailer": {"message": "Guarda il Trailer"}, + "addToFavorites": {"message": "Aggiungi ai preferiti"}, + "removeFromFavorites": {"message": "Rimuovi dai preferiti"}, + "notAvailable": {"message": "Non disponibile"}, + "mainCast": {"message": "Cast Principale"}, + "seasonsAndEpisodes": {"message": "Stagioni ed Episodi"}, + "similarContent": {"message": "Contenuti Simili"}, + "filmography": {"message": "Filmografia"}, + "availableOn": {"message": "Disponibile su"}, + "episodesCount": {"message": "$count$ Episodi", "placeholders": {"count": {"content": "$1"}}}, + "seasonsCount": {"message": "$count$ Stagioni", "placeholders": {"count": {"content": "$1"}}}, + "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, + "noTrailerFound": {"message": "Nessun trailer trovato per questo titolo."}, + "fatalInitError": {"message": "Errore fatale di inizializzazione"}, + "fatalInitErrorSub": {"message": "Impossibile caricare l'applicazione."}, + "invalidStreamInfo": {"message": "Informazioni stream non valide."}, + "dbUnavailableForStreams": {"message": "Database locale non disponibile."}, + "noPlexServersForStreams": {"message": "Nessun server Plex."}, + "notFoundOnServers": {"message": "\"$query$\" non trovato sui server Plex.", "placeholders": {"query": {"content": "$1"}}}, + "relativeTime_justNow": { "message": "Poco fa" }, + "relativeTime_minutesAgo": { "message": "$count$ minuti fa", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_hoursAgo": { "message": "$count$ ore fa", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_yesterday": { "message": "Ieri" }, + "relativeTime_daysAgo": { "message": "$count$ giorni fa", "placeholders": { "count": { "content": "$1" } } }, + "errorLoadingDetails": { "message": "Errore nel Caricamento dei Dettagli" }, + "errorLoadingLocalContent": { "message": "Errore durante il caricamento del contenuto locale." }, + "errorServerResponse": { "message": "Risposta del server non riuscita." }, + "errorPlexApi": { "message": "Errore API di Plex $status$.", "placeholders": { "status": { "content": "$1" } } }, + "errorParsingPlexXml": { "message": "Errore durante l'analisi del XML di Plex." }, + "untitled": { "message": "Senza titolo" }, + "itemCount": { "message": "$count$ elementi", "placeholders": { "count": { "content": "$1" } } }, + "noPhotoServers": { "message": "Nessun server di foto" }, + "jellyfinScanInProgress": { "message": "La scansione di Jellyfin è già in corso." }, + "jellyfinScanning": { "message": "Scansione di Jellyfin in corso..." }, + "jellyfinMissingCredentials": { "message": "Per favore, completa l'URL e il nome utente di Jellyfin." }, + "jellyfinConnecting": { "message": "Connessione a Jellyfin su: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Autenticazione Jellyfin fallita: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Autenticazione Jellyfin riuscita." }, + "jellyfinFetchingLibraries": { "message": "Recupero librerie in corso..." }, + "jellyfinFetchFailed": { "message": "Errore durante il recupero delle librerie: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Nessuna libreria di film o serie trovata in Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ libreria/e multimediale/i trovata/e.", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Successo] '$libraryName' scansionata, $count$ titoli aggiunti.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Errore durante la scansione della libreria '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Scansione Jellyfin completata. Aggiunti $movies$ film e $series$ serie.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Credenziali di Jellyfin non configurate." }, + "notFoundOnJellyfin": { "message": "\"$query$\" non trovato su Jellyfin.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnAnyServer": { "message": "\"$query$\" non trovato su nessun server.", "placeholders": {"query": {"content": "$1"}}}, + "localOnPlex": { "message": "Su Plex" }, + "searchOnPlex": { "message": "Cerca su Plex" }, + "jellyfinTitle": { "message": "Contenuti di Jellyfin" }, + "noJellyfinContent": { "message": "Nessun contenuto di Jellyfin trovato." }, + "noJellyfinContentSub": { "message": "Assicurati di aver scansionato il tuo server Jellyfin nelle impostazioni." }, + "activityViewerTitle": { "message": "Visualizzatore Attività del Server" }, + "activitySelectServer": { "message": "Seleziona un server" }, + "activityCheckBtn": { "message": "Aggiorna" }, + "activityNoSessions": { "message": "Non ci sono sessioni attive su questo server." }, + "activitySessionUser": { "message": "Utente" }, + "activitySessionDevice": { "message": "Dispositivo" }, + "activitySessionContent": { "message": "Contenuto" }, + "activitySessionState": { "message": "Stato" }, + "activitySessionIdentifier": { "message": "Identificatore del Client" }, + "activityCopyID": { "message": "Copia ID" }, + "activityError": { "message": "Impossibile ottenere l'attività del server." }, + "activityCopied": { "message": "Identificatore copiato negli appunti!" }, + "activityCopyError": { "message": "Errore durante la copia dell'identificatore." }, + "noProvidersFound": { "message": "Nessun fornitore trovato." }, + "availableOnPlex": { "message": "Disponibile su Plex" }, + "m3uGeneratorTitle": { "message": "Generatore di Liste M3U" }, + "selectAServer": { "message": "Seleziona un server..." }, + "downloadM3u": { "message": "Scarica M3U" }, + "m3uGenerator": { "message": "Generatore M3U" }, + "selectLibraries": { "message": "Seleziona Librerie" }, + "howToUse": { "message": "Come si Usa" }, + "m3uInstruction1": { "message": "Scegli un server dalla lista." }, + "m3uInstruction2": { "message": "Seleziona una o più librerie da includere." }, + "m3uInstruction3": { "message": "Clicca sul pulsante di download." }, + "m3uInstruction4": { "message": "Importa il file .m3u nel tuo lettore compatibile." }, + "chatOpen": { "message": "Apri Chat" }, + "chatTitle": { "message": "Assistente AI" }, + "chatClose": { "message": "X" }, + "chatPlaceholder": { "message": "Scrivi il tuo messaggio..." }, + "chatSend": { "message": "➤" }, + "chatWelcome": { "message": "Benvenuto! Sono il tuo assistente CinePlex. Chiedimi di film, serie o qualsiasi altra cosa tu voglia sapere." }, + "chatGoogleApiKeyMissing": { "message": "La chiave API di Google Gemini non è configurata. Per favore, configurala nelle impostazioni dell'estensione per utilizzare l'assistente AI." }, + "chatApiInvalidResponse": { "message": "L'API ha restituito una risposta non valida. Per favore, riprova." }, + "chatApiError": { "message": "Errore di comunicazione con l'assistente AI" }, + "downloadAll": { "message": "Scarica tutto" }, + "download": { "message": "Scarica" }, + "aiToolSearchLibraryDesc": { "message": "Cerca nella libreria Plex dell'utente film o serie per titolo." }, + "aiToolSearchLibraryQueryParamDesc": { "message": "Il titolo del film o della serie da cercare." }, + "aiToolSearchLibraryTypeParamDesc": { "message": "Il tipo di contenuto da cercare. Può essere 'movie' per i film o 'series' per le serie. (Opzionale)." }, + "aiToolSearchLibraryResolutionParamDesc": { "message": "La risoluzione video da cercare (es. '4k', '1080p'). (Opzionale)." }, + "aiToolSearchLibraryContainerParamDesc": { "message": "Il formato contenitore del video da cercare (es. 'mkv', 'mp4'). (Opzionale)." }, + "aiToolNavigateToPageDesc": { "message": "Indirizza l'utente a una pagina specifica dell'interfaccia dell'applicazione." }, + "aiToolNavigateToPagePageParamDesc": { "message": "Il nome della pagina a cui navigare, ad esempio: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator' o 'music'." }, + "aiToolGetUserStatsDesc": { "message": "Ottiene e visualizza le statistiche della libreria dell'utente, come il numero totale di film, serie e artisti unici." }, + "aiToolShowItemDetailsDesc": { "message": "Mostra la pagina dei dettagli di un film o di una serie specifica tramite il suo titolo e tipo." }, + "aiToolShowItemDetailsTitleParamDesc": { "message": "Il titolo esatto del film o della serie." }, + "aiToolShowItemDetailsTypeParamDesc": { "message": "Il tipo di contenuto. Deve essere 'movie' o 'series'." }, + "aiToolAddToPlaylistDesc": { "message": "Aggiunge un film o una serie alla playlist corrente dell'utente per lo streaming su un server PHP configurato." }, + "aiToolAddToPlaylistTitleParamDesc": { "message": "Il titolo del film o della serie da aggiungere." }, + "aiToolAddToPlaylistTypeParamDesc": { "message": "Il tipo di contenuto. Deve essere 'movie' o 'series'." }, + "aiToolDownloadSingleMovieM3UDesc": { "message": "Genera e scarica un file di playlist M3U per un singolo film disponibile localmente." }, + "aiToolDownloadSingleMovieM3UTitleParamDesc": { "message": "Il titolo del film per cui verrà generato il file M3U." }, + "aiToolDownloadSingleMovieM3UYearParamDesc": { "message": "L'anno di uscita del film (opzionale, per maggiore precisione)." }, + "aiToolDownloadSeriesSeasonM3UDesc": { "message": "Genera e scarica un file di playlist M3U per una stagione specifica di una serie disponibile localmente." }, + "aiToolDownloadSeriesSeasonM3UTitleParamDesc": { "message": "Il titolo della serie." }, + "aiToolDownloadSeriesSeasonM3USeasonParamDesc": { "message": "Il numero della stagione da scaricare." }, + "aiToolDownloadSeriesSeasonM3UYearParamDesc": { "message": "L'anno di uscita della serie (opzionale)." }, + "aiToolCheckAndDownloadDesc": { "message": "Verifica la disponibilità di un elenco di titoli di film o serie sui server locali dell'utente e, se trovati, genera e scarica un file di playlist M3U con gli stream trovati." }, + "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Un array di titoli di film o serie da cercare e scaricare." }, + "aiToolCheckAndDownloadTypeParamDesc": { "message": "Il tipo di contenuto dell'elenco. Deve essere 'movie' o 'series'." }, + "aiToolCheckAndDownloadFilenameParamDesc": { "message": "Il nome del file M3U da scaricare (es. 'LaMiaLista.m3u'). Se non fornito, verrà utilizzato un nome predefinito." }, + "aiToolToggleFavoriteDesc": { "message": "Aggiunge o rimuove un film o una serie dalla lista dei preferiti dell'utente." }, + "aiToolToggleFavoriteTitleParamDesc": { "message": "Il titolo del film o della serie." }, + "aiToolToggleFavoriteTypeParamDesc": { "message": "Il tipo di contenuto. Deve essere 'movie' o 'series'." }, + "aiToolGetRecommendationsDesc": { "message": "Genera e visualizza un elenco di raccomandazioni di film o serie basate sulla cronologia di visione e sui preferiti dell'utente." }, + "aiToolApplyFiltersDesc": { "message": "Applica filtri alla vista corrente di film o serie, consentendo di affinare i risultati per tipo, genere, anno e ordine di classificazione." }, + "aiToolApplyFiltersTypeParamDesc": { "message": "Il tipo di contenuto a cui applicare i filtri. Deve essere 'movie' o 'series'." }, + "aiToolApplyFiltersGenreParamDesc": { "message": "Il nome del genere per cui filtrare (es. 'Azione', 'Drammatico')." }, + "aiToolApplyFiltersYearParamDesc": { "message": "L'anno di uscita per cui filtrare (es. '2023')." }, + "aiToolApplyFiltersSortParamDesc": { "message": "Il criterio di ordinamento per i risultati. Valori validi: 'popularity.desc' (popolari), 'vote_average.desc' (più votati), 'release_date.desc' (recenti per i film) o 'first_air_date.desc' (recenti per le serie)." }, + "aiToolListAvailableMusicGenresDesc": { "message": "Elenca tutti i generi musicali unici disponibili nella libreria locale dell'utente." }, + "aiToolSearchMusicByGenreDesc": { "message": "Cerca artisti nella libreria musicale dell'utente che appartengono a un genere specifico." }, + "aiToolSearchMusicByGenreNameParamDesc": { "message": "Il nome del genere musicale da cercare (es. 'Rock', 'Pop', 'Jazz')." }, + "aiToolPlayMusicByArtistDesc": { "message": "Apre il lettore musicale e inizia a riprodurre le canzoni di un artista specifico dalla libreria dell'utente." }, + "aiToolPlayMusicByArtistNameParamDesc": { "message": "Il nome esatto dell'artista le cui canzoni si desidera riprodurre." }, + "aiToolClearChatHistoryDesc": { "message": "Cancella l'intera cronologia dei messaggi della conversazione corrente con l'assistente AI." }, + "aiToolDeleteDatabaseDesc": { "message": "Elimina l'intero database locale dell'estensione, inclusi i contenuti scansionati, le impostazioni e i preferiti. Questa azione è irreversibile e ricaricherà l'applicazione." }, + "aiToolUpdateAllTokensDesc": { "message": "Avvia una scansione completa di tutti i server e le librerie Plex associati ai token configurati nell'estensione. Aggiorna tutti i film, le serie, gli artisti e le foto." }, + "aiToolAddPlexTokenDesc": { "message": "Aggiunge un nuovo token X-Plex alla configurazione dell'estensione, consentendo all'applicazione di scansionare contenuti da nuovi server Plex." }, + "aiToolAddPlexTokenTokenParamDesc": { "message": "La stringa del token X-Plex da aggiungere." }, + "aiToolChangeRegionDesc": { "message": "Cambia la regione utilizzata per la scoperta di contenuti nell'API di TMDB. Ciò influenzerà i risultati visualizzati nelle sezioni di film e serie, nonché i fornitori di streaming." }, + "aiToolChangeRegionRegionParamDesc": { "message": "Il codice paese ISO 3166-1 a due lettere per la nuova regione (es. 'US' per Stati Uniti, 'IT' per Italia, 'ES' per Spagna)." }, + "aiToolClearAllFavoritesDesc": { "message": "Rimuove tutti i film e le serie che l'utente ha contrassegnato come preferiti." }, + "aiToolClearViewingHistoryDesc": { "message": "Cancella la cronologia di visione dell'utente dalla pagina della cronologia." }, + "aiToolClearRecommendationsViewDesc": { "message": "Pulisce la vista delle raccomandazioni e rimuove le raccomandazioni memorizzate nella cache." }, + "aiToolSearchNotFound": { "message": "Impossibile trovare '$query' nella tua libreria.", "placeholders": { "query": { "content": "$1" } } }, + "aiToolNavigateSuccess": { "message": "Navigato alla pagina $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolNavigateError": { "message": "Errore durante la navigazione alla pagina $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolStatsError": { "message": "Errore nel recupero delle statistiche." }, + "aiToolItemNotFound": { "message": "Elemento '$title' non trovato.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolShowItemDetailsSuccess": { "message": "Mostrando i dettagli di '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolAddToPlaylistSuccess": { "message": "Aggiunto '$title' alla playlist.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteAdded": { "message": "Aggiunto '$title' ai preferiti.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteRemoved": { "message": "Rimosso '$title' dai preferiti.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolRecommendationsSuccess": { "message": "Mostrando le raccomandazioni." }, + "aiToolApplyFiltersGenreNotFound": { "message": "Genere '$genre' non trovato.", "placeholders": { "genre": { "content": "$1" } } }, + "aiToolApplyFiltersSuccess": { "message": "Filtri applicati correttamente." }, + "aiToolSearchMusicByGenreNotFound": { "message": "Non ho trovato artisti del genere '$genre_name' nella tua libreria.", "placeholders": { "genre_name": { "content": "$1" } } }, + "aiToolPlayMusicNotReady": { "message": "Il lettore musicale non è pronto. Assicurati che la tua libreria musicale di Plex sia stata scansionata." }, + "aiToolPlayMusicArtistNotFound": { "message": "Artista '$artist_name' non trovato.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicNoSongs": { "message": "Nessuna canzone trovata per '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicSuccess": { "message": "Riproduzione di musica di '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolChatHistoryCleared": { "message": "Cronologia chat cancellata." }, + "aiToolConfirmDeleteDatabase": { "message": "Sei sicuro di voler eliminare il database locale? Questa azione è irreversibile." }, + "aiToolDeleteDatabaseCancelled": { "message": "Eliminazione del database annullata." }, + "aiToolExecutionError": { "message": "Errore durante l'esecuzione dello strumento '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, + "aiToolUnknown": { "message": "Strumento sconosciuto: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, + "aiToolFavoritesCleared": { "message": "Preferiti eliminati." }, + "aiToolFavoritesClearError": { "message": "Errore durante l'eliminazione dei preferiti: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolRecommendationsCleared": { "message": "Raccomandazioni eliminate." }, + "aiToolRecommendationsClearError": { "message": "Errore durante l'eliminazione delle raccomandazioni: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleted": { "message": "Database eliminato. La pagina verrà ricaricata." }, + "aiToolDatabaseDeleteError": { "message": "Errore durante l'eliminazione del database: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleteBlocked": { "message": "L'eliminazione del database è bloccata. Chiudi altre schede dell'applicazione." }, + "aiToolUpdateAllTokensSuccess": { "message": "Tutti i token sono stati aggiornati correttamente." }, + "aiToolUpdateAllTokensError": { "message": "Errore durante l'aggiornamento dei token: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolAddPlexTokenSuccess": { "message": "Token di Plex aggiunto correttamente." }, + "aiToolAddPlexTokenError": { "message": "Errore durante l'aggiunta del token di Plex: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolChangeRegionSuccess": { "message": "Regione cambiata in $region$. Il contenuto è in fase di aggiornamento.", "placeholders": { "region": { "content": "$1" } } }, + "aiToolChangeRegionError": { "message": "Errore durante il cambio di regione: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolViewingHistoryCleared": { "message": "Cronologia di visione cancellata." }, + "aiToolViewingHistoryClearError": { "message": "Errore durante la cancellazione della cronologia di visione: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSingle": { "message": "Avvio del download del file M3U per '$movie_title'.", "placeholders": { "movie_title": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSeason": { "message": "Avvio del download del file M3U per la stagione $1 di '$2'.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolM3UNoTitlesProvided": { "message": "Per favore, fornisci un elenco di titoli per creare la playlist." }, + "aiToolM3UCheckingTitles": { "message": "Controllo dei titoli sui tuoi server locali..." }, + "aiToolM3UNoLocalMatchesForDownload": { "message": "Non ho trovato nessuno dei film o delle serie dell'elenco sui tuoi server locali." }, + "aiToolM3UDownloadStarted": { "message": "Fatto! Ho trovato $1 dei $2 titoli sui tuoi server e ho avviato il download della playlist M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolTrailerNotFoundSpecific": { "message": "Mi dispiace, non sono riuscito a trovare un trailer disponibile per '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiSystemPrompt_v4": { + "message": "Sei un assistente virtuale integrato in un'estensione di Chrome che interagisce con i server Plex e Jellyfin. La tua funzione principale è aiutare l'utente a cercare, gestire, riprodurre e scaricare contenuti multimediali, oltre a gestire impostazioni personalizzate.\n\nMASSIMA PRIORITÀ: Ogni volta che la domanda dell'utente si riferisce a contenuti multimediali (film, serie, musica), DEVI presumere che si riferisca alla sua libreria locale. Utilizza gli strumenti per cercare nel suo database PRIMA di cercare sul web.\n\n🎯 Regole generali di comportamento:\nRispondi sempre in modo chiaro, conciso e diretto. Sii proattivo e fornisci tutte le informazioni pertinenti in una sola volta per evitare domande di follow-up. Ad esempio, quando confermi la disponibilità di una serie, includi i dettagli delle stagioni.\n\nConfronta la data attuale con i risultati di ricerca di Google quando ti vengono richieste informazioni esterne per garantire che siano aggiornate.\n\nUsa i nomi esatti dei comandi definiti nel sistema (function.name) quando chiami gli strumenti.\n\n📦 Funzioni chiave per i contenuti multimediali:\nPer generare un M3U per un singolo film, usa download_single_movie_m3u.\nPer scaricare una stagione specifica di una serie, usa download_series_season_m3u.\nPer più titoli (film o serie complete), usa sempre check_and_download_titles_list.\nPer cercare contenuti locali: search_library.\nPer cercare su TMDB: search_tmdb_content.\nPer contenuti di tendenza: get_trending_content.\nPer mostrare i dettagli di un titolo: show_item_details.\nPer aggiungere alla playlist PHP: add_to_playlist.\nPer verificare la disponibilità locale: check_local_availability.\nSe una serie è disponibile localmente, informa di quante stagioni ci sono e su quali server usando get_local_series_seasons.\nPer vedere le raccomandazioni: get_recommendations.\nPer applicare filtri: apply_filters.\nPer visualizzare la cronologia o i preferiti: view_history, view_favorites.\nPer contrassegnare come preferito: toggle_favorite.\nPer riprodurre il trailer: play_trailer.\n\n🎵 Funzioni musicali:\nSe l'utente chiede raccomandazioni generali su generi musicali (es. 'raccomandami un genere per tirarmi su di morale'), usa prima list_available_music_genres per vedere quali generi ha e basa la tua raccomandazione su quella lista.\nPer elencare tutti i generi musicali disponibili nella libreria: list_available_music_genres.\nPer cercare artisti per genere: search_music_by_genre.\nPer riprodurre canzoni per titolo e/o artista: play_song.\nPer riprodurre musica di un artista: play_music_by_artist.\n\n🧰 Funzioni di gestione e configurazione:\nPer ottenere le statistiche dell'utente: get_user_stats.\nPer navigare a sezioni specifiche: navigate_to_page.\nPer aggiornare i token: update_all_tokens, add_plex_token.\nPer cambiare la regione dei contenuti: change_region.\nPer esportare o importare il database locale: export_local_database, import_local_database.\nPer eliminare il database: delete_database.\nPer cancellare preferiti, cronologia o raccomandazioni: clear_all_favorites, clear_viewing_history, clear_recommendations_view.\nPer attivare/disattivare la modalità chiara/scura: toggle_light_mode.\nPer mostrare o nascondere la sezione hero: toggle_hero_section.\n\n⚠️ Considerazioni aggiuntive:\nDai priorità ai contenuti disponibili localmente. Usa check_local_availability prima di mostrare le opzioni di riproduzione o download.\nSe un'azione fallisce, segnalalo in modo chiaro e diretto.\nEvita di ripetere inutilmente la richiesta dell'utente, a meno che non aiuti a contestualizzare la risposta." + }, + "backToProviders": { "message": "Torna ai Fornitori" }, + "artistsCounterSingle": { "message": "$total$ Artista", "placeholders": { "total": { "content": "$1" } } }, + "artistsCounterLoading": { "message": "Caricamento in corso..." }, + "downloadingSong": { "message": "Avvio del download di \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "songDownloaded": { "message": "\"$title$\" scaricato.", "placeholders": { "title": { "content": "$1" } } }, + "errorDownloadingSong": { "message": "Errore durante il download di \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "generatingAlbumM3U": { "message": "Generazione M3U per \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, + "albumM3UGenerated": { "message": "M3U per l'album \"$artist$\" generato.", "placeholders": { "artist": { "content": "$1" } } }, + "retyingSection": { "message": "Riprovo la sezione \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "retrySuccess": { "message": "[SUCCESSO] Riprova di \"$title$\" completata.", "placeholders": { "title": { "content": "$1" } } }, + "retryError": { "message": "[ERRORE FINALE] Riprova per \"$title$\" fallita: $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, + "startingRetryPhase": { "message": "Avvio della fase di ritentativi per $count$ sezioni...", "placeholders": { "count": { "content": "$1" } } }, + "tokenFoundServers": { "message": "Token $token$... ha trovato $count$ server.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, + "errorProcessingToken": { "message": "Errore nell'elaborazione del token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, + "plexScanFatalError": { "message": "ERRORE FATALE: $message$", "placeholders": { "message": { "content": "$1" } } }, + "errorDuringScan": { "message": "Errore durante la scansione: $message$", "placeholders": { "message": { "content": "$1" } } }, + "stoppingPlexScan": { "message": "Interruzione scansione Plex..." }, + "invalidTokenProvided": { "message": "Token fornito non valido." }, + "tokenAlreadyExists": { "message": "Il token esiste già." }, + "tokenAddedSuccessfully": { "message": "Token aggiunto correttamente." }, + "noStreamsFoundForSelection": { "message": "Nessun stream trovato per la selezione." }, + "autoplayBlocked": { "message": "Riproduzione automatica bloccata." }, + "welcomeToCinePlex": { "message": "" }, + "page": { "message": "Pagina" }, + "all": { "message": "Tutto" }, + "userScore": { "message": "Punteggio" }, + "duration": { "message": "Durata" }, + "min": { "message": "Min" }, + "max": { "message": "Max" }, + "aiToolFindStreamingProvidersDesc": { "message": "Trova dove guardare un film o una serie sui servizi di streaming." }, + "aiToolFindStreamingProvidersTitleParamDesc": { "message": "Il titolo del film o della serie da cercare." }, + "aiToolFindStreamingProvidersTypeParamDesc": { "message": "Il tipo di contenuto (film o serie)." }, + "aiToolFindStreamingProvidersYearParamDesc": { "message": "L'anno di uscita del contenuto (opzionale)." }, + "aiToolNoStreamingProviders": { "message": "Nessun fornitore di streaming trovato per {title}." }, + "aiToolStreamingProvidersFound": { "message": "{title} è disponibile sui seguenti servizi: {providers}." }, + "aiToolStreamingProviderError": { "message": "Errore durante la ricerca dei fornitori di streaming: {message}." }, + "aiToolGetLocalSeriesSeasonsDesc": { "message": "Verifica se una serie TV è disponibile localmente e restituisce un resoconto dettagliato delle stagioni disponibili su ciascun server." }, + "aiToolGetLocalSeriesSeasonsTitleParamDesc": { "message": "Il titolo della serie da verificare." }, + "aiToolGetLocalSeriesSeasonsYearParamDesc": { "message": "L'anno di uscita della serie (opzionale per maggiore precisione)." }, + "aiToolLocalSeriesNoSeasons": { "message": "La serie '$series_title' è nella tua libreria, ma non sono stati trovati dettagli sulle stagioni.", "placeholders": { "series_title": { "content": "$1" } } }, + "artist": { "message": "Artista" }, + "tracks": { "message": "tracce" }, + "noSongsFound": { "message": "Nessuna canzone trovata per questo artista." }, + "durationMin": { "message": "Durata (Min)" }, + "score": { "message": "Punteggio" }, + "searchGenre": { "message": "Cerca genere..." }, + "searchArtists": { "message": "Cerca artisti..." }, + "preparingMusicLibrary": { "message": "Preparazione della tua libreria musicale in corso..." }, + "preparingMusicLibraryDesc": { "message": "Questo processo una tantum potrebbe richiedere alcuni minuti se hai molti artisti." }, + "artistsProgress": { "message": "0 / 0 artisti" }, + "starting": { "message": "Avvio in corso..." }, + "artistName": { "message": "Nome Artista" }, + "playPause": { "message": "Riproduci/Pausa" }, + "noLocalFilesFound": { "message": "Nessun file locale trovato per questo titolo." }, + "server": { "message": "Server" }, + "title": { "message": "Titolo" }, + "year": { "message": "Anno" }, + "resolution": { "message": "Risoluzione" }, + "size": { "message": "Dimensione" }, + "container": { "message": "Contenitore" }, + "action": { "message": "Azione" }, + "generate": { "message": "Genera" }, + "availableLocalFiles": { "message": "File Locali Disponibili" }, + "downloadSeason": { "message": "Scarica Stagione" }, + "errorLoadingServersM3u": { "message": "Errore durante il caricamento dei server per il generatore M3U:" }, + "errorFetchingLibraries": { "message": "Errore durante il recupero delle librerie." }, + "selectServerAndLibrary": { "message": "Seleziona un server e almeno una libreria." }, + "generating": { "message": "Generazione in corso..." }, + "errorProcessingLibrary": { "message": "Errore durante l'elaborazione della libreria" }, + "errorProcessingLibrarySkipping": { "message": "Errore durante l'elaborazione della libreria. Salto." }, + "allLibrariesFailed": { "message": "Tutte le librerie selezionate non sono state elaborate." }, + "m3uGeneratedWithErrors": { "message": "M3U generato con alcuni errori. Alcune librerie potrebbero mancare." }, + "m3uDownloadedSuccess": { "message": "Playlist M3U scaricata con successo." }, + "errorGeneratingM3uFile": { "message": "Errore durante la generazione del file M3U." }, + "chatSources": { "message": "Fonti" }, + "chatUnnamedSource": { "message": "Fonte senza nome" }, + "googleApiFailure": { "message": "Chiamata API di Google AI fallita:" } } \ No newline at end of file diff --git a/_locales/pt/messages.json b/_locales/pt/messages.json index fe51aee..ffdfbd5 100644 --- a/_locales/pt/messages.json +++ b/_locales/pt/messages.json @@ -1,449 +1,516 @@ { - "appName": { "message": "CinePlex" }, - "appDescription": { "message": "Examina servidores Plex em busca de conteúdo e o exibe na interface" }, - "appTagline": { "message": "Filmes, Séries e Música" }, - "appLocaleCode": { "message": "pt-BR" }, - "toggleNavigation": { "message": "Alternar navegação" }, - "searchPlaceholder": { "message": "Pesquisar filmes ou séries..." }, - "openMusicPlayer": { "message": "Abrir reprodutor de música" }, - "settings": { "message": "Configurações" }, - "navMovies": { "message": "Filmes" }, - "navSeries": { "message": "Séries" }, - "navProviders": { "message": "Provedores" }, - "navPhotos": { "message": "Fotos" }, - "navStats": { "message": "Estatísticas" }, - "navFavorites": { "message": "Favoritos" }, - "navHistory": { "message": "Histórico" }, - "navRecommendations": { "message": "Recomendações" }, - "navMusic": { "message": "Música" }, - "navM3uGenerator": { "message": "Gerador de M3U" }, - "heroWelcome": { "message": "" }, - "heroSubtitle": { "message": "Explore milhares de filmes e séries." }, - "addStream": { "message": "Adicionar stream" }, - "moreInfo": { "message": "Mais informações" }, - "popularMovies": { "message": "Filmes populares" }, - "allGenres": { "message": "Todos os gêneros" }, - "allYears": { "message": "Todos os anos" }, - "sortPopular": { "message": "Mais populares" }, - "sortTopRated": { "message": "Mais bem avaliados" }, - "sortRecent": { "message": "Mais recentes" }, - "loadMore": { "message": "Carregar mais" }, - "photosBreadcrumbHome": { "message": "Álbuns" }, - "selectServer": { "message": "Selecione um servidor" }, - "loading": { "message": "Carregando..." }, - "loadingLibraries": { "message": "Carregando bibliotecas..." }, - "photosEmptyState": { "message": "Nenhum álbum ou foto encontrado." }, - "photosEmptyStateSub": { "message": "Selecione um servidor ou verifique se você tem uma biblioteca de fotos no Plex." }, - "statsTitle": { "message": "Estatísticas da biblioteca" }, - "statsAllTokens": { "message": "Todos os tokens" }, - "statsAnalyzing": { "message": "Analisando sua biblioteca..." }, - "statsActiveTokens": { "message": "Tokens ativos" }, - "statsServersFound": { "message": "Servidores encontrados" }, - "statsUniqueMovies": { "message": "Filmes únicos" }, - "statsUniqueSeries": { "message": "Séries únicas" }, - "statsUniqueArtists": { "message": "Artistas únicos" }, - "statsTokenServers": { "message": "Servidores de token" }, - "statsChartMoviesByGenre": { "message": "Conteúdo por gênero (Filmes)" }, - "statsChartSeriesByGenre": { "message": "Conteúdo por gênero (Séries)" }, - "statsChartByDecade": { "message": "Conteúdo por década" }, - "recommendationsTitle": { "message": "Recomendações para você" }, - "historyTitle": { "message": "Histórico de visualização" }, - "clearHistory": { "message": "Limpar tudo" }, - "consoleTitle": { "message": "Console de verificação do Plex" }, - "footerCredit": { "message": "Uma interface para o seu universo Plex." }, - "closeTrailer": { "message": "Fechar trailer" }, - "close": { "message": "Fechar" }, - "photoViewer": { "message": "Visualizador de fotos" }, - "previous": { "message": "Anterior" }, - "next": { "message": "Próximo" }, - "notificationTemplateText": { "message": "Notificação" }, - "settingsTitleFull": { "message": "Configurações e ajustes" }, - "settingsTabGeneral": { "message": "Geral" }, - "settingsTabPlex": { "message": "Plex" }, - "settingsTabJellyfin": { "message": "Jellyfin" }, - "settingsTabPhpGen": { "message": "Gerador de PHP" }, - "settingsTabData": { "message": "Dados" }, - "settingsApiServer": { "message": "Configurações de API e servidor" }, - "settingsTmdbApiLabel": { "message": "Chave de API do TMDB (opcional)" }, - "settingsTmdbApiPlaceholder": { "message": "A chave padrão será usada se o campo for deixado em branco" }, - "settingsGoogleApiLabel": { "message": "Chave de API do Google Gemini (opcional)" }, - "settingsGoogleApiPlaceholder": { "message": "Necessária para usar o assistente de IA" }, - "settingsRegionLabel": { "message": "Região para descoberta de conteúdo" }, - "allRegions": { "message": "Todas as regiões" }, - "settingsPhpUrlLabel": { "message": "URL do servidor para adicionar streams" }, - "settingsPhpUrlPlaceholder": { "message": "https://seu-servidor.com/caminho/para/script.php" }, - "settingsInterface": { "message": "Interface" }, - "settingsLightTheme": { "message": "Modo claro" }, - "settingsShowHero": { "message": "Mostrar seção de boas-vindas 'Hero'" }, - "settingsScanContent": { "message": "Verificação de conteúdo" }, - "settingsScanDesc": { "message": "Selecione o que verificar e pressione o botão." }, - "settingsScanMovies": { "message": "Filmes" }, - "settingsScanShows": { "message": "Séries" }, - "settingsScanArtists": { "message": "Música" }, - "settingsScanPhotos": { "message": "Fotos" }, - "settingsSelectAll": { "message": "Selecionar tudo" }, - "settingsStartScan": { "message": "Iniciar verificação" }, - "settingsPlexTokens": { "message": "Tokens do Plex" }, - "settingsPlexTokensDesc": { "message": "Edite a lista de tokens do Plex (formato JSON)." }, - "settingsSaveTokens": { "message": "Salvar tokens" }, - "settingsJellyfinTitle": { "message": "Configurações do Jellyfin" }, - "settingsJellyfinDesc": { "message": "Adicione os detalhes do seu servidor Jellyfin para verificar o conteúdo dele." }, - "jellyfinUrlLabel": { "message": "URL do servidor Jellyfin" }, - "jellyfinUserLabel": { "message": "Nome de usuário" }, - "jellyfinPassLabel": { "message": "Senha" }, - "jellyfinConnectAndScan": { "message": "Conectar e verificar" }, - "settingsPhpGenTitle": { "message": "Gerador de script PHP para servidor" }, - "settingsPhpFileOptions": { "message": "Opções de arquivo" }, - "settingsPhpSavePathLabel": { "message": "Caminho para salvar no servidor" }, - "settingsPhpSavePathPlaceholder": { "message": "Ex: /var/www/html/listas (em branco para a mesma pasta)" }, - "settingsPhpFilenameLabel": { "message": "Nome do arquivo" }, - "settingsPhpFileAction": { "message": "Ação do arquivo" }, - "settingsPhpActionAppend": { "message": "Anexar ao final do arquivo (cumulativo)" }, - "settingsPhpActionOverwrite": { "message": "Substituir o arquivo (começar do zero)" }, - "settingsPhpSecurity": { "message": "Segurança (opcional)" }, - "settingsPhpUseSecretKey": { "message": "Usar chave secreta (recomendado)" }, - "settingsPhpSecretKeyPlaceholder": { "message": "Digite uma chave secreta segura" }, - "settingsPhpGeneratedCode": { "message": "Código gerado" }, - "settingsPhpGeneratedPlaceholder": { "message": "O código PHP gerado aparecerá aqui." }, - "settingsGenerateScript": { "message": "Gerar script" }, - "settingsCopyScript": { "message": "Copiar script" }, - "settingsDataManagement": { "message": "Gerenciamento do banco de dados local" }, - "settingsImportDb": { "message": "Importar banco de dados de um arquivo" }, - "settingsExportDb": { "message": "Exportar banco de dados para um arquivo" }, - "settingsClearContent": { "message": "Limpar dados de conteúdo local" }, - "settingsClearContentDesc": { "message": "Esta ação excluirá filmes, séries e músicas do banco de dados local, mas não afetará seus favoritos ou suas configurações." }, - "settingsClose": { "message": "Fechar" }, - "settingsSave": { "message": "Salvar configurações" }, - "musicSidenavTitle": { "message": "Música do Plex" }, - "musicAllServers": { "message": "Todos os servidores" }, - "musicSearchArtistPlaceholder": { "message": "Pesquisar um artista..." }, - "musicSearchDiscographyPlaceholder": { "message": "Pesquisar na discografia..." }, - "musicNothingPlaying": { "message": "Nada tocando" }, - "musicSelectSong": { "message": "Selecione uma música" }, - "musicToStart": { "message": "para começar a tocar" }, - "miniplayerDownloadSong": { "message": "Baixar música" }, - "miniplayerDownloadAlbum": { "message": "Baixar álbum M3U" }, - "miniplayerVolume": { "message": "Volume" }, - "miniplayerShuffle": { "message": "Aleatório" }, - "miniplayerEqualizer": { "message": "Equalizador" }, - "miniplayerOpenList": { "message": "Abrir lista" }, - "eqTitle": { "message": "Equalizador gráfico" }, - "eqPresetsLabel": { "message": "Predefinições" }, - "eqPresetFlat": { "message": "Plano" }, - "eqPresetRock": { "message": "Rock" }, - "eqPresetPop": { "message": "Pop" }, - "eqPresetJazz": { "message": "Jazz" }, - "eqPresetClassical": { "message": "Clássico" }, - "eqPresetBassBoost": { "message": "Reforço de graves" }, - "eqPreampLabel": { "message": "Pré-amplificador" }, - "infoModalTitle": { "message": "Informações" }, - "infoModalFieldTitle": { "message": "Título:" }, - "infoModalFieldArtist": { "message": "Artista:" }, - "infoModalFieldAlbum": { "message": "Álbum:" }, - "infoModalFieldSong": { "message": "Música:" }, - "infoModalFieldYear": { "message": "Ano:" }, - "infoModalFieldGenre": { "message": "Gênero:" }, - "lang_en": { "message": "Inglês" }, - "lang_es": { "message": "Espanhol" }, - "lang_fr": { "message": "Francês" }, - "lang_de": { "message": "Alemão" }, - "lang_it": { "message": "Italiano" }, - "lang_pt": { "message": "Português" }, - "essentialFeaturesNotSupported": { "message": "Seu navegador não suporta recursos essenciais." }, - "dbAccessError": { "message": "Erro ao acessar o banco de dados local." }, - "dbUpdateNeeded": { "message": "O banco de dados precisa ser atualizado, recarregue a página." }, - "dbBlocked": { "message": "Feche outras abas deste aplicativo para continuar." }, - "deletingContentData": { "message": "Excluindo dados de conteúdo local..." }, - "noContentDataToDelete": { "message": "Nenhum dado de conteúdo para excluir." }, - "contentDataDeleted": { "message": "Dados de conteúdo excluídos do IndexedDB." }, - "errorDeletingData": { "message": "Erro ao excluir dados: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailable": { "message": "Editor de texto não disponível." }, - "errorLoadingTokens": { "message": "Erro ao carregar tokens para edição." }, - "errorLoadingTokensMessage": { "message": "Erro ao carregar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aceEditorNotAvailableToSave": { "message": "Editor não disponível para salvar." }, - "invalidJsonFormat": { "message": "Formato JSON inválido. Deve ser { \"tokens\": [...] }" }, - "tokensSaved": { "message": "Tokens salvos com sucesso." }, - "errorSavingTokens": { "message": "Erro ao salvar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "dbNotAvailable": { "message": "O IndexedDB não está disponível." }, - "dbExported": { "message": "Banco de dados exportado com sucesso." }, - "errorExportingDb": { "message": "Erro ao exportar o banco de dados: $message$", "placeholders": { "message": { "content": "$1" } } }, - "invalidJsonFile": { "message": "O arquivo não contém um objeto JSON válido." }, - "noDataToImport": { "message": "O arquivo não contém dados para as seções atuais do banco de dados." }, - "dbImported": { "message": "Banco de dados importado com sucesso." }, - "errorImportingDb": { "message": "Erro ao importar o banco de dados: $message$", "placeholders": { "message": { "content": "$1" } } }, - "updatingView": { "message": "Atualizando a visualização com novos dados..." }, - "confirmClearContent": { "message": "Tem certeza de que deseja excluir os dados de conteúdo local (filmes, séries, músicas, etc.)? Favoritos e configurações NÃO serão excluídos." }, - "trailerNotFound": { "message": "Nenhum trailer encontrado para este título." }, - "confirmClearHistory": { "message": "Tem certeza de que deseja limpar todo o seu histórico de visualização? Esta ação não pode ser desfeita." }, - "historyCleared": { "message": "Histórico de visualização limpo." }, - "historyItemDeleted": { "message": "Item excluído do histórico." }, - "errorGeneratingScript": { "message": "Primeiro, gere um script para poder copiá-lo." }, - "scriptCopied": { "message": "Script PHP copiado para a área de transferência." }, - "errorCopyingScript": { "message": "Erro ao copiar o script." }, - "scriptGenerated": { "message": "Script PHP gerado." }, - "errorLoadingAlbum": { "message": "Erro ao carregar o álbum: $message$", "placeholders": { "message": { "content": "$1" } } }, - "noPhotoServerSelected": { "message": "Erro: nenhum servidor de fotos foi selecionado." }, - "loadingGenres": { "message": "Carregando gêneros..." }, - "errorLoadingGenres": { "message": "Erro ao carregar" }, - "noContentFound": { "message": "Nenhum resultado encontrado." }, - "couldNotLoadContent": { "message": "Não foi possível carregar o conteúdo." }, - "noFavorites": { "message": "Você ainda não tem favoritos." }, - "errorLoadingFavorites": { "message": "Erro ao carregar os favoritos." }, - "historyEmpty": { "message": "Seu histórico está vazio." }, - "historyEmptySub": { "message": "Explore e assista a conteúdo para que ele apareça aqui." }, - "errorGeneratingRecommendations": { "message": "Erro ao gerar recomendações." }, - "noRecommendations": { "message": "Precisamos conhecê-lo melhor para dar recomendações!" }, - "errorGeneratingStats": { "message": "Erro ao gerar estatísticas." }, - "noServersForToken": { "message": "Nenhum servidor associado encontrado para este token." }, - "searchingActorContent": { "message": "Pesquisando conteúdo de $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, - "errorLoadingActorContent": { "message": "Não foi possível carregar o conteúdo de $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, - "errorAddingStream": { "message": "Erro ao adicionar stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, - "phpUrlNotConfigured": { "message": "A URL do servidor PHP não está configurada. Configure-a nas Configurações." }, - "searchingStreams": { "message": "Pesquisando streams para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "sendingStreams": { "message": "Enviando $count$ stream(s) para o servidor...", "placeholders": { "count": { "content": "$1" } } }, - "streamAddedSuccess": { "message": "Stream(s) adicionado(s) com sucesso." }, - "generatingM3U": { "message": "Gerando M3U para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "m3uDownloaded": { "message": "\"$title$\" baixado.", "placeholders": { "title": { "content": "$1" } } }, - "errorGeneratingM3U": { "message": "Erro ao gerar M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, - "settingsSavedSuccess": { "message": "Configurações salvas com sucesso." }, - "errorSavingSettings": { "message": "Erro ao salvar as configurações no banco de dados." }, - "languageChangeReload": { "message": "Idioma alterado. O aplicativo será recarregado agora." }, - "addedToFavorites": { "message": "Adicionado aos favoritos." }, - "removedFromFavorites": { "message": "Removido dos favoritos." }, - "plexScanInProgress": { "message": "A verificação do Plex já está em andamento." }, - "plexScanStarting": { "message": "Iniciando a verificação do Plex..." }, - "noPlexTokens": { "message": "Nenhum token do Plex configurado." }, - "clearingSections": { "message": "Limpando seções: $sections$", "placeholders": { "sections": { "content": "$1" } } }, - "initialScanPhaseComplete": { "message": "Fase de verificação inicial concluída." }, - "retryPhaseFinished": { "message": "Fase de nova tentativa concluída." }, - "plexScanFinished": { "message": "Verificação concluída. Atualizando conteúdo..." }, - "scanCancelled": { "message": "Verificação cancelada pelo usuário." }, - "scanCancelledInfo": { "message": "Verificação cancelada." }, - "errorInitializingMusicPlayer": { "message": "Erro ao inicializar o reprodutor de música." }, - "criticalErrorLoadingMusic": { "message": "Erro crítico ao carregar os dados de música." }, - "errorLoadingArtists": { "message": "Erro ao carregar os artistas." }, - "dbUnavailableError": { "message": "Erro: banco de dados indisponível." }, - "updatingMusicData": { "message": "Atualizando dados de música..." }, - "musicDataUpdated": { "message": "Dados de música atualizados." }, - "errorFetchingArtistSongs": { "message": "Erro ao buscar as músicas do artista." }, - "errorLoadingSongs": { "message": "Erro ao carregar as músicas." }, - "noArtistsFound": { "message": "Nenhum artista encontrado." }, - "shuffleOn": { "message": "Modo aleatório ativado." }, - "shuffleOff": { "message": "Modo aleatório desativado." }, - "playbackError": { "message": "Erro de reprodução" }, - "errorLabel": { "message": "Erro" }, - "reloadingPage": { "message": "Recarregando a página..." }, - "viewed": { "message": "Visto" }, - "local": { "message": "Local" }, - "topRatedSort": {"message": "Mais bem avaliados"}, - "recentSort": {"message": "Recentes"}, - "popularSort": {"message": "Populares"}, - "moviesSectionTitle": {"message": "Filmes"}, - "seriesSectionTitle": {"message": "Séries"}, - "searchResultsFor": {"message": "Resultados para \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, - "contentFrom": {"message": "Conteúdo de $actor$", "placeholders": {"actor": {"content": "$1"}}}, - "explore": {"message": "Explorar"}, - "noGenre": {"message": "Sem categoria"}, - "synopsis": {"message": "Sinopse"}, - "noSynopsis": {"message": "Nenhuma sinopse disponível."}, - "director": {"message": "Diretor:"}, - "writer": {"message": "Roteirista:"}, - "viewOnImdb": {"message": "Ver no IMDb"}, - "watchTrailer": {"message": "Assistir ao trailer"}, - "addToFavorites": {"message": "Adicionar aos favoritos"}, - "removeFromFavorites": {"message": "Remover dos favoritos"}, - "notAvailable": {"message": "Não disponível"}, - "mainCast": {"message": "Elenco principal"}, - "seasonsAndEpisodes": {"message": "Temporadas e episódios"}, - "similarContent": {"message": "Conteúdo semelhante"}, - "filmography": {"message": "Filmografia"}, - "availableOn": {"message": "Disponível em"}, - "episodesCount": {"message": "$count$ episódios", "placeholders": {"count": {"content": "$1"}}}, - "seasonsCount": {"message": "$count$ temporadas", "placeholders": {"count": {"content": "$1"}}}, - "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, - "noTrailerFound": {"message": "Nenhum trailer encontrado para este título."}, - "fatalInitError": {"message": "Erro fatal de inicialização"}, - "fatalInitErrorSub": {"message": "Não foi possível carregar o aplicativo."}, - "invalidStreamInfo": {"message": "Informações inválidas."}, - "dbUnavailableForStreams": {"message": "Banco de dados local indisponível."}, - "noPlexServersForStreams": {"message": "Nenhum servidor Plex."}, - "notFoundOnServers": {"message": "\"$query$\" não encontrado nos servidores Plex.", "placeholders": {"query": {"content": "$1"}}}, - "relativeTime_justNow": { "message": "Agora mesmo" }, - "relativeTime_minutesAgo": { "message": "Há $count$ minutos", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_hoursAgo": { "message": "Há $count$ horas", "placeholders": { "count": { "content": "$1" } } }, - "relativeTime_yesterday": { "message": "Ontem" }, - "relativeTime_daysAgo": { "message": "Há $count$ dias", "placeholders": { "count": { "content": "$1" } } }, - "errorLoadingDetails": { "message": "Erro ao carregar os detalhes" }, - "errorLoadingLocalContent": { "message": "Erro ao carregar o conteúdo local." }, - "errorServerResponse": { "message": "Resposta do servidor sem sucesso." }, - "errorPlexApi": { "message": "Erro $status$ da API do Plex.", "placeholders": { "status": { "content": "$1" } } }, - "errorParsingPlexXml": { "message": "Erro ao analisar o XML do Plex." }, - "untitled": { "message": "Sem título" }, - "itemCount": { "message": "$count$ itens", "placeholders": { "count": { "content": "$1" } } }, - "noPhotoServers": { "message": "Nenhum servidor de fotos" }, - "jellyfinScanInProgress": { "message": "A verificação do Jellyfin já está em andamento." }, - "jellyfinScanning": { "message": "Verificando o Jellyfin..." }, - "jellyfinMissingCredentials": { "message": "Preencha a URL e o nome de usuário do Jellyfin." }, - "jellyfinConnecting": { "message": "Conectando-se ao Jellyfin em: $url$", "placeholders": { "url": { "content": "$1" } } }, - "jellyfinAuthFailed": { "message": "A autenticação do Jellyfin falhou: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinAuthSuccess": { "message": "Autenticação do Jellyfin bem-sucedida." }, - "jellyfinFetchingLibraries": { "message": "Buscando bibliotecas..." }, - "jellyfinFetchFailed": { "message": "Erro ao buscar bibliotecas: $message$", "placeholders": { "message": { "content": "$1" } } }, - "jellyfinNoMediaLibraries": { "message": "Nenhuma biblioteca de filmes ou séries encontrada no Jellyfin." }, - "jellyfinLibrariesFound": { "message": "$count$ biblioteca(s) de mídia encontrada(s).", "placeholders": { "count": { "content": "$1" } } }, - "jellyfinLibraryScanSuccess": { "message": "[Sucesso] '$libraryName' verificada, $count$ títulos adicionados.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, - "jellyfinLibraryScanFailed": { "message": "Erro ao verificar a biblioteca '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, - "jellyfinScanSuccess": { "message": "Verificação do Jellyfin concluída. Adicionados $movies$ filmes e $series$ séries.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, - "noJellyfinCredentials": { "message": "Credenciais do Jellyfin não configuradas." }, - "notFoundOnJellyfin": { "message": "\"$query$\" não encontrado no Jellyfin.", "placeholders": { "query": { "content": "$1" } } }, - "notFoundOnAnyServer": { "message": "\"$query$\" não encontrado em nenhum servidor.", "placeholders": { "query": { "content": "$1" } } }, - "localOnPlex": { "message": "No Plex" }, - "searchOnPlex": { "message": "Pesquisar no Plex" }, - "jellyfinTitle": { "message": "Conteúdo do Jellyfin" }, - "noJellyfinContent": { "message": "Nenhum conteúdo do Jellyfin encontrado." }, - "noJellyfinContentSub": { "message": "Verifique se você verificou seu servidor Jellyfin nas configurações." }, - "activityViewerTitle": { "message": "Visualizador de atividades do servidor" }, - "activitySelectServer": { "message": "Selecione um servidor" }, - "activityCheckBtn": { "message": "Atualizar" }, - "activityNoSessions": { "message": "Nenhuma sessão ativa neste servidor." }, - "activitySessionUser": { "message": "Usuário" }, - "activitySessionDevice": { "message": "Dispositivo" }, - "activitySessionContent": { "message": "Conteúdo" }, - "activitySessionState": { "message": "Estado" }, - "activitySessionIdentifier": { "message": "Identificador do cliente" }, - "activityCopyID": { "message": "Copiar ID" }, - "activityError": { "message": "Não foi possível obter a atividade do servidor." }, - "activityCopied": { "message": "Identificador copiado para a área de transferência!" }, - "activityCopyError": { "message": "Erro ao copiar o identificador." }, - "noProvidersFound": { "message": "Nenhum provedor encontrado." }, - "availableOnPlex": { "message": "Disponível no Plex" }, - "m3uGeneratorTitle": { "message": "Gerador de listas M3U" }, - "selectAServer": { "message": "Selecione um servidor..." }, - "downloadM3u": { "message": "Baixar M3U" }, - "m3uGenerator": { "message": "Gerador de M3U" }, - "selectLibraries": { "message": "Selecionar bibliotecas" }, - "howToUse": { "message": "Como usar" }, - "m3uInstruction1": { "message": "Escolha um servidor na lista." }, - "m3uInstruction2": { "message": "Selecione uma ou mais bibliotecas para incluir." }, - "m3uInstruction3": { "message": "Clique no botão de download." }, - "m3uInstruction4": { "message": "Importe o arquivo .m3u para o seu reprodutor compatível." }, - "chatOpen": { "message": "Abrir chat" }, - "chatTitle": { "message": "Assistente de IA" }, - "chatClose": { "message": "X" }, - "chatPlaceholder": { "message": "Digite sua mensagem..." }, - "chatSend": { "message": "➤" }, - "chatWelcome": { "message": "Bem-vindo! Eu sou seu assistente CinePlex. Pergunte-me sobre filmes, séries ou qualquer outra coisa que você queira saber." }, - "chatGoogleApiKeyMissing": { "message": "A chave de API do Google Gemini não está configurada. Defina-a nas configurações da extensão para usar o assistente de IA." }, - "chatApiInvalidResponse": { "message": "A API retornou uma resposta inválida. Tente novamente." }, - "chatApiError": { "message": "Erro ao se comunicar com o assistente de IA" }, - "downloadAll": { "message": "Baixar tudo" }, - "download": { "message": "Baixar" }, - "aiToolSearchLibraryDesc": { "message": "Pesquisa na biblioteca Plex do usuário por filmes ou séries por título." }, - "aiToolSearchLibraryQueryParamDesc": { "message": "O título do filme ou série a ser pesquisado." }, - "aiToolSearchLibraryTypeParamDesc": { "message": "O tipo de conteúdo a ser pesquisado. Pode ser 'movie' para filmes ou 'series' para séries. (Opcional)." }, - "aiToolSearchLibraryResolutionParamDesc": { "message": "A resolução de vídeo a ser pesquisada (por exemplo, '4k', '1080p'). (Opcional)." }, - "aiToolSearchLibraryContainerParamDesc": { "message": "O formato do contêiner de vídeo a ser pesquisado (por exemplo, 'mkv', 'mp4'). (Opcional)." }, - "aiToolNavigateToPageDesc": { "message": "Navega o usuário para uma página específica da interface do aplicativo." }, - "aiToolNavigateToPagePageParamDesc": { "message": "O nome da página para a qual navegar, por exemplo: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers' ou 'm3u-generator'." }, - "aiToolGetUserStatsDesc": { "message": "Obtém e exibe as estatísticas da biblioteca do usuário, como o número total de filmes, séries e artistas únicos." }, - "aiToolShowItemDetailsDesc": { "message": "Exibe a página de detalhes de um filme ou série específica por seu título e tipo." }, - "aiToolShowItemDetailsTitleParamDesc": { "message": "O título exato do filme ou série." }, - "aiToolShowItemDetailsTypeParamDesc": { "message": "O tipo de conteúdo. Deve ser 'movie' ou 'series'." }, - "aiToolAddToPlaylistDesc": { "message": "Adiciona um filme ou série à lista de reprodução atual do usuário para transmiti-lo para um servidor PHP configurado." }, - "aiToolAddToPlaylistTitleParamDesc": { "message": "O título do filme ou série a ser adicionado." }, - "aiToolAddToPlaylistTypeParamDesc": { "message": "O tipo de conteúdo. Deve ser 'movie' ou 'series'." }, - "aiToolCheckAndDownloadDesc": { "message": "Verifica a disponibilidade de uma lista de títulos de filmes ou séries nos servidores locais do usuário e, se encontrados, gera e baixa um arquivo de lista de reprodução M3U com os streams encontrados." }, - "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Uma matriz de títulos de filmes ou séries para pesquisar e baixar." }, - "aiToolCheckAndDownloadTypeParamDesc": { "message": "O tipo de conteúdo da lista. Deve ser 'movie' ou 'series'." }, - "aiToolCheckAndDownloadFilenameParamDesc": { "message": "O nome do arquivo M3U a ser baixado (por exemplo, 'MinhaLista.m3u'). Se não for fornecido, um nome padrão será usado." }, - "aiToolToggleFavoriteDesc": { "message": "Adiciona ou remove um filme ou série da lista de favoritos do usuário." }, - "aiToolToggleFavoriteTitleParamDesc": { "message": "O título do filme ou série." }, - "aiToolToggleFavoriteTypeParamDesc": { "message": "O tipo de conteúdo. Deve ser 'movie' ou 'series'." }, - "aiToolGetRecommendationsDesc": { "message": "Gera e exibe uma lista de recomendações de filmes ou séries com base no histórico de visualização e nos favoritos do usuário." }, - "aiToolApplyFiltersDesc": { "message": "Aplica filtros à visualização atual de filmes ou séries, permitindo refinar os resultados por tipo, gênero, ano e ordem de classificação." }, - "aiToolApplyFiltersTypeParamDesc": { "message": "O tipo de conteúdo ao qual aplicar os filtros. Deve ser 'movie' ou 'series'." }, - "aiToolApplyFiltersGenreParamDesc": { "message": "O nome do gênero pelo qual filtrar (por exemplo, 'Ação', 'Drama')." }, - "aiToolApplyFiltersYearParamDesc": { "message": "O ano de lançamento pelo qual filtrar (por exemplo, '2023')." }, - "aiToolApplyFiltersSortParamDesc": { "message": "O critério de classificação para os resultados. Valores válidos: 'popularity.desc' (populares), 'vote_average.desc' (mais bem avaliados), 'release_date.desc' (recentes para filmes) ou 'first_air_date.desc' (recentes para séries)." }, - "aiToolPlayMusicByArtistDesc": { "message": "Abre o reprodutor de música e começa a tocar músicas de um artista específico da biblioteca do usuário." }, - "aiToolPlayMusicByArtistNameParamDesc": { "message": "O nome exato do artista cujas músicas devem ser tocadas." }, - "aiToolClearChatHistoryDesc": { "message": "Limpa todo o histórico de mensagens da conversa atual com o assistente de IA." }, - "aiToolDeleteDatabaseDesc": { "message": "Exclui todo o banco de dados local da extensão, incluindo conteúdo verificado, configurações e favoritos. Esta ação é irreversível e recarregará o aplicativo." }, - "aiToolUpdateAllTokensDesc": { "message": "Inicia uma verificação completa de todos os servidores e bibliotecas Plex associados aos tokens configurados na extensão. Atualiza todos os filmes, séries, artistas e fotos." }, - "aiToolAddPlexTokenDesc": { "message": "Adiciona um novo token X-Plex à configuração da extensão, permitindo que o aplicativo verifique o conteúdo de novos servidores Plex." }, - "aiToolAddPlexTokenTokenParamDesc": { "message": "A string do token X-Plex a ser adicionada." }, - "aiToolChangeRegionDesc": { "message": "Altera a região usada para a descoberta de conteúdo na API do TMDB. Isso afetará os resultados exibidos nas seções de filmes e séries, bem como os provedores de streaming." }, - "aiToolChangeRegionRegionParamDesc": { "message": "O código de país de duas letras ISO 3166-1 para a nova região (por exemplo, 'US' para os Estados Unidos, 'ES' para a Espanha, 'MX' para o México)." }, - "aiToolClearAllFavoritesDesc": { "message": "Remove todos os filmes e séries que o usuário marcou como favoritos." }, - "aiToolClearViewingHistoryDesc": { "message": "Limpa o histórico de visualização do usuário da página de histórico." }, - "aiToolClearRecommendationsViewDesc": { "message": "Limpa a visualização de recomendações e remove as recomendações em cache." }, - "aiToolSearchNotFound": { "message": "'$query' não encontrado em sua biblioteca.", "placeholders": { "query": { "content": "$1" } } }, - "aiToolNavigateSuccess": { "message": "Navegado para a página $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolNavigateError": { "message": "Erro ao navegar para a página $page$.", "placeholders": { "page": { "content": "$1" } } }, - "aiToolStatsError": { "message": "Erro ao obter estatísticas." }, - "aiToolItemNotFound": { "message": "Item '$title' não encontrado.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolShowItemDetailsSuccess": { "message": "Mostrando detalhes de '$title'.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolAddToPlaylistSuccess": { "message": "Adicionado '$title' à lista de reprodução.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteAdded": { "message": "Adicionado '$title' aos favoritos.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolFavoriteRemoved": { "message": "Removido '$title' dos favoritos.", "placeholders": { "title": { "content": "$1" } } }, - "aiToolRecommendationsSuccess": { "message": "Mostrando recomendações." }, - "aiToolApplyFiltersGenreNotFound": { "message": "Gênero '$genre' não encontrado.", "placeholders": { "genre": { "content": "$1" } } }, - "aiToolApplyFiltersSuccess": { "message": "Filtros aplicados com sucesso." }, - "aiToolPlayMusicNotReady": { "message": "O reprodutor de música não está pronto. Verifique se a sua biblioteca de música do Plex foi verificada." }, - "aiToolPlayMusicArtistNotFound": { "message": "Artista '$artist_name' não encontrado.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicNoSongs": { "message": "Nenhuma música encontrada para '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolPlayMusicSuccess": { "message": "Tocando música de '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, - "aiToolChatHistoryCleared": { "message": "Histórico do chat limpo." }, - "aiToolConfirmDeleteDatabase": { "message": "Tem certeza de que deseja excluir o banco de dados local? Esta ação é irreversível." }, - "aiToolDeleteDatabaseCancelled": { "message": "Exclusão do banco de dados cancelada." }, - "aiToolExecutionError": { "message": "Erro ao executar a ferramenta '$toolName$': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, - "aiToolUnknown": { "message": "Ferramenta desconhecida: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, - "aiToolFavoritesCleared": { "message": "Favoritos limpos." }, - "aiToolFavoritesClearError": { "message": "Erro ao limpar os favoritos: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolRecommendationsCleared": { "message": "Recomendações limpas." }, - "aiToolRecommendationsClearError": { "message": "Erro ao limpar as recomendações: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleted": { "message": "Banco de dados excluído. A página será recarregada." }, - "aiToolDatabaseDeleteError": { "message": "Erro ao excluir o banco de dados: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolDatabaseDeleteBlocked": { "message": "A exclusão do banco de dados está bloqueada. Feche outras abas do aplicativo." }, - "aiToolUpdateAllTokensSuccess": { "message": "Todos os tokens foram atualizados com sucesso." }, - "aiToolUpdateAllTokensError": { "message": "Erro ao atualizar os tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolAddPlexTokenSuccess": { "message": "Token do Plex adicionado com sucesso." }, - "aiToolAddPlexTokenError": { "message": "Erro ao adicionar o token do Plex: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolChangeRegionSuccess": { "message": "Região alterada para $region$. O conteúdo está sendo atualizado.", "placeholders": { "region": { "content": "$1" } } }, - "aiToolChangeRegionError": { "message": "Erro ao alterar a região: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiToolViewingHistoryCleared": { "message": "Histórico de visualização limpo." }, - "aiToolViewingHistoryClearError": { "message": "Erro ao limpar o histórico de visualização: $message$", "placeholders": { "message": { "content": "$1" } } }, - "aiSystemPrompt_v3": { "message": "Você é um assistente especialista em filmes e séries chamado CinePlex. Sua função principal é ajudar os usuários a descobrir conteúdo e interagir com sua biblioteca. Siga estas regras rigorosamente: 1. **NUNCA** finja que realizou uma ação se não usou uma ferramenta para isso. Por exemplo, não diga 'Eu baixei X' se não usou a ferramenta de download. 2. Para solicitações de recomendações ou listas (por exemplo, 'diga-me 5 filmes de terror'), use seu próprio conhecimento para gerar a lista. Apresente-a em formato numerado ou com marcadores. Depois de exibir a lista, pergunte proativamente ao usuário se ele deseja que você verifique a disponibilidade em seus servidores locais e crie um arquivo M3U. 3. **SOMENTE** se o usuário confirmar que deseja verificar ou baixar a lista, use a ferramenta `check_and_download_titles_list`. Não a use sem confirmação explícita. 4. Para qualquer outra ação, como navegar, obter estatísticas, pesquisar um título específico ou filtrar por resolução ou contêiner, use as ferramentas apropriadas. Seja sempre conciso, amigável e eficiente." }, - "aiToolM3UNoTitlesProvided": { "message": "Forneça uma lista de títulos para criar a lista de reprodução." }, - "aiToolM3UCheckingTitles": { "message": "Verificando os títulos em seus servidores locais..." }, - "aiToolM3UNoLocalMatchesForDownload": { "message": "Não encontrei nenhum dos filmes ou séries da lista em seus servidores locais." }, - "aiToolM3UDownloadStarted": { "message": "Pronto! Encontrei $1 de $2 títulos em seus servidores e iniciei o download da lista de reprodução M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, - "backToProviders": { "message": "Voltar para os provedores" }, - "artistsCounterSingle": { "message": "$total$ artista", "placeholders": { "total": { "content": "$1" } } }, - "artistsCounterLoading": { "message": "Carregando..." }, - "downloadingSong": { "message": "Iniciando o download de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "songDownloaded": { "message": "\"$title$\" baixado.", "placeholders": { "title": { "content": "$1" } } }, - "errorDownloadingSong": { "message": "Erro ao baixar \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "generatingAlbumM3U": { "message": "Gerando M3U para \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, - "albumM3UGenerated": { "message": "M3U para o álbum \"$artist$\" gerado.", "placeholders": { "artist": { "content": "$1" } } }, - "retyingSection": { "message": "Tentando novamente a seção \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, - "retrySuccess": { "message": "[SUCESSO] Nova tentativa de \"$title$\" concluída.", "placeholders": { "title": { "content": "$1" } } }, - "retryError": { "message": "[ERRO FINAL] A nova tentativa para \"$title$\" falhou: $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, - "startingRetryPhase": { "message": "Iniciando a fase de nova tentativa para $count$ seções...", "placeholders": { "count": { "content": "$1" } } }, - "tokenFoundServers": { "message": "O token $token$... encontrou $count$ servidores.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, - "errorProcessingToken": { "message": "Erro ao processar o token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, - "plexScanFatalError": { "message": "ERRO FATAL: $message$", "placeholders": { "message": { "content": "$1" } } }, - "errorDuringScan": { "message": "Erro durante a verificação: $message$", "placeholders": { "message": { "content": "$1" } } }, - "stoppingPlexScan": { "message": "Parando a verificação do Plex..." }, - "invalidTokenProvided": { "message": "Token inválido fornecido." }, - "tokenAlreadyExists": { "message": "O token já existe." }, - "tokenAddedSuccessfully": { "message": "Token adicionado com sucesso." }, - "noStreamsFoundForSelection": { "message": "Nenhum stream encontrado para a seleção." }, - "autoplayBlocked": { "message": "Reprodução automática bloqueada." }, - "page": { "message": "Página" }, - "all": { "message": "Todos" }, - "userScore": { "message": "Pontuação dos usuários" }, - "duration": { "message": "Duração" }, - "min": { "message": "Mín" }, - "max": { "message": "Máx" } + "appName": { "message": "CinePlex" }, + "appDescription": { "message": "Escaneia servidores Plex para encontrar conteúdo e o exibe na interface" }, + "appTagline": { "message": "Filmes, Séries e Música" }, + "appLocaleCode": { "message": "pt-BR" }, + "toggleNavigation": { "message": "Alternar Navegação" }, + "searchPlaceholder": { "message": "Buscar filmes ou séries..." }, + "openMusicPlayer": { "message": "Abrir Player de Música" }, + "settings": { "message": "Configurações" }, + "navMovies": { "message": "Filmes" }, + "navSeries": { "message": "Séries" }, + "navProviders": { "message": "Provedores" }, + "navPhotos": { "message": "Fotos" }, + "navStats": { "message": "Estatísticas" }, + "navFavorites": { "message": "Favoritos" }, + "navHistory": { "message": "Histórico" }, + "navRecommendations": { "message": "Recomendações" }, + "navMusic": { "message": "Música" }, + "musicFeaturedPlaylists": { "message": "Playlists em Destaque" }, + "musicRecentlyAdded": { "message": "Adicionado Recentemente" }, + "navM3uGenerator": { "message": "Gerador M3U" }, + "heroWelcome": { "message": "" }, + "heroSubtitle": { "message": "Explore milhares de filmes e séries." }, + "addStream": { "message": "Adicionar Stream" }, + "moreInfo": { "message": "Mais informações" }, + "popularMovies": { "message": "Filmes Populares" }, + "allGenres": { "message": "Todos os gêneros" }, + "allYears": { "message": "Todos os anos" }, + "sortPopular": { "message": "Mais populares" }, + "sortTopRated": { "message": "Melhor avaliados" }, + "sortRecent": { "message": "Mais recentes" }, + "loadMore": { "message": "Carregar mais" }, + "photosBreadcrumbHome": { "message": "Álbuns" }, + "selectServer": { "message": "Selecione um servidor" }, + "loading": { "message": "Carregando..." }, + "loadingLibraries": { "message": "Carregando bibliotecas..." }, + "photosEmptyState": { "message": "Nenhum álbum ou foto encontrado." }, + "photosEmptyStateSub": { "message": "Por favor, selecione um servidor ou certifique-se de ter uma biblioteca de fotos no Plex." }, + "statsTitle": { "message": "Estatísticas da Biblioteca" }, + "statsAllTokens": { "message": "Todos os Tokens" }, + "statsAnalyzing": { "message": "Analisando sua biblioteca..." }, + "statsActiveTokens": { "message": "Tokens Ativos" }, + "statsServersFound": { "message": "Servidores Encontrados" }, + "statsUniqueMovies": { "message": "Filmes Únicos" }, + "statsUniqueSeries": { "message": "Séries Únicas" }, + "statsUniqueArtists": { "message": "Artistas Únicos" }, + "statsTokenServers": { "message": "Servidores do Token" }, + "statsChartMoviesByGenre": { "message": "Conteúdo por Gênero (Filmes)" }, + "statsChartSeriesByGenre": { "message": "Conteúdo por Gênero (Séries)" }, + "statsChartByDecade": { "message": "Conteúdo por Década" }, + "recommendationsTitle": { "message": "Recomendações para você" }, + "historyTitle": { "message": "Histórico de Visualização" }, + "clearHistory": { "message": "Limpar Tudo" }, + "consoleTitle": { "message": "Console de Escaneamento Plex" }, + "footerCredit": { "message": "Uma interface para o seu universo Plex." }, + "closeTrailer": { "message": "Fechar trailer" }, + "close": { "message": "Fechar" }, + "photoViewer": { "message": "Visualizador de fotos" }, + "previous": { "message": "Anterior" }, + "next": { "message": "Próximo" }, + "notificationTemplateText": { "message": "Notificação" }, + "settingsTitleFull": { "message": "Configurações e Ajustes" }, + "settingsTabGeneral": { "message": "Geral" }, + "settingsTabPlex": { "message": "Plex" }, + "settingsTabJellyfin": { "message": "Jellyfin" }, + "settingsTabPhpGen": { "message": "Gerador PHP" }, + "settingsTabData": { "message": "Dados" }, + "settingsApiServer": { "message": "Configuração de API e Servidor" }, + "settingsTmdbApiLabel": { "message": "Chave de API do TMDB (Opcional)" }, + "settingsTmdbApiPlaceholder": { "message": "A chave padrão será usada se deixado em branco" }, + "settingsGoogleApiLabel": { "message": "Chave de API do Google Gemini (Opcional)" }, + "settingsGoogleApiPlaceholder": { "message": "Necessária para usar o assistente de IA" }, + "settingsRegionLabel": { "message": "Região para descoberta de conteúdo" }, + "allRegions": { "message": "Todas as regiões" }, + "settingsPhpUrlLabel": { "message": "URL do Servidor para Adicionar Streams" }, + "settingsPhpUrlPlaceholder": { "message": "https://seu-servidor.com/caminho/para/script.php" }, + "settingsInterface": { "message": "Interface" }, + "settingsLightTheme": { "message": "Modo Claro" }, + "settingsShowHero": { "message": "Mostrar seção de boas-vindas 'Hero'" }, + "settingsScanContent": { "message": "Escaneamento de Conteúdo" }, + "settingsScanDesc": { "message": "Selecione o que escanear e pressione o botão." }, + "settingsScanMovies": { "message": "Filmes" }, + "settingsScanShows": { "message": "Séries" }, + "settingsScanArtists": { "message": "Música" }, + "settingsScanPhotos": { "message": "Fotos" }, + "settingsSelectAll": { "message": "Selecionar Tudo" }, + "settingsStartScan": { "message": "Iniciar Escaneamento" }, + "settingsPlexTokens": { "message": "Tokens do Plex" }, + "settingsPlexTokensDesc": { "message": "Edite a lista de tokens do Plex (formato JSON)." }, + "settingsSaveTokens": { "message": "Salvar Tokens" }, + "settingsJellyfinTitle": { "message": "Configuração do Jellyfin" }, + "settingsJellyfinDesc": { "message": "Adicione os dados do seu servidor Jellyfin para escanear seu conteúdo." }, + "jellyfinUrlLabel": { "message": "URL do Servidor Jellyfin" }, + "jellyfinUserLabel": { "message": "Nome de Usuário" }, + "jellyfinPassLabel": { "message": "Senha" }, + "jellyfinConnectAndScan": { "message": "Conectar e Escanear" }, + "settingsPhpGenTitle": { "message": "Gerador de Script PHP para o Servidor" }, + "settingsPhpFileOptions": { "message": "Opções do Arquivo" }, + "settingsPhpSavePathLabel": { "message": "Caminho de Salvamento no Servidor" }, + "settingsPhpSavePathPlaceholder": { "message": "Ex: /var/www/html/listas (em branco para a mesma pasta)" }, + "settingsPhpFilenameLabel": { "message": "Nome do Arquivo" }, + "settingsPhpFileAction": { "message": "Ação sobre o Arquivo" }, + "settingsPhpActionAppend": { "message": "Adicionar ao final do arquivo (acumulativo)" }, + "settingsPhpActionOverwrite": { "message": "Sobrescrever o arquivo (começar de novo)" }, + "settingsPhpSecurity": { "message": "Segurança (Opcional)" }, + "settingsPhpUseSecretKey": { "message": "Usar chave secreta (Recomendado)" }, + "settingsPhpSecretKeyPlaceholder": { "message": "Digite uma chave secreta segura" }, + "settingsPhpGeneratedCode": { "message": "Código Gerado" }, + "settingsPhpGeneratedPlaceholder": { "message": "O código PHP gerado aparecerá aqui." }, + "settingsGenerateScript": { "message": "Gerar Script" }, + "settingsCopyScript": { "message": "Copiar Script" }, + "settingsDataManagement": { "message": "Gerenciamento do Banco de Dados Local" }, + "settingsImportDb": { "message": "Importar BD de Arquivo" }, + "settingsExportDb": { "message": "Exportar BD para Arquivo" }, + "settingsClearContent": { "message": "Limpar Dados de Conteúdo Local" }, + "settingsClearContentDesc": { "message": "Esta ação excluirá filmes, séries e músicas do banco de dados local, mas não afetará seus favoritos ou configurações." }, + "settingsClose": { "message": "Fechar" }, + "settingsSave": { "message": "Salvar Configurações" }, + "musicSidenavTitle": { "message": "Música do Plex" }, + "musicAllServers": { "message": "Todos os Servidores" }, + "musicSearchArtistPlaceholder": { "message": "Buscar artista..." }, + "musicSearchDiscographyPlaceholder": { "message": "Buscar na discografia..." }, + "musicNothingPlaying": { "message": "Nada tocando" }, + "musicSelectSong": { "message": "Selecione uma música" }, + "musicToStart": { "message": "para começar a tocar" }, + "miniplayerDownloadSong": { "message": "Baixar música" }, + "miniplayerDownloadAlbum": { "message": "Baixar M3U" }, + "miniplayerVolume": { "message": "Volume" }, + "miniplayerShuffle": { "message": "Aleatório" }, + "miniplayerEqualizer": { "message": "Equalizador" }, + "miniplayerOpenList": { "message": "Abrir lista" }, + "eqTitle": { "message": "Equalizador Gráfico" }, + "eqPresetsLabel": { "message": "Predefinições" }, + "eqPresetFlat": { "message": "Plano" }, + "eqPresetRock": { "message": "Rock" }, + "eqPresetPop": { "message": "Pop" }, + "eqPresetJazz": { "message": "Jazz" }, + "eqPresetClassical": { "message": "Clássica" }, + "eqPresetBassBoost": { "message": "Reforço de Graves" }, + "eqPreampLabel": { "message": "Pré-amplificador" }, + "infoModalTitle": { "message": "Informação" }, + "infoModalFieldTitle": { "message": "Título:" }, + "infoModalFieldArtist": { "message": "Artista:" }, + "infoModalFieldAlbum": { "message": "Álbum:" }, + "infoModalFieldSong": { "message": "Música:" }, + "infoModalFieldYear": { "message": "Ano:" }, + "infoModalFieldGenre": { "message": "Gênero:" }, + "lang_en": { "message": "Inglês" }, + "lang_es": { "message": "Espanhol" }, + "lang_fr": { "message": "Francês" }, + "lang_de": { "message": "Alemão" }, + "lang_it": { "message": "Italiano" }, + "lang_pt": { "message": "Português" }, + "essentialFeaturesNotSupported": { "message": "Seu navegador não suporta recursos essenciais." }, + "dbAccessError": { "message": "Erro ao acessar o banco de dados local." }, + "dbUpdateNeeded": { "message": "O banco de dados precisa ser atualizado, por favor, recarregue a página." }, + "dbBlocked": { "message": "Por favor, feche outras abas desta aplicação para continuar." }, + "deletingContentData": { "message": "Excluindo dados de conteúdo local..." }, + "noContentDataToDelete": { "message": "Não há dados de conteúdo para excluir." }, + "contentDataDeleted": { "message": "Dados de conteúdo excluídos do IndexedDB." }, + "errorDeletingData": { "message": "Erro ao excluir dados: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailable": { "message": "Editor de texto não disponível." }, + "errorLoadingTokens": { "message": "Erro ao carregar tokens para edição." }, + "errorLoadingTokensMessage": { "message": "Erro ao carregar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aceEditorNotAvailableToSave": { "message": "Editor não disponível para salvar." }, + "invalidJsonFormat": { "message": "Formato JSON inválido. Deve ser { \"tokens\": [...] }" }, + "tokensSaved": { "message": "Tokens salvos com sucesso." }, + "errorSavingTokens": { "message": "Erro ao salvar tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "dbNotAvailable": { "message": "IndexedDB não está disponível." }, + "dbExported": { "message": "Banco de dados exportado com sucesso." }, + "errorExportingDb": { "message": "Erro ao exportar banco de dados: $message$", "placeholders": { "message": { "content": "$1" } } }, + "invalidJsonFile": { "message": "O arquivo não contém um objeto JSON válido." }, + "noDataToImport": { "message": "O arquivo não contém dados para as seções atuais do BD." }, + "dbImported": { "message": "Banco de dados importado com sucesso." }, + "errorImportingDb": { "message": "Erro ao importar banco de dados: $message$", "placeholders": { "message": { "content": "$1" } } }, + "updatingView": { "message": "Atualizando a visualização com os novos dados..." }, + "confirmClearContent": { "message": "Você tem certeza de que deseja excluir os dados de conteúdo local (Filmes, Séries, Música, etc.)? Favoritos e Configurações NÃO serão excluídos." }, + "trailerNotFound": { "message": "Nenhum trailer encontrado para este título." }, + "confirmClearHistory": { "message": "Você tem certeza de que deseja excluir todo o seu histórico de visualização? Esta ação não pode ser desfeita." }, + "historyCleared": { "message": "Histórico de visualização limpo." }, + "historyItemDeleted": { "message": "Item excluído do histórico." }, + "errorGeneratingScript": { "message": "Primeiro gere um script para poder copiá-lo." }, + "scriptCopied": { "message": "Script PHP copiado para a área de transferência." }, + "errorCopyingScript": { "message": "Erro ao copiar o script." }, + "scriptGenerated": { "message": "Script PHP gerado." }, + "errorLoadingAlbum": { "message": "Erro ao carregar álbum: $message$", "placeholders": { "message": { "content": "$1" } } }, + "noPhotoServerSelected": { "message": "Erro: Nenhum servidor de fotos foi selecionado." }, + "loadingGenres": { "message": "Carregando gêneros..." }, + "errorLoadingGenres": { "message": "Erro ao carregar" }, + "noContentFound": { "message": "Nenhum resultado encontrado." }, + "couldNotLoadContent": { "message": "Não foi possível carregar o conteúdo." }, + "noFavorites": { "message": "Você ainda não tem favoritos." }, + "errorLoadingFavorites": { "message": "Erro ao carregar favoritos." }, + "historyEmpty": { "message": "Seu histórico está vazio." }, + "historyEmptySub": { "message": "Explore e assista a conteúdo para que ele apareça aqui." }, + "errorGeneratingRecommendations": { "message": "Erro ao gerar recomendações." }, + "noRecommendations": { "message": "Precisamos te conhecer melhor para dar recomendações!" }, + "errorGeneratingStats": { "message": "Erro ao gerar estatísticas." }, + "noServersForToken": { "message": "Nenhum servidor associado encontrado para este token." }, + "searchingActorContent": { "message": "Buscando conteúdo de $actorName$", "placeholders": { "actorName": { "content": "$1" } } }, + "errorLoadingActorContent": { "message": "Não foi possível carregar o conteúdo para $actorName$.", "placeholders": { "actorName": { "content": "$1" } } }, + "errorAddingStream": { "message": "Erro ao adicionar stream(s): $message$", "placeholders": { "message": { "content": "$1" } } }, + "phpUrlNotConfigured": { "message": "A URL do servidor PHP não está configurada. Por favor, configure-a nas Configurações." }, + "searchingStreams": { "message": "Buscando streams para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "sendingStreams": { "message": "Enviando $count$ stream(s) para o servidor...", "placeholders": { "count": { "content": "$1" } } }, + "streamAddedSuccess": { "message": "Stream(s) adicionado(s) com sucesso." }, + "generatingM3U": { "message": "Gerando M3U para \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "m3uDownloaded": { "message": "\"$title$\" baixado.", "placeholders": { "title": { "content": "$1" } } }, + "errorGeneratingM3U": { "message": "Erro ao gerar M3U: $message$", "placeholders": { "message": { "content": "$1" } } }, + "settingsSavedSuccess": { "message": "Configurações salvas com sucesso." }, + "errorSavingSettings": { "message": "Erro ao salvar as configurações no banco de dados." }, + "languageChangeReload": { "message": "Idioma alterado. A aplicação será recarregada agora." }, + "addedToFavorites": { "message": "Adicionado aos favoritos." }, + "removedFromFavorites": { "message": "Removido dos favoritos." }, + "plexScanInProgress": { "message": "O escaneamento do Plex já está em andamento." }, + "plexScanStarting": { "message": "Iniciando escaneamento do Plex..." }, + "noPlexTokens": { "message": "Não há tokens do Plex configurados." }, + "clearingSections": { "message": "Limpando seções: $sections$", "placeholders": { "sections": { "content": "$1" } } }, + "initialScanPhaseComplete": { "message": "Fase de escaneamento inicial finalizada." }, + "retryPhaseFinished": { "message": "Fase de novas tentativas finalizada." }, + "plexScanFinished": { "message": "Escaneamento finalizado. Atualizando conteúdo..." }, + "scanCancelled": { "message": "Escaneamento cancelado pelo usuário." }, + "scanCancelledInfo": { "message": "Escaneamento cancelado." }, + "errorInitializingMusicPlayer": { "message": "Erro ao inicializar o player de música." }, + "criticalErrorLoadingMusic": { "message": "Erro crítico ao carregar dados de música." }, + "errorLoadingArtists": { "message": "Erro ao carregar artistas." }, + "dbUnavailableError": { "message": "Erro: Banco de dados não disponível." }, + "updatingMusicData": { "message": "Atualizando dados de música..." }, + "musicDataUpdated": { "message": "Dados de música atualizados." }, + "errorFetchingArtistSongs": { "message": "Erro ao buscar as músicas do artista." }, + "errorLoadingSongs": { "message": "Erro ao carregar músicas." }, + "noArtistsFound": { "message": "Nenhum artista encontrado." }, + "shuffleOn": { "message": "Modo aleatório ativado." }, + "shuffleOff": { "message": "Modo aleatório desativado." }, + "playbackError": { "message": "Erro de reprodução" }, + "errorLabel": { "message": "Erro" }, + "reloadingPage": { "message": "Recarregando a página..." }, + "viewed": { "message": "Visto" }, + "local": { "message": "Local" }, + "topRatedSort": {"message": "Melhor Avaliados"}, + "recentSort": {"message": "Recentes"}, + "popularSort": {"message": "Populares"}, + "moviesSectionTitle": {"message": "Filmes"}, + "seriesSectionTitle": {"message": "Séries"}, + "searchResultsFor": {"message": "Resultados para \"$query$\"", "placeholders": {"query": {"content": "$1"}}}, + "contentFrom": {"message": "Conteúdo de $actor$", "placeholders": {"actor": {"content": "$1"}}}, + "explore": {"message": "Explorar"}, + "noGenre": {"message": "Sem categoria"}, + "synopsis": {"message": "Sinopse"}, + "noSynopsis": {"message": "Nenhuma sinopse disponível."}, + "director": {"message": "Diretor:"}, + "writer": {"message": "Roteirista:"}, + "viewOnImdb": {"message": "Ver no IMDb"}, + "watchTrailer": {"message": "Trailer"}, + "addToFavorites": {"message": "Adicionar aos favoritos"}, + "removeFromFavorites": {"message": "Remover dos favoritos"}, + "notAvailable": {"message": "Não disponível"}, + "mainCast": {"message": "Elenco Principal"}, + "seasonsAndEpisodes": {"message": "Temporadas e Episódios"}, + "similarContent": {"message": "Conteúdo Similar"}, + "filmography": {"message": "Filmografia"}, + "availableOn": {"message": "Disponível em"}, + "episodesCount": {"message": "$count$ Episódios", "placeholders": {"count": {"content": "$1"}}}, + "seasonsCount": {"message": "$count$ Temporadas", "placeholders": {"count": {"content": "$1"}}}, + "runtimeMinutes": {"message": "$count$ min", "placeholders": {"count": {"content": "$1"}}}, + "noTrailerFound": {"message": "Nenhum trailer encontrado para este título."}, + "fatalInitError": {"message": "Erro fatal de inicialização"}, + "fatalInitErrorSub": {"message": "Não foi possível carregar a aplicação."}, + "invalidStreamInfo": {"message": "Informação de stream inválida."}, + "dbUnavailableForStreams": {"message": "Banco de dados local não disponível."}, + "noPlexServersForStreams": {"message": "Nenhum servidor Plex."}, + "notFoundOnServers": {"message": "\"$query$\" não encontrado nos servidores Plex.", "placeholders": {"query": {"content": "$1"}}}, + "relativeTime_justNow": { "message": "Agora mesmo" }, + "relativeTime_minutesAgo": { "message": "Há $count$ minutos", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_hoursAgo": { "message": "Há $count$ horas", "placeholders": { "count": { "content": "$1" } } }, + "relativeTime_yesterday": { "message": "Ontem" }, + "relativeTime_daysAgo": { "message": "Há $count$ dias", "placeholders": { "count": { "content": "$1" } } }, + "errorLoadingDetails": { "message": "Erro ao Carregar os Detalhes" }, + "errorLoadingLocalContent": { "message": "Erro ao carregar o conteúdo local." }, + "errorServerResponse": { "message": "Resposta não bem-sucedida do servidor." }, + "errorPlexApi": { "message": "Erro $status$ da API do Plex.", "placeholders": { "status": { "content": "$1" } } }, + "errorParsingPlexXml": { "message": "Erro ao analisar o XML do Plex." }, + "untitled": { "message": "Sem título" }, + "itemCount": { "message": "$count$ itens", "placeholders": { "count": { "content": "$1" } } }, + "noPhotoServers": { "message": "Nenhum servidor de fotos" }, + "jellyfinScanInProgress": { "message": "O escaneamento do Jellyfin já está em andamento." }, + "jellyfinScanning": { "message": "Escaneando Jellyfin..." }, + "jellyfinMissingCredentials": { "message": "Por favor, preencha a URL e o usuário do Jellyfin." }, + "jellyfinConnecting": { "message": "Conectando ao Jellyfin em: $url$", "placeholders": { "url": { "content": "$1" } } }, + "jellyfinAuthFailed": { "message": "Autenticação do Jellyfin falhou: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinAuthSuccess": { "message": "Autenticação do Jellyfin bem-sucedida." }, + "jellyfinFetchingLibraries": { "message": "Obtendo bibliotecas..." }, + "jellyfinFetchFailed": { "message": "Erro ao obter bibliotecas: $message$", "placeholders": { "message": { "content": "$1" } } }, + "jellyfinNoMediaLibraries": { "message": "Nenhuma biblioteca de filmes ou séries encontrada no Jellyfin." }, + "jellyfinLibrariesFound": { "message": "$count$ biblioteca(s) de mídia encontrada(s).", "placeholders": { "count": { "content": "$1" } } }, + "jellyfinLibraryScanSuccess": { "message": "[Sucesso] '$libraryName' escaneada, $count$ títulos adicionados.", "placeholders": { "libraryName": { "content": "$1" }, "count": { "content": "$2" } } }, + "jellyfinLibraryScanFailed": { "message": "Erro ao escanear a biblioteca '$libraryName'.", "placeholders": { "libraryName": { "content": "$1" } } }, + "jellyfinScanSuccess": { "message": "Escaneamento do Jellyfin concluído. Adicionados $movies$ filmes e $series$ séries.", "placeholders": { "movies": { "content": "$1" }, "series": { "content": "$2" } } }, + "noJellyfinCredentials": { "message": "Credenciais do Jellyfin não configuradas." }, + "notFoundOnJellyfin": { "message": "\"$query$\" não encontrado no Jellyfin.", "placeholders": {"query": {"content": "$1"}}}, + "notFoundOnAnyServer": { "message": "\"$query$\" não encontrado em nenhum servidor.", "placeholders": {"query": {"content": "$1"}}}, + "localOnPlex": { "message": "No Plex" }, + "searchOnPlex": { "message": "Buscar no Plex" }, + "jellyfinTitle": { "message": "Conteúdo do Jellyfin" }, + "noJellyfinContent": { "message": "Nenhum conteúdo do Jellyfin encontrado." }, + "noJellyfinContentSub": { "message": "Certifique-se de ter escaneado seu servidor Jellyfin nas configurações." }, + "activityViewerTitle": { "message": "Visualizador de Atividade do Servidor" }, + "activitySelectServer": { "message": "Selecione um servidor" }, + "activityCheckBtn": { "message": "Atualizar" }, + "activityNoSessions": { "message": "Não há sessões ativas neste servidor." }, + "activitySessionUser": { "message": "Usuário" }, + "activitySessionDevice": { "message": "Dispositivo" }, + "activitySessionContent": { "message": "Conteúdo" }, + "activitySessionState": { "message": "Estado" }, + "activitySessionIdentifier": { "message": "Identificador do Cliente" }, + "activityCopyID": { "message": "Copiar ID" }, + "activityError": { "message": "Não foi possível obter a atividade do servidor." }, + "activityCopied": { "message": "Identificador copiado para a área de transferência!" }, + "activityCopyError": { "message": "Erro ao copiar o identificador." }, + "noProvidersFound": { "message": "Nenhum provedor encontrado." }, + "availableOnPlex": { "message": "Disponível no Plex" }, + "m3uGeneratorTitle": { "message": "Gerador de Listas M3U" }, + "selectAServer": { "message": "Selecione um servidor..." }, + "downloadM3u": { "message": "Baixar M3U" }, + "m3uGenerator": { "message": "Gerador M3U" }, + "selectLibraries": { "message": "Selecionar Bibliotecas" }, + "howToUse": { "message": "Como Usar" }, + "m3uInstruction1": { "message": "Escolha um servidor da lista." }, + "m3uInstruction2": { "message": "Selecione uma ou mais bibliotecas para incluir." }, + "m3uInstruction3": { "message": "Clique no botão de download." }, + "m3uInstruction4": { "message": "Importe o arquivo .m3u no seu player compatível." }, + "chatOpen": { "message": "Abrir Chat" }, + "chatTitle": { "message": "Assistente de IA" }, + "chatClose": { "message": "X" }, + "chatPlaceholder": { "message": "Digite sua mensagem..." }, + "chatSend": { "message": "➤" }, + "chatWelcome": { "message": "Bem-vindo! Eu sou seu assistente CinePlex. Pergunte-me sobre filmes, séries ou qualquer outra coisa que queira saber." }, + "chatGoogleApiKeyMissing": { "message": "A chave da API do Google Gemini não está configurada. Por favor, configure-a nas configurações da extensão para usar o assistente de IA." }, + "chatApiInvalidResponse": { "message": "A API retornou uma resposta inválida. Por favor, tente novamente." }, + "chatApiError": { "message": "Erro ao se comunicar com o assistente de IA" }, + "downloadAll": { "message": "Baixar tudo" }, + "download": { "message": "Baixar" }, + "aiToolSearchLibraryDesc": { "message": "Pesquisa na biblioteca Plex do usuário por filmes ou séries por título." }, + "aiToolSearchLibraryQueryParamDesc": { "message": "O título do filme ou série a ser pesquisado." }, + "aiToolSearchLibraryTypeParamDesc": { "message": "O tipo de conteúdo a ser pesquisado. Pode ser 'movie' para filmes ou 'series' para séries. (Opcional)." }, + "aiToolSearchLibraryResolutionParamDesc": { "message": "A resolução de vídeo a ser pesquisada (ex: '4k', '1080p'). (Opcional)." }, + "aiToolSearchLibraryContainerParamDesc": { "message": "O formato do contêiner de vídeo a ser pesquisado (ex: 'mkv', 'mp4'). (Opcional)." }, + "aiToolNavigateToPageDesc": { "message": "Navega o usuário para uma página específica da interface da aplicação." }, + "aiToolNavigateToPagePageParamDesc": { "message": "O nome da página para a qual navegar, por exemplo: 'movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator' ou 'music'." }, + "aiToolGetUserStatsDesc": { "message": "Obtém e exibe as estatísticas da biblioteca do usuário, como o número total de filmes, séries e artistas únicos." }, + "aiToolShowItemDetailsDesc": { "message": "Exibe a página de detalhes de um filme ou série específica por seu título e tipo." }, + "aiToolShowItemDetailsTitleParamDesc": { "message": "O título exato do filme ou série." }, + "aiToolShowItemDetailsTypeParamDesc": { "message": "O tipo de conteúdo. Deve ser 'movie' ou 'series'." }, + "aiToolAddToPlaylistDesc": { "message": "Adiciona um filme ou série à lista de reprodução atual do usuário para transmitir para um servidor PHP configurado." }, + "aiToolAddToPlaylistTitleParamDesc": { "message": "O título do filme ou série a ser adicionado." }, + "aiToolAddToPlaylistTypeParamDesc": { "message": "O tipo de conteúdo. Deve ser 'movie' ou 'series'." }, + "aiToolDownloadSingleMovieM3UDesc": { "message": "Gera e baixa um arquivo de lista de reprodução M3U para um único filme disponível localmente." }, + "aiToolDownloadSingleMovieM3UTitleParamDesc": { "message": "O título do filme para o qual o M3U será gerado." }, + "aiToolDownloadSingleMovieM3UYearParamDesc": { "message": "O ano de lançamento do filme (opcional, para maior precisão)." }, + "aiToolDownloadSeriesSeasonM3UDesc": { "message": "Gera e baixa um arquivo de lista de reprodução M3U para uma temporada específica de uma série disponível localmente." }, + "aiToolDownloadSeriesSeasonM3UTitleParamDesc": { "message": "O título da série." }, + "aiToolDownloadSeriesSeasonM3USeasonParamDesc": { "message": "O número da temporada a ser baixada." }, + "aiToolDownloadSeriesSeasonM3UYearParamDesc": { "message": "O ano de lançamento da série (opcional)." }, + "aiToolCheckAndDownloadDesc": { "message": "Verifica a disponibilidade de uma lista de títulos de filmes ou séries nos servidores locais do usuário e, se encontrados, gera e baixa um arquivo de lista de reprodução M3U com os streams encontrados." }, + "aiToolCheckAndDownloadTitlesParamDesc": { "message": "Um array de títulos de filmes ou séries para pesquisar e baixar." }, + "aiToolCheckAndDownloadTypeParamDesc": { "message": "O tipo de conteúdo da lista. Deve ser 'movie' ou 'series'." }, + "aiToolCheckAndDownloadFilenameParamDesc": { "message": "O nome do arquivo M3U a ser baixado (ex: 'MinhaLista.m3u'). Se não for fornecido, um nome padrão será usado." }, + "aiToolToggleFavoriteDesc": { "message": "Adiciona ou remove um filme ou série da lista de favoritos do usuário." }, + "aiToolToggleFavoriteTitleParamDesc": { "message": "O título do filme ou série." }, + "aiToolToggleFavoriteTypeParamDesc": { "message": "O tipo de conteúdo. Deve ser 'movie' ou 'series'." }, + "aiToolGetRecommendationsDesc": { "message": "Gera e exibe uma lista de recomendações de filmes ou séries com base no histórico de visualização e nos favoritos do usuário." }, + "aiToolApplyFiltersDesc": { "message": "Aplica filtros à visualização atual de filmes ou séries, permitindo refinar os resultados por tipo, gênero, ano и ordem de classificação." }, + "aiToolApplyFiltersTypeParamDesc": { "message": "O tipo de conteúdo ao qual aplicar os filtros. Deve ser 'movie' ou 'series'." }, + "aiToolApplyFiltersGenreParamDesc": { "message": "O nome do gênero pelo qual filtrar (ex: 'Ação', 'Drama')." }, + "aiToolApplyFiltersYearParamDesc": { "message": "O ano de lançamento pelo qual filtrar (ex: '2023')." }, + "aiToolApplyFiltersSortParamDesc": { "message": "O critério de ordenação para os resultados. Valores válidos: 'popularity.desc' (populares), 'vote_average.desc' (melhor avaliados), 'release_date.desc' (recentes para filmes) ou 'first_air_date.desc' (recentes para séries)." }, + "aiToolListAvailableMusicGenresDesc": { "message": "Lista todos os gêneros musicais únicos disponíveis na biblioteca local do usuário." }, + "aiToolSearchMusicByGenreDesc": { "message": "Pesquisa por artistas na biblioteca de música do usuário que pertencem a um gênero específico." }, + "aiToolSearchMusicByGenreNameParamDesc": { "message": "O nome do gênero musical a ser pesquisado (ex: 'Rock', 'Pop', 'Jazz')." }, + "aiToolPlayMusicByArtistDesc": { "message": "Abre o player de música e começa a tocar músicas de um artista específico da biblioteca do usuário." }, + "aiToolPlayMusicByArtistNameParamDesc": { "message": "O nome exato do artista cujas músicas devem ser tocadas." }, + "aiToolClearChatHistoryDesc": { "message": "Limpa todo o histórico de mensagens da conversa atual com o assistente de IA." }, + "aiToolDeleteDatabaseDesc": { "message": "Exclui todo o banco de dados local da extensão, incluindo conteúdo escaneado, configurações e favoritos. Esta ação é irreversível e recarregará a aplicação." }, + "aiToolUpdateAllTokensDesc": { "message": "Inicia um escaneamento completo de todos os servidores e bibliotecas do Plex associados aos tokens configurados na extensão. Atualiza todos os filmes, séries, artistas e fotos." }, + "aiToolAddPlexTokenDesc": { "message": "Adiciona um novo token X-Plex à configuração da extensão, permitindo que a aplicação escaneie conteúdo de novos servidores Plex." }, + "aiToolAddPlexTokenTokenParamDesc": { "message": "A string do token X-Plex a ser adicionada." }, + "aiToolChangeRegionDesc": { "message": "Muda a região usada для descoberta de conteúdo na API do TMDB. Isso afetará os resultados exibidos nas seções de filmes e séries, bem como os provedores de streaming." }, + "aiToolChangeRegionRegionParamDesc": { "message": "O código de país ISO 3166-1 de duas letras para a nova região (ex: 'US' para Estados Unidos, 'BR' para Brasil, 'PT' para Portugal)." }, + "aiToolClearAllFavoritesDesc": { "message": "Remove todos os filmes e séries que o usuário marcou como favoritos." }, + "aiToolClearViewingHistoryDesc": { "message": "Limpa o histórico de visualização do usuário da página de histórico." }, + "aiToolClearRecommendationsViewDesc": { "message": "Limpa a visualização de recomendações e remove as recomendações em cache." }, + "aiToolSearchNotFound": { "message": "Não foi encontrado '$query' em sua biblioteca.", "placeholders": { "query": { "content": "$1" } } }, + "aiToolNavigateSuccess": { "message": "Navegado para a página de $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolNavigateError": { "message": "Erro ao navegar para a página de $page$.", "placeholders": { "page": { "content": "$1" } } }, + "aiToolStatsError": { "message": "Erro ao obter estatísticas." }, + "aiToolItemNotFound": { "message": "Item '$title' não encontrado.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolShowItemDetailsSuccess": { "message": "Mostrando detalhes de '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolAddToPlaylistSuccess": { "message": "Adicionado '$title' à lista de reprodução.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteAdded": { "message": "Adicionado '$title' aos favoritos.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolFavoriteRemoved": { "message": "Removido '$title' dos favoritos.", "placeholders": { "title": { "content": "$1" } } }, + "aiToolRecommendationsSuccess": { "message": "Mostrando recomendações." }, + "aiToolApplyFiltersGenreNotFound": { "message": "Gênero '$genre' não encontrado.", "placeholders": { "genre": { "content": "$1" } } }, + "aiToolApplyFiltersSuccess": { "message": "Filtros aplicados com sucesso." }, + "aiToolSearchMusicByGenreNotFound": { "message": "Não encontrei artistas do gênero '$genre_name' em sua biblioteca.", "placeholders": { "genre_name": { "content": "$1" } } }, + "aiToolPlayMusicNotReady": { "message": "O player de música não está pronto. Certifique-se de que sua biblioteca de música do Plex foi escaneada." }, + "aiToolPlayMusicArtistNotFound": { "message": "Artista '$artist_name' não encontrado.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicNoSongs": { "message": "Nenhuma música encontrada para '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolPlayMusicSuccess": { "message": "Tocando música de '$artist_name'.", "placeholders": { "artist_name": { "content": "$1" } } }, + "aiToolChatHistoryCleared": { "message": "Histórico do chat limpo." }, + "aiToolConfirmDeleteDatabase": { "message": "Você tem certeza de que deseja excluir o banco de dados local? Esta ação é irreversível." }, + "aiToolDeleteDatabaseCancelled": { "message": "Exclusão do banco de dados cancelada." }, + "aiToolExecutionError": { "message": "Erro ao executar a ferramenta '$toolName': $message$", "placeholders": { "toolName": { "content": "$1" }, "message": { "content": "$2" } } }, + "aiToolUnknown": { "message": "Ferramenta desconhecida: '$toolName'.", "placeholders": { "toolName": { "content": "$1" } } }, + "aiToolFavoritesCleared": { "message": "Favoritos excluídos." }, + "aiToolFavoritesClearError": { "message": "Erro ao excluir os favoritos: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolRecommendationsCleared": { "message": "Recomendações excluídas." }, + "aiToolRecommendationsClearError": { "message": "Erro ao excluir as recomendações: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleted": { "message": "Banco de dados excluído. A página será recarregada." }, + "aiToolDatabaseDeleteError": { "message": "Erro ao excluir o banco de dados: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolDatabaseDeleteBlocked": { "message": "A exclusão do banco de dados está bloqueada. Feche outras abas da aplicação." }, + "aiToolUpdateAllTokensSuccess": { "message": "Todos os tokens foram atualizados com sucesso." }, + "aiToolUpdateAllTokensError": { "message": "Erro ao atualizar os tokens: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolAddPlexTokenSuccess": { "message": "Token do Plex adicionado com sucesso." }, + "aiToolAddPlexTokenError": { "message": "Erro ao adicionar o token do Plex: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolChangeRegionSuccess": { "message": "Região alterada para $region$. O conteúdo está sendo atualizado.", "placeholders": { "region": { "content": "$1" } } }, + "aiToolChangeRegionError": { "message": "Erro ao alterar a região: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolViewingHistoryCleared": { "message": "Histórico de visualização limpo." }, + "aiToolViewingHistoryClearError": { "message": "Erro ao limpar o histórico de visualização: $message$", "placeholders": { "message": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSingle": { "message": "Iniciando o download do M3U para '$movie_title'.", "placeholders": { "movie_title": { "content": "$1" } } }, + "aiToolM3UDownloadStartedSeason": { "message": "Iniciando o download do M3U para a temporada $1 de '$2'.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolM3UNoTitlesProvided": { "message": "Por favor, forneça uma lista de títulos para criar a lista de reprodução." }, + "aiToolM3UCheckingTitles": { "message": "Verificando os títulos em seus servidores locais..." }, + "aiToolM3UNoLocalMatchesForDownload": { "message": "Não encontrei nenhum dos filmes ou séries da lista em seus servidores locais." }, + "aiToolM3UDownloadStarted": { "message": "Pronto! Encontrei $1 dos $2 títulos em seus servidores e iniciei o download da lista de reprodução M3U.", "placeholders": { "1": { "content": "$1" }, "2": { "content": "$2" } } }, + "aiToolTrailerNotFoundSpecific": { "message": "Desculpe, não consegui encontrar um trailer disponível para '$title'.", "placeholders": { "title": { "content": "$1" } } }, + "aiSystemPrompt_v4": { + "message": "Você é um assistente virtual integrado a uma extensão do Chrome que interage com servidores Plex e Jellyfin. Sua função principal é ajudar o usuário a pesquisar, gerenciar, reproduzir e baixar conteúdo multimídia, bem como gerenciar configurações personalizadas.\n\nPRIORIDADE MÁXIMA: Sempre que a pergunta do usuário se referir a conteúdo multimídia (filmes, séries, música), VOCÊ DEVE presumir que se refere à sua biblioteca local. Use as ferramentas para pesquisar em seu banco de dados ANTES de pesquisar na web.\n\n🎯 Regras gerais de comportamento:\nResponda sempre de forma clara, concisa e direta. Seja proativo e forneça todas as informações relevantes de uma vez para evitar perguntas de acompanhamento. Por exemplo, ao confirmar a disponibilidade de uma série, inclua os detalhes das temporadas.\n\nCompare a data atual com os resultados da pesquisa do Google quando solicitado por informações externas para garantir que estejam atualizadas.\n\nUse os nomes exatos dos comandos definidos no sistema (function.name) ao chamar as ferramentas.\n\n📦 Funções-chave para conteúdo multimídia:\nPara gerar um M3U para um único filme, use download_single_movie_m3u.\nPara baixar uma temporada específica de uma série, use download_series_season_m3u.\nPara múltiplos títulos (filmes ou séries completas), use sempre check_and_download_titles_list.\nPara pesquisar conteúdo local: search_library.\nPara pesquisar no TMDB: search_tmdb_content.\nPara conteúdo em alta: get_trending_content.\nPara mostrar detalhes de um título: show_item_details.\nPara adicionar à lista de reprodução PHP: add_to_playlist.\nPara verificar a disponibilidade local: check_local_availability.\nSe uma série estiver disponível localmente, informe quantas temporadas existem e em quais servidores usando get_local_series_seasons.\nPara ver recomendações: get_recommendations.\nPara aplicar filtros: apply_filters.\nPara ver histórico ou favoritos: view_history, view_favorites.\nPara marcar como favorito: toggle_favorite.\nPara reproduzir trailer: play_trailer.\n\n🎵 Funções de música:\nSe o usuário pedir recomendações de gêneros musicais de forma geral (ex: 'recomende-me um gênero para me animar'), primeiro use list_available_music_genres para ver quais gêneros ele tem e baseie sua recomendação nessa lista.\nPara listar todos os gêneros musicais disponíveis na biblioteca: list_available_music_genres.\nPara pesquisar artistas por gênero: search_music_by_genre.\nPara tocar músicas por título e/ou artista: play_song.\nPara tocar música de um artista: play_music_by_artist.\n\n🧰 Funções de gerenciamento e configuração:\nPara obter estatísticas do usuário: get_user_stats.\nPara navegar para seções específicas: navigate_to_page.\nPara atualizar tokens: update_all_tokens, add_plex_token.\nPara alterar a região do conteúdo: change_region.\nPara exportar ou importar o banco de dados local: export_local_database, import_local_database.\nPara excluir o banco de dados: delete_database.\nPara limpar favoritos, histórico ou recomendações: clear_all_favorites, clear_viewing_history, clear_recommendations_view.\nPara alternar o modo claro/escuro: toggle_light_mode.\nPara mostrar ou ocultar a seção hero: toggle_hero_section.\n\n⚠️ Considerações adicionais:\nDê prioridade ao conteúdo disponível localmente. Use check_local_availability antes de mostrar opções de reprodução ou download.\nSe uma ação falhar, informe de maneira clara e direta.\nEvite repetir desnecessariamente a solicitação do usuário, a menos que ajude a contextualizar a resposta." + }, + "backToProviders": { "message": "Voltar aos Provedores" }, + "artistsCounterSingle": { "message": "$total$ Artista", "placeholders": { "total": { "content": "$1" } } }, + "artistsCounterLoading": { "message": "Carregando..." }, + "downloadingSong": { "message": "Iniciando o download de \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "songDownloaded": { "message": "\"$title$\" baixado.", "placeholders": { "title": { "content": "$1" } } }, + "errorDownloadingSong": { "message": "Erro ao baixar \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "generatingAlbumM3U": { "message": "Gerando M3U para \"$artist$\"", "placeholders": { "artist": { "content": "$1" } } }, + "albumM3UGenerated": { "message": "M3U para o álbum \"$artist$\" gerado.", "placeholders": { "artist": { "content": "$1" } } }, + "retyingSection": { "message": "Tentando novamente a seção \"$title$\"", "placeholders": { "title": { "content": "$1" } } }, + "retrySuccess": { "message": "[SUCESSO] Nova tentativa de \"$title$\" concluída.", "placeholders": { "title": { "content": "$1" } } }, + "retryError": { "message": "[ERRO FINAL] Falha na nova tentativa para \"$title$\": $message$", "placeholders": { "title": { "content": "$1" }, "message": { "content": "$2" } } }, + "startingRetryPhase": { "message": "Iniciando fase de novas tentativas para $count$ seções...", "placeholders": { "count": { "content": "$1" } } }, + "tokenFoundServers": { "message": "Token $token$... encontrou $count$ servidores.", "placeholders": { "token": { "content": "$1" }, "count": { "content": "$2" } } }, + "errorProcessingToken": { "message": "Erro ao processar o token $token$...: $message$", "placeholders": { "token": { "content": "$1" }, "message": { "content": "$2" } } }, + "plexScanFatalError": { "message": "ERRO FATAL: $message$", "placeholders": { "message": { "content": "$1" } } }, + "errorDuringScan": { "message": "Erro durante o escaneamento: $message$", "placeholders": { "message": { "content": "$1" } } }, + "stoppingPlexScan": { "message": "Parando escaneamento do Plex..." }, + "invalidTokenProvided": { "message": "Token fornecido inválido." }, + "tokenAlreadyExists": { "message": "O token já existe." }, + "tokenAddedSuccessfully": { "message": "Token adicionado com sucesso." }, + "noStreamsFoundForSelection": { "message": "Nenhum stream encontrado para a seleção." }, + "autoplayBlocked": { "message": "Reprodução automática bloqueada." }, + "welcomeToCinePlex": { "message": "" }, + "page": { "message": "Página" }, + "all": { "message": "Tudo" }, + "userScore": { "message": "Pontuação" }, + "duration": { "message": "Duração" }, + "min": { "message": "Mín" }, + "max": { "message": "Máx" }, + "aiToolFindStreamingProvidersDesc": { "message": "Encontra onde assistir a um filme ou série em serviços de streaming." }, + "aiToolFindStreamingProvidersTitleParamDesc": { "message": "O título do filme ou série a ser pesquisado." }, + "aiToolFindStreamingProvidersTypeParamDesc": { "message": "O tipo de conteúdo (filme ou série)." }, + "aiToolFindStreamingProvidersYearParamDesc": { "message": "O ano de lançamento do conteúdo (opcional)." }, + "aiToolNoStreamingProviders": { "message": "Nenhum provedor de streaming encontrado para {title}." }, + "aiToolStreamingProvidersFound": { "message": "{title} está disponível nos seguintes serviços: {providers}." }, + "aiToolStreamingProviderError": { "message": "Erro ao pesquisar provedores de streaming: {message}." }, + "aiToolGetLocalSeriesSeasonsDesc": { "message": "Verifica se uma série de TV está disponível localmente e retorna um detalhamento das temporadas disponíveis em cada servidor." }, + "aiToolGetLocalSeriesSeasonsTitleParamDesc": { "message": "O título da série a ser verificada." }, + "aiToolGetLocalSeriesSeasonsYearParamDesc": { "message": "O ano de lançamento da série (opcional para maior precisão)." }, + "aiToolLocalSeriesNoSeasons": { "message": "A série '$series_title' está em sua biblioteca, mas não foram encontrados detalhes das temporadas.", "placeholders": { "series_title": { "content": "$1" } } }, + "artist": { "message": "Artista" }, + "tracks": { "message": "faixas" }, + "noSongsFound": { "message": "Nenhuma música encontrada para este artista." }, + "durationMin": { "message": "Duração (Min)" }, + "score": { "message": "Pontuação" }, + "searchGenre": { "message": "Buscar gênero..." }, + "searchArtists": { "message": "Buscar artistas..." }, + "preparingMusicLibrary": { "message": "Preparando sua biblioteca de música..." }, + "preparingMusicLibraryDesc": { "message": "Este processo único pode levar alguns minutos se você tiver muitos artistas." }, + "artistsProgress": { "message": "0 / 0 artistas" }, + "starting": { "message": "Iniciando..." }, + "artistName": { "message": "Nome do Artista" }, + "playPause": { "message": "Tocar/Pausar" }, + "noLocalFilesFound": { "message": "Nenhum arquivo local encontrado para este título." }, + "server": { "message": "Servidor" }, + "title": { "message": "Título" }, + "year": { "message": "Ano" }, + "resolution": { "message": "Resolução" }, + "size": { "message": "Tamanho" }, + "container": { "message": "Contêiner" }, + "action": { "message": "Ação" }, + "generate": { "message": "Gerar" }, + "availableLocalFiles": { "message": "Arquivos Locais Disponíveis" }, + "downloadSeason": { "message": "Baixar Temporada" }, + "errorLoadingServersM3u": { "message": "Erro ao carregar os servidores para o gerador M3U:" }, + "errorFetchingLibraries": { "message": "Erro ao buscar as bibliotecas." }, + "selectServerAndLibrary": { "message": "Por favor, selecione um servidor e pelo menos uma biblioteca." }, + "generating": { "message": "Gerando..." }, + "errorProcessingLibrary": { "message": "Erro ao processar a biblioteca" }, + "errorProcessingLibrarySkipping": { "message": "Erro ao processar a biblioteca. Pulando." }, + "allLibrariesFailed": { "message": "Todas as bibliotecas selecionadas falharam ao processar." }, + "m3uGeneratedWithErrors": { "message": "M3U gerado com alguns erros. Algumas bibliotecas могут estar faltando." }, + "m3uDownloadedSuccess": { "message": "Lista de reprodução M3U baixada com sucesso." }, + "errorGeneratingM3uFile": { "message": "Erro ao gerar o arquivo M3U." }, + "chatSources": { "message": "Fontes" }, + "chatUnnamedSource": { "message": "Fonte sem nome" }, + "googleApiFailure": { "message": "Falha na chamada da API do Google AI:" } } \ No newline at end of file diff --git a/css/base.css b/css/base.css new file mode 100644 index 0000000..4ac2cfb --- /dev/null +++ b/css/base.css @@ -0,0 +1,376 @@ +:root { + --primary: #0a0a0f; + --secondary: #101116; + --accent: #00bfff; + --accent-dark: #0072ff; + --text-primary: #f0f0f5; + --text-secondary: rgba(240, 240, 245, 0.7); + --gradient: linear-gradient(135deg, var(--accent), var(--accent-dark)); + --card-bg: rgba(20, 21, 27, 0.8); + --glass: rgba(255, 255, 255, 0.05); + --glass-border: rgba(255, 255, 255, 0.1); + --shadow: 0 10px 30px rgba(0, 0, 0, 0.35); + --transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); + --success: #4caf50; + --warning: #ffc107; + --danger: #f44336; + --info: #2196f3; + --border-radius-lg: 18px; + --border-radius-md: 14px; + --border-radius-sm: 10px; + --topbar-height: 60px; + --sidebar-width: 240px; +} + +body.light-theme { + --primary: #f4f7fa; + --secondary: #ffffff; + --text-primary: #1f2937; + --text-secondary: #6b7280; + --card-bg: #ffffff; + --glass: rgba(0, 0, 0, 0.03); + --glass-border: rgba(0, 0, 0, 0.08); + --shadow: 0 10px 30px rgba(0, 0, 0, 0.1); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; +} + +body.unlocalized { + visibility: hidden; +} + +body { + background-color: var(--primary); + color: var(--text-primary); + font-family: 'Montserrat', sans-serif; + line-height: 1.6; + min-height: 100vh; + overflow-x: hidden; + position: relative; + transition: background-color 0.3s, color 0.3s; +} + +#main-container { + padding-left: 0; + transition: padding-left 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +body.details-view-active { + overflow: hidden; +} + +#particles-js { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: -1; + opacity: 0.25; +} + +body.light-theme #particles-js { + opacity: 0.5; +} + +@keyframes spin { + 0% { + transform: translate(-50%, -50%) rotate(0deg); + } + + 100% { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@media (min-width: 992px) { + #main-container.sidebar-open { + padding-left: var(--sidebar-width); + } +} + +@media (max-width: 576px) { + :root { + --border-radius-lg: 14px; + --border-radius-md: 10px; + --border-radius-sm: 8px; + --topbar-height: 55px; + } +} + +body::-webkit-scrollbar, .item-details::-webkit-scrollbar { + width: 12px; +} + +body::-webkit-scrollbar-track, .item-details::-webkit-scrollbar-track { + background: var(--primary); + border-left: 1px solid var(--glass-border); +} + +body::-webkit-scrollbar-thumb, .item-details::-webkit-scrollbar-thumb { + background-color: var(--accent-dark); + border-radius: 10px; + border: 3px solid var(--primary); +} + +body::-webkit-scrollbar-thumb:hover, .item-details::-webkit-scrollbar-thumb:hover { + background-color: var(--accent); +} + +body.light-theme::-webkit-scrollbar-track, body.light-theme .item-details::-webkit-scrollbar-track { + background: var(--secondary); + border-left: 1px solid var(--glass-border); +} + +body.light-theme::-webkit-scrollbar-thumb, body.light-theme .item-details::-webkit-scrollbar-thumb { + background-color: #bdc3c7; + border-color: var(--secondary); +} + +body.light-theme::-webkit-scrollbar-thumb:hover, body.light-theme .item-details::-webkit-scrollbar-thumb:hover { + background-color: #a3aab1; +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.7rem; + padding: 0.7rem 1.8rem; + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + border: none; + border-radius: 50px; + cursor: pointer; + transition: var(--transition); + letter-spacing: 1px; + line-height: 1.5; + position: relative; + overflow: hidden; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); +} + +.btn i { + line-height: 1; +} + +.btn:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.btn-primary { + background: var(--gradient); + color: var(--primary) !important; + box-shadow: 0 6px 20px rgba(0, 224, 255, 0.25); +} + +.btn-primary:hover:not(:disabled) { + background: linear-gradient(135deg, var(--accent-dark), var(--accent)); + box-shadow: 0 8px 25px rgba(0, 224, 255, 0.35); + transform: translateY(-3px); +} + +.btn-secondary { + background: var(--glass); + color: var(--text-primary); + border: 1px solid var(--glass-border); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); +} + +.btn-secondary:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.15); + transform: translateY(-3px); + color: var(--text-primary); +} + +.light-theme .btn-secondary:hover:not(:disabled) { + background: rgba(0, 0, 0, 0.05); + border-color: rgba(0, 0, 0, 0.1); +} + +.btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 1.25rem; + width: 40px; + height: 40px; + border-radius: 50%; + cursor: pointer; + transition: var(--transition); +} + +.btn-icon:hover { + color: var(--accent); + background-color: var(--glass); +} + +.spinner { + display: none; + width: 45px; + height: 45px; + border: 5px solid rgba(240, 240, 245, 0.2); + border-top: 5px solid var(--accent); + border-radius: 50%; + animation: spin 1s linear infinite; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1050; +} + +.light-theme .spinner { + border: 5px solid rgba(0, 0, 0, 0.1); + border-top: 5px solid var(--accent-dark); +} + +#consoleOutput { + border: 1px solid var(--glass-border); + padding: 15px; + margin: 20px 0; + height: 250px; + overflow-y: scroll; + background-color: rgba(10, 10, 15, 0.9); + color: var(--text-secondary); + font-family: monospace; + font-size: 0.85rem; + border-radius: var(--border-radius-md); + display: none; + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2); +} + +.light-theme #consoleOutput { + background-color: var(--primary); +} + +#consoleOutput .console-log-entry { + margin-bottom: 6px; + line-height: 1.4; + word-break: break-word; + white-space: pre-wrap; +} + +#consoleOutput .log-time { + color: var(--accent); + margin-right: 8px; + font-weight: 500; +} + +#consoleOutput .log-message { + color: var(--text-secondary); +} + +#consoleOutput .log-error .log-message { + color: var(--danger); +} + +#consoleOutput .log-warning .log-message { + color: var(--warning); +} + +#consoleOutput .log-success .log-message { + color: var(--success); +} + +.form-control { + background-color: var(--glass); + border: 1px solid var(--glass-border); + color: var(--text-primary); + border-radius: var(--border-radius-sm); + padding: .6rem 1rem; +} +.form-control:focus { + background-color: var(--glass); + color: var(--text-primary); + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(0,224,255,.2); +} +.form-control::placeholder { + color: var(--text-secondary); +} +.form-label { + color: var(--text-primary); + font-weight: 500; +} + +.toggle-switch { + position: relative; + display: inline-block; + width: 50px; + height: 28px; +} +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} +.toggle-switch label { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--glass-border); + transition: .4s; + border-radius: 28px; +} +.toggle-switch label:before { + position: absolute; + content: ""; + height: 20px; + width: 20px; + left: 4px; + bottom: 4px; + background-color: white; + transition: .4s; + border-radius: 50%; +} +.toggle-switch input:checked + label { + background: var(--gradient); +} +.toggle-switch input:checked + label:before { + transform: translateX(22px); +} + +body.unlocalized #main-container, +body.unlocalized footer, +body.unlocalized header, +body.unlocalized .sidebar-nav { + opacity: 0; + visibility: hidden; +} + +body.unlocalized #spinner { + display: block; +} + +#main-container, footer, header, .sidebar-nav { + transition: opacity 0.4s ease-in-out; +} \ No newline at end of file diff --git a/css/chat.css b/css/chat.css new file mode 100644 index 0000000..09ccbb7 --- /dev/null +++ b/css/chat.css @@ -0,0 +1,332 @@ +#fab-container { + position: fixed; + bottom: 2rem; + right: 2rem; + z-index: 1050; + display: flex; + flex-direction: row-reverse; + gap: 1rem; + align-items: flex-end; + transition: bottom 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +body.miniplayer-active #fab-container { + bottom: calc(85px + 2rem); +} + +.chat-fab, .fab-btn { + position: relative; + right: auto; + bottom: auto; +} + +.chat-fab { + width: 60px; + height: 60px; + background: var(--gradient); + color: var(--primary); + border-radius: 50%; + border: none; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.8rem; + cursor: pointer; + box-shadow: 0 5px 20px rgba(0, 224, 255, 0.3); + transition: var(--transition); +} +.chat-fab:hover { + transform: scale(1.1); + box-shadow: 0 8px 25px rgba(0, 224, 255, 0.4); +} + +.chat-window { + position: fixed; + bottom: 95px; + right: 2rem; + width: 400px; + height: 520px; + max-width: 90vw; + max-height: 70vh; + background-color: var(--primary); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow); + border: 1px solid var(--glass-border); + display: flex; + flex-direction: column; + overflow: hidden; + z-index: 1051; + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); + background: rgba(10, 10, 15, 0.8); + cursor: default; +} +.light-theme .chat-window { + background: rgba(244, 247, 250, 0.8); +} + +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.8rem 1.2rem; + background-color: rgba(255,255,255,0.05); + border-bottom: 1px solid var(--glass-border); + cursor: move; + flex-shrink: 0; +} +.chat-title { + font-family: 'Orbitron', sans-serif; + color: var(--text-primary); + font-size: 1.1rem; + margin: 0; +} +.chat-close-btn { + background: none; + border: none; + color: var(--text-secondary); + font-size: 1.2rem; + cursor: pointer; + transition: var(--transition); +} +.chat-close-btn:hover { + color: var(--accent); + transform: rotate(90deg); +} + +.chat-messages { + flex-grow: 1; + padding: 1rem; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.chat-messages::-webkit-scrollbar { + width: 8px; +} + +.chat-messages::-webkit-scrollbar-track { + background: transparent; +} + +.chat-messages::-webkit-scrollbar-thumb { + background-color: var(--glass-border); + border-radius: 4px; +} + +.message-wrapper { + display: flex; + align-items: flex-end; + gap: 0.75rem; + max-width: 90%; + animation: slide-in-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; +} + +.assistant-wrapper { + align-self: flex-start; +} + +.user-wrapper { + align-self: flex-end; + flex-direction: row-reverse; +} + +.avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--gradient); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: 0 0 10px rgba(0, 224, 255, 0.3); +} + +.avatar svg { + width: 20px; + height: 20px; + color: var(--primary); +} + +.message { + padding: 0.8rem 1.2rem; + border-radius: var(--border-radius-md); + line-height: 1.6; + word-wrap: break-word; +} +.message p { + margin: 0; + white-space: pre-wrap; + word-break: break-word; +} + +.user-message { + background: var(--gradient); + color: var(--primary); + border-bottom-right-radius: 4px; +} + +.assistant-message { + background-color: var(--secondary); + color: var(--text-primary); + border-bottom-left-radius: 4px; +} + +.chat-item-actions { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--glass-border); +} + +.chat-action-title { + display: block; + font-weight: 600; + color: var(--accent); + margin-bottom: 0.75rem; + font-size: 0.9rem; +} + +.chat-action-buttons { + display: flex; + flex-wrap: wrap; + gap: .5rem; +} + +.chat-action-buttons button { + background-color: var(--glass); + border: 1px solid var(--glass-border); + color: var(--text-secondary); + padding: .4rem .8rem; + border-radius: 20px; + font-size: .8rem; + cursor: pointer; + transition: var(--transition); +} + +.chat-action-buttons button:hover { + background-color: var(--accent); + color: var(--primary); + border-color: var(--accent); + transform: translateY(-2px); +} + +.chat-download-all { + margin-top: 1rem; +} + +.typing-indicator-bubble { + display: flex; + gap: 6px; + align-items: center; + padding: 14px 16px; +} +.typing-indicator-bubble span { + width: 8px; + height: 8px; + background-color: var(--text-secondary); + border-radius: 50%; + animation: typing-pulse 1.4s infinite; +} +.typing-indicator-bubble span:nth-child(2) { animation-delay: 0.2s; } +.typing-indicator-bubble span:nth-child(3) { animation-delay: 0.4s; } + +@keyframes typing-pulse { + 0%, 100% { opacity: 0.2; transform: scale(0.8); } + 50% { opacity: 1; transform: scale(1); } +} + +#chat-input-form { + display: flex; + padding: 0.8rem; + border-top: 1px solid var(--glass-border); + gap: 0.8rem; + background: linear-gradient(to top, rgba(16, 17, 22, 0.8), rgba(16, 17, 22, 0.5)); + align-items: flex-end; +} + +.chat-input { + flex-grow: 1; + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: 20px; + padding: 0.7rem 1.2rem; + color: var(--text-primary); + resize: none; + font-family: 'Montserrat', sans-serif; + font-size: 0.95rem; + transition: all 0.3s ease; +} + +.chat-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 15px rgba(0, 224, 255, 0.2); + background: var(--secondary); +} + +.chat-send-btn { + flex-shrink: 0; + background: var(--accent); + color: var(--primary); + border: none; + border-radius: 12px; + width: 42px; + height: 42px; + font-size: 1.1rem; + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; +} +.chat-send-btn svg { + transition: transform 0.3s ease; +} +.chat-send-btn:hover { + background: var(--accent-dark); + transform: scale(1.1); + box-shadow: 0 0 10px rgba(0, 224, 255, 0.4); +} +.chat-send-btn:hover svg { + transform: translateX(2px) rotate(-15deg); +} +.chat-send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + box-shadow: none; +} +.chat-send-btn:disabled:hover svg { + transform: none; +} + +@keyframes slide-in-bottom { + 0% { + transform: translateY(20px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@media (max-width: 768px) { + body.miniplayer-active #fab-container { + bottom: calc(110px + 1rem); + } +} + +@media (max-width: 480px) { + .chat-window { + width: 100%; + height: 100%; + bottom: 0; + right: 0; + border-radius: 0; + max-height: none; + } +} \ No newline at end of file diff --git a/css/content.css b/css/content.css new file mode 100644 index 0000000..8ad0f3a --- /dev/null +++ b/css/content.css @@ -0,0 +1,371 @@ +.main-content { + padding: 0 2rem 4rem; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.section-title { + font-family: 'Orbitron', sans-serif; + font-size: clamp(1.6rem, 4vw, 2rem); + font-weight: 600; + position: relative; + padding-bottom: 0.7rem; +} + +.section-title::after { + content: ''; + position: absolute; + left: 0; + bottom: 0; + height: 4px; + width: 70px; + background: var(--gradient); + border-radius: 3px; +} + +.section-subtitle { + font-family: 'Orbitron', sans-serif; + font-size: 1.7rem; + font-weight: 600; + padding-bottom: 0.8rem; + margin-bottom: 2rem; + border-bottom: 1px solid var(--glass-border); + position: relative; + color: var(--text-primary); +} + +.filters { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-bottom: 2rem; +} + +.filter-select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + padding: 0.7rem 2.8rem 0.7rem 1.4rem; + font-size: 0.9rem; + color: var(--text-primary); + background-color: var(--secondary); + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f0f0f5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 1rem; + border: 1px solid var(--glass-border); + border-radius: 50px; + cursor: pointer; + transition: var(--transition); +} + +.light-theme .filter-select { + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%231f2937'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} + +.filter-select option { + background: var(--primary); + color: var(--text-primary); + border: none; +} + +.filter-select:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); +} + +#main-view { + min-height: calc(100vh - var(--topbar-height) - 100px); +} + +.content-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1.8rem; +} + +.item-card { + background: var(--card-bg); + border-radius: var(--border-radius-lg); + overflow: hidden; + position: relative; + transition: var(--transition); + box-shadow: var(--shadow); + cursor: pointer; + border: 1px solid transparent; + display: flex; + flex-direction: column; +} + +.item-card:hover { + transform: translateY(-10px) scale(1.03); + box-shadow: 0 18px 45px rgba(0, 0, 0, 0.5); + border-color: rgba(0, 224, 255, 0.5); + z-index: 10; +} + +.item-card .badge { + position: absolute; + top: 1rem; + font-size: 0.7rem; + font-weight: 600; + padding: 0.35rem 0.8rem; + border-radius: 20px; + z-index: 3; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.item-card .top-badge { + left: 1rem; + background: var(--warning); + color: var(--primary); +} + +.item-card .available-badge { + right: 1rem; + background: var(--success); + color: var(--primary); +} + +.item-poster { + display: block; + width: 100%; + height: auto; + aspect-ratio: 2 / 3; + object-fit: cover; + background-color: var(--secondary); + background-size: cover; + background-position: center; + transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1); +} + +.item-card:hover .item-poster { + transform: scale(1.08); +} + +.item-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to top, rgba(10, 10, 15, 0.9) 0%, transparent 50%); + opacity: 0; + transition: opacity 0.4s ease; + z-index: 1; + display: flex; + align-items: flex-end; + justify-content: center; +} + +.item-card:hover .item-overlay { + opacity: 1; +} + +.item-info { + padding: 1.2rem; + position: relative; + z-index: 2; + margin-top: auto; + background: var(--card-bg); + transition: var(--transition); +} + +.item-title { + font-size: 1.05rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 0.5rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transition: color 0.3s ease; +} + +.item-card:hover .item-title { + color: var(--accent); +} + +.item-meta { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.85rem; + color: var(--text-secondary); +} + +.item-meta span { + display: flex; + align-items: center; + gap: 0.4rem; +} + +.item-rating { + font-weight: 600; +} + +.item-rating.rating-good { + color: var(--success); +} + +.item-rating.rating-ok { + color: var(--warning); +} + +.item-rating.rating-bad { + color: var(--danger); +} + +.item-actions { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + z-index: 3; + opacity: 0; + pointer-events: none; + transform: translateY(10px); + transition: opacity 0.4s ease, transform 0.4s ease; +} + +.item-card:hover .item-actions { + opacity: 1; + transform: translateY(0); + pointer-events: auto; +} + +.action-btn { + display: flex; + justify-content: center; + align-items: center; + width: 44px; + height: 44px; + background: rgba(255, 255, 255, 0.15); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + color: var(--text-primary); + border-radius: 50%; + border: 1px solid var(--glass-border); + cursor: pointer; + transition: var(--transition); + font-size: 1rem; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); +} + +.action-btn:hover:not(:disabled) { + background: var(--accent); + color: var(--primary); + transform: scale(1.1); + border-color: transparent; + box-shadow: 0 6px 15px rgba(0, 224, 255, 0.3); +} + +.action-btn.favorites-btn.active { + background: var(--danger); + color: white; +} + +.action-btn.favorites-btn.active:hover { + background: #c62828; +} + +.action-btn.disabled-btn { + cursor: not-allowed; + opacity: 0.6; + background: rgba(80, 80, 80, 0.3); +} + +.action-btn.disabled-btn:hover { + transform: none; + background: rgba(80, 80, 80, 0.3); + color: var(--text-primary); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); +} + +.recommendations-section { + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-lg); + padding: 2.5rem; + margin-top: 4rem; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.recommendations-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 2rem; +} + +.empty-state { + grid-column: 1 / -1; + text-align: center; + padding: 4rem 2rem; + background: var(--glass); + border: 1px dashed var(--glass-border); + border-radius: var(--border-radius-lg); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); +} + +.empty-state i.fas, +.empty-state i.far { + font-size: 3.5rem; + color: var(--accent); + opacity: 0.6; + margin-bottom: 1.5rem; + display: block; +} + +.empty-state p.lead { + font-size: 1.3rem; + color: var(--text-primary); + margin-bottom: 0.8rem; +} + +.empty-state p.text-muted { + font-size: 1rem; + color: var(--text-secondary); + margin-bottom: 1.5rem; +} + +.empty-state .btn { + margin-top: 1rem; +} + +@media (max-width: 992px) { + .content-grid { + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1.5rem; + } +} + +@media (max-width: 768px) { + .section-title { + font-size: 1.5rem; + } + .content-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 1.2rem; + } +} + +@media (max-width: 576px) { + .content-grid { + grid-template-columns: repeat(auto-fill, minmax(125px, 1fr)); + gap: 1rem; + } +} \ No newline at end of file diff --git a/css/custom-filters.css b/css/custom-filters.css index 8fbb2f3..483b12d 100644 --- a/css/custom-filters.css +++ b/css/custom-filters.css @@ -101,3 +101,47 @@ input[id$="-min"].form-range { .form-range::-moz-range-thumb:hover { background-color: var(--accent); } + +.filter-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.filter-group label { + font-size: 0.9rem; + color: var(--text-secondary); + white-space: nowrap; +} + +.filter-input { + width: 80px; + padding: 0.7rem 1rem; + font-size: 0.9rem; + color: var(--text-primary); + background-color: var(--secondary); + border: 1px solid var(--glass-border); + border-radius: 50px; + transition: var(--transition); +} + +.filter-input::placeholder { + color: var(--text-secondary); + opacity: 0.7; +} + +.filter-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); +} + +.filter-input::-webkit-outer-spin-button, +.filter-input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.filter-input[type=number] { + -moz-appearance: textfield; +} \ No newline at end of file diff --git a/css/details-view.css b/css/details-view.css new file mode 100644 index 0000000..6ecbad4 --- /dev/null +++ b/css/details-view.css @@ -0,0 +1,667 @@ +.item-details { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: none; + overflow-y: auto; + background: var(--primary); + z-index: 1035; + clip-path: inset(0 0 0 0); +} + +.item-details.active { + display: block; +} + +.back-button { + position: absolute; + top: 2rem; + left: 2rem; + z-index: 10; + display: flex; + justify-content: center; + align-items: center; + width: 42px; + height: 42px; + background: var(--glass); + color: var(--text-primary); + border-radius: 50%; + border: 1px solid var(--glass-border); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + cursor: pointer; + transition: var(--transition); +} + +.back-button:hover { + background: var(--accent); + color: var(--primary); + transform: scale(1.1); + box-shadow: 0 4px 15px rgba(0, 224, 255, 0.3); +} + +.details-backdrop-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 60vh; + overflow: hidden; + z-index: 0; +} + +.details-backdrop-img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + opacity: 0.4; +} + +.details-backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to top, var(--primary) 15%, transparent 50%), + linear-gradient(to right, var(--primary) 10%, transparent 70%); +} + +.item-details-container { + max-width: 1200px; + margin: 0 auto; + position: relative; + z-index: 1; + padding: 8rem 2rem 5rem; +} + +.item-details-header { + display: flex; + flex-direction: row; + gap: 3rem; + margin-bottom: 3rem; + align-items: flex-start; +} + +.item-details-poster-wrapper { + flex-shrink: 0; + width: 100%; + max-width: 320px; +} + +.item-details-poster { + display: block; + width: 100%; + height: auto; + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow); + transition: var(--transition); +} + +.item-details-poster:hover { + transform: scale(1.03); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6); +} + +#item-details-content { + opacity: 0; +} + +.item-details-content { + flex: 1; + min-width: 0; +} + +.item-details-title { + font-family: 'Orbitron', sans-serif; + font-size: clamp(2rem, 5vw, 3.2rem); + font-weight: 700; + margin-bottom: 0.5rem; + line-height: 1.2; +} + +.item-details-tagline { + font-size: 1.2rem; + font-style: italic; + color: var(--text-secondary); + margin-bottom: 1.5rem; + font-weight: 300; +} + +.item-details-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 1rem 1.8rem; + margin-bottom: 1.8rem; + font-size: 0.95rem; +} + +.item-details-meta-item { + display: flex; + align-items: center; + gap: 0.6rem; + color: var(--text-secondary); +} + +.item-details-meta-item i { + color: var(--accent); + font-size: 1.05rem; +} + +.item-details-overview { + font-size: 1.05rem; + margin-bottom: 2rem; + line-height: 1.8; + color: rgba(240, 240, 245, 0.85); +} + +.item-details-genres { + display: flex; + flex-wrap: wrap; + gap: 0.8rem; + margin-bottom: 1.8rem; +} + +.genre-badge { + padding: 0.5rem 1.1rem; + font-size: 0.8rem; + font-weight: 500; + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: 50px; + transition: var(--transition); + cursor: default; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.item-details-crew p { + margin-bottom: 0.5rem; + font-size: 0.95rem; + color: var(--text-secondary); +} + +.item-details-crew strong { + color: var(--text-primary); + margin-right: 0.5em; +} + +.item-details-external-links a { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--text-secondary); + text-decoration: none; + padding: 0.4rem 0.9rem; + border: 1px solid var(--glass-border); + border-radius: 50px; + font-size: 0.85rem; + background: var(--glass); + transition: var(--transition); +} + +.item-details-external-links a:hover { + color: var(--accent); + border-color: var(--accent); + background: rgba(0, 224, 255, 0.1); +} + +.item-details-actions { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 2rem; +} + +.item-details-section { + margin-bottom: 3.5rem; +} + +.cast-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); + gap: 1.8rem; +} + +.cast-card { + text-align: center; + transition: var(--transition); + cursor: pointer; + background: var(--secondary); + padding: 1rem; + border-radius: var(--border-radius-md); +} + +.cast-card:hover { + transform: translateY(-8px); + background: var(--glass); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); +} + +.cast-photo { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; + margin: 0 auto 1rem auto; + border: 3px solid var(--glass-border); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + transition: var(--transition); +} + +.cast-card:hover .cast-photo { + transform: scale(1.05); + border-color: var(--accent); + box-shadow: 0 6px 18px rgba(0, 224, 255, 0.25); +} + +.cast-name { + font-size: 1rem; + font-weight: 600; + margin-bottom: 0.3rem; + color: var(--text-primary); +} + +.cast-character { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.similar-items-grid, .filmography-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1.5rem; +} + +.similar-item-card { + background: transparent; + border-radius: var(--border-radius-md); + overflow: hidden; + transition: var(--transition); + cursor: pointer; + position: relative; +} + +.similar-item-card:hover { + transform: translateY(-8px); + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); +} + +.similar-item-card:hover .similar-item-poster { + transform: scale(1.05); +} + +.similar-item-poster { + display: block; + width: 100%; + height: auto; + border-radius: var(--border-radius-md); + aspect-ratio: 2 / 3; + object-fit: cover; + transition: transform 0.4s ease; +} + +.similar-item-info { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: 0.8rem; + background: linear-gradient(to top, rgba(10, 10, 15, 0.95) 0%, transparent 100%); + border-bottom-left-radius: var(--border-radius-md); + border-bottom-right-radius: var(--border-radius-md); +} + +.similar-item-title { + color: var(--text-primary); + font-size: 0.9rem; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.seasons-tabs { + background: var(--card-bg); + border-radius: var(--border-radius-lg); + border: 1px solid var(--glass-border); + overflow: hidden; +} + +.season-tabs-list { + display: flex; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + border-bottom: 1px solid var(--glass-border); + padding: 0.5rem 1.5rem 0; + margin-bottom: 1rem; +} + +.season-tabs-list::-webkit-scrollbar { + height: 8px; +} +.season-tabs-list::-webkit-scrollbar-thumb { + background-color: var(--accent); + border-radius: 4px; +} +.season-tabs-list::-webkit-scrollbar-track { + background: transparent; +} + +.season-tab-btn { + background: transparent; + border: none; + color: var(--text-secondary); + padding: 1rem 1.5rem; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); + white-space: nowrap; + position: relative; + border-bottom: 3px solid transparent; +} + +.season-tab-btn.is-local::after { + content: ''; + position: absolute; + top: 12px; + right: 12px; + width: 8px; + height: 8px; + background-color: var(--success); + border-radius: 50%; + border: 1px solid var(--primary); + box-shadow: 0 0 5px var(--success); +} + +.season-tab-btn:hover { + color: var(--text-primary); +} + +.season-tab-btn.active { + color: var(--accent); + border-bottom-color: var(--accent); +} + +.episodes-container { + padding: 1.5rem; +} + +.episodes-content-header { + display: flex; + gap: 1.5rem; + padding-bottom: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 1px solid var(--glass-border); +} + +.episode-content-details { + flex-grow: 1; +} + +.season-title { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 0.5rem; +} +.season-meta { + display: flex; + gap: 1.5rem; + font-size: 0.9rem; + color: var(--text-secondary); + margin-bottom: 0.8rem; +} +.season-meta span { + display: flex; + align-items: center; + gap: 0.5rem; +} +.season-overview { + font-size: 0.9rem; + line-height: 1.6; + color: var(--text-secondary); +} + +.season-header-actions { + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.season-local-badge { + background: var(--gradient); + color: var(--primary); + font-size: 0.7rem; + padding: 0.4rem 1rem; + border-radius: 20px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + display: inline-flex; + align-items: center; + gap: 0.4rem; + box-shadow: 0 0 10px rgba(0, 224, 255, 0.4); +} + +.download-season-btn { + width: 42px; + height: 42px; + font-size: 1rem; + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: 50%; + color: var(--text-primary); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: var(--transition); +} +.download-season-btn:hover { + background: var(--accent); + color: var(--primary); + transform: scale(1.1); +} + +.episodes-list { + display: grid; + gap: 1rem; +} + +.episode-item { + display: grid; + grid-template-columns: 180px 1fr; + gap: 1.5rem; + padding: 1rem; + background: var(--secondary); + border-radius: var(--border-radius-md); + transition: background-color 0.3s ease; + cursor: pointer; +} + +.episode-item:hover { + background: var(--glass); +} + +.episode-item-image { + width: 100%; + border-radius: var(--border-radius-sm); + aspect-ratio: 16 / 9; + object-fit: cover; +} + +.episode-item-content { + display: flex; + flex-direction: column; +} + +.episode-item-header { + display: flex; + align-items: baseline; + gap: 1rem; + margin-bottom: 0.5rem; +} + +.episode-item-number { + font-size: 1.2rem; + font-weight: 700; + color: var(--accent); +} + +.episode-item-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); +} + +.episode-item-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1.5rem; + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 0.5rem; +} + +.episode-item-overview { + font-size: 0.9rem; + line-height: 1.6; + color: var(--text-secondary); + margin-top: 0.5rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +} + +.episode-item.expanded .episode-item-overview { + display: block; + -webkit-line-clamp: unset; +} + +.watch-providers-section { + margin-top: 2rem; +} + +.watch-providers-grid { + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: center; +} + +.watch-providers-grid .provider-link { + display: block; + transition: var(--transition); + border-radius: var(--border-radius-md); + overflow: hidden; +} + +.watch-providers-grid .provider-link:hover { + transform: scale(1.1); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); +} + +.watch-providers-grid .provider-logo { + display: block; + width: 50px; + height: 50px; + object-fit: cover; + border-radius: var(--border-radius-md); + background-color: var(--glass); + border: 1px solid var(--glass-border); +} + +.local-files-list { + background: var(--secondary); + border-radius: var(--border-radius-md); + border: 1px solid var(--glass-border); + padding: 1rem; + overflow-x: auto; +} + +.local-files-list table { + width: 100%; + border-collapse: collapse; +} + +.local-files-list th, .local-files-list td { + padding: 0.75rem 1rem; + text-align: left; + border-bottom: 1px solid var(--glass-border); + font-size: 0.9rem; +} + +.local-files-list th { + font-weight: 600; + color: var(--text-primary); + position: sticky; + top: 0; + background: var(--secondary); +} + +.local-files-list td { + color: var(--text-secondary); +} + +.local-files-list tbody tr:last-child td { + border-bottom: none; +} + +.local-files-list tbody tr:hover { + background-color: var(--glass); +} + +.local-files-list .btn-sm { + padding: 0.25rem 0.6rem; + font-size: 0.8rem; +} + +@media (max-width: 992px) { + .item-details-header { + flex-direction: column; + align-items: center; + text-align: center; + } + .item-details-container { padding-top: calc(5vh + var(--topbar-height)); } + .item-details-poster-wrapper { max-width: 350px; margin: 0 auto 1rem; } + .item-details-content { text-align: center; } + .item-details-meta, .item-details-genres, .item-details-crew, .item-details-actions, .item-details-external-links { + justify-content: center; + } +} + +@media (max-width: 768px) { + .item-details-title { font-size: 2rem; } + .cast-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 1.5rem; } + .similar-items-grid, .filmography-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 1rem; } + .back-button { top: 1rem; left: 1rem; width: 38px; height: 38px; } + .item-details-poster-wrapper { max-width: 280px; } + .episodes-content-header { flex-direction: column; } + .season-header-actions { flex-direction: row; justify-content: center; width: 100%; } + .episode-item { + grid-template-columns: 1fr; + } +} + +@media (max-width: 576px) { + .item-details-poster-wrapper { max-width: 240px; } + .item-details-title { font-size: 1.8rem; } + .cast-grid { grid-template-columns: repeat(auto-fill, minmax(95px, 1fr)); gap: 1rem; } + .cast-photo { width: 80px; height: 80px; } + .similar-items-grid, .filmography-grid { grid-template-columns: repeat(2, 1fr); gap: 1rem; } +} + +@media (min-width: 992px) { + .back-button { + left: calc(var(--sidebar-width) + 2rem); + } +} diff --git a/css/footer.css b/css/footer.css new file mode 100644 index 0000000..f7bd324 --- /dev/null +++ b/css/footer.css @@ -0,0 +1,80 @@ +.footer { + background: var(--secondary); + padding: 1.5rem 2rem; + border-top: 1px solid var(--glass-border); + margin-top: 0; +} + +.footer .container { + max-width: 1200px; + margin: 0 auto; + padding: 0; +} + +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1.5rem; +} + +.footer-logo-link { + text-decoration: none; +} + +.footer-logo-text { + font-family: 'Orbitron', sans-serif; + font-size: 1.5rem; + font-weight: 700; + letter-spacing: 1px; + background: var(--gradient); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + transition: var(--transition); + display: inline-block; +} + +.footer-logo-link:hover .footer-logo-text { + transform: scale(1.03); + filter: brightness(1.1); +} + +.footer-links { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; + justify-content: center; +} + +.footer-link { + color: var(--text-secondary); + text-decoration: none; + transition: var(--transition); + font-size: 0.9rem; + font-weight: 500; +} + +.footer-link:hover { + color: var(--accent); + transform: translateY(-2px); +} + +.footer-credit { + font-size: 0.9rem; + color: var(--text-secondary); + opacity: 0.8; + margin: 0; +} + +@media (max-width: 768px) { + .footer-content { + flex-direction: column; + gap: 1.2rem; + } + + .footer { + padding: 2rem 1rem; + } +} \ No newline at end of file diff --git a/css/header.css b/css/header.css new file mode 100644 index 0000000..1894f50 --- /dev/null +++ b/css/header.css @@ -0,0 +1,141 @@ +#main-view { + padding-top: var(--topbar-height); +} + +.top-bar { + display: flex; + align-items: center; + justify-content: space-between; + height: var(--topbar-height); + padding: 0 1.5rem; + background: rgba(10, 10, 15, 0.85); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid var(--glass-border); + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 1030; + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s ease, box-shadow 0.3s ease; +} + +body.light-theme .top-bar { + background: rgba(255, 255, 255, 0.85); +} + +body.details-view-active .top-bar { + transform: translateY(-110%); +} + +.top-bar-left, .top-bar-right { + display: flex; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; +} + +.top-bar-center { + flex-grow: 1; + display: flex; + justify-content: center; +} + +.logo { + font-family: 'Orbitron', sans-serif; + font-size: 1.8rem; + font-weight: 700; + letter-spacing: 1px; + background: var(--gradient); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + transition: var(--transition); + cursor: pointer; + padding: 0.5rem 0; +} + +.logo:hover { + transform: scale(1.03); + filter: brightness(1.1); +} + +.search-bar { + position: relative; + width: 100%; + max-width: 450px; +} + +.search-input { + width: 100%; + padding: 0.6rem 1.2rem 0.6rem 2.6rem; + font-size: 0.9rem; + color: var(--text-primary); + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: 50px; + transition: var(--transition); +} + +.search-input::placeholder { + color: var(--text-secondary); +} + +.search-input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); +} + +.search-icon { + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-secondary); + transition: var(--transition); + font-size: 0.9rem; +} + +.search-input:focus+.search-icon { + color: var(--accent); +} + +@media (max-width: 768px) { + .top-bar-center { + display: none; + } + .top-bar { + justify-content: space-between; + } + .logo { + margin-left: 0.5rem; + } + .search-bar { + margin: 1rem; + position: absolute; + top: var(--topbar-height); + left: 0; + right: 0; + width: auto; + z-index: 1025; + padding: 0 1rem; + display: block; + max-width: none; + } + +} + +@media (max-width: 576px) { + .top-bar { + padding: 0 0.8rem; + } + .logo { + font-size: 1.5rem; + } + .btn-icon { + width: 36px; + height: 36px; + font-size: 1.1rem; + } +} \ No newline at end of file diff --git a/css/hero.css b/css/hero.css new file mode 100644 index 0000000..710973e --- /dev/null +++ b/css/hero.css @@ -0,0 +1,139 @@ +.hero { + display: flex; + align-items: flex-end; + position: relative; + height: 70vh; + min-height: 550px; + max-height: 800px; + overflow: hidden; + background-color: var(--primary); + margin-bottom: 0; +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to top, var(--primary) 5%, rgba(0, 0, 0, 0.8) 40%, rgba(0, 0, 0, 0.4) 70%, transparent 100%), + linear-gradient(to right, var(--primary) 10%, transparent 70%); + z-index: 1; +} + +.hero.no-overlay::before { + display: none; +} + +.hero-background-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + overflow: hidden; +} + +.hero-background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center 20%; + opacity: 0; + transform: scale(1.1); +} + +.hero-content { + position: relative; + z-index: 2; + max-width: 700px; + padding: 0 2rem 4rem; + opacity: 0; +} + +.hero-title { + font-family: 'Orbitron', sans-serif; + font-size: clamp(2.5rem, 5vw, 3.8rem); + font-weight: 700; + line-height: 1.15; + margin-bottom: 1rem; + text-shadow: 0 3px 15px rgba(0, 0, 0, 0.4); + color: var(--text-primary); +} + +.hero-subtitle { + font-size: clamp(1rem, 2vw, 1.2rem); + font-weight: 400; + color: var(--text-secondary); + margin-bottom: 1.5rem; + max-width: 550px; +} + +.hero-meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.8rem 1.5rem; + margin-bottom: 2rem; + font-size: 0.95rem; + color: var(--text-secondary); +} + +.hero-meta-item { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.hero-meta-item i { + color: var(--accent); + font-size: 1.05rem; +} + +.hero-buttons { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +.hero.loading .hero-content { + opacity: 0; +} + +.hero:not(.loading) .hero-content { + opacity: 1; +} + + +@media (max-width: 992px) { + .hero { + min-height: 450px; + height: 60vh; + } +} + +@media (max-width: 768px) { + .hero { + height: auto; + min-height: unset; + padding-bottom: 3rem; + margin-bottom: 2rem; + } + + .hero-content { + padding: 0 1rem 2rem; + } + + .hero-title { + font-size: clamp(2rem, 7vw, 2.8rem); + } + + .hero-subtitle { + font-size: clamp(0.9rem, 3vw, 1rem); + } +} \ No newline at end of file diff --git a/css/history.css b/css/history.css new file mode 100644 index 0000000..a471f29 --- /dev/null +++ b/css/history.css @@ -0,0 +1,118 @@ +#history-section .section-header { + align-items: center; +} + +#history-section .btn-danger.btn-sm { + padding: 0.4rem 1rem; + font-size: 0.8rem; + font-weight: 500; +} + +#history-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.history-item { + display: flex; + align-items: center; + gap: 1.5rem; + padding: 1rem; + background: var(--card-bg); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-md); + transition: var(--transition); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.history-item:hover { + background: rgba(0, 224, 255, 0.08); + transform: translateX(8px); + border-color: var(--accent); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.history-poster { + width: 60px; + height: 90px; + object-fit: cover; + border-radius: var(--border-radius-sm); + flex-shrink: 0; + cursor: pointer; +} + +.history-info { + flex: 1; + min-width: 0; + cursor: pointer; +} + +.history-title-wrapper { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.3rem; +} + +.history-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: var(--text-primary); + flex-grow: 1; +} + +.badge.local-badge-history { + background-color: var(--success); + color: var(--primary); + font-size: 0.7rem; + padding: 0.25rem 0.6rem; + border-radius: 20px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + flex-shrink: 0; +} + +.history-meta { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.history-actions { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.history-actions .action-btn { + width: 36px; + height: 36px; + font-size: 0.9rem; + background: var(--glass); + border: 1px solid transparent; +} + +.history-actions .action-btn:hover { + border-color: transparent; +} + +.history-actions .delete-btn:hover { + background: var(--danger); +} + +@media (max-width: 576px) { + .history-item { + flex-direction: column; + align-items: flex-start; + } + + .history-actions { + width: 100%; + justify-content: space-around; + } +} \ No newline at end of file diff --git a/css/m3u-generator.css b/css/m3u-generator.css new file mode 100644 index 0000000..ed3a8fe --- /dev/null +++ b/css/m3u-generator.css @@ -0,0 +1,231 @@ +.m3u-animated-item { + opacity: 0; + transform: translateY(20px); +} + +#m3u-generator-section { + max-width: 1400px; + margin: 0 auto; +} + +.m3u-container { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 2.5rem; + margin-top: 2rem; +} + +.m3u-config-panel { + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-lg); + padding: 2rem; + box-shadow: var(--shadow); +} + +.m3u-step { + margin-bottom: 2.5rem; +} + +.m3u-step:last-child { + margin-bottom: 0; +} + +.m3u-step-header { + display: flex; + align-items: center; + margin-bottom: 1.5rem; +} + +.m3u-step-number { + font-size: 1.2rem; + font-weight: 700; + color: var(--primary); + background: var(--gradient); + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 1rem; +} + +.m3u-step-title { + font-family: 'Orbitron', sans-serif; + font-size: 1.4rem; + font-weight: 600; + color: var(--text-primary); +} + +#m3u-server-select { + width: 100%; +} + +#m3u-libraries-container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 1rem; + max-height: 400px; + overflow-y: auto; + padding-right: 1rem; +} + +#m3u-libraries-container::-webkit-scrollbar { + width: 8px; +} + +#m3u-libraries-container::-webkit-scrollbar-track { + background: transparent; +} + +#m3u-libraries-container::-webkit-scrollbar-thumb { + background-color: var(--glass-border); + border-radius: 8px; +} + +#m3u-libraries-container .form-check { + background: rgba(0, 0, 0, 0.15); + padding: 0.8rem 1rem; + border-radius: var(--border-radius-md); + transition: background-color 0.2s; + display: flex; + align-items: center; + cursor: pointer; +} + +#m3u-libraries-container .form-check:hover { + background: rgba(0, 0, 0, 0.25); +} + +#m3u-libraries-container .form-check-input { + width: 1.1em; + height: 1.1em; + margin-right: 0.8rem; + background-color: var(--secondary); + border: 1px solid var(--glass-border); + flex-shrink: 0; +} + +#m3u-libraries-container .form-check-label { + font-weight: 500; + cursor: pointer; + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.2; +} + +#m3u-libraries-container .form-check-input:checked { + background-color: var(--accent); + border-color: var(--accent); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230a0a0f' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); +} + +#m3u-libraries-container .form-check-label i { + color: var(--text-secondary); + width: 20px; + text-align: center; + margin-right: 0.5rem; + transition: color 0.2s; +} + +#m3u-libraries-container .form-check-input:checked + .form-check-label i { + color: var(--accent); +} + +.m3u-info-panel { + background: var(--card-bg); + border-radius: var(--border-radius-lg); + padding: 2rem; + text-align: center; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.m3u-info-title { + font-family: 'Orbitron', sans-serif; + font-size: 1.4rem; + margin-bottom: 1.5rem; + color: white; +} + +.m3u-instructions { + text-align: left; + margin-left: 1.5rem; + color: var(--text-secondary); + font-size: 0.95rem; + margin-bottom: 2rem; +} + +.m3u-instructions li { + margin-bottom: 1rem; +} + +#download-m3u-btn { + width: 100%; + padding: 1rem; + font-size: 1rem; + border-radius: var(--border-radius-md); + transition: all 0.3s ease; +} + +#download-m3u-btn:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-lg); +} + +#download-m3u-btn span { + margin-left: 0.5rem; +} + +#m3u-libraries-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 150px; + color: var(--text-secondary); +} + +#m3u-libraries-loader .spinner-border { + width: 3rem; + height: 3rem; + margin-bottom: 1rem; +} + +.light-theme .m3u-config-panel { + background: var(--secondary); +} + +.light-theme #m3u-libraries-container .form-check { + background: rgba(0, 0, 0, 0.03); +} + +.light-theme #m3u-libraries-container .form-check:hover { + background: rgba(0, 0, 0, 0.06); +} + +.light-theme #m3u-libraries-container .form-check-input { + background-color: #e9ecef; + border-color: #ced4da; +} + +.light-theme #m3u-libraries-container .form-check-input:checked { + background-color: var(--accent-dark); + border-color: var(--accent-dark); +} + +.light-theme #m3u-libraries-container .form-check-label i { + color: var(--text-secondary); +} + +.light-theme #m3u-libraries-container .form-check-input:checked + .form-check-label i { + color: var(--accent-dark); +} + +@media (max-width: 992px) { + .m3u-container { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/css/main.css b/css/main.css index d3a4b0f..5134768 100644 --- a/css/main.css +++ b/css/main.css @@ -1,4312 +1,18 @@ -/* - Plex Extension CSS Bundle - Generated and Refactored by Gemini -*/ - -.filter-group { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.filter-group label { - font-size: 0.9rem; - color: var(--text-secondary); - white-space: nowrap; -} - -.filter-input { - width: 80px; - padding: 0.7rem 1rem; - font-size: 0.9rem; - color: var(--text-primary); - background-color: var(--secondary); - border: 1px solid var(--glass-border); - border-radius: 50px; - transition: var(--transition); -} - -.filter-input::placeholder { - color: var(--text-secondary); - opacity: 0.7; -} - -.filter-input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); -} - -/* Remove arrows from number input */ -.filter-input::-webkit-outer-spin-button, -.filter-input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -.filter-input[type=number] { - -moz-appearance: textfield; -} - -:root { - --primary: #0a0a0f; - --secondary: #101116; - --accent: #00e0ff; - --accent-dark: #0072ff; - --text-primary: #f0f0f5; - --text-secondary: rgba(240, 240, 245, 0.7); - --gradient: linear-gradient(135deg, var(--accent), var(--accent-dark)); - --card-bg: rgba(20, 21, 27, 0.8); - --glass: rgba(255, 255, 255, 0.05); - --glass-border: rgba(255, 255, 255, 0.1); - --shadow: 0 10px 30px rgba(0, 0, 0, 0.35); - --transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); - --success: #4caf50; - --warning: #ffc107; - --danger: #f44336; - --info: #2196f3; - --border-radius-lg: 18px; - --border-radius-md: 14px; - --border-radius-sm: 10px; - --topbar-height: 60px; - --sidebar-width: 240px; -} - -body.light-theme { - --primary: #f4f7fa; - --secondary: #ffffff; - --text-primary: #1f2937; - --text-secondary: #6b7280; - --card-bg: #ffffff; - --glass: rgba(0, 0, 0, 0.03); - --glass-border: rgba(0, 0, 0, 0.08); - --shadow: 0 10px 30px rgba(0, 0, 0, 0.1); -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -html { - scroll-behavior: smooth; -} - -body.unlocalized { - visibility: hidden; -} - -body { - background-color: var(--primary); - color: var(--text-primary); - font-family: 'Montserrat', sans-serif; - line-height: 1.7; - min-height: 100vh; - overflow-x: hidden; - position: relative; - /* padding-top: var(--topbar-height); */ - transition: background-color 0.3s, color 0.3s; -} - -#main-container { - padding-left: 0; - transition: padding-left 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -body.details-view-active { - overflow: hidden; -} - -#particles-js { - position: fixed; - width: 100%; - height: 100%; - top: 0; - left: 0; - z-index: -1; - opacity: 0.25; -} - -body.light-theme #particles-js { - opacity: 0.5; -} - -@keyframes spin { - 0% { - transform: translate(-50%, -50%) rotate(0deg); - } - - 100% { - transform: translate(-50%, -50%) rotate(360deg); - } -} - -@keyframes fadeIn { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@media (min-width: 992px) { - #main-container.sidebar-open { - padding-left: var(--sidebar-width); - } -} - -@media (max-width: 576px) { - :root { - --border-radius-lg: 14px; - --border-radius-md: 10px; - --border-radius-sm: 8px; - --topbar-height: 55px; - } -} - -body::-webkit-scrollbar, .item-details::-webkit-scrollbar { - width: 12px; -} - -body::-webkit-scrollbar-track, .item-details::-webkit-scrollbar-track { - background: var(--primary); - border-left: 1px solid var(--glass-border); -} - -body::-webkit-scrollbar-thumb, .item-details::-webkit-scrollbar-thumb { - background-color: var(--accent-dark); - border-radius: 10px; - border: 3px solid var(--primary); -} - -body::-webkit-scrollbar-thumb:hover, .item-details::-webkit-scrollbar-thumb:hover { - background-color: var(--accent); -} - -body.light-theme::-webkit-scrollbar-track, body.light-theme .item-details::-webkit-scrollbar-track { - background: var(--secondary); - border-left: 1px solid var(--glass-border); -} - -body.light-theme::-webkit-scrollbar-thumb, body.light-theme .item-details::-webkit-scrollbar-thumb { - background-color: #bdc3c7; - border-color: var(--secondary); -} - -body.light-theme::-webkit-scrollbar-thumb:hover, body.light-theme .item-details::-webkit-scrollbar-thumb:hover { - background-color: #a3aab1; -} - - -/* --- components.css --- */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.7rem; - padding: 0.7rem 1.8rem; - font-size: 0.9rem; - font-weight: 600; - text-transform: uppercase; - border: none; - border-radius: 50px; - cursor: pointer; - transition: var(--transition); - letter-spacing: 1px; - line-height: 1.5; - position: relative; - overflow: hidden; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); -} - -.btn i { - line-height: 1; -} - -.btn:disabled { - cursor: not-allowed; - opacity: 0.6; -} - -.btn-primary { - background: var(--gradient); - color: var(--primary) !important; - box-shadow: 0 6px 20px rgba(0, 224, 255, 0.25); -} - -.btn-primary:hover:not(:disabled) { - background: linear-gradient(135deg, var(--accent-dark), var(--accent)); - box-shadow: 0 8px 25px rgba(0, 224, 255, 0.35); - transform: translateY(-3px); -} - -.btn-secondary { - background: var(--glass); - color: var(--text-primary); - border: 1px solid var(--glass-border); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); -} - -.btn-secondary:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.1); - border-color: rgba(255, 255, 255, 0.15); - transform: translateY(-3px); - color: var(--text-primary); -} - -.light-theme .btn-secondary:hover:not(:disabled) { - background: rgba(0, 0, 0, 0.05); - border-color: rgba(0, 0, 0, 0.1); -} - -.btn-icon { - display: inline-flex; - align-items: center; - justify-content: center; - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 1.25rem; - width: 40px; - height: 40px; - border-radius: 50%; - cursor: pointer; - transition: var(--transition); -} - -.btn-icon:hover { - color: var(--accent); - background-color: var(--glass); -} - -.main-content { - padding: 0 2rem 4rem; -} - -.section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; -} - -.section-title { - font-family: 'Orbitron', sans-serif; - font-size: clamp(1.6rem, 4vw, 2rem); - font-weight: 600; - position: relative; - padding-bottom: 0.7rem; -} - -.section-title::after { - content: ''; - position: absolute; - left: 0; - bottom: 0; - height: 4px; - width: 70px; - background: var(--gradient); - border-radius: 3px; -} - -.section-subtitle { - font-family: 'Orbitron', sans-serif; - font-size: 1.7rem; - font-weight: 600; - padding-bottom: 0.8rem; - margin-bottom: 2rem; - border-bottom: 1px solid var(--glass-border); - position: relative; - color: var(--text-primary); -} - -.filters { - display: flex; - flex-wrap: wrap; - gap: 1rem; - margin-bottom: 2rem; -} - -.filter-select { - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - padding: 0.7rem 2.8rem 0.7rem 1.4rem; - font-size: 0.9rem; - color: var(--text-primary); - background-color: var(--secondary); - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f0f0f5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 1rem center; - background-size: 1rem; - border: 1px solid var(--glass-border); - border-radius: 50px; - cursor: pointer; - transition: var(--transition); -} - -.light-theme .filter-select { - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%231f2937'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); -} - -.filter-select option { - background: var(--primary); - color: var(--text-primary); - border: none; -} - -.filter-select:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); -} - -.spinner { - display: none; - width: 45px; - height: 45px; - border: 5px solid rgba(240, 240, 245, 0.2); - border-top: 5px solid var(--accent); - border-radius: 50%; - animation: spin 1s linear infinite; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1050; -} - -.light-theme .spinner { - border: 5px solid rgba(0, 0, 0, 0.1); - border-top: 5px solid var(--accent-dark); -} - -#consoleOutput { - border: 1px solid var(--glass-border); - padding: 15px; - margin: 20px 0; - height: 250px; - overflow-y: scroll; - background-color: rgba(10, 10, 15, 0.9); - color: var(--text-secondary); - font-family: monospace; - font-size: 0.85rem; - border-radius: var(--border-radius-md); - display: none; - box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2); -} - -.light-theme #consoleOutput { - background-color: var(--primary); -} - -#consoleOutput .console-log-entry { - margin-bottom: 6px; - line-height: 1.4; - word-break: break-word; - white-space: pre-wrap; -} - -#consoleOutput .log-time { - color: var(--accent); - margin-right: 8px; - font-weight: 500; -} - -#consoleOutput .log-message { - color: var(--text-secondary); -} - -#consoleOutput .log-error .log-message { - color: var(--danger); -} - -#consoleOutput .log-warning .log-message { - color: var(--warning); -} - -#consoleOutput .log-success .log-message { - color: var(--success); -} - -.form-control { - background-color: var(--glass); - border: 1px solid var(--glass-border); - color: var(--text-primary); - border-radius: var(--border-radius-sm); - padding: .6rem 1rem; -} -.form-control:focus { - background-color: var(--glass); - color: var(--text-primary); - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(0,224,255,.2); -} -.form-control::placeholder { - color: var(--text-secondary); -} -.form-label { - color: var(--text-primary); - font-weight: 500; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 50px; - height: 28px; -} -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} -.toggle-switch label { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--glass-border); - transition: .4s; - border-radius: 28px; -} -.toggle-switch label:before { - position: absolute; - content: ""; - height: 20px; - width: 20px; - left: 4px; - bottom: 4px; - background-color: white; - transition: .4s; - border-radius: 50%; -} -.toggle-switch input:checked + label { - background: var(--gradient); -} -.toggle-switch input:checked + label:before { - transform: translateX(22px); -} - -@media (max-width: 768px) { - .section-title { - font-size: 1.5rem; - } -} - - -/* -============================================== - MAIN & GLOBAL STYLES -============================================== -*/ - -.hero.loading .hero-content { - opacity: 0; -} - -.hero:not(.loading) .hero-content { - opacity: 1; -} - -#settingsModal .text-muted { - color: var(--text-primary); -} - -#main-view { - padding-top: var(--topbar-height); -} - - -/* --- navbar.css --- */ -.top-bar { - display: flex; - align-items: center; - justify-content: space-between; - height: var(--topbar-height); - padding: 0 1.5rem; - background: rgba(10, 10, 15, 0.85); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border-bottom: 1px solid var(--glass-border); - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 1030; - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s ease, box-shadow 0.3s ease; -} - -body.light-theme .top-bar { - background: rgba(255, 255, 255, 0.85); -} - -body.details-view-active .top-bar { - transform: translateY(-110%); -} - -.top-bar-left, .top-bar-right { - display: flex; - align-items: center; - gap: 0.5rem; - flex-shrink: 0; -} - -.top-bar-center { - flex-grow: 1; - display: flex; - justify-content: center; -} - -.logo { - font-family: 'Orbitron', sans-serif; - font-size: 1.8rem; - font-weight: 700; - letter-spacing: 1px; - background: var(--gradient); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - transition: var(--transition); - cursor: pointer; - padding: 0.5rem 0; -} - -.logo:hover { - transform: scale(1.03); - filter: brightness(1.1); -} - -.search-bar { - position: relative; - width: 100%; - max-width: 450px; -} - -.search-input { - width: 100%; - padding: 0.6rem 1.2rem 0.6rem 2.6rem; - font-size: 0.9rem; - color: var(--text-primary); - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: 50px; - transition: var(--transition); -} - -.search-input::placeholder { - color: var(--text-secondary); -} - -.search-input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); -} - -.search-icon { - position: absolute; - left: 1rem; - top: 50%; - transform: translateY(-50%); - color: var(--text-secondary); - transition: var(--transition); - font-size: 0.9rem; -} - -.search-input:focus+.search-icon { - color: var(--accent); -} - -.sidebar-nav { - position: fixed; - top: var(--topbar-height); - left: 0; - width: var(--sidebar-width); - height: calc(100vh - var(--topbar-height)); - background: var(--secondary); - z-index: 1020; - border-right: 1px solid var(--glass-border); - transform: translateX(-100%); - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); - padding: 1.5rem 0; -} - -body.light-theme .sidebar-nav { - background: #eef2f7; -} - -.sidebar-nav.open { - transform: translateX(0); -} - -.sidebar-menu { - list-style: none; - padding: 0; - margin: 0; -} - -.sidebar-menu .nav-link { - display: flex; - align-items: center; - gap: 1.2rem; - color: var(--text-secondary); - font-weight: 500; - padding: 0.9rem 1.8rem; - transition: var(--transition); - border-left: 4px solid transparent; -} - -.sidebar-menu .nav-link i { - font-size: 1.1rem; - width: 20px; - text-align: center; -} - -.sidebar-menu .nav-link:hover { - color: var(--text-primary); - background-color: var(--glass); -} - -.sidebar-menu .nav-link.active { - color: var(--accent); - font-weight: 600; - background: var(--glass); - border-left-color: var(--accent); -} - -@media (min-width: 992px) { - /* #sidebar-toggle { - display: none; - } */ - .sidebar-nav { - transform: translateX(0); - } - #main-container { - padding-left: var(--sidebar-width); - } -} - -@media (max-width: 991px) { - #sidebar-toggle { - display: inline-flex; - } -} - -@media (max-width: 768px) { - .top-bar-center { - display: none; - } - .top-bar { - justify-content: space-between; - } - .logo { - margin-left: 0.5rem; - } - .search-bar { - margin: 1rem; - position: absolute; - top: var(--topbar-height); - left: 0; - right: 0; - width: auto; - z-index: 1025; - padding: 0 1rem; - display: block; - max-width: none; - } - -} - -@media (max-width: 576px) { - .top-bar { - padding: 0 0.8rem; - } - .logo { - font-size: 1.5rem; - } - .sidebar-nav { - width: 100%; - transform: translateX(-105%); - } - .btn-icon { - width: 36px; - height: 36px; - font-size: 1.1rem; - } -} - -body.sidebar-open .sidebar-nav { - transform: translateX(0); -} - -body.sidebar-collapsed .sidebar-nav { - transform: translateX(-100%); -} - -body.sidebar-collapsed #main-container { - padding-left: 0; -} - - - -/* --- hero.css --- */ -.hero { - display: flex; - align-items: flex-end; - position: relative; - height: 70vh; - min-height: 550px; - max-height: 800px; - overflow: hidden; - background-color: var(--primary); - margin-bottom: 0; -} - -.hero::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(to top, var(--primary) 5%, rgba(0, 0, 0, 0.8) 40%, rgba(0, 0, 0, 0.4) 70%, transparent 100%), - linear-gradient(to right, var(--primary) 10%, transparent 70%); - z-index: 1; -} - -.hero.no-overlay::before { - display: none; -} - -.hero-background-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; -} - -.hero-background { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-size: cover; - background-position: center center; - opacity: 0; - transform: scale(1.1); -} - -.hero-content { - position: relative; - z-index: 2; - max-width: 700px; - padding: 0 2rem 4rem; - opacity: 0; -} - -.hero-title { - font-family: 'Orbitron', sans-serif; - font-size: clamp(2.5rem, 5vw, 3.8rem); - font-weight: 700; - line-height: 1.15; - margin-bottom: 1rem; - text-shadow: 0 3px 15px rgba(0, 0, 0, 0.4); - color: var(--text-primary); -} - -.hero-subtitle { - font-size: clamp(1rem, 2vw, 1.2rem); - font-weight: 400; - color: var(--text-secondary); - margin-bottom: 1.5rem; - max-width: 550px; -} - -.hero-meta { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0.8rem 1.5rem; - margin-bottom: 2rem; - font-size: 0.95rem; - color: var(--text-secondary); -} - -.hero-meta-item { - display: flex; - align-items: center; - gap: 0.6rem; -} - -.hero-meta-item i { - color: var(--accent); - font-size: 1.05rem; -} - -.hero-buttons { - display: flex; - flex-wrap: wrap; - gap: 1rem; -} - -@media (max-width: 992px) { - .hero { - min-height: 450px; - height: 60vh; - } -} - -@media (max-width: 768px) { - .hero { - height: auto; - min-height: unset; - padding-bottom: 3rem; - margin-bottom: 2rem; - } - - .hero-content { - padding: 0 1rem 2rem; - } - - .hero-title { - font-size: clamp(2rem, 7vw, 2.8rem); - } - - .hero-subtitle { - font-size: clamp(0.9rem, 3vw, 1rem); - } -} - -/* --- content-grid.css --- */ -#main-view { - min-height: calc(100vh - var(--topbar-height) - 100px); -} - -.content-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 1.8rem; -} - -.item-card { - background: var(--card-bg); - border-radius: var(--border-radius-lg); - overflow: hidden; - position: relative; - transition: var(--transition); - box-shadow: var(--shadow); - cursor: pointer; - border: 1px solid transparent; - display: flex; - flex-direction: column; -} - -.item-card:hover { - transform: translateY(-10px) scale(1.03); - box-shadow: 0 18px 45px rgba(0, 0, 0, 0.5); - border-color: rgba(0, 224, 255, 0.5); - z-index: 10; -} - -.item-card .badge { - position: absolute; - top: 1rem; - font-size: 0.7rem; - font-weight: 600; - padding: 0.35rem 0.8rem; - border-radius: 20px; - z-index: 3; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.item-card .top-badge { - left: 1rem; - background: var(--warning); - color: var(--primary); -} - -.item-card .available-badge { - right: 1rem; - background: var(--success); - color: var(--primary); -} - -.item-poster { - display: block; - width: 100%; - height: auto; - aspect-ratio: 2 / 3; - object-fit: cover; - background-color: var(--secondary); - background-size: cover; - background-position: center; - transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1); -} - -.item-card:hover .item-poster { - transform: scale(1.08); -} - -.item-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(to top, rgba(10, 10, 15, 0.9) 0%, transparent 50%); - opacity: 0; - transition: opacity 0.4s ease; - z-index: 1; - display: flex; - align-items: flex-end; - justify-content: center; -} - -.item-card:hover .item-overlay { - opacity: 1; -} - -.item-info { - padding: 1.2rem; - position: relative; - z-index: 2; - margin-top: auto; - background: var(--card-bg); - transition: var(--transition); -} - -.item-title { - font-size: 1.05rem; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 0.5rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - transition: color 0.3s ease; -} - -.item-card:hover .item-title { - color: var(--accent); -} - -.item-meta { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.85rem; - color: var(--text-secondary); -} - -.item-meta span { - display: flex; - align-items: center; - gap: 0.4rem; -} - -.item-rating { - font-weight: 600; -} - -.item-rating.rating-good { - color: var(--success); -} - -.item-rating.rating-ok { - color: var(--warning); -} - -.item-rating.rating-bad { - color: var(--danger); -} - -.item-actions { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - gap: 1rem; - z-index: 3; - opacity: 0; - pointer-events: none; - transform: translateY(10px); - transition: opacity 0.4s ease, transform 0.4s ease; -} - -.item-card:hover .item-actions { - opacity: 1; - transform: translateY(0); - pointer-events: auto; -} - -.action-btn { - display: flex; - justify-content: center; - align-items: center; - width: 44px; - height: 44px; - background: rgba(255, 255, 255, 0.15); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - color: var(--text-primary); - border-radius: 50%; - border: 1px solid var(--glass-border); - cursor: pointer; - transition: var(--transition); - font-size: 1rem; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); -} - -.action-btn:hover:not(:disabled) { - background: var(--accent); - color: var(--primary); - transform: scale(1.1); - border-color: transparent; - box-shadow: 0 6px 15px rgba(0, 224, 255, 0.3); -} - -.action-btn.favorites-btn.active { - background: var(--danger); - color: white; -} - -.action-btn.favorites-btn.active:hover { - background: #c62828; -} - -.action-btn.disabled-btn { - cursor: not-allowed; - opacity: 0.6; - background: rgba(80, 80, 80, 0.3); -} - -.action-btn.disabled-btn:hover { - transform: none; - background: rgba(80, 80, 80, 0.3); - color: var(--text-primary); - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); -} - - -.recommendations-section { - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-lg); - padding: 2.5rem; - margin-top: 4rem; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); -} - -.recommendations-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 2rem; -} - -.empty-state { - grid-column: 1 / -1; - text-align: center; - padding: 4rem 2rem; - background: var(--glass); - border: 1px dashed var(--glass-border); - border-radius: var(--border-radius-lg); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); -} - -.empty-state i.fas, -.empty-state i.far { - font-size: 3.5rem; - color: var(--accent); - opacity: 0.6; - margin-bottom: 1.5rem; - display: block; -} - -.empty-state p.lead { - font-size: 1.3rem; - color: var(--text-primary); - margin-bottom: 0.8rem; -} - -.empty-state p.text-muted { - font-size: 1rem; - color: var(--text-secondary); - margin-bottom: 1.5rem; -} - -.empty-state .btn { - margin-top: 1rem; -} - - - -@media (max-width: 992px) { - .content-grid { - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - gap: 1.5rem; - } -} - -@media (max-width: 768px) { - .content-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 1.2rem; - } -} - -@media (max-width: 576px) { - .content-grid { - grid-template-columns: repeat(auto-fill, minmax(125px, 1fr)); - gap: 1rem; - } -} - -/* --- details.css --- */ -.item-details { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - display: none; - overflow-y: auto; - background: var(--primary); - z-index: 1035; - clip-path: inset(0 0 0 0); -} - -.item-details.active { - display: block; -} - -.back-button { - position: absolute; - top: 2rem; - left: 2rem; - z-index: 10; - display: flex; - justify-content: center; - align-items: center; - width: 42px; - height: 42px; - background: var(--glass); - color: var(--text-primary); - border-radius: 50%; - border: 1px solid var(--glass-border); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - cursor: pointer; - transition: var(--transition); -} - -.back-button:hover { - background: var(--accent); - color: var(--primary); - transform: scale(1.1); - box-shadow: 0 4px 15px rgba(0, 224, 255, 0.3); -} - -.details-backdrop-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 60vh; - overflow: hidden; - z-index: 0; -} - -.details-backdrop-img { - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - opacity: 0.4; -} - -.details-backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(to top, var(--primary) 15%, transparent 50%), - linear-gradient(to right, var(--primary) 10%, transparent 70%); -} - -.item-details-container { - max-width: 1200px; - margin: 0 auto; - position: relative; - z-index: 1; - padding: 8rem 2rem 5rem; -} - -.item-details-header { - display: flex; - flex-direction: row; - gap: 3rem; - margin-bottom: 3rem; - align-items: flex-start; -} - -.item-details-poster-wrapper { - flex-shrink: 0; - width: 100%; - max-width: 320px; -} - -.item-details-poster { - display: block; - width: 100%; - height: auto; - border-radius: var(--border-radius-lg); - box-shadow: var(--shadow); - transition: var(--transition); -} - -.item-details-poster:hover { - transform: scale(1.03); - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6); -} - -#item-details-content { - opacity: 0; -} - -.item-details-content { - flex: 1; - min-width: 0; -} - -.item-details-title { - font-family: 'Orbitron', sans-serif; - font-size: clamp(2rem, 5vw, 3.2rem); - font-weight: 700; - margin-bottom: 0.5rem; - line-height: 1.2; -} - -.item-details-tagline { - font-size: 1.2rem; - font-style: italic; - color: var(--text-secondary); - margin-bottom: 1.5rem; - font-weight: 300; -} - -.item-details-meta { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 1rem 1.8rem; - margin-bottom: 1.8rem; - font-size: 0.95rem; -} - -.item-details-meta-item { - display: flex; - align-items: center; - gap: 0.6rem; - color: var(--text-secondary); -} - -.item-details-meta-item i { - color: var(--accent); - font-size: 1.05rem; -} - -.item-details-overview { - font-size: 1.05rem; - margin-bottom: 2rem; - line-height: 1.8; - color: rgba(240, 240, 245, 0.85); -} - -.item-details-genres { - display: flex; - flex-wrap: wrap; - gap: 0.8rem; - margin-bottom: 1.8rem; -} - -.genre-badge { - padding: 0.5rem 1.1rem; - font-size: 0.8rem; - font-weight: 500; - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: 50px; - transition: var(--transition); - cursor: default; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.item-details-crew p { - margin-bottom: 0.5rem; - font-size: 0.95rem; - color: var(--text-secondary); -} - -.item-details-crew strong { - color: var(--text-primary); - margin-right: 0.5em; -} - -.item-details-external-links a { - display: inline-flex; - align-items: center; - gap: 0.5rem; - color: var(--text-secondary); - text-decoration: none; - padding: 0.4rem 0.9rem; - border: 1px solid var(--glass-border); - border-radius: 50px; - font-size: 0.85rem; - background: var(--glass); - transition: var(--transition); -} - -.item-details-external-links a:hover { - color: var(--accent); - border-color: var(--accent); - background: rgba(0, 224, 255, 0.1); -} - -.item-details-actions { - display: flex; - flex-wrap: wrap; - gap: 1rem; - margin-top: 2rem; -} - -.item-details-section { - margin-bottom: 3.5rem; -} - -.cast-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); - gap: 1.8rem; -} - -.cast-card { - text-align: center; - transition: var(--transition); - cursor: pointer; - background: var(--secondary); - padding: 1rem; - border-radius: var(--border-radius-md); -} - -.cast-card:hover { - transform: translateY(-8px); - background: var(--glass); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); -} - -.cast-photo { - width: 100px; - height: 100px; - border-radius: 50%; - object-fit: cover; - margin: 0 auto 1rem auto; - border: 3px solid var(--glass-border); - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); - transition: var(--transition); -} - -.cast-card:hover .cast-photo { - transform: scale(1.05); - border-color: var(--accent); - box-shadow: 0 6px 18px rgba(0, 224, 255, 0.25); -} - -.cast-name { - font-size: 1rem; - font-weight: 600; - margin-bottom: 0.3rem; - color: var(--text-primary); -} - -.cast-character { - font-size: 0.85rem; - color: var(--text-secondary); -} - -.similar-items-grid, .filmography-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - gap: 1.5rem; -} - -.similar-item-card { - background: transparent; - border-radius: var(--border-radius-md); - overflow: hidden; - transition: var(--transition); - cursor: pointer; - position: relative; -} - -.similar-item-card:hover { - transform: translateY(-8px); - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); -} - -.similar-item-card:hover .similar-item-poster { - transform: scale(1.05); -} - -.similar-item-poster { - display: block; - width: 100%; - height: auto; - border-radius: var(--border-radius-md); - aspect-ratio: 2 / 3; - object-fit: cover; - transition: transform 0.4s ease; -} - -.similar-item-info { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: 0.8rem; - background: linear-gradient(to top, rgba(10, 10, 15, 0.95) 0%, transparent 100%); - border-bottom-left-radius: var(--border-radius-md); - border-bottom-right-radius: var(--border-radius-md); -} - -.similar-item-title { - color: var(--text-primary); - font-size: 0.9rem; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.seasons-accordion .accordion-item { - background: transparent; - border-radius: var(--border-radius-lg); - border: 1px solid var(--glass-border); - margin-bottom: 1.5rem; - overflow: hidden; -} - -.seasons-accordion .accordion-button { - background: var(--card-bg); - color: var(--text-primary); - font-size: 1.1rem; - padding: 1.2rem 1.5rem; - border: none; - box-shadow: none !important; - transition: background-color 0.3s ease; -} - -.seasons-accordion .accordion-button:not(.collapsed) { - background: var(--glass); - color: var(--accent); - border-bottom: 1px solid var(--glass-border); -} - -.seasons-accordion .accordion-button:hover { - background: var(--glass); -} - -.seasons-accordion .accordion-button::after { - filter: brightness(0) invert(1) opacity(0.7); - transition: transform 0.3s ease; -} - -.seasons-accordion .accordion-button:not(.collapsed)::after { - filter: invert(1) opacity(1) brightness(1.5) contrast(200%); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2300e0ff'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); -} - -.seasons-accordion .accordion-collapse { - overflow: hidden; - transition: height 0.35s ease; -} - -.season-info { - display: flex; - align-items: center; - gap: 1.5rem; - width: 100%; -} - -.season-poster { - width: 70px; - height: 105px; - object-fit: cover; - border-radius: var(--border-radius-sm); - flex-shrink: 0; -} - -.season-details { - flex-grow: 1; - min-width: 0; -} - -.season-title { - font-size: 1.3rem; - font-weight: 600; - margin-bottom: 0.4rem; -} - -.season-meta { - display: flex; - gap: 1.2rem; - font-size: 0.9rem; - color: var(--text-secondary); - margin-bottom: 0.5rem; -} - -.season-meta span { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.season-overview { - font-size: 0.9rem; - line-height: 1.5; - color: var(--text-secondary); - margin-top: 0.5rem; -} - -.season-episodes { - padding: 1rem 1.5rem; - background: var(--secondary); - border-top: 1px solid var(--glass-border); - max-height: 500px; - overflow-y: auto; -} - -.episode-card { - display: flex; - gap: 1rem; - padding: 1.2rem 0.5rem; - border-bottom: 1px solid var(--glass-border); - transition: background-color 0.3s ease; -} - -.episode-card:last-child { - border-bottom: none; - padding-bottom: 0.5rem; -} - -.episode-card:first-child { - padding-top: 0.5rem; -} - -.episode-card:hover { - background: var(--glass); -} - -.episode-number { - flex-shrink: 0; - width: 36px; - height: 36px; - background: var(--glass); - border: 1px solid var(--glass-border); - color: var(--text-primary); - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - font-weight: 600; - font-size: 1rem; - margin-top: 5px; -} - -.episode-info { - flex: 1; - min-width: 0; -} - -.episode-title { - font-size: 1.1rem; - font-weight: 500; - margin-bottom: 0.4rem; - color: var(--text-primary); -} - -.episode-meta { - display: flex; - flex-wrap: wrap; - gap: 0.5rem 1rem; - font-size: 0.85rem; - color: var(--text-secondary); - margin-bottom: 0.5rem; -} - -.episode-meta span { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.episode-overview { - font-size: 0.9rem; - line-height: 1.6; - color: var(--text-secondary); - margin-top: 0.7rem; -} - -@media (max-width: 992px) { - .item-details-header { - flex-direction: column; - align-items: center; - text-align: center; - } - - .item-details-container { - padding-top: calc(5vh + var(--topbar-height)); - } - - .item-details-poster-wrapper { - max-width: 350px; - margin: 0 auto 1rem; - } - - .item-details-content { - text-align: center; - } - - .item-details-meta, - .item-details-genres, - .item-details-crew, - .item-details-actions, - .item-details-external-links { - justify-content: center; - } -} - -@media (max-width: 768px) { - .item-details-title { - font-size: 2rem; - } - - .cast-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 1.5rem; - } - - .similar-items-grid, .filmography-grid { - grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); - gap: 1rem; - } - - .back-button { - top: 1rem; - left: 1rem; - width: 38px; - height: 38px; - } - - .item-details-poster-wrapper { - max-width: 280px; - } - - .season-info { - flex-direction: column; - align-items: flex-start; - gap: 1rem; - text-align: left; - } - - .season-poster { - width: 100%; - max-width: 150px; - height: auto; - margin: 0; - } - - .episode-card { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .episode-number { - margin-bottom: 0.8rem; - width: 32px; - height: 32px; - font-size: 0.9rem; - } -} - -@media (max-width: 576px) { - .item-details-poster-wrapper { - max-width: 240px; - } - - .item-details-title { - font-size: 1.8rem; - } - - .cast-grid { - grid-template-columns: repeat(auto-fill, minmax(95px, 1fr)); - gap: 1rem; - } - - .cast-photo { - width: 80px; - height: 80px; - } - - .similar-items-grid, .filmography-grid { - grid-template-columns: repeat(2, 1fr); - gap: 1rem; - } -} - -@media (min-width: 992px) { - .back-button { - left: calc(var(--sidebar-width) + 2rem); - } -} - - - -/* --- stats.css --- */ -#stats-section { - padding-top: 2rem; -} - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 2rem; - margin-top: 2rem; -} - -.stats-card { - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-lg); - padding: 2rem; - display: flex; - flex-direction: column; - justify-content: space-between; - transition: var(--transition); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); -} - -.stats-card:hover { - transform: translateY(-8px); - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); - border-color: var(--accent); -} - -.stat-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 1.5rem; -} - -.stat-icon { - font-size: 2.2rem; - color: var(--accent); - background: var(--gradient); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - opacity: 0.8; -} - -.stat-label { - font-size: 1.1rem; - font-weight: 500; - color: var(--text-secondary); - text-align: right; -} - -.stat-value { - font-family: 'Orbitron', sans-serif; - font-size: clamp(2.5rem, 5vw, 3.5rem); - font-weight: 700; - color: var(--text-primary); - line-height: 1; - text-align: right; -} - -.chart-container { - background: var(--card-bg); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-lg); - padding: 2rem; - box-shadow: var(--shadow); - grid-column: span 1; -} - -.chart-container.full-width { - grid-column: 1 / -1; -} - -.chart-title { - font-size: 1.3rem; - font-weight: 600; - margin-bottom: 1.5rem; - color: var(--text-primary); - text-align: center; -} - -.chart-container canvas { - max-height: 400px; - width: 100% !important; -} - -#stats-filters { - display: flex; - justify-content: flex-end; - margin-bottom: 2rem; -} - -.token-details-card { - grid-column: 1 / -1; - display: none; -} - -.token-details-card .card-body { - padding: 1.5rem; -} - -.token-details-card .server-list { - list-style: none; - padding: 0; - margin: 0; - max-height: 200px; - overflow-y: auto; -} - -.token-details-card .server-list li { - padding: 0.75rem 1rem; - border-bottom: 1px solid var(--glass-border); - font-size: 0.95rem; - color: var(--text-secondary); -} - -.token-details-card .server-list li:last-child { - border-bottom: none; -} - -.token-details-card .server-list strong { - color: var(--text-primary); - margin-right: 0.5rem; -} - - -@media (max-width: 768px) { - .stats-card { - padding: 1.5rem; - } -} - -@media (max-width: 576px) { - .stats-grid { - grid-template-columns: 1fr; - } -} - -/* --- history.css --- */ -#history-section .section-header { - align-items: center; -} - -#history-section .btn-danger.btn-sm { - padding: 0.4rem 1rem; - font-size: 0.8rem; - font-weight: 500; -} - -#history-list { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.history-item { - display: flex; - align-items: center; - gap: 1.5rem; - padding: 1rem; - background: var(--card-bg); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-md); - transition: var(--transition); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); -} - -.history-item:hover { - background: rgba(0, 224, 255, 0.08); - transform: translateX(8px); - border-color: var(--accent); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); -} - -.history-poster { - width: 60px; - height: 90px; - object-fit: cover; - border-radius: var(--border-radius-sm); - flex-shrink: 0; - cursor: pointer; -} - -.history-info { - flex: 1; - min-width: 0; - cursor: pointer; -} - -.history-title-wrapper { - display: flex; - align-items: center; - gap: 0.75rem; - margin-bottom: 0.3rem; -} - -.history-title { - font-size: 1.1rem; - font-weight: 600; - margin-bottom: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: var(--text-primary); - flex-grow: 1; -} - -.badge.local-badge-history { - background-color: var(--success); - color: var(--primary); - font-size: 0.7rem; - padding: 0.25rem 0.6rem; - border-radius: 20px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - flex-shrink: 0; -} - -.history-meta { - font-size: 0.85rem; - color: var(--text-secondary); -} - -.history-actions { - display: flex; - gap: 0.5rem; - align-items: center; -} - -.history-actions .action-btn { - width: 36px; - height: 36px; - font-size: 0.9rem; - background: var(--glass); - border: 1px solid transparent; -} - -.history-actions .action-btn:hover { - border-color: transparent; -} - -.history-actions .delete-btn:hover { - background: var(--danger); -} - -.watch-providers-section { - margin-top: 2rem; -} - -.watch-providers-grid { - display: flex; - flex-wrap: wrap; - gap: 1rem; - align-items: center; -} - -.provider-link { - display: block; - transition: var(--transition); - border-radius: var(--border-radius-md); - overflow: hidden; -} - -.provider-link:hover { - transform: scale(1.1); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); -} - -.provider-logo { - display: block; - width: 50px; - height: 50px; - border-radius: var(--border-radius-md); - background-color: var(--glass); - border: 1px solid var(--glass-border); -} - -@media (max-width: 576px) { - .history-item { - flex-direction: column; - align-items: flex-start; - } - - .history-actions { - width: 100%; - justify-content: space-around; - } -} - - -/* --- footer.css --- */ -.footer { - background: var(--secondary); - padding: 1.5rem 2rem; - border-top: 1px solid var(--glass-border); - margin-top: 0; -} - -.footer .container { - max-width: 1200px; - margin: 0 auto; - padding: 0; -} - -.footer-content { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: 1.5rem; -} - -.footer-logo-link { - text-decoration: none; -} - -.footer-logo-text { - font-family: 'Orbitron', sans-serif; - font-size: 1.5rem; - font-weight: 700; - letter-spacing: 1px; - background: var(--gradient); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - transition: var(--transition); - display: inline-block; -} - -.footer-logo-link:hover .footer-logo-text { - transform: scale(1.03); - filter: brightness(1.1); -} - -.footer-links { - display: flex; - gap: 1.5rem; - flex-wrap: wrap; - justify-content: center; -} - -.footer-link { - color: var(--text-secondary); - text-decoration: none; - transition: var(--transition); - font-size: 0.9rem; - font-weight: 500; -} - -.footer-link:hover { - color: var(--accent); - transform: translateY(-2px); -} - -.footer-credit { - font-size: 0.9rem; - color: var(--text-secondary); - opacity: 0.8; - margin: 0; -} - -@media (max-width: 768px) { - .footer-content { - flex-direction: column; - gap: 1.2rem; - } - - .footer { - padding: 2rem 1rem; - } -} - - -/* --- overlays.css --- */ -.lightbox { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(10, 10, 15, 0.96); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - z-index: 2000; - justify-content: center; - align-items: center; - opacity: 0; - transition: opacity 0.4s ease; -} - -.lightbox.active { - display: flex; - opacity: 1; -} - -.lightbox-content { - position: relative; - width: 90%; - max-width: 960px; - background: var(--secondary); - padding: 1rem; - border-radius: var(--border-radius-lg); - box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5); - border: 1px solid var(--glass-border); - transform: scale(0.95); - transition: transform 0.4s ease; -} - -.lightbox.active .lightbox-content { - transform: scale(1); -} - -.lightbox-close { - position: absolute; - top: -15px; - right: -15px; - width: 38px; - height: 38px; - display: flex; - justify-content: center; - align-items: center; - background: var(--accent); - color: var(--primary); - border: none; - border-radius: 50%; - font-size: 1.1rem; - cursor: pointer; - transition: var(--transition); - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); -} - -.lightbox-close:hover { - transform: scale(1.1) rotate(90deg); - background: var(--accent-dark); - box-shadow: 0 6px 15px rgba(0, 224, 255, 0.4); -} - -.video-container { - position: relative; - padding-bottom: 56.25%; - height: 0; - overflow: hidden; - border-radius: var(--border-radius-md); -} - -.video-container iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; -} - -.notification { - position: relative; - min-width: 280px; - max-width: 350px; - margin-bottom: 1rem; - padding: 1.1rem 1.5rem; - border-radius: var(--border-radius-md); - background: var(--secondary); - color: var(--text-primary); - box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); - border: 1px solid var(--glass-border); - border-left-width: 5px; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - transform: translateX(120%); - opacity: 0; - transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s ease; - overflow: hidden; -} - -.notification.show { - transform: translateX(0); - opacity: 1; -} - -.notification-content { - display: flex; - align-items: center; - gap: 1rem; -} - -.notification i.fas { - font-size: 1.2rem; - line-height: 1; -} - -.notification span { - flex: 1; -} - -.notification.success { - border-left-color: var(--success); -} - -.notification.error { - border-left-color: var(--danger); -} - -.notification.info { - border-left-color: var(--info); -} - -.notification.warning { - border-left-color: var(--warning); -} - -.light-theme .notification { - color: var(--text-primary); -} - -.modal-backdrop { - background-color: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(3px); - -webkit-backdrop-filter: blur(3px); -} - -.modal-content { - background-color: var(--secondary); - color: var(--text-primary); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-lg); - box-shadow: var(--shadow); - overflow: hidden; -} - -.modal-header { - border-bottom: 1px solid var(--glass-border); - background: rgba(255, 255, 255, 0.03); -} - -.modal-title { - color: var(--accent); - font-family: 'Orbitron', sans-serif; - font-size: 1.3rem; -} - -.modal-footer { - border-top: 1px solid var(--glass-border); - background: rgba(10, 10, 15, 0.5); - padding: 1rem; -} - -.modal .btn-close { - filter: invert(1) grayscale(100%) brightness(150%) opacity(0.8); - transition: transform 0.3s ease; -} - -.modal .btn-close:hover { - filter: invert(1) grayscale(100%) brightness(200%) opacity(1); - transform: rotate(90deg); -} - -.light-theme .modal-content { - background-color: var(--secondary); -} -.light-theme .modal-header, .light-theme .modal-footer { - background-color: var(--primary); -} - -#settingsModal .nav-tabs { - border-bottom: 1px solid var(--glass-border); - padding: 0.5rem 1rem 0; - background-color: rgba(10,10,15,0.7); -} -.light-theme #settingsModal .nav-tabs { - background-color: var(--primary); -} - -#settingsModal .nav-tabs .nav-link { - border: none; - color: var(--text-secondary); - border-bottom: 3px solid transparent; - transition: var(--transition); - padding: 0.8rem 1.2rem; - font-weight: 500; -} - -#settingsModal .nav-tabs .nav-link:hover { - color: var(--text-primary); - border-bottom-color: var(--glass-border); -} - -#settingsModal .nav-tabs .nav-link.active { - color: var(--accent); - background-color: transparent; - border-bottom-color: var(--accent); - font-weight: 600; -} - -#settingsModal .tab-content label { - font-weight: 500; -} - -#settingsModal .tab-content p, -#settingsModal .tab-content .text-muted { - color: var(--text-secondary); -} - -#settingsModal .tab-content h5 { - color: var(--text-primary); -} - -#settingsModal .tab-content input[type="checkbox"] { - margin-right: 0.6rem; - transform: scale(1.1); - accent-color: var(--accent); -} - -#editor { - height: 300px; - width: 100%; - border-radius: var(--border-radius-md); - border: 1px solid var(--glass-border); - font-family: monospace; -} - -/* --- music-player.css --- */ -#musicPlayerContainer { - position: fixed; - top: 0; - left: 0; - width: 320px; - height: 100%; - background: var(--secondary); - box-shadow: 5px 0 35px rgba(0, 0, 0, 0.3); - display: flex; - flex-direction: column; - z-index: 1040; - border-right: 1px solid var(--glass-border); - transform: translateX(-100%); - transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s ease; -} - -body.miniplayer-active #musicPlayerContainer { - height: calc(100% - 85px); -} - -.sidenav { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - background: transparent; - position: relative; -} - -.sidenav-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.7rem 1.2rem; - background: var(--primary); - color: var(--text-primary); - border-bottom: 1px solid var(--glass-border); - flex-shrink: 0; - position: relative; - z-index: 3; -} - -.sidenav-header h4 { - margin: 0; - font-family: 'Orbitron', sans-serif; - font-size: 1.4rem; - color: var(--accent); - line-height: 1; -} - -.sidenav-header button { - background: none; - border: none; - color: var(--text-secondary); - font-size: 1.4rem; - cursor: pointer; - padding: 0.5rem; - line-height: 1; - transition: color 0.3s ease, transform 0.3s ease; -} - -.sidenav-header button:hover { - color: var(--accent); - transform: rotate(90deg); -} - -.music-panel { - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - min-height: 0; - position: absolute; - top: 57px; - left: 0; - width: 100%; - height: calc(100% - 57px); - background-color: var(--secondary); -} - -#artistListContainer { - z-index: 2; - transform: translateX(0); -} -#songListContainer { - z-index: 3; - transform: translateX(100%); - opacity: 0; - visibility: hidden; -} - -.panel-controls { - padding: 1rem; - border-bottom: 1px solid var(--glass-border); - flex-shrink: 0; -} - -.search-wrapper { - position: relative; -} -.search-wrapper i { - position: absolute; - left: 1.1rem; - top: 50%; - transform: translateY(-50%); - color: var(--text-secondary); - font-size: 0.9em; -} -.search-wrapper input, -.search-wrapper-songs input { - width: 100%; - padding: 0.7rem 1.3rem 0.7rem 2.5rem; - font-size: 0.9rem; - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: 50px; - color: var(--text-primary); - transition: var(--transition); -} -.search-wrapper input:focus, -.search-wrapper-songs input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); - background: rgba(0, 224, 255, 0.08); -} - -.artist-grid { - padding: 0.5rem; - display: grid; - grid-template-columns: 1fr; - gap: 0.25rem; - overflow-y: auto; - flex-grow: 1; -} - -.artist-card { - background: transparent; - border-radius: var(--border-radius-sm); - border: 1px solid transparent; - overflow: hidden; - cursor: pointer; - transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; - display: flex; - flex-direction: row; - align-items: center; - padding: 0.6rem 0.75rem; - gap: 1rem; -} - -.artist-card:hover { - background: var(--glass); -} - -.artist-card.current-artist { - background: rgba(0, 224, 255, 0.1); - border-color: rgba(0, 224, 255, 0.2); -} - -.artist-thumb-wrapper { - background-color: var(--primary); - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - width: 40px; - height: 40px; - flex-shrink: 0; - border-radius: 50%; - border: 1px solid var(--glass-border); -} - -.artist-thumb { - width: 100%; - height: 100%; - object-fit: cover; -} - -.artist-thumb-placeholder { - font-size: 1.5rem; - color: var(--text-secondary); -} - -.artist-card-title { - padding: 0; - font-size: 0.9rem; - font-weight: 500; - color: var(--text-secondary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex-grow: 1; - transition: color 0.2s ease-in-out; -} - -.artist-card:hover .artist-card-title, -.artist-card.current-artist .artist-card-title { - color: var(--text-primary); -} - -.pagination-controls { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem 1rem; - border-top: 1px solid var(--glass-border); - flex-shrink: 0; -} - -.pagination-controls #artistCounter { - font-size: 0.85rem; - color: var(--text-secondary); - font-weight: 500; -} - -.btn-icon-sm { - background: var(--glass); - border: 1px solid var(--glass-border); - color: var(--text-secondary); - width: 32px; - height: 32px; - border-radius: 50%; - display: inline-flex; - align-items: center; - justify-content: center; - transition: var(--transition); -} - -.btn-icon-sm:hover { - background-color: var(--accent); - color: var(--primary); - border-color: var(--accent); -} - -.custom-select { - position: relative; - width: 100%; - margin-bottom: 1rem; -} - -.select-selected { - background-color: var(--glass); - color: var(--text-primary); - padding: 0.7rem 2.5rem 0.7rem 1.3rem; - border: 1px solid var(--glass-border); - border-radius: 50px; - cursor: pointer; - user-select: none; - display: flex; - justify-content: space-between; - align-items: center; - transition: var(--transition); -} -.select-selected:hover { - border-color: var(--accent); -} - -.select-items { - position: absolute; - background-color: var(--primary); - top: 105%; - left: 0; - right: 0; - z-index: 99; - border-radius: var(--border-radius-md); - border: 1px solid var(--glass-border); - overflow-y: auto; - box-shadow: 0 8px 20px rgba(0,0,0,0.3); - max-height: 250px; -} -.select-hide { display: none; } -.select-option { - color: var(--text-secondary); - padding: 0.8rem 1.3rem; - cursor: pointer; - user-select: none; -} -.select-option:hover { - background-color: var(--glass); - color: var(--text-primary); -} - -.artist-grid::-webkit-scrollbar, -.select-items::-webkit-scrollbar, -.song-list::-webkit-scrollbar { - width: 8px; -} -.artist-grid::-webkit-scrollbar-thumb, -.select-items::-webkit-scrollbar-thumb, -.song-list::-webkit-scrollbar-thumb { - background-color: var(--accent); - border-radius: 4px; -} -.artist-grid::-webkit-scrollbar-track, -.select-items::-webkit-scrollbar-track, -.song-list::-webkit-scrollbar-track { - background: transparent; -} - - -.song-list-controls { - display: flex; - align-items: center; - gap: 1rem; - padding: 0.75rem 1rem; -} -.back-btn-icon { - flex-shrink: 0; -} -#artist-header-info { - display: flex; - align-items: center; - gap: 1rem; - overflow: hidden; - flex-grow: 1; - justify-content: center; -} -#artist-header-thumb { - width: 45px; - height: 45px; - object-fit: cover; - border-radius: 50%; - border: 2px solid var(--glass-border); -} -#artist-header-title { - font-size: 1.2rem; - font-weight: 600; - color: var(--text-primary); - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.search-wrapper-songs { - position: relative; - padding: 0 1rem 1rem; - border-bottom: 1px solid var(--glass-border); -} - -.song-list { - flex-grow: 1; - overflow-y: auto; - padding: 1rem; -} - -.album-group { - margin-bottom: 1.5rem; -} -.album-group-title { - font-size: 0.8rem; - font-weight: 600; - color: var(--accent); - text-transform: uppercase; - letter-spacing: 1px; - margin-bottom: 0.8rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid var(--glass-border); -} - -.song-item { - display: flex; - align-items: center; - padding: 0.6rem 0.5rem; - border-radius: var(--border-radius-sm); - cursor: pointer; - transition: background-color 0.2s ease; -} -.song-item:hover { - background: var(--glass); -} -.song-item.current-song { - background: var(--accent); -} -.song-item.current-song .song-number, -.song-item.current-song .item-title { - color: var(--primary) !important; -} - -.song-number { - font-size: 0.9rem; - color: var(--text-secondary); - width: 2rem; - text-align: center; - flex-shrink: 0; -} -.song-details { - flex-grow: 1; - overflow: hidden; -} -.song-details .item-title { - font-size: 0.9rem; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.song-item .play-icon { - color: var(--text-secondary); - opacity: 0; - transition: opacity 0.2s ease; -} - -@keyframes spin-song { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.song-item .loading-spinner { - width: 16px; - height: 16px; - border: 2px solid var(--text-secondary); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin-song 0.8s linear infinite; -} -.song-item:hover .play-icon, -.song-item.current-song .play-icon { - opacity: 1; -} -.song-item.current-song .play-icon { - color: var(--primary); -} -.list-item-empty { - padding: 2rem 1rem; - text-align: center; - color: var(--text-secondary); - font-style: italic; - background-color: transparent; - border: 1px dashed var(--glass-border); - border-radius: var(--border-radius-md); -} - -#side-nav-now-playing { - display: flex; - align-items: center; - gap: 1rem; - padding: 1rem; - background: var(--primary); - border-top: 1px solid var(--glass-border); - flex-shrink: 0; - cursor: pointer; -} -#side-nav-now-playing .details { - overflow: hidden; -} -#side-nav-now-playing img { - width: 45px; - height: 45px; - border-radius: var(--border-radius-sm); - flex-shrink: 0; -} -#side-nav-now-playing p { - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -#side-nav-track-title { - font-weight: 600; -} -#side-nav-track-artist { - font-size: 0.8rem; - color: var(--text-secondary); -} -#side-nav-play-pause { - margin-left: auto; - flex-shrink: 0; - font-size: 1.2rem; -} - -#miniplayer { - position: fixed; - bottom: 0; - left: 0; - width: 100%; - height: 85px; - padding: 0 1.5rem; - display: grid; - grid-template-columns: minmax(200px, 1fr) 2fr minmax(200px, 1fr); - gap: 1.5rem; - align-items: center; - z-index: 1045; - color: var(--text-primary); - transform: translateY(110%); -} - -.miniplayer-bg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(16, 17, 22, 0.8); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - border-top: 1px solid var(--glass-border); - z-index: -1; -} - -.player-left-info { - display: flex; - align-items: center; - gap: 1rem; - overflow: hidden; - min-width: 0; -} -.album-cover { - width: 55px; - height: 55px; - border-radius: var(--border-radius-sm); - flex-shrink: 0; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); - object-fit: cover; - cursor: pointer; - transition: transform 0.3s ease; -} -#trackInfo .details { - display: flex; - flex-direction: column; - justify-content: center; - overflow: hidden; - gap: 0.1rem; -} -#trackTitle, #trackArtist { - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -#trackTitle { font-weight: 600; } -#trackArtist { font-size: 0.8rem; color: var(--text-secondary); } - -.player-center-controls { - display: flex; - flex-direction: column; - gap: 0.5rem; - width: 100%; - min-width: 250px; -} -#player-controls { - display: flex; - align-items: center; - justify-content: center; - gap: 0.8rem; -} -.control-btn { - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 1rem; - cursor: pointer; - transition: all 0.2s ease; - width: 38px; - height: 38px; - border-radius: 50%; - display: inline-flex; - align-items: center; - justify-content: center; -} -.control-btn:hover { - color: var(--accent); - background: var(--glass); -} -.control-btn.active { color: var(--accent); } -.control-btn.play-pause-main { - font-size: 1.5rem; - width: 48px; - height: 48px; - background: var(--accent); - color: var(--primary); -} -.control-btn.play-pause-main:hover { - transform: scale(1.1); - box-shadow: 0 0 15px rgba(0, 224, 255, 0.4); -} - -#closeMiniplayerBtn { - color: var(--text-secondary); -} - -#closeMiniplayerBtn:hover { - color: var(--accent); -} - -.fab-btn { - position: relative; - width: 60px; - height: 60px; - border-radius: 50%; - background-color: var(--accent); - color: var(--primary); - border: none; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - display: flex; - align-items: center; - justify-content: center; - font-size: 1.5rem; - cursor: pointer; - transition: transform 0.3s ease, box-shadow 0.3s ease; - z-index: 1030; -} - -.fab-btn:hover { - transform: scale(1.1); - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); -} - -.time-and-progress { - display: flex; - align-items: center; - width: 100%; - gap: 1rem; -} -.time-label { font-size: 0.75rem; color: var(--text-secondary); } - -#progressBarContainer { - flex-grow: 1; - height: 5px; - background-color: rgba(255, 255, 255, 0.15); - border-radius: 2.5px; - cursor: pointer; - position: relative; - transition: height 0.2s ease; -} -#progressBarContainer:hover { - height: 8px; -} -#seek-hover-bar { - position: absolute; - height: 100%; - background-color: rgba(255, 255, 255, 0.25); - border-radius: inherit; - width: 0%; -} -#played-bar { - position: relative; - height: 100%; - background: var(--accent); - border-radius: inherit; - width: 0%; -} -#progress-handle { - position: absolute; - top: 50%; - transform: translate(-50%, -50%) scale(0); - width: 14px; - height: 14px; - background-color: var(--text-primary); - border-radius: 50%; - transition: transform 0.2s ease; -} -#progressBarContainer:hover #progress-handle { - transform: translate(-50%, -50%) scale(1); -} - -.player-right-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 0.5rem; -} -#volumeControl { position: relative; } -.volume-slider-wrapper { - position: absolute; - top: 50%; - left: calc(100% + 15px); - transform: translateY(-50%); - background: var(--secondary); - padding: 0.5rem 1rem; - border-radius: var(--border-radius-md); - border: 1px solid var(--glass-border); - box-shadow: 0 5px 15px rgba(0,0,0,0.3); - opacity: 0; - visibility: hidden; - transition: all 0.3s ease; -} -.volume-slider-wrapper.active { - opacity: 1; - visibility: visible; - left: calc(100% + 5px); -} -#volumeSlider { - -webkit-appearance: none; - appearance: none; - width: 100px; - height: 8px; - background: var(--glass); - border-radius: 4px; -} -#volumeSlider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 16px; - height: 16px; - background: var(--accent); - border-radius: 50%; - cursor: pointer; -} - -#audioPlayer { display: none; } -body.miniplayer-active { padding-bottom: 85px; } - -@media (max-width: 768px) { - body.miniplayer-active { padding-bottom: 110px; } - - body.miniplayer-active #musicPlayerContainer { - height: calc(100% - 110px); - } - - #miniplayer { - grid-template-columns: 1fr auto; - grid-template-rows: auto auto; - grid-template-areas: - "info actions" - "center center"; - height: 110px; - padding: 0.5rem 1rem; - gap: 0.5rem; - } - .player-left-info { grid-area: info; } - .player-center-controls { grid-area: center; padding: 0 1rem; } - .player-right-actions { grid-area: actions; justify-content: flex-end; } - #downloadBtn, #downloadAlbumBtn, #eqBtn, #volumeControl { display: none; } -} - -@media (max-width: 576px) { - #player-controls { gap: 1.5rem; } -} - - -/* --- photos.css --- */ -#photos-section { - padding-top: 2rem; -} - -.photos-header { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: center; - gap: 1rem; - margin-bottom: 2rem; - padding: 1.5rem; - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-lg); -} - -#photos-breadcrumb { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0.5rem; - padding: 0; - margin: 0; - list-style: none; - font-size: 0.95rem; -} - -#photos-breadcrumb .breadcrumb-item a { - color: var(--text-secondary); - text-decoration: none; - transition: var(--transition); - padding: 0.3rem 0.6rem; - border-radius: var(--border-radius-sm); -} - -#photos-breadcrumb .breadcrumb-item a:hover { - color: var(--accent); - background-color: var(--glass); -} - -#photos-breadcrumb .breadcrumb-item.active { - color: var(--text-primary); - font-weight: 600; -} - -#photos-breadcrumb .breadcrumb-divider { - color: var(--text-secondary); - opacity: 0.5; - margin: 0 0.3rem; -} - -#photos-token-select { - min-width: 250px; -} - -#photos-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 1.8rem; -} - -.photo-card, .album-card { - background: var(--card-bg); - border-radius: var(--border-radius-lg); - overflow: hidden; - position: relative; - transition: var(--transition); - box-shadow: var(--shadow); - cursor: pointer; - border: 1px solid transparent; - aspect-ratio: 1 / 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: 1rem; -} - -.photo-card:hover, .album-card:hover { - transform: translateY(-10px) scale(1.03); - box-shadow: 0 18px 45px rgba(0, 0, 0, 0.5); - border-color: rgba(0, 224, 255, 0.5); - z-index: 10; -} - -.album-card-icon { - font-size: 4rem; - color: var(--accent); - margin-bottom: 1rem; - transition: transform 0.4s ease; -} - -.album-card:hover .album-card-icon { - transform: scale(1.1); -} - -.album-card-title { - font-size: 1.1rem; - font-weight: 600; - color: var(--text-primary); -} - -.album-card-meta { - font-size: 0.85rem; - color: var(--text-secondary); - margin-top: 0.3rem; -} - -.photo-card { - padding: 0; - justify-content: flex-end; -} - -.photo-card-img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1); -} - -.photo-card:hover .photo-card-img { - transform: scale(1.08); -} - -.photo-card-caption { - position: relative; - z-index: 1; - width: 100%; - padding: 0.8rem; - background: linear-gradient(to top, rgba(10, 10, 15, 0.95) 20%, transparent 100%); - color: var(--text-primary); - font-size: 0.9rem; - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - opacity: 0; - transform: translateY(10px); - transition: var(--transition); -} - -.photo-card:hover .photo-card-caption { - opacity: 1; - transform: translateY(0); -} - -#photo-lightbox { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(10, 10, 15, 0.96); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - z-index: 2050; - display: none; - justify-content: center; - align-items: center; - opacity: 0; -} - -#photo-lightbox.active { - display: flex; -} - -.photo-lightbox-container { - position: relative; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.photo-lightbox-img { - max-width: 90vw; - max-height: 85vh; - object-fit: contain; - box-shadow: 0 10px 40px rgba(0,0,0,0.5); - border-radius: var(--border-radius-sm); -} - -.photo-lightbox-btn { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(255,255,255,0.1); - border: 1px solid var(--glass-border); - color: var(--text-primary); - width: 50px; - height: 70px; - border-radius: var(--border-radius-sm); - font-size: 1.5rem; - cursor: pointer; - transition: var(--transition); - backdrop-filter: blur(5px); - -webkit-backdrop-filter: blur(5px); -} -.photo-lightbox-btn:hover { - background: var(--accent); - color: var(--primary); - transform: translateY(-50%) scale(1.05); -} - -#photo-lightbox-prev { - left: 2vw; -} -#photo-lightbox-next { - right: 2vw; -} - -#photo-lightbox-close { - position: absolute; - top: 2rem; - right: 2rem; - width: 42px; - height: 42px; - top: 20px; - right: 20px; -} - -.photo-lightbox-caption { - position: absolute; - bottom: 2vh; - left: 50%; - transform: translateX(-50%); - background: rgba(10,10,15,0.8); - color: var(--text-primary); - padding: 0.8rem 1.5rem; - border-radius: var(--border-radius-md); - font-size: 1rem; - text-align: center; - max-width: 70vw; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - - -/* --- activity-viewer.css --- */ -#activityViewerModal .modal-body { - max-height: 70vh; - overflow-y: auto; -} - -.session-card { - display: flex; - gap: 1.5rem; - padding: 1.2rem; - background-color: var(--glass); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-md); - margin-bottom: 1rem; - transition: var(--transition); -} - -.session-card:hover { - background-color: rgba(255, 255, 255, 0.08); - border-color: var(--accent); -} - -.session-poster { - width: 80px; - height: 120px; - object-fit: cover; - border-radius: var(--border-radius-sm); - flex-shrink: 0; -} - -.session-info { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: 1rem; -} - -.session-details p { - margin: 0 0 0.5rem 0; - font-size: 0.9rem; - color: var(--text-secondary); -} - -.session-details strong { - color: var(--text-primary); - font-weight: 600; -} - -.session-identifier { - margin-top: auto; -} - -.session-identifier label { - font-size: 0.8rem; - font-weight: 500; - color: var(--text-secondary); - margin-bottom: 0.25rem; -} - -.session-identifier .input-group .form-control { - background-color: var(--primary) !important; - font-family: monospace; - font-size: 0.85rem; -} - -#activity-results .empty-state { - padding: 2rem; - margin-top: 1rem; -} - - -/* --- m3u-generator.css --- */ -.m3u-animated-item { - opacity: 0; - transform: translateY(20px); -} - -#m3u-generator-section { - max-width: 1400px; - margin: 0 auto; -} - -.m3u-container { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 2.5rem; - margin-top: 2rem; -} - -.m3u-config-panel { - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: var(--border-radius-lg); - padding: 2rem; - box-shadow: var(--shadow); -} - -.m3u-step { - margin-bottom: 2.5rem; -} - -.m3u-step:last-child { - margin-bottom: 0; -} - -.m3u-step-header { - display: flex; - align-items: center; - margin-bottom: 1.5rem; -} - -.m3u-step-number { - font-size: 1.2rem; - font-weight: 700; - color: var(--primary); - background: var(--gradient); - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - margin-right: 1rem; -} - -.m3u-step-title { - font-family: 'Orbitron', sans-serif; - font-size: 1.4rem; - font-weight: 600; - color: var(--text-primary); -} - -#m3u-server-select { - width: 100%; -} - -#m3u-libraries-container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); - gap: 1rem; - max-height: 400px; - overflow-y: auto; - padding-right: 1rem; -} - -#m3u-libraries-container::-webkit-scrollbar { - width: 8px; -} - -#m3u-libraries-container::-webkit-scrollbar-track { - background: transparent; -} - -#m3u-libraries-container::-webkit-scrollbar-thumb { - background-color: var(--glass-border); - border-radius: 8px; -} - -#m3u-libraries-container .form-check { - background: rgba(0, 0, 0, 0.15); - padding: 0.8rem 1rem; - border-radius: var(--border-radius-md); - transition: background-color 0.2s; - display: flex; - align-items: center; - cursor: pointer; -} - -#m3u-libraries-container .form-check:hover { - background: rgba(0, 0, 0, 0.25); -} - -#m3u-libraries-container .form-check-input { - width: 1.1em; - height: 1.1em; - margin-right: 0.8rem; - background-color: var(--secondary); - border: 1px solid var(--glass-border); - flex-shrink: 0; -} - -#m3u-libraries-container .form-check-label { - font-weight: 500; - cursor: pointer; - color: var(--text-secondary); - font-size: 0.9rem; - line-height: 1.2; -} - -#m3u-libraries-container .form-check-input:checked { - background-color: var(--accent); - border-color: var(--accent); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%230a0a0f' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); -} - -#m3u-libraries-container .form-check-label i { - color: var(--text-secondary); - width: 20px; - text-align: center; - margin-right: 0.5rem; - transition: color 0.2s; -} - -#m3u-libraries-container .form-check-input:checked + .form-check-label i { - color: var(--accent); -} - -.m3u-info-panel { - background: var(--card-bg); - border-radius: var(--border-radius-lg); - padding: 2rem; - text-align: center; - display: flex; - flex-direction: column; - justify-content: space-between; -} - -.m3u-info-title { - font-family: 'Orbitron', sans-serif; - font-size: 1.4rem; - margin-bottom: 1.5rem; - color: white; -} - -.m3u-instructions { - text-align: left; - margin-left: 1.5rem; - color: var(--text-secondary); - font-size: 0.95rem; - margin-bottom: 2rem; -} - -.m3u-instructions li { - margin-bottom: 1rem; -} - -#download-m3u-btn { - width: 100%; - padding: 1rem; - font-size: 1rem; - border-radius: var(--border-radius-md); - transition: all 0.3s ease; -} - -#download-m3u-btn:hover { - transform: translateY(-3px); - box-shadow: var(--shadow-lg); -} - -#download-m3u-btn span { - margin-left: 0.5rem; -} - -#m3u-libraries-loader { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 150px; - color: var(--text-secondary); -} - -#m3u-libraries-loader .spinner-border { - width: 3rem; - height: 3rem; - margin-bottom: 1rem; -} - -.light-theme .m3u-config-panel { - background: var(--secondary); -} - -.light-theme #m3u-libraries-container .form-check { - background: rgba(0, 0, 0, 0.03); -} - -.light-theme #m3u-libraries-container .form-check:hover { - background: rgba(0, 0, 0, 0.06); -} - -.light-theme #m3u-libraries-container .form-check-input { - background-color: #e9ecef; - border-color: #ced4da; -} - -.light-theme #m3u-libraries-container .form-check-input:checked { - background-color: var(--accent-dark); - border-color: var(--accent-dark); -} - -.light-theme #m3u-libraries-container .form-check-label i { - color: var(--text-secondary); -} - -.light-theme #m3u-libraries-container .form-check-input:checked + .form-check-label i { - color: var(--accent-dark); -} - -@media (max-width: 992px) { - .m3u-container { - grid-template-columns: 1fr; - } -} - - - -/* --- equalizer.css --- */ -#equalizer-panel { - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%) translateY(100%); - width: 520px; - background-color: var(--secondary); - border-top: 1px solid var(--glass-border); - border-left: 1px solid var(--glass-border); - border-right: 1px solid var(--glass-border); - border-radius: 12px 12px 0 0; - box-shadow: 0 -5px 25px rgba(0,0,0,0.3); - z-index: 100; - overflow: hidden; - display: none; - font-family: 'Montserrat', sans-serif; -} - -.equalizer-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 15px; - background-color: var(--primary); - color: var(--text-primary); - border-bottom: 1px solid var(--glass-border); -} - -.equalizer-header h5 { - margin: 0; - font-weight: 600; - font-family: 'Orbitron', sans-serif; -} - -.close-btn { - background: none; - border: none; - color: var(--text-secondary); - font-size: 1.2rem; - cursor: pointer; - transition: color 0.2s; -} -.close-btn:hover { color: var(--text-primary); } - -.equalizer-top-bar { - display: flex; - justify-content: space-around; - align-items: center; - padding: 15px; - border-bottom: 1px solid var(--glass-border); - gap: 20px; -} - -.control-group { - display: flex; - align-items: center; - gap: 10px; -} - -.control-group label { - font-size: 0.8rem; - color: var(--text-secondary); - font-weight: 500; - white-space: nowrap; -} - -.control-group.preamp { - flex-grow: 1; -} - -.equalizer-bands-grid { - display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 20px 15px; - padding: 20px 15px; -} - -.band { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.band label { - font-size: 0.75rem; - color: var(--text-secondary); - font-weight: 500; -} - -.eq-slider { - -webkit-appearance: none; - appearance: none; - width: 100%; - height: 6px; - background: var(--glass); - outline: none; - border-radius: 3px; - cursor: pointer; - transition: opacity 0.2s; -} - -.eq-slider::-webkit-slider-runnable-track { - width: 100%; - height: 6px; - cursor: pointer; - background: var(--glass); - border-radius: 3px; -} - -.eq-slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 18px; - height: 18px; - background: var(--accent); - border: 3px solid var(--secondary); - border-radius: 50%; - cursor: pointer; - margin-top: -6px; - transition: background 0.2s, box-shadow 0.2s; - box-shadow: 0 0 5px rgba(0, 224, 255, 0.4); -} - -.eq-slider:hover::-webkit-slider-thumb { - background: var(--accent); - box-shadow: 0 0 10px rgba(0, 224, 255, 0.6); -} - -.eq-slider::-moz-range-track { - width: 100%; - height: 6px; - cursor: pointer; - background: var(--glass); - border-radius: 3px; -} - -.eq-slider::-moz-range-thumb { - width: 18px; - height: 18px; - background: var(--accent); - border: 3px solid var(--secondary); - border-radius: 50%; - cursor: pointer; - box-shadow: 0 0 5px rgba(0, 224, 255, 0.4); -} - -.slider-value { - font-size: 0.7rem; - color: var(--text-secondary); - background: var(--primary); - padding: 2px 5px; - border-radius: 3px; - min-width: 35px; - text-align: center; -} - -.custom-select-sm { - background-color: var(--primary); - color: var(--text-primary); - border: 1px solid var(--glass-border); - border-radius: 4px; - padding: 5px 8px; - font-size: 0.8rem; -} - -.visualizer-container { - height: 80px; - background-color: var(--primary); - border-top: 1px solid var(--glass-border); - padding: 0; - margin: 0; - overflow: hidden; -} - -#visualizer-canvas { - width: 100%; - height: 100%; - display: block; -} - - -/* --- providers.css --- */ -.providers-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 30px; - padding: 30px; - perspective: 1000px; -} - -.provider-card { - position: relative; - width: 140px; - height: 140px; - background-color: transparent; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: transform 0.4s ease, box-shadow 0.4s ease; - box-shadow: 0 0 0px rgba(0, 0, 0, 0.3); - overflow: hidden; - border: none; -} - -.provider-card:hover { - transform: scale(1.1) translateY(-5px); - box-shadow: 0 0 25px rgba(0, 224, 255, 0.7); -} - -.provider-logo { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 0; - transition: filter 0.3s ease; -} - -.provider-card:hover .provider-logo { - filter: brightness(1.1); -} - -.provider-name { - display: none; -} - -.provider-card.available { - box-shadow: 0 0 15px #00e0ff; -} - -.provider-card.available::after { - content: '00c'; - font-family: 'Font Awesome 5 Free'; - font-weight: 900; - position: absolute; - top: -5px; - right: -5px; - background-color: #00e0ff; - color: #121212; - border-radius: 50%; - width: 25px; - height: 25px; - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - box-shadow: 0 0 10px rgba(0, 224, 255, 0.5); - z-index: 11; -} - -.provider-tooltip { - position: absolute; - bottom: -35px; - background-color: #00e0ff; - color: #121212; - padding: 5px 12px; - border-radius: 15px; - font-size: 14px; - font-weight: 600; - white-space: nowrap; - opacity: 0; - transform: translateY(10px); - transition: opacity 0.3s ease, transform 0.3s ease; - pointer-events: none; - z-index: 10; -} - -.provider-card:hover .provider-tooltip { - opacity: 1; - transform: translateY(0); -} - -/* --- chat.css --- */ -#fab-container { - position: fixed; - bottom: 2rem; - right: 2rem; - z-index: 1050; - display: flex; - flex-direction: row-reverse; - gap: 1rem; - align-items: flex-end; - transition: bottom 0.5s cubic-bezier(0.4, 0, 0.2, 1); -} - -body.miniplayer-active #fab-container { - bottom: calc(85px + 2rem); -} - -.chat-fab, .fab-btn { - position: relative; - right: auto; - bottom: auto; -} - -.chat-fab { - width: 60px; - height: 60px; - background: var(--gradient); - color: var(--primary); - border-radius: 50%; - border: none; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.8rem; - cursor: pointer; - box-shadow: 0 5px 20px rgba(0, 224, 255, 0.3); - transition: var(--transition); -} -.chat-fab:hover { - transform: scale(1.1); - box-shadow: 0 8px 25px rgba(0, 224, 255, 0.4); -} - -.chat-window { - position: fixed; - bottom: 95px; - right: 2rem; - width: 400px; - height: 520px; - max-width: 90vw; - max-height: 70vh; - background-color: var(--primary); - border-radius: var(--border-radius-lg); - box-shadow: var(--shadow); - border: 1px solid var(--glass-border); - display: flex; - flex-direction: column; - overflow: hidden; - z-index: 1051; - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - background: rgba(10, 10, 15, 0.8); - cursor: default; -} -.light-theme .chat-window { - background: rgba(244, 247, 250, 0.8); -} - -.chat-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.8rem 1.2rem; - background-color: rgba(255,255,255,0.05); - border-bottom: 1px solid var(--glass-border); - cursor: move; - flex-shrink: 0; -} -.chat-title { - font-family: 'Orbitron', sans-serif; - color: var(--text-primary); - font-size: 1.1rem; - margin: 0; -} -.chat-close-btn { - background: none; - border: none; - color: var(--text-secondary); - font-size: 1.2rem; - cursor: pointer; - transition: var(--transition); -} -.chat-close-btn:hover { - color: var(--accent); - transform: rotate(90deg); -} - -.chat-messages { - flex-grow: 1; - padding: 1rem; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 1.5rem; /* Aumentamos el espacio entre mensajes */ -} - -.chat-messages::-webkit-scrollbar { - width: 8px; -} - -.chat-messages::-webkit-scrollbar-track { - background: transparent; -} - -.chat-messages::-webkit-scrollbar-thumb { - background-color: var(--glass-border); - border-radius: 4px; -} - -.message-wrapper { - display: flex; - align-items: flex-end; - gap: 0.75rem; - max-width: 90%; - animation: slide-in-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; -} - -.assistant-wrapper { - align-self: flex-start; -} - -.user-wrapper { - align-self: flex-end; - flex-direction: row-reverse; -} - -.avatar { - width: 36px; - height: 36px; - border-radius: 50%; - background: var(--gradient); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - box-shadow: 0 0 10px rgba(0, 224, 255, 0.3); -} - -.avatar svg { - width: 20px; - height: 20px; - color: var(--primary); -} - -.message { - padding: 0.8rem 1.2rem; - border-radius: var(--border-radius-md); - line-height: 1.6; - word-wrap: break-word; -} -.message p { - margin: 0; - white-space: pre-wrap; - word-break: break-word; -} - -.user-message { - background: var(--gradient); - color: var(--primary); - border-bottom-right-radius: 4px; /* Cola de la burbuja */ -} - -.assistant-message { - background-color: var(--secondary); - color: var(--text-primary); - border-bottom-left-radius: 4px; /* Cola de la burbuja */ -} - -.chat-item-actions { - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid var(--glass-border); -} - -.chat-action-title { - display: block; - font-weight: 600; - color: var(--accent); - margin-bottom: 0.75rem; - font-size: 0.9rem; -} - -.chat-action-buttons { - display: flex; - flex-wrap: wrap; - gap: .5rem; -} - -.chat-action-buttons button { - background-color: var(--glass); - border: 1px solid var(--glass-border); - color: var(--text-secondary); - padding: .4rem .8rem; - border-radius: 20px; - font-size: .8rem; - cursor: pointer; - transition: var(--transition); -} - -.chat-action-buttons button:hover { - background-color: var(--accent); - color: var(--primary); - border-color: var(--accent); - transform: translateY(-2px); -} - -.chat-download-all { - margin-top: 1rem; -} - -/* MEJORADO: Indicador de escritura */ -.typing-indicator-bubble { - display: flex; - gap: 6px; - align-items: center; - padding: 14px 16px; -} -.typing-indicator-bubble span { - width: 8px; - height: 8px; - background-color: var(--text-secondary); - border-radius: 50%; - animation: typing-pulse 1.4s infinite; -} -.typing-indicator-bubble span:nth-child(2) { animation-delay: 0.2s; } -.typing-indicator-bubble span:nth-child(3) { animation-delay: 0.4s; } - -@keyframes typing-pulse { - 0%, 100% { opacity: 0.2; transform: scale(0.8); } - 50% { opacity: 1; transform: scale(1); } -} - -#chat-input-form { - display: flex; - padding: 0.8rem; - border-top: 1px solid var(--glass-border); - gap: 0.8rem; - background: linear-gradient(to top, rgba(16, 17, 22, 0.8), rgba(16, 17, 22, 0.5)); - align-items: flex-end; -} - -.chat-input { - flex-grow: 1; - background: var(--glass); - border: 1px solid var(--glass-border); - border-radius: 20px; - padding: 0.7rem 1.2rem; - color: var(--text-primary); - resize: none; - font-family: 'Montserrat', sans-serif; - font-size: 0.95rem; - transition: all 0.3s ease; -} - -.chat-input:focus { - outline: none; - border-color: var(--accent); - box-shadow: 0 0 15px rgba(0, 224, 255, 0.2); - background: var(--secondary); -} - -.chat-send-btn { - flex-shrink: 0; - background: var(--accent); - color: var(--primary); - border: none; - border-radius: 12px; /* Más tecnológico que un círculo */ - width: 42px; - height: 42px; - font-size: 1.1rem; - cursor: pointer; - transition: var(--transition); - display: flex; - align-items: center; - justify-content: center; -} -.chat-send-btn svg { - transition: transform 0.3s ease; -} -.chat-send-btn:hover { - background: var(--accent-dark); - transform: scale(1.1); - box-shadow: 0 0 10px rgba(0, 224, 255, 0.4); -} -.chat-send-btn:hover svg { - transform: translateX(2px) rotate(-15deg); -} -.chat-send-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; - box-shadow: none; -} -.chat-send-btn:disabled:hover svg { - transform: none; -} - -/* NUEVO: Animación para los mensajes */ -@keyframes slide-in-bottom { - 0% { - transform: translateY(20px); - opacity: 0; - } - 100% { - transform: translateY(0); - opacity: 1; - } -} - -@media (max-width: 768px) { - body.miniplayer-active #fab-container { - bottom: calc(110px + 1rem); - } -} - -@media (max-width: 480px) { - .chat-window { - width: 100%; - height: 100%; - bottom: 0; - right: 0; - border-radius: 0; - max-height: none; - } -} \ No newline at end of file +@import url('base.css'); +@import url('header.css'); +@import url('sidebar.css'); +@import url('hero.css'); +@import url('content.css'); +@import url('details-view.css'); +@import url('stats.css'); +@import url('history.css'); +@import url('providers.css'); +@import url('photos.css'); +@import url('m3u-generator.css'); +@import url('music-player.css'); +@import url('music.css'); +@import url('chat.css'); +@import url('modals.css'); +@import url('footer.css'); +@import url('custom-filters.css'); +@import url('settings.css'); \ No newline at end of file diff --git a/css/modals.css b/css/modals.css new file mode 100644 index 0000000..991340b --- /dev/null +++ b/css/modals.css @@ -0,0 +1,452 @@ +.lightbox { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(10, 10, 15, 0.96); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + z-index: 2000; + justify-content: center; + align-items: center; + opacity: 0; + transition: opacity 0.4s ease; +} + +.lightbox.active { + display: flex; + opacity: 1; +} + +.lightbox-content { + position: relative; + width: 90%; + max-width: 960px; + background: var(--secondary); + padding: 1rem; + border-radius: var(--border-radius-lg); + box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5); + border: 1px solid var(--glass-border); + transform: scale(0.95); + transition: transform 0.4s ease; +} + +.lightbox.active .lightbox-content { + transform: scale(1); +} + +.lightbox-close { + position: absolute; + top: -15px; + right: -15px; + width: 38px; + height: 38px; + display: flex; + justify-content: center; + align-items: center; + background: var(--accent); + color: var(--primary); + border: none; + border-radius: 50%; + font-size: 1.1rem; + cursor: pointer; + transition: var(--transition); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); +} + +.lightbox-close:hover { + transform: scale(1.1) rotate(90deg); + background: var(--accent-dark); + box-shadow: 0 6px 15px rgba(0, 224, 255, 0.4); +} + +.video-container { + position: relative; + padding-bottom: 56.25%; + height: 0; + overflow: hidden; + border-radius: var(--border-radius-md); +} + +.video-container iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.notification { + position: relative; + min-width: 280px; + max-width: 350px; + margin-bottom: 1rem; + padding: 1.1rem 1.5rem; + border-radius: var(--border-radius-md); + background: var(--secondary); + color: var(--text-primary); + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); + border: 1px solid var(--glass-border); + border-left-width: 5px; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + transform: translateX(120%); + opacity: 0; + transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s ease; + overflow: hidden; +} + +.notification.show { + transform: translateX(0); + opacity: 1; +} + +.notification-content { + display: flex; + align-items: center; + gap: 1rem; +} + +.notification i.fas { + font-size: 1.2rem; + line-height: 1; +} + +.notification span { + flex: 1; +} + +.notification.success { + border-left-color: var(--success); +} + +.notification.error { + border-left-color: var(--danger); +} + +.notification.info { + border-left-color: var(--info); +} + +.notification.warning { + border-left-color: var(--warning); +} + +.light-theme .notification { + color: var(--text-primary); +} + +.modal-backdrop { + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(3px); + -webkit-backdrop-filter: blur(3px); +} + +.modal-content { + background-color: var(--secondary); + color: var(--text-primary); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow); + overflow: hidden; +} + +.modal-header { + border-bottom: 1px solid var(--glass-border); + background: rgba(255, 255, 255, 0.03); +} + +.modal-title { + color: var(--accent); + font-family: 'Orbitron', sans-serif; + font-size: 1.3rem; +} + +.modal-footer { + border-top: 1px solid var(--glass-border); + background: rgba(10, 10, 15, 0.5); + padding: 1rem; +} + +.modal .btn-close { + filter: invert(1) grayscale(100%) brightness(150%) opacity(0.8); + transition: transform 0.3s ease; +} + +.modal .btn-close:hover { + filter: invert(1) grayscale(100%) brightness(200%) opacity(1); + transform: rotate(90deg); +} + +.light-theme .modal-content { + background-color: #f9f9f9; /* Lighter background for light theme */ +} +.light-theme .modal-header, .light-theme .modal-footer { + background-color: var(--primary); +} + +#settingsModal .nav-tabs { + border-bottom: 1px solid var(--glass-border); + padding: 0.5rem 1rem 0; + background-color: rgba(20, 21, 27, 0.9); /* Slightly lighter background */ + color: var(--text-primary); /* Use primary text color for better contrast */ +} + +.light-theme #settingsModal .nav-tabs { + background-color: var(--secondary); + color: var(--text-primary); +} + +#settingsModal .nav-tabs .nav-link { + border: none; + color: var(--text-secondary); + border-bottom: 3px solid transparent; + transition: var(--transition); + padding: 0.8rem 1.2rem; + font-weight: 500; +} + +#settingsModal .nav-tabs .nav-link:hover { + color: var(--text-primary); + border-bottom-color: var(--glass-border); +} + +#settingsModal .nav-tabs .nav-link.active { + color: var(--accent); + background-color: transparent; + border-bottom-color: var(--accent); + font-weight: 600; +} + +#settingsModal .modal-body { + max-height: 70vh; + overflow-y: auto; + padding: 1rem; +} + +#settingsModal .tab-content label { + font-weight: 500; +} + +#settingsModal .tab-content p, +#settingsModal .tab-content .text-muted { + color: var(--text-secondary); +} + +#settingsModal .tab-content h5 { + color: var(--text-primary); +} + +#settingsModal .tab-content input[type="checkbox"] { + margin-right: 0.6rem; + transform: scale(1.1); + accent-color: var(--accent); +} + +#editor { + height: 300px; + width: 100%; + border-radius: var(--border-radius-md); + border: 1px solid var(--glass-border); + font-family: monospace; +} + +#photo-lightbox { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(10, 10, 15, 0.96); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + z-index: 2050; + display: none; + justify-content: center; + align-items: center; + opacity: 0; +} + +#photo-lightbox.active { + display: flex; +} + +.photo-lightbox-container { + position: relative; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.photo-lightbox-img { + max-width: 90vw; + max-height: 85vh; + object-fit: contain; + box-shadow: 0 10px 40px rgba(0,0,0,0.5); + border-radius: var(--border-radius-sm); +} + +.photo-lightbox-btn { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(255,255,255,0.1); + border: 1px solid var(--glass-border); + color: var(--text-primary); + width: 50px; + height: 70px; + border-radius: var(--border-radius-sm); + font-size: 1.5rem; + cursor: pointer; + transition: var(--transition); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); +} +.photo-lightbox-btn:hover { + background: var(--accent); + color: var(--primary); + transform: translateY(-50%) scale(1.05); +} + +#photo-lightbox-prev { + left: 2vw; +} +#photo-lightbox-next { + right: 2vw; +} + +#photo-lightbox-close { + position: absolute; + top: 2rem; + right: 2rem; + width: 42px; + height: 42px; + top: 20px; + right: 20px; +} + +.photo-lightbox-caption { + position: absolute; + bottom: 2vh; + left: 50%; + transform: translateX(-50%); + background: rgba(10,10,15,0.8); + color: var(--text-primary); + padding: 0.8rem 1.5rem; + border-radius: var(--border-radius-md); + font-size: 1rem; + text-align: center; + max-width: 70vw; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#activityViewerModal .modal-body { + max-height: 70vh; + overflow-y: auto; +} + +.session-card { + display: flex; + gap: 1.5rem; + padding: 1.2rem; + background-color: var(--glass); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-md); + margin-bottom: 1rem; + transition: var(--transition); +} + +.session-card:hover { + background-color: rgba(255, 255, 255, 0.08); + border-color: var(--accent); +} + +.session-poster { + width: 80px; + height: 120px; + object-fit: cover; + border-radius: var(--border-radius-sm); + flex-shrink: 0; +} + +.session-info { + display: flex; + flex-direction: column; + flex-grow: 1; + gap: 1rem; +} + +.session-details p { + margin: 0 0 0.5rem 0; + font-size: 0.9rem; + color: var(--text-secondary); +} + +.session-details strong { + color: var(--text-primary); + font-weight: 600; +} + +.session-identifier { + margin-top: auto; +} + +.session-identifier label { + font-size: 0.8rem; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 0.25rem; +} + +#settingsModal .modal-body::-webkit-scrollbar { + width: 8px; +} + +#settingsModal .modal-body::-webkit-scrollbar-track { + background: var(--secondary); +} + +#settingsModal .modal-body::-webkit-scrollbar-thumb { + background-color: var(--accent-dark); + border-radius: 4px; +} + +#settingsModal .modal-body::-webkit-scrollbar-thumb:hover { + background-color: var(--accent); +} + +.light-theme #settingsModal .modal-body::-webkit-scrollbar-track { + background: var(--primary); +} + +.light-theme #settingsModal .modal-body::-webkit-scrollbar-thumb { + background-color: #bdc3c7; +} + +.light-theme #settingsModal .modal-body::-webkit-scrollbar-thumb:hover { + background-color: #a3aab1; +} + +.session-identifier .input-group .form-control { + background-color: var(--primary) !important; + font-family: monospace; + font-size: 0.85rem; +} + +#activity-results .empty-state { + padding: 2rem; + margin-top: 1rem; +} + +#settingsModal .modal-body { + max-height: 70vh; + overflow-y: auto; + padding: 1rem; +} \ No newline at end of file diff --git a/css/music-player.css b/css/music-player.css new file mode 100644 index 0000000..12522da --- /dev/null +++ b/css/music-player.css @@ -0,0 +1,908 @@ +#musicPlayerContainer { + position: fixed; + top: 0; + left: 0; + width: 320px; + height: 100%; + background: var(--secondary); + box-shadow: 5px 0 35px rgba(0, 0, 0, 0.3); + display: flex; + flex-direction: column; + z-index: 1040; + border-right: 1px solid var(--glass-border); + transform: translateX(-100%); + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s ease; +} + +body.miniplayer-active #musicPlayerContainer { + height: calc(100% - 85px); +} + +.sidenav { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + background: transparent; + position: relative; +} + +.sidenav-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.7rem 1.2rem; + background: var(--primary); + color: var(--text-primary); + border-bottom: 1px solid var(--glass-border); + flex-shrink: 0; + position: relative; + z-index: 3; +} + +.sidenav-header h4 { + margin: 0; + font-family: 'Orbitron', sans-serif; + font-size: 1.4rem; + color: var(--accent); + line-height: 1; +} + +.sidenav-header button { + background: none; + border: none; + color: var(--text-secondary); + font-size: 1.4rem; + cursor: pointer; + padding: 0.5rem; + line-height: 1; + transition: color 0.3s ease, transform 0.3s ease; +} + +.sidenav-header button:hover { + color: var(--accent); + transform: rotate(90deg); +} + +.music-panel { + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + min-height: 0; + position: absolute; + top: 57px; + left: 0; + width: 100%; + height: calc(100% - 57px); + background-color: var(--secondary); +} + +#artistListContainer { + z-index: 2; + transform: translateX(0); +} +#songListContainer { + z-index: 3; + transform: translateX(100%); + opacity: 0; + visibility: hidden; +} + +.panel-controls { + padding: 1rem; + border-bottom: 1px solid var(--glass-border); + flex-shrink: 0; +} + +.search-wrapper { + position: relative; +} +.search-wrapper i { + position: absolute; + left: 1.1rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-secondary); + font-size: 0.9em; +} +.search-wrapper input, +.search-wrapper-songs input { + width: 100%; + padding: 0.7rem 1.3rem 0.7rem 2.5rem; + font-size: 0.9rem; + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: 50px; + color: var(--text-primary); + transition: var(--transition); +} +.search-wrapper input:focus, +.search-wrapper-songs input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(0, 224, 255, 0.2); + background: rgba(0, 224, 255, 0.08); +} + +.artist-grid { + padding: 0.5rem; + display: grid; + grid-template-columns: 1fr; + gap: 0.25rem; + overflow-y: auto; + flex-grow: 1; +} + +.artist-card { + background: transparent; + border-radius: var(--border-radius-sm); + border: 1px solid transparent; + overflow: hidden; + cursor: pointer; + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; + display: flex; + flex-direction: row; + align-items: center; + padding: 0.6rem 0.75rem; + gap: 1rem; +} + +.artist-card:hover { + background: var(--glass); +} + +.artist-card.current-artist { + background: rgba(0, 224, 255, 0.1); + border-color: rgba(0, 224, 255, 0.2); +} + +.artist-thumb-wrapper { + background-color: var(--primary); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + width: 40px; + height: 40px; + flex-shrink: 0; + border-radius: 50%; + border: 1px solid var(--glass-border); +} + +.artist-thumb { + width: 100%; + height: 100%; + object-fit: cover; +} + +.artist-thumb-placeholder { + font-size: 1.5rem; + color: var(--text-secondary); +} + +.artist-card-title { + padding: 0; + font-size: 0.9rem; + font-weight: 500; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-grow: 1; + transition: color 0.2s ease-in-out; +} + +.artist-card:hover .artist-card-title, +.artist-card.current-artist .artist-card-title { + color: var(--text-primary); +} + +.pagination-controls { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + border-top: 1px solid var(--glass-border); + flex-shrink: 0; +} + +.pagination-controls #artistCounter { + font-size: 0.85rem; + color: var(--text-secondary); + font-weight: 500; +} + +.btn-icon-sm { + background: var(--glass); + border: 1px solid var(--glass-border); + color: var(--text-secondary); + width: 32px; + height: 32px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + transition: var(--transition); +} + +.btn-icon-sm:hover { + background-color: var(--accent); + color: var(--primary); + border-color: var(--accent); +} + +.custom-select { + position: relative; + width: 100%; + margin-bottom: 1rem; +} + +.select-selected { + background-color: var(--glass); + color: var(--text-primary); + padding: 0.7rem 2.5rem 0.7rem 1.3rem; + border: 1px solid var(--glass-border); + border-radius: 50px; + cursor: pointer; + user-select: none; + display: flex; + justify-content: space-between; + align-items: center; + transition: var(--transition); +} +.select-selected:hover { + border-color: var(--accent); +} + +.select-items { + position: absolute; + background-color: var(--primary); + top: 105%; + left: 0; + right: 0; + z-index: 99; + border-radius: var(--border-radius-md); + border: 1px solid var(--glass-border); + overflow-y: auto; + box-shadow: 0 8px 20px rgba(0,0,0,0.3); + max-height: 250px; +} +.select-hide { display: none; } +.select-option { + color: var(--text-secondary); + padding: 0.8rem 1.3rem; + cursor: pointer; + user-select: none; +} +.select-option:hover { + background-color: var(--glass); + color: var(--text-primary); +} + +.artist-grid::-webkit-scrollbar, +.select-items::-webkit-scrollbar, +.song-list::-webkit-scrollbar { + width: 8px; +} +.artist-grid::-webkit-scrollbar-thumb, +.select-items::-webkit-scrollbar-thumb, +.song-list::-webkit-scrollbar-thumb { + background-color: var(--accent); + border-radius: 4px; +} +.artist-grid::-webkit-scrollbar-track, +.select-items::-webkit-scrollbar-track, +.song-list::-webkit-scrollbar-track { + background: transparent; +} + +.song-list-controls { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.75rem 1rem; +} +.back-btn-icon { + flex-shrink: 0; +} +#artist-header-info { + display: flex; + align-items: center; + gap: 1rem; + overflow: hidden; + flex-grow: 1; + justify-content: center; +} +#artist-header-thumb { + width: 45px; + height: 45px; + object-fit: cover; + border-radius: 50%; + border: 2px solid var(--glass-border); +} +#artist-header-title { + font-size: 1.2rem; + font-weight: 600; + color: var(--text-primary); + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.search-wrapper-songs { + position: relative; + padding: 0 1rem 1rem; + border-bottom: 1px solid var(--glass-border); +} + +.song-list { + flex-grow: 1; + overflow-y: auto; + padding: 1rem; +} + +.album-group { + margin-bottom: 1.5rem; +} +.album-group-title { + font-size: 0.8rem; + font-weight: 600; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 0.8rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--glass-border); +} + +.song-item { + display: flex; + align-items: center; + padding: 0.6rem 0.5rem; + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: background-color 0.2s ease; +} +.song-item:hover { + background: var(--glass); +} +.song-item.current-song { + background: var(--accent); +} +.song-item.current-song .song-number, +.song-item.current-song .item-title { + color: var(--primary) !important; +} + +.song-number { + font-size: 0.9rem; + color: var(--text-secondary); + width: 2rem; + text-align: center; + flex-shrink: 0; +} +.song-details { + flex-grow: 1; + overflow: hidden; +} +.song-details .item-title { + font-size: 0.9rem; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.song-item .play-icon { + color: var(--text-secondary); + opacity: 0; + transition: opacity 0.2s ease; +} + +@keyframes spin-song { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.song-item .loading-spinner { + width: 16px; + height: 16px; + border: 2px solid var(--text-secondary); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin-song 0.8s linear infinite; +} +.song-item:hover .play-icon, +.song-item.current-song .play-icon { + opacity: 1; +} +.song-item.current-song .play-icon { + color: var(--primary); +} +.list-item-empty { + padding: 2rem 1rem; + text-align: center; + color: var(--text-secondary); + font-style: italic; + background-color: transparent; + border: 1px dashed var(--glass-border); + border-radius: var(--border-radius-md); +} + +#side-nav-now-playing { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + background: var(--primary); + border-top: 1px solid var(--glass-border); + flex-shrink: 0; + cursor: pointer; +} +#side-nav-now-playing .details { + overflow: hidden; +} +#side-nav-now-playing img { + width: 45px; + height: 45px; + border-radius: var(--border-radius-sm); + flex-shrink: 0; +} +#side-nav-now-playing p { + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#side-nav-track-title { + font-weight: 600; +} +#side-nav-track-artist { + font-size: 0.8rem; + color: var(--text-secondary); +} +#side-nav-play-pause { + margin-left: auto; + flex-shrink: 0; + font-size: 1.2rem; +} + +#miniplayer { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + height: 85px; + padding: 0 1.5rem; + display: grid; + grid-template-columns: minmax(200px, 1fr) 2fr minmax(200px, 1fr); + gap: 1.5rem; + align-items: center; + z-index: 1045; + color: var(--text-primary); + transform: translateY(110%); +} + +.miniplayer-bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(16, 17, 22, 0.8); + backdrop-filter: blur(15px); + -webkit-backdrop-filter: blur(15px); + border-top: 1px solid var(--glass-border); + z-index: -1; +} + +.player-left-info { + display: flex; + align-items: center; + gap: 1rem; + overflow: hidden; + min-width: 0; +} +.album-cover { + width: 55px; + height: 55px; + border-radius: var(--border-radius-sm); + flex-shrink: 0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + object-fit: cover; + cursor: pointer; + transition: transform 0.3s ease; +} +#trackInfo .details { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + gap: 0.1rem; +} +#trackTitle, #trackArtist { + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#trackTitle { font-weight: 600; } +#trackArtist { font-size: 0.8rem; color: var(--text-secondary); } + +.player-center-controls { + display: flex; + flex-direction: column; + gap: 0.5rem; + width: 100%; + min-width: 250px; +} +#player-controls { + display: flex; + align-items: center; + justify-content: center; + gap: 0.8rem; +} +.control-btn { + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 1rem; + cursor: pointer; + transition: all 0.2s ease; + width: 38px; + height: 38px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; +} +.control-btn:hover { + color: var(--accent); + background: var(--glass); +} +.control-btn.active { color: var(--accent); } +.control-btn.play-pause-main { + font-size: 1.5rem; + width: 48px; + height: 48px; + background: var(--accent); + color: var(--primary); +} +.control-btn.play-pause-main:hover { + transform: scale(1.1); + box-shadow: 0 0 15px rgba(0, 224, 255, 0.4); +} + +#closeMiniplayerBtn { + color: var(--text-secondary); +} + +#closeMiniplayerBtn:hover { + color: var(--accent); +} + +.fab-btn { + position: relative; + width: 60px; + height: 60px; + border-radius: 50%; + background-color: var(--accent); + color: var(--primary); + border: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + cursor: pointer; + transition: transform 0.3s ease, box-shadow 0.3s ease; + z-index: 1030; +} + +.fab-btn:hover { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); +} + +.time-and-progress { + display: flex; + align-items: center; + width: 100%; + gap: 1rem; +} +.time-label { font-size: 0.75rem; color: var(--text-secondary); } + +#progressBarContainer { + flex-grow: 1; + height: 5px; + background-color: rgba(255, 255, 255, 0.15); + border-radius: 2.5px; + cursor: pointer; + position: relative; + transition: height 0.2s ease; +} +#progressBarContainer:hover { + height: 8px; +} +#seek-hover-bar { + position: absolute; + height: 100%; + background-color: rgba(255, 255, 255, 0.25); + border-radius: inherit; + width: 0%; +} +#played-bar { + position: relative; + height: 100%; + background: var(--accent); + border-radius: inherit; + width: 0%; +} +#progress-handle { + position: absolute; + top: 50%; + transform: translate(-50%, -50%) scale(0); + width: 14px; + height: 14px; + background-color: var(--text-primary); + border-radius: 50%; + transition: transform 0.2s ease; +} +#progressBarContainer:hover #progress-handle { + transform: translate(-50%, -50%) scale(1); +} + +.player-right-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 0.5rem; +} +#volumeControl { position: relative; } +.volume-slider-wrapper { + position: absolute; + top: 50%; + left: calc(100% + 15px); + transform: translateY(-50%); + background: var(--secondary); + padding: 0.5rem 1rem; + border-radius: var(--border-radius-md); + border: 1px solid var(--glass-border); + box-shadow: 0 5px 15px rgba(0,0,0,0.3); + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; +} +.volume-slider-wrapper.active { + opacity: 1; + visibility: visible; + left: calc(100% + 5px); +} +#volumeSlider { + -webkit-appearance: none; + appearance: none; + width: 100px; + height: 8px; + background: var(--glass); + border-radius: 4px; +} +#volumeSlider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + background: var(--accent); + border-radius: 50%; + cursor: pointer; +} + +#audioPlayer { display: none; } +body.miniplayer-active { padding-bottom: 85px; } + +@media (max-width: 768px) { + body.miniplayer-active { padding-bottom: 110px; } + + body.miniplayer-active #musicPlayerContainer { + height: calc(100% - 110px); + } + + #miniplayer { + grid-template-columns: 1fr auto; + grid-template-rows: auto auto; + grid-template-areas: + "info actions" + "center center"; + height: 110px; + padding: 0.5rem 1rem; + gap: 0.5rem; + } + .player-left-info { grid-area: info; } + .player-center-controls { grid-area: center; padding: 0 1rem; } + .player-right-actions { grid-area: actions; justify-content: flex-end; } + #downloadBtn, #downloadAlbumBtn, #eqBtn, #volumeControl { display: none; } +} + +@media (max-width: 576px) { + #player-controls { gap: 1.5rem; } +} + +#equalizer-panel { + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%) translateY(100%); + width: 520px; + background-color: var(--secondary); + border-top: 1px solid var(--glass-border); + border-left: 1px solid var(--glass-border); + border-right: 1px solid var(--glass-border); + border-radius: 12px 12px 0 0; + box-shadow: 0 -5px 25px rgba(0,0,0,0.3); + z-index: 100; + overflow: hidden; + display: none; + font-family: 'Montserrat', sans-serif; +} + +.equalizer-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 15px; + background-color: var(--primary); + color: var(--text-primary); + border-bottom: 1px solid var(--glass-border); +} + +.equalizer-header h5 { + margin: 0; + font-weight: 600; + font-family: 'Orbitron', sans-serif; +} + +.close-btn { + background: none; + border: none; + color: var(--text-secondary); + font-size: 1.2rem; + cursor: pointer; + transition: color 0.2s; +} +.close-btn:hover { color: var(--text-primary); } + +.equalizer-top-bar { + display: flex; + justify-content: space-around; + align-items: center; + padding: 15px; + border-bottom: 1px solid var(--glass-border); + gap: 20px; +} + +.control-group { + display: flex; + align-items: center; + gap: 10px; +} + +.control-group label { + font-size: 0.8rem; + color: var(--text-secondary); + font-weight: 500; + white-space: nowrap; +} + +.control-group.preamp { + flex-grow: 1; +} + +.equalizer-bands-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 20px 15px; + padding: 20px 15px; +} + +.band { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.band label { + font-size: 0.75rem; + color: var(--text-secondary); + font-weight: 500; +} + +.eq-slider { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 6px; + background: var(--glass); + outline: none; + border-radius: 3px; + cursor: pointer; + transition: opacity 0.2s; +} + +.eq-slider::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + cursor: pointer; + background: var(--glass); + border-radius: 3px; +} + +.eq-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + background: var(--accent); + border: 3px solid var(--secondary); + border-radius: 50%; + cursor: pointer; + margin-top: -6px; + transition: background 0.2s, box-shadow 0.2s; + box-shadow: 0 0 5px rgba(0, 224, 255, 0.4); +} + +.eq-slider:hover::-webkit-slider-thumb { + background: var(--accent); + box-shadow: 0 0 10px rgba(0, 224, 255, 0.6); +} + +.eq-slider::-moz-range-track { + width: 100%; + height: 6px; + cursor: pointer; + background: var(--glass); + border-radius: 3px; +} + +.eq-slider::-moz-range-thumb { + width: 18px; + height: 18px; + background: var(--accent); + border: 3px solid var(--secondary); + border-radius: 50%; + cursor: pointer; + box-shadow: 0 0 5px rgba(0, 224, 255, 0.4); +} + +.slider-value { + font-size: 0.7rem; + color: var(--text-secondary); + background: var(--primary); + padding: 2px 5px; + border-radius: 3px; + min-width: 35px; + text-align: center; +} + +.custom-select-sm { + background-color: var(--primary); + color: var(--text-primary); + border: 1px solid var(--glass-border); + border-radius: 4px; + padding: 5px 8px; + font-size: 0.8rem; +} + +.visualizer-container { + height: 80px; + background-color: var(--primary); + border-top: 1px solid var(--glass-border); + padding: 0; + margin: 0; + overflow: hidden; +} + +#visualizer-canvas { + width: 100%; + height: 100%; + display: block; +} \ No newline at end of file diff --git a/css/music.css b/css/music.css new file mode 100644 index 0000000..9410216 --- /dev/null +++ b/css/music.css @@ -0,0 +1,426 @@ +#music-section { + padding: 2rem; + animation: fadeIn 0.5s ease-in-out; +} + +#music-section .section-header { + margin-bottom: 2rem; +} + +.music-controls { + display: flex; + gap: 1rem; + align-items: center; +} + +.music-search-bar { + position: relative; + min-width: 280px; +} + +.music-search-bar .search-input { + width: 100%; +} + +.genre-card { + background: var(--gradient); + border-radius: var(--border-radius-md); + padding: 2rem 1rem; + text-align: center; + color: var(--primary); + font-size: 1.2rem; + font-weight: 600; + cursor: pointer; + transition: var(--transition); + box-shadow: 0 5px 15px rgba(0, 224, 255, 0.2); + text-transform: capitalize; +} + +.genre-card:hover { + transform: translateY(-5px) scale(1.05); + box-shadow: 0 10px 25px rgba(0, 224, 255, 0.3); +} + +.artist-card-spotify { + background: transparent; + border-radius: var(--border-radius-md); + padding: 1rem; + text-align: center; + transition: background-color 0.3s ease; + cursor: pointer; + position: relative; +} + +.artist-card-spotify:hover { + background-color: var(--glass); +} + +.artist-card-spotify.current-artist { + background-color: var(--glass); +} + +.artist-card-img-container { + position: relative; + width: 100%; + padding-top: 100%; + margin-bottom: 1rem; +} + +.artist-card-img { + position: absolute; + top: 0; + left: 0; + width: 90%; + height: 90%; + object-fit: cover; + border-radius: 50%; + box-shadow: 0 8px 24px rgba(0,0,0,0.5); +} + +.artist-card-spotify:hover .artist-card-img { + transform: scale(1.05); +} + +.artist-card-play-btn { + position: absolute; + bottom: 0; + right: 0; + width: 48px; + height: 48px; + background-color: var(--accent); + color: var(--primary); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + box-shadow: 0 4px 10px rgba(0,0,0,0.3); + opacity: 0; + transform: translateY(10px) scale(0.8); + transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.artist-card-spotify:hover .artist-card-play-btn { + opacity: 1; + transform: translateY(0) scale(1); +} + +.artist-card-info { + padding: 0; +} + +.artist-card-title-spotify { + font-size: 1rem; + font-weight: 600; + color: var(--text-primary); + margin: 0 0 0.25rem 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.artist-card-subtitle { + font-size: 0.85rem; + color: var(--text-secondary); + text-transform: capitalize; +} + +.song-list-header-spotify { + display: flex; + align-items: flex-end; + gap: 1.5rem; + padding: 2rem; + margin-bottom: 2rem; + position: relative; + min-height: 300px; + border-radius: var(--border-radius-lg); + overflow: hidden; + transition: background 0.5s ease; +} + +#back-to-artists-btn { + position: absolute; + top: 1rem; + left: 1rem; + z-index: 2; + background: rgba(0,0,0,0.3); +} +#back-to-artists-btn:hover { + background: rgba(0,0,0,0.5); +} + +.artist-header-thumb-spotify { + width: 180px; + height: 180px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 8px 30px rgba(0,0,0,0.5); + z-index: 1; +} + +.artist-header-info-spotify { + z-index: 1; + text-shadow: 0 2px 10px rgba(0,0,0,0.5); +} + +.artist-header-type { + font-weight: 700; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +.artist-header-info-spotify h1 { + font-size: clamp(2.5rem, 5vw, 4.5rem); + font-weight: 900; + margin: 0; + line-height: 1.1; + font-family: 'Orbitron', sans-serif; +} + +.album-group-container-spotify { + margin-bottom: 2.5rem; +} + +.album-group-header-spotify { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + padding: 0 1rem; +} + +.album-play-btn { + width: 42px; + height: 42px; + background-color: var(--accent); + color: var(--primary); + border: none; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 1.1rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + margin-left: auto; +} +.album-play-btn:hover { + transform: scale(1.1); + box-shadow: 0 4px 15px rgba(0, 224, 255, 0.4); +} + +.album-info-spotify { + display: flex; + flex-direction: column; +} + +.album-track-count { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.album-group-cover-art-spotify { + width: 50px; + height: 50px; + object-fit: cover; + border-radius: var(--border-radius-sm); + box-shadow: 0 4px 10px rgba(0,0,0,0.3); +} + +.album-group-title-spotify { + font-size: 1.2rem; + font-weight: 600; +} + +.song-list-grid { + display: grid; + grid-template-columns: 1fr; + gap: 0.25rem; +} + +.song-list-grid-header, +.song-grid-row { + display: grid; + grid-template-columns: 40px 1fr 60px; + gap: 1rem; + align-items: center; + padding: 0.75rem 1rem; + border-radius: var(--border-radius-sm); + transition: background-color 0.2s ease-in-out; +} + +.song-list-grid-header { + color: var(--text-secondary); + font-size: 0.8rem; + text-transform: uppercase; + border-bottom: 1px solid var(--glass-border); + padding-bottom: 0.5rem; + margin-bottom: 0.5rem; +} + +.song-grid-row { + cursor: pointer; +} + +.song-grid-row:hover { + background-color: var(--glass); +} + +.song-grid-row:hover .track-number { + display: none; +} + +.song-grid-row:hover .play-icon-spotify { + display: block; +} + +.song-grid-row.current-song { + background-color: rgba(0, 224, 255, 0.1); +} +.song-grid-row.current-song .song-title-spotify { color: var(--accent); } +.song-grid-row.current-song .track-number { display: none; } +.song-grid-row.current-song .play-icon-spotify { display: none; } +.song-grid-row.current-song .playing-indicator { display: flex; } + +.song-index { + color: var(--text-secondary); + text-align: center; + position: relative; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.play-icon-spotify { + color: var(--text-primary); + display: none; +} + +.playing-indicator { + display: none; + height: 16px; + align-items: flex-end; + gap: 2px; +} +.playing-indicator div { + width: 3px; + background-color: var(--accent); + animation-duration: 1.2s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + animation-name: music-wave; +} +.playing-indicator div:nth-child(1) { height: 10px; animation-delay: -1.2s; } +.playing-indicator div:nth-child(2) { height: 16px; animation-delay: -1.0s; } +.playing-indicator div:nth-child(3) { height: 6px; animation-delay: -0.8s; } + +@keyframes music-wave { + 50% { height: 2px; } +} + +.song-title-artist { + display: flex; + flex-direction: column; +} + +.song-title-spotify { + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.song-duration-header, +.song-duration { + text-align: right; + color: var(--text-secondary); + font-size: 0.9rem; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +#music-classification-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(10, 10, 15, 0.95); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: var(--text-primary); + padding: 2rem; + padding-bottom: 15vh; +} + +.classification-content { + max-width: 500px; +} + +.classification-icon { + font-size: 5rem; + color: var(--accent); + margin-bottom: 1.5rem; + animation: spin-vinyl 4s linear infinite; +} + +@keyframes spin-vinyl { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.classification-title { + font-family: 'Orbitron', sans-serif; + font-size: 1.8rem; + margin-bottom: 0.5rem; +} + +.classification-subtitle { + color: var(--text-secondary); + margin-bottom: 2rem; + font-size: 0.95rem; +} + +.classification-progress-bar { + width: 100%; + height: 10px; + background-color: var(--glass); + border-radius: 5px; + overflow: hidden; + margin-bottom: 0.5rem; +} + +.classification-progress-fill { + width: 0%; + height: 100%; + background: var(--gradient); + border-radius: 5px; + transition: width 0.3s ease-in-out; +} + +.classification-progress-text { + display: flex; + justify-content: space-between; + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 1.5rem; + font-weight: 500; +} + +.classification-status-text { + min-height: 1.5em; + font-style: italic; + color: var(--accent); + transition: opacity 0.3s; +} \ No newline at end of file diff --git a/css/photos.css b/css/photos.css new file mode 100644 index 0000000..06327d6 --- /dev/null +++ b/css/photos.css @@ -0,0 +1,150 @@ +#photos-section { + padding-top: 2rem; +} + +.photos-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + gap: 1rem; + margin-bottom: 2rem; + padding: 1.5rem; + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-lg); +} + +#photos-breadcrumb { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem; + padding: 0; + margin: 0; + list-style: none; + font-size: 0.95rem; +} + +#photos-breadcrumb .breadcrumb-item a { + color: var(--text-secondary); + text-decoration: none; + transition: var(--transition); + padding: 0.3rem 0.6rem; + border-radius: var(--border-radius-sm); +} + +#photos-breadcrumb .breadcrumb-item a:hover { + color: var(--accent); + background-color: var(--glass); +} + +#photos-breadcrumb .breadcrumb-item.active { + color: var(--text-primary); + font-weight: 600; +} + +#photos-breadcrumb .breadcrumb-divider { + color: var(--text-secondary); + opacity: 0.5; + margin: 0 0.3rem; +} + +#photos-token-select { + min-width: 250px; +} + +#photos-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1.8rem; +} + +.photo-card, .album-card { + background: var(--card-bg); + border-radius: var(--border-radius-lg); + overflow: hidden; + position: relative; + transition: var(--transition); + box-shadow: var(--shadow); + cursor: pointer; + border: 1px solid transparent; + aspect-ratio: 1 / 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 1rem; +} + +.photo-card:hover, .album-card:hover { + transform: translateY(-10px) scale(1.03); + box-shadow: 0 18px 45px rgba(0, 0, 0, 0.5); + border-color: rgba(0, 224, 255, 0.5); + z-index: 10; +} + +.album-card-icon { + font-size: 4rem; + color: var(--accent); + margin-bottom: 1rem; + transition: transform 0.4s ease; +} + +.album-card:hover .album-card-icon { + transform: scale(1.1); +} + +.album-card-title { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); +} + +.album-card-meta { + font-size: 0.85rem; + color: var(--text-secondary); + margin-top: 0.3rem; +} + +.photo-card { + padding: 0; + justify-content: flex-end; +} + +.photo-card-img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1); +} + +.photo-card:hover .photo-card-img { + transform: scale(1.08); +} + +.photo-card-caption { + position: relative; + z-index: 1; + width: 100%; + padding: 0.8rem; + background: linear-gradient(to top, rgba(10, 10, 15, 0.95) 20%, transparent 100%); + color: var(--text-primary); + font-size: 0.9rem; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: 0; + transform: translateY(10px); + transition: var(--transition); +} + +.photo-card:hover .photo-card-caption { + opacity: 1; + transform: translateY(0); +} \ No newline at end of file diff --git a/css/providers.css b/css/providers.css new file mode 100644 index 0000000..99188e4 --- /dev/null +++ b/css/providers.css @@ -0,0 +1,90 @@ +.providers-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 30px; + padding: 30px; + perspective: 1000px; +} + +.provider-card { + position: relative; + width: 120px; + height: 120px; + background-color: transparent; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: transform 0.4s ease, box-shadow 0.4s ease; + box-shadow: 0 0 0px rgba(0, 0, 0, 0.3); + overflow: hidden; + border: none; +} + +.provider-card:hover { + transform: scale(1.1) translateY(-5px); + box-shadow: 0 0 25px rgba(0, 224, 255, 0.7); +} + +.provider-logo { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 0; + transition: filter 0.3s ease; +} + +.provider-card:hover .provider-logo { + filter: brightness(1.1); +} + +.provider-name { + display: none; +} + +.provider-card.available { + box-shadow: 0 0 15px #00e0ff; +} + +.provider-card.available::after { + content: '\f00c'; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + position: absolute; + top: -5px; + right: -5px; + background-color: #00e0ff; + color: #121212; + border-radius: 50%; + width: 25px; + height: 25px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + box-shadow: 0 0 10px rgba(0, 224, 255, 0.5); + z-index: 11; +} + +.provider-tooltip { + position: absolute; + bottom: -35px; + background-color: #00e0ff; + color: #121212; + padding: 5px 12px; + border-radius: 15px; + font-size: 14px; + font-weight: 600; + white-space: nowrap; + opacity: 0; + transform: translateY(10px); + transition: opacity 0.3s ease, transform 0.3s ease; + pointer-events: none; + z-index: 10; +} + +.provider-card:hover .provider-tooltip { + opacity: 1; + transform: translateY(0); +} \ No newline at end of file diff --git a/css/settings.css b/css/settings.css new file mode 100644 index 0000000..db1484f --- /dev/null +++ b/css/settings.css @@ -0,0 +1,109 @@ +#settings-section .settings-container { + display: flex; + background: var(--card-bg); + border-radius: var(--border-radius-lg); + border: 1px solid var(--glass-border); + overflow: hidden; + min-height: 70vh; +} + +.settings-nav { + flex: 0 0 220px; + background: rgba(0,0,0,0.1); + padding: 1.5rem 0.5rem; + border-right: 1px solid var(--glass-border); +} + +.settings-nav .nav-item { + display: flex; + align-items: center; + padding: 0.8rem 1.2rem; + border-radius: var(--border-radius-md); + color: var(--text-secondary); + text-decoration: none; + margin-bottom: 0.5rem; + transition: background-color 0.2s ease, color 0.2s ease; + cursor: pointer; + font-weight: 500; +} + +.settings-nav .nav-item:hover { + background-color: var(--glass); + color: var(--text-primary); +} + +.settings-nav .nav-item.active { + background-color: var(--accent); + color: var(--primary); + font-weight: 600; + box-shadow: 0 4px 15px rgba(0, 224, 255, 0.2); +} + +.settings-nav .nav-item i { + width: 24px; + text-align: center; +} + +.settings-content-wrapper { + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.settings-content { + padding: 2.5rem; + flex-grow: 1; + overflow-y: auto; +} + +.settings-content .tab-pane { + display: none; +} + +.settings-content .tab-pane.active { + display: block; + animation: fadeIn 0.4s ease-in-out; +} + +.settings-footer { + padding: 1.5rem 2.5rem; + margin-top: auto; + border-top: 1px solid var(--glass-border); + display: flex; + justify-content: flex-end; + gap: 1rem; + background: rgba(0,0,0,0.1); +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +@media (max-width: 992px) { + #settings-section .settings-container { + flex-direction: column; + } + + .settings-nav { + flex: 0 0 auto; + border-right: none; + border-bottom: 1px solid var(--glass-border); + display: flex; + overflow-x: auto; + padding: 0.5rem; + } + + .settings-nav::-webkit-scrollbar { height: 4px; } + .settings-nav::-webkit-scrollbar-thumb { background: var(--glass-border); border-radius: 2px; } + + .settings-nav .nav-item { + flex: 0 0 auto; + margin-bottom: 0; + margin-right: 0.5rem; + } + + .settings-content { + padding: 1.5rem; + } +} \ No newline at end of file diff --git a/css/sidebar.css b/css/sidebar.css new file mode 100644 index 0000000..4c791d5 --- /dev/null +++ b/css/sidebar.css @@ -0,0 +1,90 @@ +.sidebar-nav { + position: fixed; + top: var(--topbar-height); + left: 0; + width: var(--sidebar-width); + height: calc(100vh - var(--topbar-height)); + background: var(--secondary); + z-index: 1020; + border-right: 1px solid var(--glass-border); + transform: translateX(-100%); + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); + padding: 1.5rem 0; +} + +body.light-theme .sidebar-nav { + background: #eef2f7; +} + +.sidebar-nav.open { + transform: translateX(0); +} + +.sidebar-menu { + list-style: none; + padding: 0; + margin: 0; +} + +.sidebar-menu .nav-link { + display: flex; + align-items: center; + gap: 1.2rem; + color: var(--text-secondary); + font-weight: 500; + padding: 0.9rem 1.8rem; + transition: var(--transition); + border-left: 4px solid transparent; +} + +.sidebar-menu .nav-link i { + font-size: 1.1rem; + width: 20px; + text-align: center; +} + +.sidebar-menu .nav-link:hover { + color: var(--text-primary); + background-color: var(--glass); +} + +.sidebar-menu .nav-link.active { + color: var(--accent); + font-weight: 600; + background: var(--glass); + border-left-color: var(--accent); +} + +@media (min-width: 992px) { + .sidebar-nav { + transform: translateX(0); + } + #main-container { + padding-left: var(--sidebar-width); + } +} + +@media (max-width: 991px) { + #sidebar-toggle { + display: inline-flex; + } +} + +@media (max-width: 576px) { + .sidebar-nav { + width: 100%; + transform: translateX(-105%); + } +} + +body.sidebar-open .sidebar-nav { + transform: translateX(0); +} + +body.sidebar-collapsed .sidebar-nav { + transform: translateX(-100%); +} + +body.sidebar-collapsed #main-container { + padding-left: 0; +} \ No newline at end of file diff --git a/css/stats.css b/css/stats.css new file mode 100644 index 0000000..a7da776 --- /dev/null +++ b/css/stats.css @@ -0,0 +1,138 @@ +#stats-section { + padding-top: 2rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + margin-top: 2rem; +} + +.stats-card { + background: var(--glass); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-lg); + padding: 2rem; + display: flex; + flex-direction: column; + justify-content: space-between; + transition: var(--transition); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +.stats-card:hover { + transform: translateY(-8px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + border-color: var(--accent); +} + +.stat-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1.5rem; +} + +.stat-icon { + font-size: 2.2rem; + color: var(--accent); + background: var(--gradient); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + opacity: 0.8; +} + +.stat-label { + font-size: 1.1rem; + font-weight: 500; + color: var(--text-secondary); + text-align: right; +} + +.stat-value { + font-family: 'Orbitron', sans-serif; + font-size: clamp(2.5rem, 5vw, 3.5rem); + font-weight: 700; + color: var(--text-primary); + line-height: 1; + text-align: right; +} + +.chart-container { + background: var(--card-bg); + border: 1px solid var(--glass-border); + border-radius: var(--border-radius-lg); + padding: 2rem; + box-shadow: var(--shadow); + grid-column: span 1; +} + +.chart-container.full-width { + grid-column: 1 / -1; +} + +.chart-title { + font-size: 1.3rem; + font-weight: 600; + margin-bottom: 1.5rem; + color: var(--text-primary); + text-align: center; +} + +.chart-container canvas { + max-height: 400px; + width: 100% !important; +} + +#stats-filters { + display: flex; + justify-content: flex-end; + margin-bottom: 2rem; +} + +.token-details-card { + grid-column: 1 / -1; + display: none; +} + +.token-details-card .card-body { + padding: 1.5rem; +} + +.token-details-card .server-list { + list-style: none; + padding: 0; + margin: 0; + max-height: 200px; + overflow-y: auto; +} + +.token-details-card .server-list li { + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--glass-border); + font-size: 0.95rem; + color: var(--text-secondary); +} + +.token-details-card .server-list li:last-child { + border-bottom: none; +} + +.token-details-card .server-list strong { + color: var(--text-primary); + margin-right: 0.5rem; +} + +@media (max-width: 768px) { + .stats-card { + padding: 1.5rem; + } +} + +@media (max-width: 576px) { + .stats-grid { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/js/ai-tools.js b/js/ai-tools.js index 64f91bd..c2d8535 100644 --- a/js/ai-tools.js +++ b/js/ai-tools.js @@ -1,7 +1,7 @@ import { state } from './state.js'; -import { getFromDB, clearStore, addItemsToStore } from './db.js'; +import { getFromDB, clearStore, addItemsToStore, exportDatabase, importDatabase } from './db.js'; import { showNotification, _ } from './utils.js'; -import { switchView, showItemDetails, addStreamToList, downloadM3U, generateStatistics, toggleFavorite, loadRecommendations, applyFilters as applyUIFilters, clearAllFavorites, clearRecommendations, loadInitialContent, loadContent, clearAllHistory } from './ui.js'; +import { switchView, showItemDetails, addStreamToList, downloadM3U, generateStatistics, toggleFavorite, loadRecommendations, applyFilters as applyUIFilters, clearAllFavorites, clearRecommendations, loadInitialContent, loadContent, clearAllHistory, applyTheme, applyHeroVisibility, getTrailerKey, showTrailer, renderGrid, showActorDetails, isContentAvailableLocally } from './ui.js'; import { fetchTMDB } from './api.js'; import { updateAllTokens, addPlexToken } from './plex.js'; import { config } from './config.js'; @@ -36,7 +36,7 @@ export class AITools { properties: { page: { type: 'string', - enum: ['movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator'], + enum: ['movies', 'series', 'stats', 'favorites', 'history', 'recommendations', 'photos', 'providers', 'm3u-generator', 'music'], description: _('aiToolNavigateToPagePageParamDesc') } }, @@ -72,6 +72,31 @@ export class AITools { required: ['title', 'type'] } }, + { + name: 'download_single_movie_m3u', + description: _('aiToolDownloadSingleMovieM3UDesc'), + parameters: { + type: 'object', + properties: { + movie_title: { type: 'string', description: _('aiToolDownloadSingleMovieM3UTitleParamDesc') }, + year: { type: 'string', description: _('aiToolDownloadSingleMovieM3UYearParamDesc') } + }, + required: ['movie_title'] + } + }, + { + name: 'download_series_season_m3u', + description: _('aiToolDownloadSeriesSeasonM3UDesc'), + parameters: { + type: 'object', + properties: { + series_title: { type: 'string', description: _('aiToolDownloadSeriesSeasonM3UTitleParamDesc') }, + season_number: { type: 'number', description: _('aiToolDownloadSeriesSeasonM3USeasonParamDesc') }, + year: { type: 'string', description: _('aiToolDownloadSeriesSeasonM3UYearParamDesc') } + }, + required: ['series_title', 'season_number'] + } + }, { name: 'check_and_download_titles_list', description: _('aiToolCheckAndDownloadDesc'), @@ -124,6 +149,22 @@ export class AITools { required: ['type'] } }, + { + name: 'list_available_music_genres', + description: _('aiToolListAvailableMusicGenresDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'search_music_by_genre', + description: _('aiToolSearchMusicByGenreDesc'), + parameters: { + type: 'object', + properties: { + genre_name: { type: 'string', description: _('aiToolSearchMusicByGenreNameParamDesc') } + }, + required: ['genre_name'] + } + }, { name: 'play_music_by_artist', description: _('aiToolPlayMusicByArtistDesc'), @@ -186,6 +227,132 @@ export class AITools { name: 'clear_recommendations_view', description: _('aiToolClearRecommendationsViewDesc'), parameters: { type: 'object', properties: {} } + }, + { + name: 'view_history', + description: _('aiToolViewHistoryDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'view_favorites', + description: _('aiToolViewFavoritesDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'play_song', + description: _('aiToolPlaySongDesc'), + parameters: { + type: 'object', + properties: { + title: { type: 'string', description: _('aiToolPlaySongTitleParamDesc') }, + artist: { type: 'string', description: _('aiToolPlaySongArtistParamDesc') } + }, + required: ['title'] + } + }, + { + name: 'toggle_light_mode', + description: _('aiToolToggleLightModeDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'toggle_hero_section', + description: _('aiToolToggleHeroSectionDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'export_local_database', + description: _('aiToolExportLocalDatabaseDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'import_local_database', + description: _('aiToolImportLocalDatabaseDesc'), + parameters: { type: 'object', properties: {} } + }, + { + name: 'search_tmdb_content', + description: _('aiToolSearchTmdbContentDesc'), + parameters: { + type: 'object', + properties: { + query: { type: 'string', description: _('aiToolSearchTmdbContentQueryParamDesc') }, + type: { type: 'string', enum: ['movie', 'series', 'person', 'all'], description: _('aiToolSearchTmdbContentTypeParamDesc') } + }, + required: ['query'] + } + }, + { + name: 'get_trending_content', + description: _('aiToolGetTrendingContentDesc'), + parameters: { + type: 'object', + properties: { + type: { type: 'string', enum: ['movie', 'series', 'all'], description: _('aiToolGetTrendingContentTypeParamDesc') } + }, + required: ['type'] + } + }, + { + name: 'show_actor_details', + description: _('aiToolShowActorDetailsDesc'), + parameters: { + type: 'object', + properties: { + actor_name: { type: 'string', description: _('aiToolShowActorDetailsNameParamDesc') } + }, + required: ['actor_name'] + } + }, + { + name: 'play_trailer', + description: _('aiToolPlayTrailerDesc'), + parameters: { + type: 'object', + properties: { + title: { type: 'string', description: _('aiToolPlayTrailerTitleParamDesc') }, + type: { type: 'string', enum: ['movie', 'series'], description: _('aiToolPlayTrailerTypeParamDesc') } + }, + required: ['title', 'type'] + } + }, + { + name: 'check_local_availability', + description: _('aiToolCheckLocalAvailabilityDesc'), + parameters: { + type: 'object', + properties: { + title: { type: 'string', description: _('aiToolCheckLocalAvailabilityTitleParamDesc') }, + type: { type: 'string', enum: ['movie', 'series'], description: _('aiToolCheckLocalAvailabilityTypeParamDesc') }, + year: { type: 'string', description: _('aiToolCheckLocalAvailabilityYearParamDesc') } + }, + required: ['title', 'type'] + } + }, + { + name: 'get_local_series_seasons', + description: _('aiToolGetLocalSeriesSeasonsDesc'), + parameters: { + type: 'object', + properties: { + series_title: { type: 'string', description: _('aiToolGetLocalSeriesSeasonsTitleParamDesc') }, + year: { type: 'string', description: _('aiToolGetLocalSeriesSeasonsYearParamDesc') } + }, + required: ['series_title'] + } + }, + { + name: 'find_streaming_providers', + description: _('aiToolFindStreamingProvidersDesc'), + parameters: { + type: 'object', + properties: { + title: { type: 'string', description: _('aiToolFindStreamingProvidersTitleParamDesc') }, + type: { type: 'string', enum: ['movie', 'series'], description: _('aiToolFindStreamingProvidersTypeParamDesc') }, + year: { type: 'string', description: _('aiToolFindStreamingProvidersYearParamDesc') } + }, + required: ['title', 'type'] + } } ]; } @@ -193,9 +360,14 @@ export class AITools { async "search_library"({ query, type, resolution, container }) { const movieEntries = await getFromDB('movies'); const seriesEntries = await getFromDB('series'); + const jellyfinMovieEntries = await getFromDB('jellyfin_movies'); + const jellyfinSeriesEntries = await getFromDB('jellyfin_series'); + const allContent = [ ...movieEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'movie' }))), - ...seriesEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'series' }))) + ...seriesEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'series' }))), + ...jellyfinMovieEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'movie' }))), + ...jellyfinSeriesEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'series' }))) ]; let results = allContent; @@ -233,13 +405,25 @@ export class AITools { async "get_user_stats"() { try { - const movieItems = (await getFromDB('movies')).flatMap(s => s.titulos); - const seriesItems = (await getFromDB('series')).flatMap(s => s.titulos); - const artistItems = (await getFromDB('artists')).flatMap(s => s.titulos); + const movieItemsPlex = (await getFromDB('movies')).flatMap(s => s.titulos); + const seriesItemsPlex = (await getFromDB('series')).flatMap(s => s.titulos); + const artistItemsPlex = (await getFromDB('artists')).flatMap(s => s.titulos); + const movieItemsJellyfin = (await getFromDB('jellyfin_movies')).flatMap(s => s.titulos); + const seriesItemsJellyfin = (await getFromDB('jellyfin_series')).flatMap(s => s.titulos); + + const allMovieTitles = new Set([...movieItemsPlex.map(item => item.title), ...movieItemsJellyfin.map(item => item.title)]); + const allSeriesTitles = new Set([...seriesItemsPlex.map(item => item.title), ...seriesItemsJellyfin.map(item => item.title)]); + const allArtistTitles = new Set(artistItemsPlex.map(item => item.title)); + + const plexConnections = await getFromDB('conexiones_locales'); + const jellyfinConnections = await getFromDB('jellyfin_settings'); + const stats = { - totalMovies: new Set(movieItems.map(item => item.title)).size, - totalSeries: new Set(seriesItems.map(item => item.title)).size, - totalArtists: new Set(artistItems.map(item => item.title)).size, + totalMovies: allMovieTitles.size, + totalSeries: allSeriesTitles.size, + totalArtists: allArtistTitles.size, + plexServers: plexConnections.length, + jellyfinServers: jellyfinConnections.length }; switchView('stats'); return JSON.stringify({ success: true, stats }); @@ -253,6 +437,7 @@ export class AITools { if (!content) { return JSON.stringify({ success: false, message: _('aiToolItemNotFound', title) }); } + state.aiTriggeredDetails = true; showItemDetails(Number(content.id), content.type); return JSON.stringify({ success: true, message: _('aiToolShowItemDetailsSuccess', title) }); } @@ -266,6 +451,25 @@ export class AITools { return JSON.stringify({ success: true, message: _('aiToolAddToPlaylistSuccess', title) }); } + async "download_single_movie_m3u"({ movie_title, year }) { + try { + downloadM3U([{ title: movie_title, type: 'movie', year: year }], null, movie_title); + return JSON.stringify({ success: true, message: _('aiToolM3UDownloadStartedSingle', movie_title) }); + } catch (error) { + return JSON.stringify({ success: false, message: `Error generating M3U for ${movie_title}: ${error.message}` }); + } + } + + async "download_series_season_m3u"({ series_title, season_number, year }) { + try { + const filename = `${series_title.replace(/[^a-z0-9]/gi, '_')}_S${season_number}`; + downloadM3U([{ title: series_title, type: 'tv', year: year, seasonNumber: season_number }], null, filename); + return JSON.stringify({ success: true, message: _('aiToolM3UDownloadStartedSeason', [String(season_number), series_title]) }); + } catch (error) { + return JSON.stringify({ success: false, message: `Error generating M3U for ${series_title} Season ${season_number}: ${error.message}` }); + } + } + async "check_and_download_titles_list"({ titles, type, filename }) { try { if (!titles || titles.length === 0) { @@ -344,6 +548,41 @@ export class AITools { return JSON.stringify({ success: true, message: _('aiToolApplyFiltersSuccess') }); } + async "list_available_music_genres"() { + if (!state.musicPlayer || !state.musicPlayer.isReady) { + return JSON.stringify({ success: false, message: _('aiToolPlayMusicNotReady') }); + } + const allArtists = state.musicPlayer._generateFullArtistListForToken('all'); + const genreSet = new Set(); + allArtists.forEach(artist => { + if (artist.genres && artist.genres.length > 0) { + artist.genres.forEach(g => genreSet.add(g)); + } + }); + const sortedGenres = Array.from(genreSet).sort((a, b) => a.localeCompare(b)); + if (sortedGenres.length === 0) { + return JSON.stringify({ success: true, genres: [], message: "No genres found in the user's library." }); + } + return JSON.stringify({ success: true, genres: sortedGenres }); + } + + async "search_music_by_genre"({ genre_name }) { + if (!state.musicPlayer || !state.musicPlayer.isReady) { + return JSON.stringify({ success: false, message: _('aiToolPlayMusicNotReady') }); + } + const allArtists = state.musicPlayer._generateFullArtistListForToken('all'); + const searchTerm = genre_name.toLowerCase().trim(); + const matchingArtists = allArtists.filter(artist => + artist.genres && artist.genres.some(g => g.toLowerCase().includes(searchTerm)) + ); + if (matchingArtists.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolSearchMusicByGenreNotFound', genre_name) }); + } + const artistNames = matchingArtists.map(a => a.title); + switchView('music'); + return JSON.stringify({ success: true, count: artistNames.length, artists: artistNames }); + } + async "play_music_by_artist"({ artist_name }) { if (!state.musicPlayer || !state.musicPlayer.isReady) { return JSON.stringify({ success: false, message: _('aiToolPlayMusicNotReady') }); @@ -459,17 +698,251 @@ export class AITools { } } + async "view_history"() { + switchView('history'); + return JSON.stringify({ success: true, message: _('aiToolNavigatedToHistory') }); + } + + async "view_favorites"() { + switchView('favorites'); + return JSON.stringify({ success: true, message: _('aiToolNavigatedToFavorites') }); + } + + async "play_song"({ title, artist }) { + if (!state.musicPlayer || !state.musicPlayer.isReady) { + return JSON.stringify({ success: false, message: _('aiToolPlayMusicNotReady') }); + } + const song = await this.findSongLocally(title, artist); + if (!song) { + return JSON.stringify({ success: false, message: _('aiToolSongNotFound', title) }); + } + state.musicPlayer.showPlayer(); + state.musicPlayer.cancionesActuales = [song]; + state.musicPlayer.playSong(0); + return JSON.stringify({ success: true, message: _('aiToolPlayingSong', [title, artist || _('unknownArtist')]) }); + } + + async "toggle_light_mode"() { + state.settings.theme = state.settings.theme === 'light' ? 'dark' : 'light'; + await addItemsToStore('settings', [{ id: 'user_settings', ...state.settings }]); + applyTheme(state.settings.theme); + return JSON.stringify({ success: true, message: state.settings.theme === 'light' ? _('aiToolLightModeOn') : _('aiToolLightModeOff') }); + } + + async "toggle_hero_section"() { + state.settings.showHero = !state.settings.showHero; + await addItemsToStore('settings', [{ id: 'user_settings', ...state.settings }]); + applyHeroVisibility(state.settings.showHero); + return JSON.stringify({ success: true, message: state.settings.showHero ? _('aiToolHeroOn') : _('aiToolHeroOff') }); + } + + async "export_local_database"() { + try { + await exportDatabase(); + return JSON.stringify({ success: true, message: _('aiToolDatabaseExported') }); + } catch (error) { + return JSON.stringify({ success: false, message: _('aiToolDatabaseExportError', error.message) }); + } + } + + async "import_local_database"() { + showNotification(_('aiToolImportPrompt'), 'info', 5000); + return JSON.stringify({ success: true, message: _('aiToolImportInitiated') }); + } + + async "search_tmdb_content"({ query, type = 'all' }) { + try { + const searchEndpoint = type === 'all' ? `search/multi?query=${encodeURIComponent(query)}` : `search/${type}?query=${encodeURIComponent(query)}`; + const searchResults = await fetchTMDB(searchEndpoint); + if (!searchResults || !searchResults.results || searchResults.results.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolTmdbSearchNoResults', query) }); + } + const filteredResults = searchResults.results.filter(item => item.media_type !== 'person').slice(0, 10); + if (filteredResults.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolTmdbSearchNoMoviesOrSeries', query) }); + } + renderGrid(filteredResults, false); + switchView('search'); + state.currentParams.query = query; + return JSON.stringify({ success: true, message: _('aiToolTmdbSearchSuccess', [filteredResults.length, query]), results: filteredResults.map(i => i.title || i.name) }); + } catch (error) { + return JSON.stringify({ success: false, message: _('aiToolTmdbSearchError', error.message) }); + } + } + + async "get_trending_content"({ type = 'all' }) { + try { + const trendingEndpoint = `trending/${type}/day`; + const trendingResults = await fetchTMDB(trendingEndpoint); + if (!trendingResults || !trendingResults.results || trendingResults.results.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolTrendingNoResults') }); + } + renderGrid(trendingResults.results.filter(item => item.media_type !== 'person').slice(0, 10), false); + switchView(type === 'movie' ? 'movies' : (type === 'tv' ? 'series' : 'home')); + return JSON.stringify({ success: true, message: _('aiToolTrendingSuccess', trendingResults.results.length) }); + } catch (error) { + return JSON.stringify({ success: false, message: _('aiToolTrendingError', error.message) }); + } + } + + async "show_actor_details"({ actor_name }) { + try { + const searchResults = await fetchTMDB(`search/person?query=${encodeURIComponent(actor_name)}`); + if (!searchResults || !searchResults.results || searchResults.results.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolActorNotFound', actor_name) }); + } + const actor = searchResults.results[0]; + showActorDetails(actor.id); + return JSON.stringify({ success: true, message: _('aiToolShowActorDetailsSuccess', actor.name) }); + } catch (error) { + return JSON.stringify({ success: false, message: _('aiToolShowActorDetailsError', error.message) }); + } + } + + async "play_trailer"({ title, type }) { + try { + const content = await this.findTmdbContent(title, type); + if (!content) { + return JSON.stringify({ success: false, message: _('aiToolItemNotFound', title) }); + } + const trailerKey = await getTrailerKey(content.id, content.type); + if (!trailerKey) { + return JSON.stringify({ success: false, message: _('aiToolTrailerNotFound') }); + } + showTrailer(trailerKey); + return JSON.stringify({ success: true, message: _('aiToolPlayingTrailer', title) }); + } catch (error) { + return JSON.stringify({ success: false, message: _('aiToolPlayTrailerError', error.message) }); + } + } + + async "check_local_availability"({ title, type, year }) { + const available = isContentAvailableLocally(title, type, year); + return JSON.stringify({ success: true, available: available, message: available ? _('aiToolAvailableLocally', title) : _('aiToolNotAvailableLocally', title) }); + } + + async "get_local_series_seasons"({ series_title, year }) { + const seriesDetails = await this.findLocalSeriesDetails(series_title, year); + + if (seriesDetails.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolItemNotFound', series_title) }); + } + + return JSON.stringify({ + success: true, + found: true, + servers: seriesDetails + }); + } + + async "find_streaming_providers"({ title, type, year }) { + const available = isContentAvailableLocally(title, type, year); + if (available) { + return JSON.stringify({ success: true, available: true, message: _('aiToolAvailableLocally', title) }); + } + + try { + const searchResults = await fetchTMDB(`search/${type}?query=${encodeURIComponent(title)}&year=${year || ''}`); + if (!searchResults || !searchResults.results || searchResults.results.length === 0) { + return JSON.stringify({ success: false, message: _('aiToolItemNotFound', title) }); + } + const item = searchResults.results[0]; + + const providersResult = await fetchTMDB(`${type}/${item.id}/watch/providers`); + const region = state.settings.watchRegion || 'US'; + const providers = providersResult.results[region]; + + if (!providers || !providers.flatrate) { + return JSON.stringify({ success: false, available: false, message: _('aiToolNoStreamingProviders', title) }); + } + + const providerNames = providers.flatrate.map(p => p.provider_name).join(', '); + return JSON.stringify({ success: true, available: false, providers: providerNames, message: _('aiToolStreamingProvidersFound', [title, providerNames]) }); + } catch (error) { + return JSON.stringify({ success: false, message: _('aiToolStreamingProviderError', error.message) }); + } + } + async findLocalContent(title, type) { const movieEntries = await getFromDB('movies'); const seriesEntries = await getFromDB('series'); + const jellyfinMovieEntries = await getFromDB('jellyfin_movies'); + const jellyfinSeriesEntries = await getFromDB('jellyfin_series'); + const allContent = [ ...movieEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'movie' }))), - ...seriesEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'series' }))) + ...seriesEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'series' }))), + ...jellyfinMovieEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'movie' }))), + ...jellyfinSeriesEntries.flatMap(e => (e.titulos || []).map(t => ({ ...t, type: 'series' }))) ]; const searchTerm = title.toLowerCase().trim(); return allContent.find(item => item.title.toLowerCase() === searchTerm && (!type || item.type === type)); } + async findLocalSeriesDetails(title, year) { + const allSeriesSources = [...(await getFromDB('series')), ...(await getFromDB('jellyfin_series'))]; + const normalizedTitle = title.toLowerCase().trim(); + const serverDetails = []; + + for (const server of allSeriesSources) { + if (server && server.titulos) { + const foundSeries = server.titulos.find(s => + s.title.toLowerCase().trim() === normalizedTitle && + (!year || s.year == year) + ); + + if (foundSeries) { + const seasons = (foundSeries.seasons || []).map(season => ({ + season_number: parseInt(season.index, 10), + episode_count: parseInt(season.episodeCount, 10) + })).sort((a, b) => a.season_number - b.season_number); + + serverDetails.push({ + serverName: server.serverName || server.nombre || 'Servidor Desconocido', + season_count: seasons.length, + seasons: seasons + }); + } + } + } + return serverDetails; + } + + async findSongLocally(title, artist) { + const allArtistsData = await getFromDB('artists'); + for (const serverData of allArtistsData) { + if (serverData && Array.isArray(serverData.titulos)) { + for (const artistItem of serverData.titulos) { + let songs; + if (artistItem.isJellyfin) { + songs = await state.musicPlayer.getArtistSongs({ + id: artistItem.id, + isJellyfin: true, + serverUrl: artistItem.serverUrl, + userId: artistItem.userId, + token: artistItem.token + }); + } else { + songs = await state.musicPlayer.getArtistSongs({ + id: artistItem.id, + isJellyfin: false, + token: artistItem.token, + protocolo: serverData.protocolo, + ip: serverData.ip, + puerto: serverData.puerto + }); + } + const foundSong = songs.find(song => + song.titulo.toLowerCase().includes(title.toLowerCase()) && + (!artist || song.artista.toLowerCase().includes(artist.toLowerCase())) + ); + if (foundSong) return foundSong; + } + } + } + return null; + } + async findTmdbContent(title, type) { try { const searchResults = await fetchTMDB(`search/${type}?query=${encodeURIComponent(title)}`); diff --git a/js/api.js b/js/api.js index 224e861..6ccd432 100644 --- a/js/api.js +++ b/js/api.js @@ -15,7 +15,6 @@ export async function fetchTMDB(endpoint, params = {}, signal) { finalParams.set('language', lang); finalParams.set('watch_region', region); - // Añadir filtros de puntuación y duración if (params.minScore) finalParams.set('vote_average.gte', params.minScore); if (params.maxScore) finalParams.set('vote_average.lte', params.maxScore); if (params.minDuration) finalParams.set('with_runtime.gte', params.minDuration); @@ -92,7 +91,8 @@ export async function getMusicUrlsFromPlex(token, protocolo, ip, puerto, artista genre: Array.from(track.querySelectorAll("Genre")).map(g => g.getAttribute('tag')).join(', ') || '', index: parseInt(track.getAttribute("index") || 0, 10), albumIndex: parseInt(track.getAttribute("parentIndex") || 0, 10), - trackIndex: parseInt(track.getAttribute("index") || 0, 10) + trackIndex: parseInt(track.getAttribute("index") || 0, 10), + duration: track.getAttribute("duration") }; }).filter(track => track !== null); @@ -146,7 +146,8 @@ export async function getMusicUrlsFromJellyfin(serverUrl, userId, token, artistI genre: track.Genres?.join(', ') || '', index: track.IndexNumber || 0, albumIndex: album.IndexNumber || 0, - trackIndex: track.IndexNumber || 0 + trackIndex: track.IndexNumber || 0, + duration: source.RunTimeTicks / 10000 }; }).filter(track => track !== null); allTracks.push(...albumTracks); @@ -167,12 +168,15 @@ export async function getMusicUrlsFromJellyfin(serverUrl, userId, token, artistI } } -export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = null) { +export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = null, seasonNumber = null, serverName = null) { if (!busqueda || !tipoContenido) return { success: false, streams: [], message: _('invalidStreamInfo') }; if (!state.db) return { success: false, streams: [], message: _('dbUnavailableForStreams') }; const plexSearchType = tipoContenido === 'movie' ? '1' : '2'; - const servers = await getFromDB('conexiones_locales'); + let servers = await getFromDB('conexiones_locales'); + if(serverName){ + servers = servers.filter(s => s.nombre === serverName); + } if (!servers || servers.length === 0) return { success: false, streams: [], message: _('noPlexServersForStreams') }; const searchTasks = servers.map(async (server) => { @@ -203,12 +207,12 @@ export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = nu if (exactMatch) { videosToProcess = [exactMatch]; } - } else { + } + + if (videosToProcess.length === 0) { const exactMatch = videos.find(v => v.getAttribute('title')?.toLowerCase() === busqueda.toLowerCase()); if (exactMatch) { videosToProcess = [exactMatch]; - } else { - videosToProcess = videos; } } @@ -243,10 +247,6 @@ export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = nu directoryToProcess = directories.find(d => d.getAttribute('title')?.toLowerCase() === busqueda.toLowerCase()); } - if (!directoryToProcess && directories.length > 0) { - directoryToProcess = directories[0]; - } - if (directoryToProcess && directoryToProcess.getAttribute("ratingKey")) { const serieKey = directoryToProcess.getAttribute("ratingKey"); const serieTitulo = directoryToProcess.getAttribute("title") || busqueda; @@ -258,7 +258,11 @@ export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = nu const leavesData = await leavesResponse.text(); const leavesXml = parser.parseFromString(leavesData, "text/xml"); if (!leavesXml.querySelector('parsererror')) { - const episodes = Array.from(leavesXml.querySelectorAll("Video")); + let episodes = Array.from(leavesXml.querySelectorAll("Video")); + + if (seasonNumber) { + episodes = episodes.filter(ep => ep.getAttribute("parentIndex") == seasonNumber); + } episodes.sort((a, b) => { const seasonA = parseInt(a.getAttribute("parentIndex") || 0, 10); @@ -278,12 +282,13 @@ export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = nu const streamUrl = `${protocolo}://${ip}:${puerto}${part.getAttribute("key")}?X-Plex-Token=${token}`; const groupTitle = `${serieTitulo}${serieYear ? ` (${serieYear})` : ''} - Temporada ${seasonNum}`.replace(/"/g, "'"); const extinfName = `${serieTitulo} T${seasonNum}E${episodeNum} ${episodeTitle}`; + const displayName = `T${seasonNum}E${episodeNum} ${episodeTitle}`; const logoUrl = episode.getAttribute("grandparentThumb") || episode.getAttribute("parentThumb") || episode.getAttribute("thumb"); const fullLogoUrl = logoUrl ? `${protocolo}://${ip}:${puerto}${logoUrl}?X-Plex-Token=${token}` : ''; serverStreams.push({ url: streamUrl, title: extinfName, - extinf: `#EXTINF:-1 tvg-name="${extinfName.replace(/"/g, "'")}" tvg-logo="${fullLogoUrl}" group-title="${groupTitle}",${extinfName}` + extinf: `#EXTINF:-1 tvg-name="${extinfName.replace(/"/g, "'")}" tvg-logo="${fullLogoUrl}" group-title="${groupTitle}",${displayName}` }); } }); @@ -323,7 +328,7 @@ export async function fetchAllStreamsFromPlex(busqueda, tipoContenido, year = nu } } -export async function fetchAllStreamsFromJellyfin(busqueda, tipoContenido) { +export async function fetchAllStreamsFromJellyfin(busqueda, tipoContenido, year = null, seasonNumber = null) { if (!busqueda || !tipoContenido) return { success: false, streams: [], message: _('invalidStreamInfo') }; const { url, userId, apiKey } = state.jellyfinSettings; @@ -366,7 +371,12 @@ export async function fetchAllStreamsFromJellyfin(busqueda, tipoContenido) { if (!episodesResponse.ok) throw new Error(`Error obteniendo episodios: ${episodesResponse.status}`); const episodesData = await episodesResponse.json(); - const sortedEpisodes = episodesData.Items.sort((a, b) => { + let episodes = episodesData.Items; + if (seasonNumber) { + episodes = episodes.filter(ep => ep.ParentIndexNumber == seasonNumber); + } + + const sortedEpisodes = episodes.sort((a, b) => { if (a.ParentIndexNumber !== b.ParentIndexNumber) return (a.ParentIndexNumber || 0) - (b.ParentIndexNumber || 0); return (a.IndexNumber || 0) - (b.IndexNumber || 0); }); @@ -378,11 +388,12 @@ export async function fetchAllStreamsFromJellyfin(busqueda, tipoContenido) { const episodeTitle = ep.Name || 'Episodio'; const groupTitle = `${itemName} - Temporada ${seasonNum}`.replace(/"/g, "'"); const extinfName = `${itemName} T${seasonNum}E${episodeNum} ${episodeTitle}`; + const displayName = `T${seasonNum}E${episodeNum} ${episodeTitle}`; streams.push({ url: streamUrl, title: extinfName, - extinf: `#EXTINF:-1 tvg-name="${extinfName.replace(/"/g, "'")}" tvg-logo="${posterUrl}" group-title="${groupTitle}",${extinfName}` + extinf: `#EXTINF:-1 tvg-name="${extinfName.replace(/"/g, "'")}" tvg-logo="${posterUrl}" group-title="${groupTitle}",${displayName}` }); }); } @@ -395,11 +406,33 @@ export async function fetchAllStreamsFromJellyfin(busqueda, tipoContenido) { } } -export async function fetchAllAvailableStreams(title, type, year = null) { - const plexPromise = fetchAllStreamsFromPlex(title, type, year); - const jellyfinPromise = fetchAllStreamsFromJellyfin(title, type); // Jellyfin no usa 'year' en su signature, lo he quitado para que no cause error si se le pasa. +export async function fetchAllAvailableStreams(title, type, year = null, seasonNumber = null, serverName = null, sourceType = null) { + const promises = []; - const results = await Promise.allSettled([plexPromise, jellyfinPromise]); + if (serverName) { + if (sourceType === 'plex' || !sourceType) { // Assume plex if sourceType is not defined + promises.push(fetchAllStreamsFromPlex(title, type, year, seasonNumber, serverName)); + } + if (sourceType === 'jellyfin' || !sourceType) { + // We need a way to check if the serverName belongs to Jellyfin. + // For now, let's assume if it's not found in Plex, it might be Jellyfin. + // This part of the logic might need refinement if there are multiple Jellyfin servers. + const plexServers = await getFromDB('conexiones_locales'); + if (!plexServers.some(s => s.nombre === serverName)){ + promises.push(fetchAllStreamsFromJellyfin(title, type, year, seasonNumber)); + } + } + } else { + const plexPromise = fetchAllStreamsFromPlex(title, type, year, seasonNumber); + const jellyfinPromise = fetchAllStreamsFromJellyfin(title, type, year, seasonNumber); + promises.push(plexPromise, jellyfinPromise); + } + + if (promises.length === 0) { + return { success: false, streams: [], message: _('notFoundOnAnyServer', title) }; + } + + const results = await Promise.allSettled(promises); let allStreams = []; const errorMessages = []; @@ -425,4 +458,28 @@ export async function fetchAllAvailableStreams(title, type, year = null) { } else { return { success: false, streams: [], message: errorMessages.join('; ') || _('notFoundOnAnyServer', title) }; } +} + +export async function fetchArtistGenresFromLastFM(artist) { + if (!config.lastFmApiKey) { + return []; + } + const url = `https://ws.audioscrobbler.com/2.0/?method=artist.gettoptags&artist=${encodeURIComponent(artist)}&api_key=${config.lastFmApiKey}&format=json`; + try { + const response = await fetchWithTimeout(url, {}, 5000); + if (!response.ok) { + return []; + } + const data = await response.json(); + if (data.toptags && data.toptags.tag) { + const tags = Array.isArray(data.toptags.tag) ? data.toptags.tag : [data.toptags.tag]; + return tags + .sort((a, b) => b.count - a.count) + .slice(0, 4) + .map(tag => tag.name.charAt(0).toUpperCase() + tag.name.slice(1)); + } + return []; + } catch (error) { + return []; + } } \ No newline at end of file diff --git a/js/chat.js b/js/chat.js index 034f1f8..63a8f82 100644 --- a/js/chat.js +++ b/js/chat.js @@ -73,7 +73,9 @@ export class Chat { gsap.to(this.dom.window, { opacity: 0, scale: 0.9, y: 20, duration: 0.3, ease: 'power2.in', onComplete: () => { this.dom.window.style.display = 'none'; }}); - gsap.fromTo(this.dom.fab, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)', delay: 0.2 }); + if (!document.body.classList.contains('miniplayer-active')) { + gsap.fromTo(this.dom.fab, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.3, ease: 'back.out(1.7)', delay: 0.2 }); + } } async sendMessage() { @@ -89,22 +91,20 @@ export class Chat { this.addTypingIndicator(); try { - const response = await this.getAIResponseWithTools(); - this.removeTypingIndicator(); - - if (response) { - this.addMessage(response, 'assistant'); - this.conversationHistory.push({ role: 'model', parts: [{ text: response }] }); - } - + await this.getAIResponseWithTools(); } catch (error) { - this.removeTypingIndicator(); this.addMessage(error.message, 'assistant', true); } finally { this.dom.sendBtn.disabled = false; + this.removeTypingIndicator(); } } + formatMessageText(text) { + const markdownLinkRegex = /\[([^\]]+?)\]\((https?:\/\/[^\s)]+?)\)/g; + return text.replace(markdownLinkRegex, `$1`); + } + addMessage(text, sender, isError = false, toolName = null) { const wrapper = document.createElement('div'); wrapper.className = `message-wrapper ${sender}-wrapper`; @@ -120,7 +120,7 @@ export class Chat { if (sender === 'assistant') { icon = ''; } else { - icon = ''; + icon = ''; } avatar.innerHTML = icon; wrapper.appendChild(avatar); @@ -128,9 +128,9 @@ export class Chat { const p = document.createElement('p'); if (sender === 'tool-call' || sender === 'tool-result') { - p.innerHTML = `${toolName}: ${text}`; + p.innerHTML = `${toolName}: ${this.formatMessageText(text)}`; } else { - p.textContent = text; + p.innerHTML = this.formatMessageText(text); } messageEl.appendChild(p); @@ -163,86 +163,111 @@ export class Chat { if (indicator) indicator.remove(); } - async getAIResponseWithTools() { + formatCitations(chunks) { + if (!chunks || chunks.length === 0) return ''; + let citationText = '\n\n**' + _('chatSources') + ':**\n'; + chunks.forEach((chunk, index) => { + if (chunk.web && chunk.web.uri) { + const title = chunk.web.title || _('chatUnnamedSource'); + citationText += `[${index + 1}] [${title}](${chunk.web.uri})\n`; + } + }); + return citationText; + } + + async getAIResponseWithTools(isRecursiveCall = false) { const apiKey = state.settings.googleApiKey; if (!apiKey) { - return _('chatGoogleApiKeyMissing'); + this.removeTypingIndicator(); + const errorMessage = _('chatGoogleApiKeyMissing'); + this.addMessage(errorMessage, 'assistant'); + this.conversationHistory.push({ role: 'model', parts: [{ text: errorMessage }] }); + return; } - const systemPrompt = _('aiSystemPrompt_v3'); - const tools = [{ - functionDeclarations: this.aiTools.toolDefinitions - }]; - const model = "gemini-2.5-flash"; const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`; + const currentDate = new Date().toLocaleDateString(_('appLocaleCode'), { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + const systemPrompt = _('aiSystemPrompt_v3', [currentDate]); + + const functionTools = [{ functionDeclarations: this.aiTools.toolDefinitions }]; + const googleSearchTool = [{ googleSearch: {} }]; + try { - const response = await fetch(url, { + if (!isRecursiveCall) { + const functionCallResponse = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey }, + body: JSON.stringify({ + contents: this.conversationHistory, + tools: functionTools, + system_instruction: { parts: [{ text: systemPrompt }] } + }) + }); + + if (!functionCallResponse.ok) throw new Error((await functionCallResponse.json()).error.message); + + const functionCallData = await functionCallResponse.json(); + const candidate = functionCallData.candidates[0]; + + if (candidate && candidate.content.parts[0].functionCall) { + const part = candidate.content.parts[0]; + this.conversationHistory.push(candidate.content); + const toolCall = { + id: `call_${Date.now()}`, + function: { name: part.functionCall.name, arguments: part.functionCall.args }, + }; + + const toolResult = await this.aiTools.executeTool(toolCall); + + this.conversationHistory.push({ + role: 'tool', + parts: [{ functionResponse: { name: part.functionCall.name, response: JSON.parse(toolResult) } }] + }); + + await this.getAIResponseWithTools(true); + return; + } + } + + const finalResponse = await fetch(url, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-goog-api-key': apiKey - }, + headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey }, body: JSON.stringify({ contents: this.conversationHistory, - tools: tools, - system_instruction: { - parts: [{ text: systemPrompt }] - } + tools: isRecursiveCall ? undefined : googleSearchTool, + system_instruction: { parts: [{ text: systemPrompt }] } }) }); - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - const errorMessage = errorData?.error?.message || `Error HTTP ${response.status}`; - throw new Error(errorMessage); + if (!finalResponse.ok) throw new Error((await finalResponse.json()).error.message); + + const finalData = await finalResponse.json(); + if (!finalData.candidates || !finalData.candidates.length) throw new Error(_('chatApiInvalidResponse')); + + const finalCandidate = finalData.candidates[0]; + let aiResponseText = finalCandidate.content.parts[0].text; + + if (finalCandidate.groundingMetadata && finalCandidate.groundingMetadata.groundingChunks) { + aiResponseText += this.formatCitations(finalCandidate.groundingMetadata.groundingChunks); } - const data = await response.json(); - - if (!data.candidates || !data.candidates.length || !data.candidates[0].content || !data.candidates[0].content.parts) { - console.error("Respuesta inesperada de la API:", data); - throw new Error(_('chatApiInvalidResponse')); - } - - const candidate = data.candidates[0]; - const part = candidate.content.parts[0]; - - if (part.functionCall) { - this.conversationHistory.push(candidate.content); - const toolCall = { - id: `call_${Date.now()}`, - function: { - name: part.functionCall.name, - arguments: part.functionCall.args, - }, - }; - - const toolResult = await this.aiTools.executeTool(toolCall); - - this.conversationHistory.push({ - role: 'tool', - parts: [{ - functionResponse: { - name: part.functionCall.name, - response: JSON.parse(toolResult) - } - }] - }); - - return await this.getAIResponseWithTools(); - } else if (part.text) { - return part.text; - } else { - return _('chatApiNoTextResponse'); - } + this.removeTypingIndicator(); + this.addMessage(aiResponseText, 'assistant'); + this.conversationHistory.push({ role: 'model', parts: [{ text: aiResponseText }] }); } catch (error) { - console.error('Fallo en la llamada a la API de Google AI:', error); + this.removeTypingIndicator(); + console.error(_('googleApiFailure'), error); const errorMessage = _('chatApiError') + `: ${error.message}`; showNotification(errorMessage, 'error'); - return errorMessage; + this.addMessage(errorMessage, 'assistant', true); } } diff --git a/js/config.js b/js/config.js index 9cde9d5..e43b9db 100644 --- a/js/config.js +++ b/js/config.js @@ -1,5 +1,10 @@ -export const config = { +export let config = { defaultApiKey: '4e44d9029b1270a757cddc766a1bcb63', + lastFmApiKey: '1182c6cd210f9ac366127a6bbe63902a', dbName: 'PlexDB', dbVersion: 9, -}; \ No newline at end of file +}; + +if (typeof chrome === 'undefined' || !chrome.storage) { + console.warn('Running outside of Chrome extension, using default config.'); +} \ No newline at end of file diff --git a/js/db.js b/js/db.js index a7c5ff1..3b50a5c 100644 --- a/js/db.js +++ b/js/db.js @@ -4,6 +4,25 @@ import { showNotification, emitirEventoActualizacion, mostrarSpinner, ocultarSpi export function initDB() { return new Promise((resolve, reject) => { + if (typeof chrome === 'undefined' || !chrome.storage) { + console.warn('IndexedDB not available, using mock DB.'); + state.db = { + objectStoreNames: [], + transaction: () => ({ + objectStore: () => ({ + clear: () => ({ + onsuccess: () => {}, + onerror: () => {} + }), + put: () => ({ + onsuccess: () => {}, + onerror: () => {} + }) + }) + }) + }; + return resolve(); + } if (!window.indexedDB) { showNotification(_("essentialFeaturesNotSupported"), "warning"); return reject("IndexedDB not supported"); diff --git a/js/eventListeners.js b/js/eventListeners.js index b19208e..e88a41e 100644 --- a/js/eventListeners.js +++ b/js/eventListeners.js @@ -1,5 +1,5 @@ import { state } from './state.js'; -import { switchView, resetView, showMainView, showItemDetails, showActorDetails, applyFilters, searchByActor, loadContent, toggleFavorite, addStreamToList, downloadM3U, showTrailer, closeTrailer, openSettingsModal, saveSettings, updateSectionTitle, generateStatistics, loadFavorites, loadLocalContent, phpScriptGenerator, initPhotosView, handlePhotoGridClick, handlePhotoTokenChange, showNextPhoto, showPrevPhoto, closePhotoLightbox, activateSettingsTab, deleteHistoryItem, clearAllHistory, getTrailerKey, initializeHeroSection } from './ui.js'; +import { switchView, resetView, showMainView, showItemDetails, showActorDetails, applyFilters, searchByActor, loadContent, toggleFavorite, addStreamToList, downloadM3U, showTrailer, closeTrailer, saveSettings, updateSectionTitle, generateStatistics, loadFavorites, loadLocalContent, phpScriptGenerator, initPhotosView, handlePhotoGridClick, handlePhotoTokenChange, showNextPhoto, showPrevPhoto, closePhotoLightbox, activateSettingsTab, deleteHistoryItem, clearAllHistory, getTrailerKey, initializeHeroSection, downloadM3UForSeason, goBack } from './ui.js'; import { loadProviderContent, changeProviderPage, backToProviders } from './providers.js'; import { debounce, showNotification, _ } from './utils.js'; import { clearContentData, loadTokensToEditor, saveTokensFromEditor, exportDatabase, importDatabase } from './db.js'; @@ -45,6 +45,14 @@ export function setupEventListeners() { } }); + document.querySelectorAll('#openMusicPlayerMobile, #openMusicPlayerDesktop').forEach(btn => { + btn.addEventListener('click', () => { + if (state.musicPlayer) { + state.musicPlayer.showPlayer(); + } + }); + }); + document.getElementById('nav-movies').addEventListener('click', (e) => { e.preventDefault(); switchView('movies'); }); document.getElementById('nav-series').addEventListener('click', (e) => { e.preventDefault(); switchView('series'); }); document.getElementById('nav-providers').addEventListener('click', (e) => { e.preventDefault(); switchView('providers'); }); @@ -53,6 +61,7 @@ export function setupEventListeners() { document.getElementById('nav-favorites').addEventListener('click', (e) => { e.preventDefault(); switchView('favorites'); }); document.getElementById('nav-history').addEventListener('click', (e) => { e.preventDefault(); switchView('history'); }); document.getElementById('nav-recommendations').addEventListener('click', (e) => { e.preventDefault(); switchView('recommendations'); }); + document.getElementById('nav-music').addEventListener('click', (e) => { e.preventDefault(); switchView('music'); }); document.getElementById('nav-m3u-generator').addEventListener('click', (e) => { e.preventDefault(); switchView('m3u-generator'); }); document.getElementById('reset-view-btn').addEventListener('click', (e) => { e.preventDefault(); resetView(); }); @@ -63,13 +72,6 @@ export function setupEventListeners() { document.getElementById('footer-favorites').addEventListener('click', (e) => { e.preventDefault(); switchView('favorites'); }); document.getElementById('activity-viewer-btn').addEventListener('click', () => state.activityViewer.show()); - - document.getElementById('load-more').addEventListener('click', () => { - if (!state.isLoading) { - state.currentPage++; - loadContent(true); - } - }); document.getElementById('search-input').addEventListener('keyup', debounce(async (e) => { const query = e.target.value.trim(); @@ -93,7 +95,7 @@ export function setupEventListeners() { document.getElementById('genre-filter').addEventListener('change', applyFilters); document.getElementById('year-filter').addEventListener('change', applyFilters); document.getElementById('sort-filter').addEventListener('change', applyFilters); - // Filter Popover Logic + const durationBtn = document.getElementById('duration-filter-btn'); const scoreBtn = document.getElementById('score-filter-btn'); const durationPopover = document.getElementById('duration-popover'); @@ -104,11 +106,9 @@ export function setupEventListeners() { button.addEventListener('click', (event) => { event.stopPropagation(); const isVisible = popover.style.display === 'block'; - // Close all popovers document.querySelectorAll('.filter-popover').forEach(p => { if (p !== popover) p.style.display = 'none'; }); - // Toggle current popover popover.style.display = isVisible ? 'none' : 'block'; }); }; @@ -116,14 +116,12 @@ export function setupEventListeners() { setupPopover(durationBtn, durationPopover); setupPopover(scoreBtn, scorePopover); - // Close popovers when clicking outside window.addEventListener('click', (event) => { if (!event.target.closest('.filter-popover') && !event.target.closest('#duration-filter-btn') && !event.target.closest('#score-filter-btn')) { document.querySelectorAll('.filter-popover').forEach(p => p.style.display = 'none'); } }); - // Range Slider Logic function setupRangeSlider(minId, maxId, fillId, minValueId, maxValueId) { const minSlider = document.getElementById(minId); const maxSlider = document.getElementById(maxId); @@ -162,7 +160,7 @@ export function setupEventListeners() { minSlider.addEventListener('change', applyFilters); maxSlider.addEventListener('change', applyFilters); - updateFill(); // Initial call + updateFill(); } setupRangeSlider('duration-min', 'duration-max', 'duration-fill', 'duration-min-value', 'duration-max-value'); @@ -189,7 +187,7 @@ export function setupEventListeners() { document.getElementById('main-view').addEventListener('click', handleMainViewClick); document.getElementById('item-details-view').addEventListener('click', handleDetailsClick); - document.getElementById('settings-btn').addEventListener('click', openSettingsModal); + document.getElementById('settings-btn').addEventListener('click', () => switchView('settings')); document.getElementById('import-db-btn').addEventListener('click', async () => { const input = document.createElement('input'); @@ -199,7 +197,7 @@ export function setupEventListeners() { const file = e.target.files[0]; if (file) { await importDatabase(file); - bootstrap.Modal.getInstance(document.getElementById('settingsModal'))?.hide(); + resetView(); } }; input.click(); @@ -211,7 +209,6 @@ export function setupEventListeners() { .filter(v => v && v !== 'on'); if (selectedTypes.length > 0) { - bootstrap.Modal.getInstance(document.getElementById('settingsModal')).hide(); document.getElementById('consoleOutputContainer').style.display = 'block'; document.getElementById('consoleOutput').style.display = 'block'; startPlexScan(selectedTypes); @@ -228,12 +225,13 @@ export function setupEventListeners() { document.getElementById('saveTokensBtn').addEventListener('click', saveTokensFromEditor); document.getElementById('exportDbBtn').addEventListener('click', exportDatabase); - document.getElementById('saveSettingsBtn').addEventListener('click', saveSettings); - - document.getElementById('settingsModal').addEventListener('shown.bs.modal', () => { - loadTokensToEditor(); - activateSettingsTab('general'); + document.getElementById('saveSettingsBtn').addEventListener('click', async () => { + await saveSettings(); + goBack(); }); + document.getElementById('cancelSettingsBtn').addEventListener('click', goBack); + + document.getElementById('updateAll').addEventListener('change', (e) => { document.querySelectorAll('#plex input[type="checkbox"]').forEach(cb => { @@ -341,7 +339,101 @@ export function setupEventListeners() { const audioPlayer = document.getElementById('audioPlayer'); audioPlayer.addEventListener('play', initializeEqualizer, { once: true }); + document.getElementById('generatePhpScriptBtn').addEventListener('click', phpScriptGenerator.generatePhpScript); + document.getElementById('copyPhpScriptBtn').addEventListener('click', phpScriptGenerator.copyScript); + phpScriptGenerator.init(); + + // Initialize Spatial Navigation + window.SpatialNavigation.init(); + window.SpatialNavigation.makeFocusable(); + + // Check if the user agent indicates an Android TV device + const isAndroidTV = navigator.userAgent.toLowerCase().includes('android tv'); + + /** + * Determines the navigation direction based on the key event. + * @param {KeyboardEvent} event - The key event. + * @param {boolean} isAndroidTV - Whether the device is an Android TV. + * @returns {string|null} - The navigation direction ('up', 'down', 'left', 'right') or null if no direction key is pressed. + */ + function getDirectionFromKeyEvent(event, isAndroidTV) { + let direction = null; + + if (isAndroidTV) { + // Android TV key codes + switch (event.keyCode) { + case 19: // Up + direction = 'up'; + break; + case 20: // Down + direction = 'down'; + break; + case 21: // Left + direction = 'left'; + break; + case 22: // Right + direction = 'right'; + break; + case 85: // Media Play/Pause + case 179: // Media Play/Pause + state.musicPlayer.togglePlayPause(); + break; + case 88: // Media Stop + case 178: // Media Stop + //TODO: Implement stop for music player (no stop function available) + break; + case 90: // Media Previous + case 177: // Media Previous + state.musicPlayer.playPrevious(); + break; + case 86: // Media Next + case 176: // Media Next + state.musicPlayer.playNext(); + break; + } + } else { + // Standard arrow keys + switch (event.key) { + case 'ArrowUp': + direction = 'up'; + break; + case 'ArrowDown': + direction = 'down'; + break; + case 'ArrowLeft': + direction = 'left'; + break; + case 'ArrowRight': + direction = 'right'; + break; + } + } + + return direction; + } + + // Add a keydown event listener to handle spatial navigation + document.addEventListener('keydown', function(event) { + const direction = getDirectionFromKeyEvent(event, isAndroidTV); + if (direction) { + window.SpatialNavigation.move(direction); + } + }); + + // TODO: Further testing and configuration may be required on Android TV and other smart TV platforms. + // TODO: Add voice control for the Gemini assistant as a future enhancement. + + window.addEventListener('scroll', debounce(async () => { + if (state.isLoading || state.currentPage >= state.totalPages) return; + + const { scrollTop, scrollHeight, clientHeight } = document.documentElement; + + if (scrollTop + clientHeight >= scrollHeight - 200) { + state.currentPage++; + await loadContent(true); + } + }, 100)); } function handleMainViewClick(e) { @@ -456,16 +548,23 @@ async function handleDetailsClick(e) { } const addStreamBtn = e.target.closest('.play-btn'); - if (addStreamBtn) { - const { title, type } = addStreamBtn.dataset; - addStreamToList(title, type, addStreamBtn); + if (addStreamBtn) { + const { title, type, year } = addStreamBtn.dataset; + addStreamToList(title, type, year, addStreamBtn); return; } const downloadM3uBtn = e.target.closest('.download-btn'); if (downloadM3uBtn) { - const { title, type } = downloadM3uBtn.dataset; - downloadM3U(title, type, downloadM3uBtn); + const { title, type, year, serverName, sourceType } = downloadM3uBtn.dataset; + downloadM3U([{ title, type, year, serverName, sourceType }], downloadM3uBtn, title); return; } -} + + const downloadSeasonBtn = e.target.closest('.download-season-btn'); + if (downloadSeasonBtn) { + e.stopPropagation(); + downloadM3UForSeason(downloadSeasonBtn); + return; + } +} \ No newline at end of file diff --git a/js/i18n.js b/js/i18n.js index 218172c..e865333 100644 --- a/js/i18n.js +++ b/js/i18n.js @@ -1,4 +1,16 @@ function localizeHtmlPage() { + if (typeof chrome === 'undefined' || !chrome.i18n) { + // Mock chrome.i18n API if not available (e.g., running in a regular browser) + const script = document.createElement('script'); + script.src = 'js/i18n-mock.js'; + script.onload = function() { + console.log('Loaded mock i18n API'); + localizeHtmlPage(); // Re-run localization after mock is loaded + }; + document.head.appendChild(script); + return; // Exit to prevent running without the mock + } + const i18nRegex = /__MSG_(\w+)__/g; function replaceMsg(match, p1) { @@ -34,7 +46,6 @@ function localizeHtmlPage() { document.documentElement.lang = chrome.i18n.getUILanguage().split('-')[0]; document.title = document.title.replace(i18nRegex, replaceMsg); - document.body.classList.remove('unlocalized'); } document.addEventListener('DOMContentLoaded', localizeHtmlPage); \ No newline at end of file diff --git a/js/m3u-generator.js b/js/m3u-generator.js index 1d8c6de..e9b22a1 100644 --- a/js/m3u-generator.js +++ b/js/m3u-generator.js @@ -38,7 +38,7 @@ document.addEventListener('DOMContentLoaded', () => { m3uServerSelect.appendChild(option); }); } catch (error) { - console.error('Error loading servers for M3U generator:', error); + console.error(_('errorLoadingServersM3u'), error); } } @@ -106,8 +106,8 @@ document.addEventListener('DOMContentLoaded', () => { gsap.from(checkboxes, { opacity: 0, y: 20, stagger: 0.05, duration: 0.3 }); downloadM3uBtn.disabled = m3uLibrariesContainer.querySelectorAll('input:checked').length === 0; } catch (error) { - console.error('Error fetching libraries:', error); - showNotification('Error fetching libraries.', 'error'); + console.error(_('errorFetchingLibraries'), error); + showNotification(_('errorFetchingLibraries'), 'error'); } finally { m3uLibrariesLoader.style.display = 'none'; // Hide loader } @@ -120,12 +120,12 @@ document.addEventListener('DOMContentLoaded', () => { const selectedLibraries = Array.from(m3uLibrariesContainer.querySelectorAll('input:checked')).map(input => input.value); if (!serverId || selectedLibraries.length === 0) { - showNotification('Please select a server and at least one library.', 'error'); + showNotification(_('selectServerAndLibrary'), 'error'); return; } downloadM3uBtn.disabled = true; - downloadM3uBtn.innerHTML = ` Generating...`; + downloadM3uBtn.innerHTML = ` ${_('generating')}`; try { const servers = await getServers(); @@ -169,13 +169,13 @@ document.addEventListener('DOMContentLoaded', () => { }); } catch (error) { hasErrors = true; - console.error(`Error processing library ${libraryKey}:`, error); - showNotification(`Error processing library ${libraryKey}. Skipping.`, 'warning'); + console.error(`${_('errorProcessingLibrary')} ${libraryKey}:`, error); + showNotification(`${_('errorProcessingLibrarySkipping')} ${libraryKey}.`, 'warning'); } } if (m3uContent.split('\n').length <= 2 && hasErrors) { - throw new Error("All selected libraries failed to process."); + throw new Error(_("allLibrariesFailed")); } const blob = new Blob([m3uContent], { type: 'audio/x-mpegurl;charset=utf-8' }); @@ -189,13 +189,13 @@ document.addEventListener('DOMContentLoaded', () => { URL.revokeObjectURL(url); if (hasErrors) { - showNotification('M3U generated with some errors. Some libraries may be missing.', 'warning'); + showNotification(_('m3uGeneratedWithErrors'), 'warning'); } else { - showNotification('M3U playlist downloaded successfully.', 'success'); + showNotification(_('m3uDownloadedSuccess'), 'success'); } } catch (error) { - console.error('Error generating M3U file:', error); - showNotification('Error generating M3U file.', 'error'); + console.error(_('errorGeneratingM3uFile'), error); + showNotification(_('errorGeneratingM3uFile'), 'error'); } finally { downloadM3uBtn.disabled = false; downloadM3uBtn.innerHTML = ` __MSG_downloadM3u__`; diff --git a/js/m3u-utils.js b/js/m3u-utils.js new file mode 100644 index 0000000..e2b3212 --- /dev/null +++ b/js/m3u-utils.js @@ -0,0 +1,36 @@ +import { showNotification, _ } from './utils.js'; + +export async function generateAndDownloadSingleM3U(mediaItem) { + try { + if (!mediaItem || !mediaItem.streamUrl || !mediaItem.title) { + showNotification(_('invalidMediaItemForM3U'), 'error'); + return; + } + + const duration = mediaItem.duration ? Math.round(mediaItem.duration / 1000) : -1; + const tvgLogo = mediaItem.thumb ? `tvg-logo="${mediaItem.thumb}"` : ''; + const groupTitle = mediaItem.seriesTitle && mediaItem.seasonNumber + ? `group-title="${mediaItem.seriesTitle} - Season ${mediaItem.seasonNumber}"` + : `group-title="${mediaItem.title}'`; // Fallback to title for group-title + + let m3uContent = '#EXTM3U\n'; + m3uContent += `#EXTINF:${duration} ${tvgLogo} ${groupTitle},${mediaItem.title}\n`; + m3uContent += `${mediaItem.streamUrl}\n`; + + const blob = new Blob([m3uContent], { type: 'audio/x-mpegurl;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${mediaItem.title}.m3u`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + showNotification(_('m3uDownloadedSuccess'), 'success'); + + } catch (error) { + console.error(_('errorGeneratingM3uFile'), error); + showNotification(_('errorGeneratingM3uFile'), 'error'); + } +} \ No newline at end of file diff --git a/js/main.js b/js/main.js index 4147d27..05bd43a 100644 --- a/js/main.js +++ b/js/main.js @@ -10,6 +10,10 @@ import { loadInitialContent, initializeFavorites, initializeUserData, loadLocalC import { showNotification, _ } from './utils.js'; async function loadSettings() { + if (typeof chrome === 'undefined' || !chrome.storage) { + console.warn('Running outside of Chrome extension, skipping settings load from DB.'); + return; + } try { const settingsData = await getFromDB('settings'); if (settingsData && settingsData.length > 0) { @@ -50,18 +54,32 @@ async function loadSettings() { } } +function debugLog(message) { + const debugOutput = document.getElementById('debug-output'); + if (debugOutput) { + const time = new Date().toLocaleTimeString(); + debugOutput.innerHTML += `
${_("fatalInitErrorSub")}
${_('loading')}
${_('noArtistsFound')}
${_('noArtistsFound')}
${_('noSongsFound')}
${title}
+${_('artist')}
+${_('noLocalFilesFound')}
`; + return; + } + + const resolutionOrder = { '4k': 5, '2160p': 5, '1440p': 4, '1080p': 3, '720p': 2, '480p': 1, 'sd': 0 }; + const getResolutionValue = (resolution) => { + if (!resolution) return -1; + const res = resolution.toLowerCase(); + for (const key in resolutionOrder) { + if (res.includes(key)) { + return resolutionOrder[key]; + } + } + const parsed = parseInt(res); + if (!isNaN(parsed)) { + if (parsed >= 2160) return 5; + if (parsed >= 1440) return 4; + if (parsed >= 1080) return 3; + if (parsed >= 720) return 2; + if (parsed >= 480) return 1; + } + return -1; + }; + + movies.sort((a, b) => { + const resA = getResolutionValue(a.resolution); + const resB = getResolutionValue(b.resolution); + return resB - resA; + }); + + let html = '${_('server')} | ${_('title')} | ${_('year')} | ${_('resolution')} | ${_('size')} | ${_('container')} | ${_('action')} |
---|---|---|---|---|---|---|
${movie.serverName || 'N/A'} | +${movie.title} | +${movie.year || 'N/A'} | +${movie.resolution || 'N/A'} | +${movie.size ? formatBytes(movie.size) : 'N/A'} | +${movie.container || 'N/A'} | ++ + | +
${ep.overview || _('noSynopsis')}
-${season.overview || ''}
+${ep.overview || _('noSynopsis')}
+${_('errorLoadingActorContent', actorName)}