diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index fef656a3..e7b86b5b 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Companion Remote Server", + "companionRemoteServerDescription": "Tillad mobilenheder på dit netværk at styre denne app" }, "search": { "hint": "Søg film, serier, musik...", @@ -778,48 +780,36 @@ "controlDesktop": "Styr en desktopenhed", "connectedTo": "Forbundet til ${name}", "session": { - "creatingSession": "Opretter fjernsession...", - "failedToCreate": "Kunne ikke oprette fjernsession:", - "noSession": "Ingen session tilgængelig", - "scanQrCode": "Scan QR-kode", - "orEnterManually": "Eller indtast manuelt", - "hostAddress": "Værtadresse", - "sessionId": "Sessions-ID", - "pin": "PIN", + "startingServer": "Starter fjernserver...", + "failedToCreate": "Kunne ikke starte fjernserver:", + "hostAddress": "Værtsadresse", "connected": "Forbundet", - "waitingForConnection": "Venter på forbindelse...", + "serverRunning": "Fjernserver aktiv", + "serverStopped": "Fjernserver stoppet", + "serverRunningDescription": "Mobilenheder på dit netværk kan finde og oprette forbindelse til denne app", + "serverStoppedDescription": "Start serveren for at tillade mobilenheder at oprette forbindelse", "usePhoneToControl": "Brug din mobilenhed til at styre denne app", - "copiedToClipboard": "${label} kopieret til udklipsholder", - "copyToClipboard": "Kopiér til udklipsholder", - "newSession": "Ny session", + "startServer": "Start server", + "stopServer": "Stop server", "minimize": "Minimér" }, "pairing": { - "scan": "Scan", - "manual": "Manuel", - "pairWithDesktop": "Par med desktop", - "enterSessionDetails": "Indtast sessionsdetaljerne vist på din desktopenhed", + "pairWithDesktop": "Opret forbindelse til computer", + "discoveryDescription": "Enheder på dit netværk, der kører Plezy med den samme Plex-konto, vises automatisk", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Indtast 8-tegns sessions-ID", - "pinHint": "Indtast 6-cifret PIN", - "connecting": "Forbinder...", - "tips": "Tips", - "tipDesktop": "Åbn Plezy på din desktop og aktivér fjernbetjening fra indstillinger eller menu", - "tipScan": "Brug fanen Scan til hurtigt at parre ved at scanne QR-koden på din desktop", - "tipWifi": "Sørg for, at begge enheder er på det samme WiFi-netværk", - "cameraPermissionRequired": "Kameratilladelse er påkrævet for at scanne QR-koder.\nGiv kameraadgang i enhedsindstillinger.", - "cameraError": "Kunne ikke starte kamera: ${error}", - "scanInstruction": "Ret kameraet mod QR-koden vist på din desktop", - "invalidQrCode": "Ugyldigt QR-kodeformat", - "validationHostRequired": "Indtast værtadresse", + "connecting": "Opretter forbindelse...", + "searchingForDevices": "Søger efter enheder...", + "noDevicesFound": "Ingen enheder fundet på dit netværk", + "noDevicesHint": "Sørg for, at Plezy er åben på din computer, og at begge enheder er på det samme WiFi-netværk", + "availableDevices": "Tilgængelige enheder", + "manualConnection": "Manuel forbindelse", + "cryptoInitFailed": "Kunne ikke initialisere sikker forbindelse. Sørg for, at du er logget ind på en Plex-konto.", + "validationHostRequired": "Angiv venligst værtsadresse", "validationHostFormat": "Format skal være IP:port (f.eks. 192.168.1.100:48632)", - "validationSessionIdRequired": "Indtast et sessions-ID", - "validationSessionIdLength": "Sessions-ID skal være 8 tegn", - "validationPinRequired": "Indtast en PIN", - "validationPinLength": "PIN skal være 6 cifre", - "connectionTimedOut": "Forbindelsen fik timeout. Tjek sessions-ID og PIN.", - "sessionNotFound": "Kunne ikke finde sessionen. Tjek dine oplysninger.", - "failedToConnect": "Kunne ikke forbinde: ${error}" + "connectionTimedOut": "Forbindelsen udløb. Sørg for, at begge enheder er på det samme netværk.", + "sessionNotFound": "Kunne ikke finde enheden. Sørg for, at Plezy kører på værten.", + "authFailed": "Godkendelse mislykkedes. Sørg for, at begge enheder bruger den samme Plex-konto.", + "failedToConnect": "Kunne ikke oprette forbindelse: ${error}" }, "remote": { "disconnectConfirm": "Vil du afbryde fra fjernsessionen?", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 573c32ae..8f5ec045 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Companion Remote Server", + "companionRemoteServerDescription": "Mobilgeräten in deinem Netzwerk erlauben, diese App zu steuern" }, "search": { "hint": "Filme, Serien, Musik suchen...", @@ -778,47 +780,35 @@ "controlDesktop": "Ein Desktop-Gerät steuern", "connectedTo": "Verbunden mit ${name}", "session": { - "creatingSession": "Fernsteuerungssitzung wird erstellt...", - "failedToCreate": "Fernsteuerungssitzung konnte nicht erstellt werden:", - "noSession": "Keine Sitzung verfügbar", - "scanQrCode": "QR-Code scannen", - "orEnterManually": "Oder manuell eingeben", + "startingServer": "Remote-Server wird gestartet...", + "failedToCreate": "Remote-Server konnte nicht gestartet werden:", "hostAddress": "Host-Adresse", - "sessionId": "Sitzungs-ID", - "pin": "PIN", "connected": "Verbunden", - "waitingForConnection": "Warte auf Verbindung...", + "serverRunning": "Remote-Server aktiv", + "serverStopped": "Remote-Server gestoppt", + "serverRunningDescription": "Mobilgeräte in deinem Netzwerk können diese App finden und sich verbinden", + "serverStoppedDescription": "Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen", "usePhoneToControl": "Verwende dein Mobilgerät, um diese App zu steuern", - "copiedToClipboard": "${label} in Zwischenablage kopiert", - "copyToClipboard": "In Zwischenablage kopieren", - "newSession": "Neue Sitzung", + "startServer": "Server starten", + "stopServer": "Server stoppen", "minimize": "Minimieren" }, "pairing": { - "scan": "Scannen", - "manual": "Manuell", - "pairWithDesktop": "Mit Desktop koppeln", - "enterSessionDetails": "Gib die Sitzungsdetails ein, die auf deinem Desktop-Gerät angezeigt werden", + "pairWithDesktop": "Mit Desktop verbinden", + "discoveryDescription": "Geräte in deinem Netzwerk, die Plezy mit demselben Plex-Konto nutzen, erscheinen automatisch", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "8-stellige Sitzungs-ID eingeben", - "pinHint": "6-stellige PIN eingeben", - "connecting": "Verbinde...", - "tips": "Tipps", - "tipDesktop": "Öffne Plezy auf deinem Desktop und aktiviere Companion Remote in den Einstellungen oder im Menü", - "tipScan": "Verwende den Scannen-Tab, um dich schnell per QR-Code auf deinem Desktop zu koppeln", - "tipWifi": "Stelle sicher, dass beide Geräte im selben WiFi-Netzwerk sind", - "cameraPermissionRequired": "Kameraberechtigung wird zum Scannen von QR-Codes benötigt.\nBitte erteile den Kamerazugriff in den Geräteeinstellungen.", - "cameraError": "Kamera konnte nicht gestartet werden: ${error}", - "scanInstruction": "Richte deine Kamera auf den QR-Code auf deinem Desktop", - "invalidQrCode": "Ungültiges QR-Code-Format", + "connecting": "Verbinden...", + "searchingForDevices": "Suche nach Geräten...", + "noDevicesFound": "Keine Geräte in deinem Netzwerk gefunden", + "noDevicesHint": "Stelle sicher, dass Plezy auf deinem Desktop geöffnet ist und beide Geräte im selben WLAN sind", + "availableDevices": "Verfügbare Geräte", + "manualConnection": "Manuelle Verbindung", + "cryptoInitFailed": "Sichere Verbindung konnte nicht initialisiert werden. Stelle sicher, dass du bei einem Plex-Konto angemeldet bist.", "validationHostRequired": "Bitte Host-Adresse eingeben", "validationHostFormat": "Format muss IP:Port sein (z.B. 192.168.1.100:48632)", - "validationSessionIdRequired": "Bitte Sitzungs-ID eingeben", - "validationSessionIdLength": "Sitzungs-ID muss 8 Zeichen lang sein", - "validationPinRequired": "Bitte PIN eingeben", - "validationPinLength": "PIN muss 6 Ziffern haben", - "connectionTimedOut": "Zeitüberschreitung. Bitte Sitzungs-ID und PIN überprüfen.", - "sessionNotFound": "Sitzung nicht gefunden. Bitte Zugangsdaten überprüfen.", + "connectionTimedOut": "Verbindung abgelaufen. Stelle sicher, dass beide Geräte im selben Netzwerk sind.", + "sessionNotFound": "Gerät nicht gefunden. Stelle sicher, dass Plezy auf dem Host läuft.", + "authFailed": "Authentifizierung fehlgeschlagen. Stelle sicher, dass beide Geräte dasselbe Plex-Konto verwenden.", "failedToConnect": "Verbindung fehlgeschlagen: ${error}" }, "remote": { diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 3d08b040..41cc356a 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -209,6 +209,8 @@ "maxVolumePercent": "${percent}%", "discordRichPresence": "Discord Rich Presence", "discordRichPresenceDescription": "Show what you're watching on Discord", + "companionRemoteServer": "Companion Remote Server", + "companionRemoteServerDescription": "Allow mobile devices on your network to control this app", "autoPip": "Auto Picture-in-Picture", "autoPipDescription": "Automatically enter picture-in-picture when leaving the app during playback", "matchContentFrameRate": "Match Content Frame Rate", @@ -778,47 +780,35 @@ "controlDesktop": "Control a desktop device", "connectedTo": "Connected to ${name}", "session": { - "creatingSession": "Creating remote session...", - "failedToCreate": "Failed to create remote session:", - "noSession": "No session available", - "scanQrCode": "Scan QR Code", - "orEnterManually": "Or enter manually", + "startingServer": "Starting remote server...", + "failedToCreate": "Failed to start remote server:", "hostAddress": "Host Address", - "sessionId": "Session ID", - "pin": "PIN", "connected": "Connected", - "waitingForConnection": "Waiting for connection...", + "serverRunning": "Remote server active", + "serverStopped": "Remote server stopped", + "serverRunningDescription": "Mobile devices on your network can discover and connect to this app", + "serverStoppedDescription": "Start the server to allow mobile devices to connect", "usePhoneToControl": "Use your mobile device to control this app", - "copiedToClipboard": "${label} copied to clipboard", - "copyToClipboard": "Copy to clipboard", - "newSession": "New Session", + "startServer": "Start Server", + "stopServer": "Stop Server", "minimize": "Minimize" }, "pairing": { - "scan": "Scan", - "manual": "Manual", - "pairWithDesktop": "Pair with Desktop", - "enterSessionDetails": "Enter the session details shown on your desktop device", + "pairWithDesktop": "Connect to Desktop", + "discoveryDescription": "Devices on your network running Plezy with the same Plex account will appear automatically", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Enter 8-character session ID", - "pinHint": "Enter 6-digit PIN", "connecting": "Connecting...", - "tips": "Tips", - "tipDesktop": "Open Plezy on your desktop and enable Companion Remote from settings or menu", - "tipScan": "Use the Scan tab to quickly pair by scanning the QR code on your desktop", - "tipWifi": "Make sure both devices are on the same WiFi network", - "cameraPermissionRequired": "Camera permission is required to scan QR codes.\nPlease grant camera access in your device settings.", - "cameraError": "Could not start camera: ${error}", - "scanInstruction": "Point your camera at the QR code shown on your desktop", - "invalidQrCode": "Invalid QR code format", + "searchingForDevices": "Looking for devices...", + "noDevicesFound": "No devices found on your network", + "noDevicesHint": "Make sure Plezy is open on your desktop and both devices are on the same WiFi network", + "availableDevices": "Available Devices", + "manualConnection": "Manual Connection", + "cryptoInitFailed": "Could not initialize secure connection. Make sure you are signed in to a Plex account.", "validationHostRequired": "Please enter host address", "validationHostFormat": "Format must be IP:port (e.g., 192.168.1.100:48632)", - "validationSessionIdRequired": "Please enter a session ID", - "validationSessionIdLength": "Session ID must be 8 characters", - "validationPinRequired": "Please enter a PIN", - "validationPinLength": "PIN must be 6 digits", - "connectionTimedOut": "Connection timed out. Please check the session ID and PIN.", - "sessionNotFound": "Could not find the session. Please check your credentials.", + "connectionTimedOut": "Connection timed out. Make sure both devices are on the same network.", + "sessionNotFound": "Could not find the device. Make sure Plezy is running on the host.", + "authFailed": "Authentication failed. Make sure both devices are on the same Plex account.", "failedToConnect": "Failed to connect: ${error}" }, "remote": { diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 96f0d662..25e8339a 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Servidor de control remoto", + "companionRemoteServerDescription": "Permitir que dispositivos móviles en tu red controlen esta aplicación" }, "search": { "hint": "Buscar películas, series, música...", @@ -778,47 +780,35 @@ "controlDesktop": "Controlar un dispositivo de escritorio", "connectedTo": "Conectado a ${name}", "session": { - "creatingSession": "Creando sesión remota...", - "failedToCreate": "Error al crear la sesión remota:", - "noSession": "No hay sesión disponible", - "scanQrCode": "Escanear código QR", - "orEnterManually": "O introducir manualmente", + "startingServer": "Iniciando servidor remoto...", + "failedToCreate": "Error al iniciar el servidor remoto:", "hostAddress": "Dirección del host", - "sessionId": "ID de sesión", - "pin": "PIN", "connected": "Conectado", - "waitingForConnection": "Esperando conexión...", - "usePhoneToControl": "Usa tu dispositivo móvil para controlar esta app", - "copiedToClipboard": "${label} copiado al portapapeles", - "copyToClipboard": "Copiar al portapapeles", - "newSession": "Nueva sesión", + "serverRunning": "Servidor remoto activo", + "serverStopped": "Servidor remoto detenido", + "serverRunningDescription": "Los dispositivos móviles en tu red pueden descubrir y conectarse a esta aplicación", + "serverStoppedDescription": "Inicia el servidor para permitir que los dispositivos móviles se conecten", + "usePhoneToControl": "Usa tu dispositivo móvil para controlar esta aplicación", + "startServer": "Iniciar servidor", + "stopServer": "Detener servidor", "minimize": "Minimizar" }, "pairing": { - "scan": "Escanear", - "manual": "Manual", - "pairWithDesktop": "Emparejar con escritorio", - "enterSessionDetails": "Introduce los datos de la sesión que aparecen en tu dispositivo de escritorio", + "pairWithDesktop": "Conectar al escritorio", + "discoveryDescription": "Los dispositivos en tu red que ejecutan Plezy con la misma cuenta de Plex aparecerán automáticamente", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Introduce ID de sesión de 8 caracteres", - "pinHint": "Introduce PIN de 6 dígitos", "connecting": "Conectando...", - "tips": "Consejos", - "tipDesktop": "Abre Plezy en tu escritorio y activa Companion Remote desde los ajustes o el menú", - "tipScan": "Usa la pestaña Escanear para emparejar rápidamente escaneando el código QR de tu escritorio", - "tipWifi": "Asegúrate de que ambos dispositivos estén en la misma red WiFi", - "cameraPermissionRequired": "Se necesita permiso de cámara para escanear códigos QR.\nPor favor, concede acceso a la cámara en los ajustes de tu dispositivo.", - "cameraError": "No se pudo iniciar la cámara: ${error}", - "scanInstruction": "Apunta tu cámara al código QR que aparece en tu escritorio", - "invalidQrCode": "Formato de código QR no válido", - "validationHostRequired": "Por favor, introduce la dirección del host", - "validationHostFormat": "El formato debe ser IP:puerto (ej., 192.168.1.100:48632)", - "validationSessionIdRequired": "Por favor, introduce un ID de sesión", - "validationSessionIdLength": "El ID de sesión debe tener 8 caracteres", - "validationPinRequired": "Por favor, introduce un PIN", - "validationPinLength": "El PIN debe tener 6 dígitos", - "connectionTimedOut": "Tiempo de conexión agotado. Verifica el ID de sesión y el PIN.", - "sessionNotFound": "No se encontró la sesión. Verifica tus credenciales.", + "searchingForDevices": "Buscando dispositivos...", + "noDevicesFound": "No se encontraron dispositivos en tu red", + "noDevicesHint": "Asegúrate de que Plezy esté abierto en tu escritorio y que ambos dispositivos estén en la misma red WiFi", + "availableDevices": "Dispositivos disponibles", + "manualConnection": "Conexión manual", + "cryptoInitFailed": "No se pudo inicializar la conexión segura. Asegúrate de haber iniciado sesión en una cuenta de Plex.", + "validationHostRequired": "Ingresa la dirección del host", + "validationHostFormat": "El formato debe ser IP:puerto (ej. 192.168.1.100:48632)", + "connectionTimedOut": "Tiempo de conexión agotado. Asegúrate de que ambos dispositivos estén en la misma red.", + "sessionNotFound": "No se encontró el dispositivo. Asegúrate de que Plezy esté ejecutándose en el host.", + "authFailed": "Autenticación fallida. Asegúrate de que ambos dispositivos usen la misma cuenta de Plex.", "failedToConnect": "Error al conectar: ${error}" }, "remote": { diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index beabdfa9..bbaedcce 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Serveur de télécommande", + "companionRemoteServerDescription": "Autoriser les appareils mobiles de votre réseau à contrôler cette application" }, "search": { "hint": "Rechercher des films, des séries, de la musique...", @@ -778,47 +780,35 @@ "controlDesktop": "Contrôler un appareil de bureau", "connectedTo": "Connecté à ${name}", "session": { - "creatingSession": "Création de la session distante...", - "failedToCreate": "Échec de la création de la session distante :", - "noSession": "Aucune session disponible", - "scanQrCode": "Scanner le QR Code", - "orEnterManually": "Ou saisir manuellement", + "startingServer": "Démarrage du serveur distant...", + "failedToCreate": "Échec du démarrage du serveur distant :", "hostAddress": "Adresse de l'hôte", - "sessionId": "ID de session", - "pin": "PIN", "connected": "Connecté", - "waitingForConnection": "En attente de connexion...", + "serverRunning": "Serveur distant actif", + "serverStopped": "Serveur distant arrêté", + "serverRunningDescription": "Les appareils mobiles de votre réseau peuvent découvrir et se connecter à cette application", + "serverStoppedDescription": "Démarrez le serveur pour permettre aux appareils mobiles de se connecter", "usePhoneToControl": "Utilisez votre appareil mobile pour contrôler cette application", - "copiedToClipboard": "${label} copié dans le presse-papiers", - "copyToClipboard": "Copier dans le presse-papiers", - "newSession": "Nouvelle session", + "startServer": "Démarrer le serveur", + "stopServer": "Arrêter le serveur", "minimize": "Réduire" }, "pairing": { - "scan": "Scanner", - "manual": "Manuel", - "pairWithDesktop": "Jumeler avec un bureau", - "enterSessionDetails": "Saisissez les détails de la session affichés sur votre appareil de bureau", + "pairWithDesktop": "Se connecter au bureau", + "discoveryDescription": "Les appareils de votre réseau exécutant Plezy avec le même compte Plex apparaîtront automatiquement", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Saisissez l'ID de session à 8 caractères", - "pinHint": "Saisissez le PIN à 6 chiffres", "connecting": "Connexion...", - "tips": "Conseils", - "tipDesktop": "Ouvrez Plezy sur votre bureau et activez Companion Remote depuis les paramètres ou le menu", - "tipScan": "Utilisez l'onglet Scanner pour jumeler rapidement en scannant le QR code sur votre bureau", - "tipWifi": "Assurez-vous que les deux appareils sont sur le même réseau WiFi", - "cameraPermissionRequired": "L'autorisation de la caméra est requise pour scanner les QR codes.\nVeuillez accorder l'accès à la caméra dans les paramètres de votre appareil.", - "cameraError": "Impossible de démarrer la caméra : ${error}", - "scanInstruction": "Pointez votre caméra vers le QR code affiché sur votre bureau", - "invalidQrCode": "Format de QR code invalide", - "validationHostRequired": "Veuillez saisir l'adresse de l'hôte", - "validationHostFormat": "Le format doit être IP:port (ex : 192.168.1.100:48632)", - "validationSessionIdRequired": "Veuillez saisir un ID de session", - "validationSessionIdLength": "L'ID de session doit contenir 8 caractères", - "validationPinRequired": "Veuillez saisir un PIN", - "validationPinLength": "Le PIN doit contenir 6 chiffres", - "connectionTimedOut": "Délai de connexion expiré. Veuillez vérifier l'ID de session et le PIN.", - "sessionNotFound": "Session introuvable. Veuillez vérifier vos identifiants.", + "searchingForDevices": "Recherche d'appareils...", + "noDevicesFound": "Aucun appareil trouvé sur votre réseau", + "noDevicesHint": "Assurez-vous que Plezy est ouvert sur votre bureau et que les deux appareils sont sur le même réseau WiFi", + "availableDevices": "Appareils disponibles", + "manualConnection": "Connexion manuelle", + "cryptoInitFailed": "Impossible d'initialiser la connexion sécurisée. Assurez-vous d'être connecté à un compte Plex.", + "validationHostRequired": "Veuillez entrer l'adresse de l'hôte", + "validationHostFormat": "Le format doit être IP:port (ex. 192.168.1.100:48632)", + "connectionTimedOut": "Délai de connexion dépassé. Assurez-vous que les deux appareils sont sur le même réseau.", + "sessionNotFound": "Appareil introuvable. Assurez-vous que Plezy est en cours d'exécution sur l'hôte.", + "authFailed": "Échec de l'authentification. Assurez-vous que les deux appareils utilisent le même compte Plex.", "failedToConnect": "Échec de la connexion : ${error}" }, "remote": { diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 6bddaf81..f7afc53e 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Server telecomando", + "companionRemoteServerDescription": "Consenti ai dispositivi mobili nella tua rete di controllare questa app" }, "search": { "hint": "Cerca film. spettacoli, musica...", @@ -778,47 +780,35 @@ "controlDesktop": "Controlla un dispositivo desktop", "connectedTo": "Connesso a ${name}", "session": { - "creatingSession": "Creazione sessione remota...", - "failedToCreate": "Impossibile creare la sessione remota:", - "noSession": "Nessuna sessione disponibile", - "scanQrCode": "Scansiona QR Code", - "orEnterManually": "Oppure inserisci manualmente", + "startingServer": "Avvio del server remoto...", + "failedToCreate": "Impossibile avviare il server remoto:", "hostAddress": "Indirizzo host", - "sessionId": "ID sessione", - "pin": "PIN", "connected": "Connesso", - "waitingForConnection": "In attesa di connessione...", + "serverRunning": "Server remoto attivo", + "serverStopped": "Server remoto arrestato", + "serverRunningDescription": "I dispositivi mobili nella tua rete possono scoprire e connettersi a questa app", + "serverStoppedDescription": "Avvia il server per consentire ai dispositivi mobili di connettersi", "usePhoneToControl": "Usa il tuo dispositivo mobile per controllare questa app", - "copiedToClipboard": "${label} copiato negli appunti", - "copyToClipboard": "Copia negli appunti", - "newSession": "Nuova sessione", + "startServer": "Avvia server", + "stopServer": "Arresta server", "minimize": "Riduci" }, "pairing": { - "scan": "Scansiona", - "manual": "Manuale", - "pairWithDesktop": "Associa con desktop", - "enterSessionDetails": "Inserisci i dettagli della sessione mostrati sul tuo dispositivo desktop", + "pairWithDesktop": "Connetti al desktop", + "discoveryDescription": "I dispositivi nella tua rete che eseguono Plezy con lo stesso account Plex appariranno automaticamente", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Inserisci ID sessione di 8 caratteri", - "pinHint": "Inserisci PIN di 6 cifre", "connecting": "Connessione...", - "tips": "Suggerimenti", - "tipDesktop": "Apri Plezy sul tuo desktop e abilita Companion Remote dalle impostazioni o dal menu", - "tipScan": "Usa la scheda Scansiona per associare rapidamente scansionando il QR code sul tuo desktop", - "tipWifi": "Assicurati che entrambi i dispositivi siano sulla stessa rete WiFi", - "cameraPermissionRequired": "L'autorizzazione della fotocamera è necessaria per scansionare i QR code.\nConcedi l'accesso alla fotocamera nelle impostazioni del dispositivo.", - "cameraError": "Impossibile avviare la fotocamera: ${error}", - "scanInstruction": "Punta la fotocamera verso il QR code mostrato sul tuo desktop", - "invalidQrCode": "Formato QR code non valido", + "searchingForDevices": "Ricerca dispositivi...", + "noDevicesFound": "Nessun dispositivo trovato nella tua rete", + "noDevicesHint": "Assicurati che Plezy sia aperto sul tuo desktop e che entrambi i dispositivi siano sulla stessa rete WiFi", + "availableDevices": "Dispositivi disponibili", + "manualConnection": "Connessione manuale", + "cryptoInitFailed": "Impossibile inizializzare la connessione sicura. Assicurati di aver effettuato l'accesso a un account Plex.", "validationHostRequired": "Inserisci l'indirizzo host", "validationHostFormat": "Il formato deve essere IP:porta (es. 192.168.1.100:48632)", - "validationSessionIdRequired": "Inserisci un ID sessione", - "validationSessionIdLength": "L'ID sessione deve essere di 8 caratteri", - "validationPinRequired": "Inserisci un PIN", - "validationPinLength": "Il PIN deve essere di 6 cifre", - "connectionTimedOut": "Connessione scaduta. Verifica l'ID sessione e il PIN.", - "sessionNotFound": "Sessione non trovata. Verifica le tue credenziali.", + "connectionTimedOut": "Connessione scaduta. Assicurati che entrambi i dispositivi siano sulla stessa rete.", + "sessionNotFound": "Dispositivo non trovato. Assicurati che Plezy sia in esecuzione sull'host.", + "authFailed": "Autenticazione fallita. Assicurati che entrambi i dispositivi utilizzino lo stesso account Plex.", "failedToConnect": "Connessione fallita: ${error}" }, "remote": { diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index d638d02f..209ea956 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "コンパニオンリモートサーバー", + "companionRemoteServerDescription": "ネットワーク上のモバイルデバイスからこのアプリを操作できるようにする" }, "search": { "hint": "映画、番組、音楽を検索...", @@ -778,47 +780,35 @@ "controlDesktop": "デスクトップデバイスを操作", "connectedTo": "${name}に接続中", "session": { - "creatingSession": "リモートセッションを作成中...", - "failedToCreate": "リモートセッションの作成に失敗しました:", - "noSession": "利用可能なセッションがありません", - "scanQrCode": "QRコードをスキャン", - "orEnterManually": "または手動で入力", + "startingServer": "リモートサーバーを起動中...", + "failedToCreate": "リモートサーバーの起動に失敗しました:", "hostAddress": "ホストアドレス", - "sessionId": "セッションID", - "pin": "PIN", "connected": "接続済み", - "waitingForConnection": "接続を待機中...", - "usePhoneToControl": "モバイルデバイスでこのアプリを操作", - "copiedToClipboard": "${label}をクリップボードにコピーしました", - "copyToClipboard": "クリップボードにコピー", - "newSession": "新しいセッション", + "serverRunning": "リモートサーバー稼働中", + "serverStopped": "リモートサーバー停止中", + "serverRunningDescription": "ネットワーク上のモバイルデバイスがこのアプリを検出して接続できます", + "serverStoppedDescription": "モバイルデバイスの接続を許可するにはサーバーを起動してください", + "usePhoneToControl": "モバイルデバイスでこのアプリを操作できます", + "startServer": "サーバーを起動", + "stopServer": "サーバーを停止", "minimize": "最小化" }, "pairing": { - "scan": "スキャン", - "manual": "手動", - "pairWithDesktop": "デスクトップとペアリング", - "enterSessionDetails": "デスクトップデバイスに表示されたセッション情報を入力", + "pairWithDesktop": "デスクトップに接続", + "discoveryDescription": "同じPlexアカウントでPlezyを実行しているネットワーク上のデバイスが自動的に表示されます", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "8文字のセッションIDを入力", - "pinHint": "6桁のPINを入力", "connecting": "接続中...", - "tips": "ヒント", - "tipDesktop": "デスクトップでPlezyを開き、設定またはメニューからコンパニオンリモートを有効にしてください", - "tipScan": "スキャンタブを使用して、デスクトップのQRコードをスキャンして素早くペアリング", - "tipWifi": "両方のデバイスが同じWiFiネットワークに接続されていることを確認", - "cameraPermissionRequired": "QRコードをスキャンするにはカメラの権限が必要です。\nデバイスの設定でカメラへのアクセスを許可してください。", - "cameraError": "カメラを起動できませんでした: ${error}", - "scanInstruction": "デスクトップに表示されたQRコードにカメラを向けてください", - "invalidQrCode": "無効なQRコード形式", + "searchingForDevices": "デバイスを検索中...", + "noDevicesFound": "ネットワーク上にデバイスが見つかりません", + "noDevicesHint": "デスクトップでPlezyが開いていること、両方のデバイスが同じWiFiネットワーク上にあることを確認してください", + "availableDevices": "利用可能なデバイス", + "manualConnection": "手動接続", + "cryptoInitFailed": "安全な接続を初期化できませんでした。Plexアカウントにサインインしていることを確認してください。", "validationHostRequired": "ホストアドレスを入力してください", "validationHostFormat": "形式はIP:ポートである必要があります(例: 192.168.1.100:48632)", - "validationSessionIdRequired": "セッションIDを入力してください", - "validationSessionIdLength": "セッションIDは8文字である必要があります", - "validationPinRequired": "PINを入力してください", - "validationPinLength": "PINは6桁である必要があります", - "connectionTimedOut": "接続がタイムアウトしました。セッションIDとPINを確認してください。", - "sessionNotFound": "セッションが見つかりませんでした。認証情報を確認してください。", + "connectionTimedOut": "接続がタイムアウトしました。両方のデバイスが同じネットワーク上にあることを確認してください。", + "sessionNotFound": "デバイスが見つかりません。ホストでPlezyが実行されていることを確認してください。", + "authFailed": "認証に失敗しました。両方のデバイスが同じPlexアカウントを使用していることを確認してください。", "failedToConnect": "接続に失敗しました: ${error}" }, "remote": { diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index fa6da526..e9772b55 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "컴패니언 리모트 서버", + "companionRemoteServerDescription": "네트워크의 모바일 기기가 이 앱을 제어할 수 있도록 허용" }, "search": { "hint": "영화, 시리즈, 음악 등을 검색하세요...", @@ -778,47 +780,35 @@ "controlDesktop": "데스크톱 기기 제어", "connectedTo": "${name}에 연결됨", "session": { - "creatingSession": "원격 세션 생성 중...", - "failedToCreate": "원격 세션 생성 실패:", - "noSession": "사용 가능한 세션이 없습니다", - "scanQrCode": "QR 코드 스캔", - "orEnterManually": "또는 수동으로 입력", + "startingServer": "원격 서버 시작 중...", + "failedToCreate": "원격 서버를 시작하지 못했습니다:", "hostAddress": "호스트 주소", - "sessionId": "세션 ID", - "pin": "PIN", "connected": "연결됨", - "waitingForConnection": "연결 대기 중...", + "serverRunning": "원격 서버 활성", + "serverStopped": "원격 서버 중지됨", + "serverRunningDescription": "네트워크의 모바일 기기가 이 앱을 검색하고 연결할 수 있습니다", + "serverStoppedDescription": "모바일 기기의 연결을 허용하려면 서버를 시작하세요", "usePhoneToControl": "모바일 기기로 이 앱을 제어하세요", - "copiedToClipboard": "${label}이(가) 클립보드에 복사되었습니다", - "copyToClipboard": "클립보드에 복사", - "newSession": "새 세션", + "startServer": "서버 시작", + "stopServer": "서버 중지", "minimize": "최소화" }, "pairing": { - "scan": "스캔", - "manual": "수동", - "pairWithDesktop": "데스크톱과 페어링", - "enterSessionDetails": "데스크톱 기기에 표시된 세션 정보를 입력하세요", + "pairWithDesktop": "데스크톱에 연결", + "discoveryDescription": "같은 Plex 계정으로 Plezy를 실행 중인 네트워크의 기기가 자동으로 표시됩니다", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "8자리 세션 ID 입력", - "pinHint": "6자리 PIN 입력", "connecting": "연결 중...", - "tips": "팁", - "tipDesktop": "데스크톱에서 Plezy를 열고 설정 또는 메뉴에서 Companion Remote를 활성화하세요", - "tipScan": "스캔 탭을 사용하여 데스크톱의 QR 코드를 스캔하면 빠르게 페어링할 수 있습니다", - "tipWifi": "두 기기가 동일한 WiFi 네트워크에 연결되어 있는지 확인하세요", - "cameraPermissionRequired": "QR 코드를 스캔하려면 카메라 권한이 필요합니다.\n기기 설정에서 카메라 접근을 허용해 주세요.", - "cameraError": "카메라를 시작할 수 없습니다: ${error}", - "scanInstruction": "데스크톱에 표시된 QR 코드에 카메라를 향하세요", - "invalidQrCode": "유효하지 않은 QR 코드 형식", + "searchingForDevices": "기기 검색 중...", + "noDevicesFound": "네트워크에서 기기를 찾을 수 없습니다", + "noDevicesHint": "데스크톱에서 Plezy가 열려 있고 두 기기가 같은 WiFi 네트워크에 있는지 확인하세요", + "availableDevices": "사용 가능한 기기", + "manualConnection": "수동 연결", + "cryptoInitFailed": "보안 연결을 초기화할 수 없습니다. Plex 계정에 로그인되어 있는지 확인하세요.", "validationHostRequired": "호스트 주소를 입력하세요", - "validationHostFormat": "IP:포트 형식이어야 합니다 (예: 192.168.1.100:48632)", - "validationSessionIdRequired": "세션 ID를 입력하세요", - "validationSessionIdLength": "세션 ID는 8자리여야 합니다", - "validationPinRequired": "PIN을 입력하세요", - "validationPinLength": "PIN은 6자리여야 합니다", - "connectionTimedOut": "연결 시간이 초과되었습니다. 세션 ID와 PIN을 확인하세요.", - "sessionNotFound": "세션을 찾을 수 없습니다. 자격 증명을 확인하세요.", + "validationHostFormat": "형식은 IP:포트여야 합니다 (예: 192.168.1.100:48632)", + "connectionTimedOut": "연결 시간이 초과되었습니다. 두 기기가 같은 네트워크에 있는지 확인하세요.", + "sessionNotFound": "기기를 찾을 수 없습니다. 호스트에서 Plezy가 실행 중인지 확인하세요.", + "authFailed": "인증에 실패했습니다. 두 기기가 같은 Plex 계정을 사용하는지 확인하세요.", "failedToConnect": "연결 실패: ${error}" }, "remote": { diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index dbf89263..9f17cae6 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Companion Remote-server", + "companionRemoteServerDescription": "Tillat mobilenheter på nettverket ditt å styre denne appen" }, "search": { "hint": "Søk i filmer, serier, musikk...", @@ -778,47 +780,35 @@ "controlDesktop": "Kontroller en stasjonær enhet", "connectedTo": "Tilkoblet ${name}", "session": { - "creatingSession": "Oppretter fjernøkt...", - "failedToCreate": "Kunne ikke opprette fjernøkt:", - "noSession": "Ingen økt tilgjengelig", - "scanQrCode": "Skann QR-kode", - "orEnterManually": "Eller skriv inn manuelt", + "startingServer": "Starter fjernserver...", + "failedToCreate": "Kunne ikke starte fjernserver:", "hostAddress": "Vertsadresse", - "sessionId": "Økt-ID", - "pin": "PIN", "connected": "Tilkoblet", - "waitingForConnection": "Venter på tilkobling...", - "usePhoneToControl": "Bruk mobilenheten din til å kontrollere denne appen", - "copiedToClipboard": "${label} kopiert til utklippstavle", - "copyToClipboard": "Kopier til utklippstavle", - "newSession": "Ny økt", + "serverRunning": "Fjernserver aktiv", + "serverStopped": "Fjernserver stoppet", + "serverRunningDescription": "Mobilenheter på nettverket ditt kan oppdage og koble til denne appen", + "serverStoppedDescription": "Start serveren for å la mobilenheter koble til", + "usePhoneToControl": "Bruk mobilenheten din til å styre denne appen", + "startServer": "Start server", + "stopServer": "Stopp server", "minimize": "Minimer" }, "pairing": { - "scan": "Skann", - "manual": "Manuell", - "pairWithDesktop": "Koble til stasjonær", - "enterSessionDetails": "Skriv inn øktdetaljene som vises på den stasjonære enheten din", + "pairWithDesktop": "Koble til datamaskin", + "discoveryDescription": "Enheter på nettverket ditt som kjører Plezy med samme Plex-konto vil vises automatisk", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Skriv inn 8-tegns økt-ID", - "pinHint": "Skriv inn 6-sifret PIN", "connecting": "Kobler til...", - "tips": "Tips", - "tipDesktop": "Åpne Plezy på datamaskinen din og aktiver Følgesvenn-fjernkontroll fra innstillinger eller meny", - "tipScan": "Bruk Skann-fanen for å raskt koble til ved å skanne QR-koden på datamaskinen din", - "tipWifi": "Sørg for at begge enhetene er på samme WiFi-nettverk", - "cameraPermissionRequired": "Kameratillatelse kreves for å skanne QR-koder.\nVennligst gi kameratilgang i enhetsinnstillingene.", - "cameraError": "Kunne ikke starte kamera: ${error}", - "scanInstruction": "Pek kameraet mot QR-koden som vises på datamaskinen din", - "invalidQrCode": "Ugyldig QR-kodeformat", - "validationHostRequired": "Vennligst skriv inn vertsadresse", - "validationHostFormat": "Formatet må være IP:port (f.eks. 192.168.1.100:48632)", - "validationSessionIdRequired": "Vennligst skriv inn en økt-ID", - "validationSessionIdLength": "Økt-ID må være 8 tegn", - "validationPinRequired": "Vennligst skriv inn en PIN", - "validationPinLength": "PIN må være 6 sifre", - "connectionTimedOut": "Tilkoblingen ble tidsavbrutt. Sjekk økt-ID og PIN.", - "sessionNotFound": "Kunne ikke finne økten. Sjekk legitimasjonen din.", + "searchingForDevices": "Søker etter enheter...", + "noDevicesFound": "Ingen enheter funnet på nettverket ditt", + "noDevicesHint": "Sørg for at Plezy er åpen på datamaskinen din og at begge enhetene er på samme WiFi-nettverk", + "availableDevices": "Tilgjengelige enheter", + "manualConnection": "Manuell tilkobling", + "cryptoInitFailed": "Kunne ikke initialisere sikker tilkobling. Sørg for at du er logget inn på en Plex-konto.", + "validationHostRequired": "Vennligst oppgi vertsadresse", + "validationHostFormat": "Format må være IP:port (f.eks. 192.168.1.100:48632)", + "connectionTimedOut": "Tilkoblingen ble tidsavbrutt. Sørg for at begge enhetene er på samme nettverk.", + "sessionNotFound": "Kunne ikke finne enheten. Sørg for at Plezy kjører på verten.", + "authFailed": "Autentisering mislyktes. Sørg for at begge enhetene bruker samme Plex-konto.", "failedToConnect": "Kunne ikke koble til: ${error}" }, "remote": { diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 1720d1f5..36b9c4ef 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Companion Remote-server", + "companionRemoteServerDescription": "Sta mobiele apparaten op je netwerk toe om deze app te bedienen" }, "search": { "hint": "Zoek films, series, muziek...", @@ -778,48 +780,36 @@ "controlDesktop": "Bedien een desktop-apparaat", "connectedTo": "Verbonden met ${name}", "session": { - "creatingSession": "Externe sessie aanmaken...", - "failedToCreate": "Kan externe sessie niet aanmaken:", - "noSession": "Geen sessie beschikbaar", - "scanQrCode": "Scan QR-code", - "orEnterManually": "Of voer handmatig in", + "startingServer": "Externe server starten...", + "failedToCreate": "Kan externe server niet starten:", "hostAddress": "Hostadres", - "sessionId": "Sessie-ID", - "pin": "PIN", "connected": "Verbonden", - "waitingForConnection": "Wachten op verbinding...", + "serverRunning": "Externe server actief", + "serverStopped": "Externe server gestopt", + "serverRunningDescription": "Mobiele apparaten op je netwerk kunnen deze app ontdekken en ermee verbinden", + "serverStoppedDescription": "Start de server om mobiele apparaten te laten verbinden", "usePhoneToControl": "Gebruik je mobiele apparaat om deze app te bedienen", - "copiedToClipboard": "${label} gekopieerd naar klembord", - "copyToClipboard": "Kopieer naar klembord", - "newSession": "Nieuwe sessie", + "startServer": "Server starten", + "stopServer": "Server stoppen", "minimize": "Minimaliseren" }, "pairing": { - "scan": "Scannen", - "manual": "Handmatig", - "pairWithDesktop": "Koppelen met desktop", - "enterSessionDetails": "Voer de sessiegegevens in die op je desktop-apparaat worden getoond", + "pairWithDesktop": "Verbinden met desktop", + "discoveryDescription": "Apparaten op je netwerk die Plezy gebruiken met hetzelfde Plex-account verschijnen automatisch", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Voer 8-tekens sessie-ID in", - "pinHint": "Voer 6-cijferige PIN in", "connecting": "Verbinden...", - "tips": "Tips", - "tipDesktop": "Open Plezy op je desktop en schakel Companion Remote in via instellingen of menu", - "tipScan": "Gebruik het tabblad Scannen om snel te koppelen door de QR-code op je desktop te scannen", - "tipWifi": "Zorg ervoor dat beide apparaten op hetzelfde WiFi-netwerk zitten", - "cameraPermissionRequired": "Cameratoestemming is vereist om QR-codes te scannen.\nGeef cameratoegang in je apparaatinstellingen.", - "cameraError": "Kan camera niet starten: ${error}", - "scanInstruction": "Richt je camera op de QR-code die op je desktop wordt getoond", - "invalidQrCode": "Ongeldig QR-codeformaat", - "validationHostRequired": "Voer een hostadres in", + "searchingForDevices": "Apparaten zoeken...", + "noDevicesFound": "Geen apparaten gevonden op je netwerk", + "noDevicesHint": "Zorg ervoor dat Plezy geopend is op je desktop en dat beide apparaten op hetzelfde WiFi-netwerk zitten", + "availableDevices": "Beschikbare apparaten", + "manualConnection": "Handmatige verbinding", + "cryptoInitFailed": "Kan beveiligde verbinding niet initialiseren. Zorg ervoor dat je bent ingelogd bij een Plex-account.", + "validationHostRequired": "Voer het hostadres in", "validationHostFormat": "Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)", - "validationSessionIdRequired": "Voer een sessie-ID in", - "validationSessionIdLength": "Sessie-ID moet 8 tekens zijn", - "validationPinRequired": "Voer een PIN in", - "validationPinLength": "PIN moet 6 cijfers zijn", - "connectionTimedOut": "Verbinding verlopen. Controleer de sessie-ID en PIN.", - "sessionNotFound": "Kan de sessie niet vinden. Controleer je gegevens.", - "failedToConnect": "Verbinden mislukt: ${error}" + "connectionTimedOut": "Verbinding verlopen. Zorg ervoor dat beide apparaten op hetzelfde netwerk zitten.", + "sessionNotFound": "Apparaat niet gevonden. Zorg ervoor dat Plezy draait op de host.", + "authFailed": "Authenticatie mislukt. Zorg ervoor dat beide apparaten hetzelfde Plex-account gebruiken.", + "failedToConnect": "Kan niet verbinden: ${error}" }, "remote": { "disconnectConfirm": "Wil je de verbinding met de externe sessie verbreken?", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 28ca1046..04fb3eb7 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Serwer zdalnego sterowania", + "companionRemoteServerDescription": "Pozwól urządzeniom mobilnym w sieci sterować tą aplikacją" }, "search": { "hint": "Szukaj filmów, seriali, muzyki...", @@ -778,47 +780,35 @@ "controlDesktop": "Steruj urządzeniem desktop", "connectedTo": "Połączono z ${name}", "session": { - "creatingSession": "Tworzenie sesji zdalnej...", - "failedToCreate": "Nie udało się utworzyć sesji zdalnej:", - "noSession": "Brak dostępnej sesji", - "scanQrCode": "Zeskanuj kod QR", - "orEnterManually": "Lub wprowadź ręcznie", + "startingServer": "Uruchamianie serwera zdalnego...", + "failedToCreate": "Nie udało się uruchomić serwera zdalnego:", "hostAddress": "Adres hosta", - "sessionId": "ID sesji", - "pin": "PIN", "connected": "Połączono", - "waitingForConnection": "Oczekiwanie na połączenie...", - "usePhoneToControl": "Użyj urządzenia mobilnego do sterowania tą aplikacją", - "copiedToClipboard": "${label} skopiowane do schowka", - "copyToClipboard": "Kopiuj do schowka", - "newSession": "Nowa sesja", + "serverRunning": "Serwer zdalny aktywny", + "serverStopped": "Serwer zdalny zatrzymany", + "serverRunningDescription": "Urządzenia mobilne w sieci mogą wykryć tę aplikację i się z nią połączyć", + "serverStoppedDescription": "Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych", + "usePhoneToControl": "Użyj urządzenia mobilnego, aby sterować tą aplikacją", + "startServer": "Uruchom serwer", + "stopServer": "Zatrzymaj serwer", "minimize": "Minimalizuj" }, "pairing": { - "scan": "Skanuj", - "manual": "Ręcznie", - "pairWithDesktop": "Sparuj z komputerem", - "enterSessionDetails": "Wprowadź dane sesji wyświetlone na urządzeniu desktop", + "pairWithDesktop": "Połącz z komputerem", + "discoveryDescription": "Urządzenia w sieci z Plezy na tym samym koncie Plex pojawią się automatycznie", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Wprowadź 8-znakowe ID sesji", - "pinHint": "Wprowadź 6-cyfrowy PIN", "connecting": "Łączenie...", - "tips": "Wskazówki", - "tipDesktop": "Otwórz Plezy na komputerze i włącz Pilota zdalnego w ustawieniach lub menu", - "tipScan": "Użyj zakładki Skanuj, aby szybko sparować skanując kod QR na komputerze", - "tipWifi": "Upewnij się, że oba urządzenia są w tej samej sieci WiFi", - "cameraPermissionRequired": "Uprawnienie do kamery jest wymagane do skanowania kodów QR.\nPrzyznaj dostęp do kamery w ustawieniach urządzenia.", - "cameraError": "Nie udało się uruchomić kamery: ${error}", - "scanInstruction": "Skieruj kamerę na kod QR wyświetlony na komputerze", - "invalidQrCode": "Nieprawidłowy format kodu QR", + "searchingForDevices": "Szukanie urządzeń...", + "noDevicesFound": "Nie znaleziono urządzeń w sieci", + "noDevicesHint": "Upewnij się, że Plezy jest otwarte na komputerze i oba urządzenia są w tej samej sieci WiFi", + "availableDevices": "Dostępne urządzenia", + "manualConnection": "Połączenie ręczne", + "cryptoInitFailed": "Nie udało się zainicjować bezpiecznego połączenia. Upewnij się, że jesteś zalogowany na konto Plex.", "validationHostRequired": "Wprowadź adres hosta", "validationHostFormat": "Format musi być IP:port (np. 192.168.1.100:48632)", - "validationSessionIdRequired": "Wprowadź ID sesji", - "validationSessionIdLength": "ID sesji musi mieć 8 znaków", - "validationPinRequired": "Wprowadź PIN", - "validationPinLength": "PIN musi mieć 6 cyfr", - "connectionTimedOut": "Upłynął czas połączenia. Sprawdź ID sesji i PIN.", - "sessionNotFound": "Nie znaleziono sesji. Sprawdź swoje dane.", + "connectionTimedOut": "Przekroczono limit czasu połączenia. Upewnij się, że oba urządzenia są w tej samej sieci.", + "sessionNotFound": "Nie znaleziono urządzenia. Upewnij się, że Plezy działa na hoście.", + "authFailed": "Uwierzytelnianie nie powiodło się. Upewnij się, że oba urządzenia używają tego samego konta Plex.", "failedToConnect": "Nie udało się połączyć: ${error}" }, "remote": { diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 0bb8d1de..96cac004 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Servidor de controlo remoto", + "companionRemoteServerDescription": "Permitir que dispositivos móveis na sua rede controlem esta aplicação" }, "search": { "hint": "Buscar filmes, séries, músicas...", @@ -778,47 +780,35 @@ "controlDesktop": "Controlar um dispositivo desktop", "connectedTo": "Conectado a ${name}", "session": { - "creatingSession": "Criando sessão remota...", - "failedToCreate": "Falha ao criar sessão remota:", - "noSession": "Nenhuma sessão disponível", - "scanQrCode": "Escanear QR Code", - "orEnterManually": "Ou insira manualmente", - "hostAddress": "Endereço do Host", - "sessionId": "ID da Sessão", - "pin": "PIN", + "startingServer": "A iniciar servidor remoto...", + "failedToCreate": "Falha ao iniciar o servidor remoto:", + "hostAddress": "Endereço do host", "connected": "Conectado", - "waitingForConnection": "Aguardando conexão...", - "usePhoneToControl": "Use seu dispositivo móvel para controlar este app", - "copiedToClipboard": "${label} copiado para a área de transferência", - "copyToClipboard": "Copiar para área de transferência", - "newSession": "Nova Sessão", + "serverRunning": "Servidor remoto ativo", + "serverStopped": "Servidor remoto parado", + "serverRunningDescription": "Dispositivos móveis na sua rede podem descobrir e conectar-se a esta aplicação", + "serverStoppedDescription": "Inicie o servidor para permitir que dispositivos móveis se conectem", + "usePhoneToControl": "Use o seu dispositivo móvel para controlar esta aplicação", + "startServer": "Iniciar servidor", + "stopServer": "Parar servidor", "minimize": "Minimizar" }, "pairing": { - "scan": "Escanear", - "manual": "Manual", - "pairWithDesktop": "Parear com Desktop", - "enterSessionDetails": "Insira os detalhes da sessão exibidos no seu dispositivo desktop", + "pairWithDesktop": "Conectar ao desktop", + "discoveryDescription": "Dispositivos na sua rede a executar Plezy com a mesma conta Plex aparecerão automaticamente", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Insira o ID da sessão de 8 caracteres", - "pinHint": "Insira o PIN de 6 dígitos", - "connecting": "Conectando...", - "tips": "Dicas", - "tipDesktop": "Abra o Plezy no desktop e ative o Controle Remoto nas configurações ou menu", - "tipScan": "Use a aba Escanear para parear rapidamente escaneando o QR code no seu desktop", - "tipWifi": "Certifique-se de que ambos os dispositivos estão na mesma rede WiFi", - "cameraPermissionRequired": "A permissão da câmera é necessária para escanear QR codes.\nConceda acesso à câmera nas configurações do dispositivo.", - "cameraError": "Não foi possível iniciar a câmera: ${error}", - "scanInstruction": "Aponte sua câmera para o QR code exibido no seu desktop", - "invalidQrCode": "Formato de QR code inválido", - "validationHostRequired": "Insira o endereço do host", - "validationHostFormat": "O formato deve ser IP:porta (ex: 192.168.1.100:48632)", - "validationSessionIdRequired": "Insira um ID de sessão", - "validationSessionIdLength": "O ID da sessão deve ter 8 caracteres", - "validationPinRequired": "Insira um PIN", - "validationPinLength": "O PIN deve ter 6 dígitos", - "connectionTimedOut": "Tempo de conexão esgotado. Verifique o ID da sessão e o PIN.", - "sessionNotFound": "Sessão não encontrada. Verifique suas credenciais.", + "connecting": "A conectar...", + "searchingForDevices": "A procurar dispositivos...", + "noDevicesFound": "Nenhum dispositivo encontrado na sua rede", + "noDevicesHint": "Certifique-se de que o Plezy está aberto no seu desktop e que ambos os dispositivos estão na mesma rede WiFi", + "availableDevices": "Dispositivos disponíveis", + "manualConnection": "Conexão manual", + "cryptoInitFailed": "Não foi possível inicializar a conexão segura. Certifique-se de que está conectado a uma conta Plex.", + "validationHostRequired": "Introduza o endereço do host", + "validationHostFormat": "O formato deve ser IP:porta (ex. 192.168.1.100:48632)", + "connectionTimedOut": "Tempo de conexão esgotado. Certifique-se de que ambos os dispositivos estão na mesma rede.", + "sessionNotFound": "Dispositivo não encontrado. Certifique-se de que o Plezy está em execução no host.", + "authFailed": "Autenticação falhou. Certifique-se de que ambos os dispositivos usam a mesma conta Plex.", "failedToConnect": "Falha ao conectar: ${error}" }, "remote": { diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index d5105501..3fecdba9 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Сервер удалённого управления", + "companionRemoteServerDescription": "Разрешить мобильным устройствам в сети управлять этим приложением" }, "search": { "hint": "Поиск фильмов, сериалов, музыки...", @@ -778,47 +780,35 @@ "controlDesktop": "Управлять десктопным устройством", "connectedTo": "Подключено к ${name}", "session": { - "creatingSession": "Создание удалённой сессии...", - "failedToCreate": "Не удалось создать удалённую сессию:", - "noSession": "Нет доступных сессий", - "scanQrCode": "Сканировать QR-код", - "orEnterManually": "Или введите вручную", + "startingServer": "Запуск удалённого сервера...", + "failedToCreate": "Не удалось запустить удалённый сервер:", "hostAddress": "Адрес хоста", - "sessionId": "ID сессии", - "pin": "PIN", "connected": "Подключено", - "waitingForConnection": "Ожидание подключения...", + "serverRunning": "Удалённый сервер активен", + "serverStopped": "Удалённый сервер остановлен", + "serverRunningDescription": "Мобильные устройства в вашей сети могут обнаруживать это приложение и подключаться к нему", + "serverStoppedDescription": "Запустите сервер, чтобы разрешить подключение мобильных устройств", "usePhoneToControl": "Используйте мобильное устройство для управления этим приложением", - "copiedToClipboard": "${label} скопировано в буфер обмена", - "copyToClipboard": "Скопировать в буфер обмена", - "newSession": "Новая сессия", + "startServer": "Запустить сервер", + "stopServer": "Остановить сервер", "minimize": "Свернуть" }, "pairing": { - "scan": "Сканировать", - "manual": "Вручную", - "pairWithDesktop": "Сопряжение с ПК", - "enterSessionDetails": "Введите данные сессии, показанные на десктопном устройстве", + "pairWithDesktop": "Подключиться к компьютеру", + "discoveryDescription": "Устройства в вашей сети с Plezy на том же аккаунте Plex появятся автоматически", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Введите 8-символьный ID сессии", - "pinHint": "Введите 6-значный PIN", "connecting": "Подключение...", - "tips": "Советы", - "tipDesktop": "Откройте Plezy на десктопе и включите пульт управления в настройках или меню", - "tipScan": "Используйте вкладку «Сканировать» для быстрого сопряжения через QR-код на десктопе", - "tipWifi": "Убедитесь, что оба устройства находятся в одной WiFi-сети", - "cameraPermissionRequired": "Для сканирования QR-кодов требуется разрешение на камеру.\nПредоставьте доступ к камере в настройках устройства.", - "cameraError": "Не удалось запустить камеру: ${error}", - "scanInstruction": "Наведите камеру на QR-код, показанный на десктопе", - "invalidQrCode": "Недопустимый формат QR-кода", + "searchingForDevices": "Поиск устройств...", + "noDevicesFound": "Устройства в вашей сети не найдены", + "noDevicesHint": "Убедитесь, что Plezy открыт на вашем компьютере и оба устройства находятся в одной сети WiFi", + "availableDevices": "Доступные устройства", + "manualConnection": "Ручное подключение", + "cryptoInitFailed": "Не удалось инициализировать безопасное соединение. Убедитесь, что вы вошли в аккаунт Plex.", "validationHostRequired": "Введите адрес хоста", "validationHostFormat": "Формат должен быть IP:порт (например, 192.168.1.100:48632)", - "validationSessionIdRequired": "Введите ID сессии", - "validationSessionIdLength": "ID сессии должен содержать 8 символов", - "validationPinRequired": "Введите PIN", - "validationPinLength": "PIN должен содержать 6 цифр", - "connectionTimedOut": "Время подключения истекло. Проверьте ID сессии и PIN.", - "sessionNotFound": "Сессия не найдена. Проверьте ваши учётные данные.", + "connectionTimedOut": "Время подключения истекло. Убедитесь, что оба устройства находятся в одной сети.", + "sessionNotFound": "Устройство не найдено. Убедитесь, что Plezy запущен на хосте.", + "authFailed": "Ошибка аутентификации. Убедитесь, что оба устройства используют один и тот же аккаунт Plex.", "failedToConnect": "Не удалось подключиться: ${error}" }, "remote": { diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 8f79e5fc..67459307 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 15 -/// Strings: 13110 (874 per locale) +/// Strings: 12960 (864 per locale) /// -/// Built on 2026-04-13 at 20:34 UTC +/// Built on 2026-04-14 at 10:29 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index 369dfff5..a476d0b4 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsDa implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Companion Remote Server'; + @override String get companionRemoteServerDescription => 'Tillad mobilenheder på dit netværk at styre denne app'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionDa implements TranslationsCompanionRemo final TranslationsDa _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Opretter fjernsession...'; - @override String get failedToCreate => 'Kunne ikke oprette fjernsession:'; - @override String get noSession => 'Ingen session tilgængelig'; - @override String get scanQrCode => 'Scan QR-kode'; - @override String get orEnterManually => 'Eller indtast manuelt'; - @override String get hostAddress => 'Værtadresse'; - @override String get sessionId => 'Sessions-ID'; - @override String get pin => 'PIN'; + @override String get startingServer => 'Starter fjernserver...'; + @override String get failedToCreate => 'Kunne ikke starte fjernserver:'; + @override String get hostAddress => 'Værtsadresse'; @override String get connected => 'Forbundet'; - @override String get waitingForConnection => 'Venter på forbindelse...'; + @override String get serverRunning => 'Fjernserver aktiv'; + @override String get serverStopped => 'Fjernserver stoppet'; + @override String get serverRunningDescription => 'Mobilenheder på dit netværk kan finde og oprette forbindelse til denne app'; + @override String get serverStoppedDescription => 'Start serveren for at tillade mobilenheder at oprette forbindelse'; @override String get usePhoneToControl => 'Brug din mobilenhed til at styre denne app'; - @override String copiedToClipboard({required Object label}) => '${label} kopieret til udklipsholder'; - @override String get copyToClipboard => 'Kopiér til udklipsholder'; - @override String get newSession => 'Ny session'; + @override String get startServer => 'Start server'; + @override String get stopServer => 'Stop server'; @override String get minimize => 'Minimér'; } @@ -1298,31 +1297,22 @@ class _TranslationsCompanionRemotePairingDa implements TranslationsCompanionRemo final TranslationsDa _root; // ignore: unused_field // Translations - @override String get scan => 'Scan'; - @override String get manual => 'Manuel'; - @override String get pairWithDesktop => 'Par med desktop'; - @override String get enterSessionDetails => 'Indtast sessionsdetaljerne vist på din desktopenhed'; + @override String get pairWithDesktop => 'Opret forbindelse til computer'; + @override String get discoveryDescription => 'Enheder på dit netværk, der kører Plezy med den samme Plex-konto, vises automatisk'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Indtast 8-tegns sessions-ID'; - @override String get pinHint => 'Indtast 6-cifret PIN'; - @override String get connecting => 'Forbinder...'; - @override String get tips => 'Tips'; - @override String get tipDesktop => 'Åbn Plezy på din desktop og aktivér fjernbetjening fra indstillinger eller menu'; - @override String get tipScan => 'Brug fanen Scan til hurtigt at parre ved at scanne QR-koden på din desktop'; - @override String get tipWifi => 'Sørg for, at begge enheder er på det samme WiFi-netværk'; - @override String get cameraPermissionRequired => 'Kameratilladelse er påkrævet for at scanne QR-koder.\nGiv kameraadgang i enhedsindstillinger.'; - @override String cameraError({required Object error}) => 'Kunne ikke starte kamera: ${error}'; - @override String get scanInstruction => 'Ret kameraet mod QR-koden vist på din desktop'; - @override String get invalidQrCode => 'Ugyldigt QR-kodeformat'; - @override String get validationHostRequired => 'Indtast værtadresse'; + @override String get connecting => 'Opretter forbindelse...'; + @override String get searchingForDevices => 'Søger efter enheder...'; + @override String get noDevicesFound => 'Ingen enheder fundet på dit netværk'; + @override String get noDevicesHint => 'Sørg for, at Plezy er åben på din computer, og at begge enheder er på det samme WiFi-netværk'; + @override String get availableDevices => 'Tilgængelige enheder'; + @override String get manualConnection => 'Manuel forbindelse'; + @override String get cryptoInitFailed => 'Kunne ikke initialisere sikker forbindelse. Sørg for, at du er logget ind på en Plex-konto.'; + @override String get validationHostRequired => 'Angiv venligst værtsadresse'; @override String get validationHostFormat => 'Format skal være IP:port (f.eks. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Indtast et sessions-ID'; - @override String get validationSessionIdLength => 'Sessions-ID skal være 8 tegn'; - @override String get validationPinRequired => 'Indtast en PIN'; - @override String get validationPinLength => 'PIN skal være 6 cifre'; - @override String get connectionTimedOut => 'Forbindelsen fik timeout. Tjek sessions-ID og PIN.'; - @override String get sessionNotFound => 'Kunne ikke finde sessionen. Tjek dine oplysninger.'; - @override String failedToConnect({required Object error}) => 'Kunne ikke forbinde: ${error}'; + @override String get connectionTimedOut => 'Forbindelsen udløb. Sørg for, at begge enheder er på det samme netværk.'; + @override String get sessionNotFound => 'Kunne ikke finde enheden. Sørg for, at Plezy kører på værten.'; + @override String get authFailed => 'Godkendelse mislykkedes. Sørg for, at begge enheder bruger den samme Plex-konto.'; + @override String failedToConnect({required Object error}) => 'Kunne ikke oprette forbindelse: ${error}'; } // Path: companionRemote.remote @@ -1600,6 +1590,8 @@ extension on TranslationsDa { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Companion Remote Server', + 'settings.companionRemoteServerDescription' => 'Tillad mobilenheder på dit netværk at styre denne app', 'search.hint' => 'Søg film, serier, musik...', 'search.tryDifferentTerm' => 'Prøv en anden søgning', 'search.searchYourMedia' => 'Søg i dine medier', @@ -1881,10 +1873,10 @@ extension on TranslationsDa { 'libraries.groupings.seasons' => 'Sæsoner', 'libraries.groupings.episodes' => 'Episoder', 'libraries.groupings.folders' => 'Mapper', - 'about.title' => 'Om', - 'about.openSourceLicenses' => 'Open source-licenser', _ => null, } ?? switch (path) { + 'about.title' => 'Om', + 'about.openSourceLicenses' => 'Open source-licenser', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'En smuk Plex-klient til Flutter', 'about.viewLicensesDescription' => 'Se licenser for tredjepartsbiblioteker', @@ -2076,46 +2068,34 @@ extension on TranslationsDa { 'companionRemote.remoteControl' => 'Fjernbetjening', 'companionRemote.controlDesktop' => 'Styr en desktopenhed', 'companionRemote.connectedTo' => ({required Object name}) => 'Forbundet til ${name}', - 'companionRemote.session.creatingSession' => 'Opretter fjernsession...', - 'companionRemote.session.failedToCreate' => 'Kunne ikke oprette fjernsession:', - 'companionRemote.session.noSession' => 'Ingen session tilgængelig', - 'companionRemote.session.scanQrCode' => 'Scan QR-kode', - 'companionRemote.session.orEnterManually' => 'Eller indtast manuelt', - 'companionRemote.session.hostAddress' => 'Værtadresse', - 'companionRemote.session.sessionId' => 'Sessions-ID', - 'companionRemote.session.pin' => 'PIN', + 'companionRemote.session.startingServer' => 'Starter fjernserver...', + 'companionRemote.session.failedToCreate' => 'Kunne ikke starte fjernserver:', + 'companionRemote.session.hostAddress' => 'Værtsadresse', 'companionRemote.session.connected' => 'Forbundet', - 'companionRemote.session.waitingForConnection' => 'Venter på forbindelse...', + 'companionRemote.session.serverRunning' => 'Fjernserver aktiv', + 'companionRemote.session.serverStopped' => 'Fjernserver stoppet', + 'companionRemote.session.serverRunningDescription' => 'Mobilenheder på dit netværk kan finde og oprette forbindelse til denne app', + 'companionRemote.session.serverStoppedDescription' => 'Start serveren for at tillade mobilenheder at oprette forbindelse', 'companionRemote.session.usePhoneToControl' => 'Brug din mobilenhed til at styre denne app', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} kopieret til udklipsholder', - 'companionRemote.session.copyToClipboard' => 'Kopiér til udklipsholder', - 'companionRemote.session.newSession' => 'Ny session', + 'companionRemote.session.startServer' => 'Start server', + 'companionRemote.session.stopServer' => 'Stop server', 'companionRemote.session.minimize' => 'Minimér', - 'companionRemote.pairing.scan' => 'Scan', - 'companionRemote.pairing.manual' => 'Manuel', - 'companionRemote.pairing.pairWithDesktop' => 'Par med desktop', - 'companionRemote.pairing.enterSessionDetails' => 'Indtast sessionsdetaljerne vist på din desktopenhed', + 'companionRemote.pairing.pairWithDesktop' => 'Opret forbindelse til computer', + 'companionRemote.pairing.discoveryDescription' => 'Enheder på dit netværk, der kører Plezy med den samme Plex-konto, vises automatisk', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Indtast 8-tegns sessions-ID', - 'companionRemote.pairing.pinHint' => 'Indtast 6-cifret PIN', - 'companionRemote.pairing.connecting' => 'Forbinder...', - 'companionRemote.pairing.tips' => 'Tips', - 'companionRemote.pairing.tipDesktop' => 'Åbn Plezy på din desktop og aktivér fjernbetjening fra indstillinger eller menu', - 'companionRemote.pairing.tipScan' => 'Brug fanen Scan til hurtigt at parre ved at scanne QR-koden på din desktop', - 'companionRemote.pairing.tipWifi' => 'Sørg for, at begge enheder er på det samme WiFi-netværk', - 'companionRemote.pairing.cameraPermissionRequired' => 'Kameratilladelse er påkrævet for at scanne QR-koder.\nGiv kameraadgang i enhedsindstillinger.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kunne ikke starte kamera: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Ret kameraet mod QR-koden vist på din desktop', - 'companionRemote.pairing.invalidQrCode' => 'Ugyldigt QR-kodeformat', - 'companionRemote.pairing.validationHostRequired' => 'Indtast værtadresse', + 'companionRemote.pairing.connecting' => 'Opretter forbindelse...', + 'companionRemote.pairing.searchingForDevices' => 'Søger efter enheder...', + 'companionRemote.pairing.noDevicesFound' => 'Ingen enheder fundet på dit netværk', + 'companionRemote.pairing.noDevicesHint' => 'Sørg for, at Plezy er åben på din computer, og at begge enheder er på det samme WiFi-netværk', + 'companionRemote.pairing.availableDevices' => 'Tilgængelige enheder', + 'companionRemote.pairing.manualConnection' => 'Manuel forbindelse', + 'companionRemote.pairing.cryptoInitFailed' => 'Kunne ikke initialisere sikker forbindelse. Sørg for, at du er logget ind på en Plex-konto.', + 'companionRemote.pairing.validationHostRequired' => 'Angiv venligst værtsadresse', 'companionRemote.pairing.validationHostFormat' => 'Format skal være IP:port (f.eks. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Indtast et sessions-ID', - 'companionRemote.pairing.validationSessionIdLength' => 'Sessions-ID skal være 8 tegn', - 'companionRemote.pairing.validationPinRequired' => 'Indtast en PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN skal være 6 cifre', - 'companionRemote.pairing.connectionTimedOut' => 'Forbindelsen fik timeout. Tjek sessions-ID og PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finde sessionen. Tjek dine oplysninger.', - 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke forbinde: ${error}', + 'companionRemote.pairing.connectionTimedOut' => 'Forbindelsen udløb. Sørg for, at begge enheder er på det samme netværk.', + 'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finde enheden. Sørg for, at Plezy kører på værten.', + 'companionRemote.pairing.authFailed' => 'Godkendelse mislykkedes. Sørg for, at begge enheder bruger den samme Plex-konto.', + 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke oprette forbindelse: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Vil du afbryde fra fjernsessionen?', 'companionRemote.remote.reconnecting' => 'Genopretter forbindelse...', 'companionRemote.remote.attemptOf' => ({required Object current}) => 'Forsøg ${current} af 5', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index b6e0801c..ca370f3a 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Companion Remote Server'; + @override String get companionRemoteServerDescription => 'Mobilgeräten in deinem Netzwerk erlauben, diese App zu steuern'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionDe implements TranslationsCompanionRemo final TranslationsDe _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Fernsteuerungssitzung wird erstellt...'; - @override String get failedToCreate => 'Fernsteuerungssitzung konnte nicht erstellt werden:'; - @override String get noSession => 'Keine Sitzung verfügbar'; - @override String get scanQrCode => 'QR-Code scannen'; - @override String get orEnterManually => 'Oder manuell eingeben'; + @override String get startingServer => 'Remote-Server wird gestartet...'; + @override String get failedToCreate => 'Remote-Server konnte nicht gestartet werden:'; @override String get hostAddress => 'Host-Adresse'; - @override String get sessionId => 'Sitzungs-ID'; - @override String get pin => 'PIN'; @override String get connected => 'Verbunden'; - @override String get waitingForConnection => 'Warte auf Verbindung...'; + @override String get serverRunning => 'Remote-Server aktiv'; + @override String get serverStopped => 'Remote-Server gestoppt'; + @override String get serverRunningDescription => 'Mobilgeräte in deinem Netzwerk können diese App finden und sich verbinden'; + @override String get serverStoppedDescription => 'Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen'; @override String get usePhoneToControl => 'Verwende dein Mobilgerät, um diese App zu steuern'; - @override String copiedToClipboard({required Object label}) => '${label} in Zwischenablage kopiert'; - @override String get copyToClipboard => 'In Zwischenablage kopieren'; - @override String get newSession => 'Neue Sitzung'; + @override String get startServer => 'Server starten'; + @override String get stopServer => 'Server stoppen'; @override String get minimize => 'Minimieren'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingDe implements TranslationsCompanionRemo final TranslationsDe _root; // ignore: unused_field // Translations - @override String get scan => 'Scannen'; - @override String get manual => 'Manuell'; - @override String get pairWithDesktop => 'Mit Desktop koppeln'; - @override String get enterSessionDetails => 'Gib die Sitzungsdetails ein, die auf deinem Desktop-Gerät angezeigt werden'; + @override String get pairWithDesktop => 'Mit Desktop verbinden'; + @override String get discoveryDescription => 'Geräte in deinem Netzwerk, die Plezy mit demselben Plex-Konto nutzen, erscheinen automatisch'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => '8-stellige Sitzungs-ID eingeben'; - @override String get pinHint => '6-stellige PIN eingeben'; - @override String get connecting => 'Verbinde...'; - @override String get tips => 'Tipps'; - @override String get tipDesktop => 'Öffne Plezy auf deinem Desktop und aktiviere Companion Remote in den Einstellungen oder im Menü'; - @override String get tipScan => 'Verwende den Scannen-Tab, um dich schnell per QR-Code auf deinem Desktop zu koppeln'; - @override String get tipWifi => 'Stelle sicher, dass beide Geräte im selben WiFi-Netzwerk sind'; - @override String get cameraPermissionRequired => 'Kameraberechtigung wird zum Scannen von QR-Codes benötigt.\nBitte erteile den Kamerazugriff in den Geräteeinstellungen.'; - @override String cameraError({required Object error}) => 'Kamera konnte nicht gestartet werden: ${error}'; - @override String get scanInstruction => 'Richte deine Kamera auf den QR-Code auf deinem Desktop'; - @override String get invalidQrCode => 'Ungültiges QR-Code-Format'; + @override String get connecting => 'Verbinden...'; + @override String get searchingForDevices => 'Suche nach Geräten...'; + @override String get noDevicesFound => 'Keine Geräte in deinem Netzwerk gefunden'; + @override String get noDevicesHint => 'Stelle sicher, dass Plezy auf deinem Desktop geöffnet ist und beide Geräte im selben WLAN sind'; + @override String get availableDevices => 'Verfügbare Geräte'; + @override String get manualConnection => 'Manuelle Verbindung'; + @override String get cryptoInitFailed => 'Sichere Verbindung konnte nicht initialisiert werden. Stelle sicher, dass du bei einem Plex-Konto angemeldet bist.'; @override String get validationHostRequired => 'Bitte Host-Adresse eingeben'; @override String get validationHostFormat => 'Format muss IP:Port sein (z.B. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Bitte Sitzungs-ID eingeben'; - @override String get validationSessionIdLength => 'Sitzungs-ID muss 8 Zeichen lang sein'; - @override String get validationPinRequired => 'Bitte PIN eingeben'; - @override String get validationPinLength => 'PIN muss 6 Ziffern haben'; - @override String get connectionTimedOut => 'Zeitüberschreitung. Bitte Sitzungs-ID und PIN überprüfen.'; - @override String get sessionNotFound => 'Sitzung nicht gefunden. Bitte Zugangsdaten überprüfen.'; + @override String get connectionTimedOut => 'Verbindung abgelaufen. Stelle sicher, dass beide Geräte im selben Netzwerk sind.'; + @override String get sessionNotFound => 'Gerät nicht gefunden. Stelle sicher, dass Plezy auf dem Host läuft.'; + @override String get authFailed => 'Authentifizierung fehlgeschlagen. Stelle sicher, dass beide Geräte dasselbe Plex-Konto verwenden.'; @override String failedToConnect({required Object error}) => 'Verbindung fehlgeschlagen: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsDe { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Companion Remote Server', + 'settings.companionRemoteServerDescription' => 'Mobilgeräten in deinem Netzwerk erlauben, diese App zu steuern', 'search.hint' => 'Filme, Serien, Musik suchen...', 'search.tryDifferentTerm' => 'Anderen Suchbegriff versuchen', 'search.searchYourMedia' => 'In den eigenen Medien suchen', @@ -1881,10 +1873,10 @@ extension on TranslationsDe { 'libraries.groupings.seasons' => 'Staffeln', 'libraries.groupings.episodes' => 'Episoden', 'libraries.groupings.folders' => 'Ordner', - 'about.title' => 'Über', - 'about.openSourceLicenses' => 'Open-Source-Lizenzen', _ => null, } ?? switch (path) { + 'about.title' => 'Über', + 'about.openSourceLicenses' => 'Open-Source-Lizenzen', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'Ein schöner Plex-Client für Flutter', 'about.viewLicensesDescription' => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen', @@ -2076,45 +2068,33 @@ extension on TranslationsDe { 'companionRemote.remoteControl' => 'Fernsteuerung', 'companionRemote.controlDesktop' => 'Ein Desktop-Gerät steuern', 'companionRemote.connectedTo' => ({required Object name}) => 'Verbunden mit ${name}', - 'companionRemote.session.creatingSession' => 'Fernsteuerungssitzung wird erstellt...', - 'companionRemote.session.failedToCreate' => 'Fernsteuerungssitzung konnte nicht erstellt werden:', - 'companionRemote.session.noSession' => 'Keine Sitzung verfügbar', - 'companionRemote.session.scanQrCode' => 'QR-Code scannen', - 'companionRemote.session.orEnterManually' => 'Oder manuell eingeben', + 'companionRemote.session.startingServer' => 'Remote-Server wird gestartet...', + 'companionRemote.session.failedToCreate' => 'Remote-Server konnte nicht gestartet werden:', 'companionRemote.session.hostAddress' => 'Host-Adresse', - 'companionRemote.session.sessionId' => 'Sitzungs-ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Verbunden', - 'companionRemote.session.waitingForConnection' => 'Warte auf Verbindung...', + 'companionRemote.session.serverRunning' => 'Remote-Server aktiv', + 'companionRemote.session.serverStopped' => 'Remote-Server gestoppt', + 'companionRemote.session.serverRunningDescription' => 'Mobilgeräte in deinem Netzwerk können diese App finden und sich verbinden', + 'companionRemote.session.serverStoppedDescription' => 'Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen', 'companionRemote.session.usePhoneToControl' => 'Verwende dein Mobilgerät, um diese App zu steuern', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} in Zwischenablage kopiert', - 'companionRemote.session.copyToClipboard' => 'In Zwischenablage kopieren', - 'companionRemote.session.newSession' => 'Neue Sitzung', + 'companionRemote.session.startServer' => 'Server starten', + 'companionRemote.session.stopServer' => 'Server stoppen', 'companionRemote.session.minimize' => 'Minimieren', - 'companionRemote.pairing.scan' => 'Scannen', - 'companionRemote.pairing.manual' => 'Manuell', - 'companionRemote.pairing.pairWithDesktop' => 'Mit Desktop koppeln', - 'companionRemote.pairing.enterSessionDetails' => 'Gib die Sitzungsdetails ein, die auf deinem Desktop-Gerät angezeigt werden', + 'companionRemote.pairing.pairWithDesktop' => 'Mit Desktop verbinden', + 'companionRemote.pairing.discoveryDescription' => 'Geräte in deinem Netzwerk, die Plezy mit demselben Plex-Konto nutzen, erscheinen automatisch', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => '8-stellige Sitzungs-ID eingeben', - 'companionRemote.pairing.pinHint' => '6-stellige PIN eingeben', - 'companionRemote.pairing.connecting' => 'Verbinde...', - 'companionRemote.pairing.tips' => 'Tipps', - 'companionRemote.pairing.tipDesktop' => 'Öffne Plezy auf deinem Desktop und aktiviere Companion Remote in den Einstellungen oder im Menü', - 'companionRemote.pairing.tipScan' => 'Verwende den Scannen-Tab, um dich schnell per QR-Code auf deinem Desktop zu koppeln', - 'companionRemote.pairing.tipWifi' => 'Stelle sicher, dass beide Geräte im selben WiFi-Netzwerk sind', - 'companionRemote.pairing.cameraPermissionRequired' => 'Kameraberechtigung wird zum Scannen von QR-Codes benötigt.\nBitte erteile den Kamerazugriff in den Geräteeinstellungen.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kamera konnte nicht gestartet werden: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Richte deine Kamera auf den QR-Code auf deinem Desktop', - 'companionRemote.pairing.invalidQrCode' => 'Ungültiges QR-Code-Format', + 'companionRemote.pairing.connecting' => 'Verbinden...', + 'companionRemote.pairing.searchingForDevices' => 'Suche nach Geräten...', + 'companionRemote.pairing.noDevicesFound' => 'Keine Geräte in deinem Netzwerk gefunden', + 'companionRemote.pairing.noDevicesHint' => 'Stelle sicher, dass Plezy auf deinem Desktop geöffnet ist und beide Geräte im selben WLAN sind', + 'companionRemote.pairing.availableDevices' => 'Verfügbare Geräte', + 'companionRemote.pairing.manualConnection' => 'Manuelle Verbindung', + 'companionRemote.pairing.cryptoInitFailed' => 'Sichere Verbindung konnte nicht initialisiert werden. Stelle sicher, dass du bei einem Plex-Konto angemeldet bist.', 'companionRemote.pairing.validationHostRequired' => 'Bitte Host-Adresse eingeben', 'companionRemote.pairing.validationHostFormat' => 'Format muss IP:Port sein (z.B. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Bitte Sitzungs-ID eingeben', - 'companionRemote.pairing.validationSessionIdLength' => 'Sitzungs-ID muss 8 Zeichen lang sein', - 'companionRemote.pairing.validationPinRequired' => 'Bitte PIN eingeben', - 'companionRemote.pairing.validationPinLength' => 'PIN muss 6 Ziffern haben', - 'companionRemote.pairing.connectionTimedOut' => 'Zeitüberschreitung. Bitte Sitzungs-ID und PIN überprüfen.', - 'companionRemote.pairing.sessionNotFound' => 'Sitzung nicht gefunden. Bitte Zugangsdaten überprüfen.', + 'companionRemote.pairing.connectionTimedOut' => 'Verbindung abgelaufen. Stelle sicher, dass beide Geräte im selben Netzwerk sind.', + 'companionRemote.pairing.sessionNotFound' => 'Gerät nicht gefunden. Stelle sicher, dass Plezy auf dem Host läuft.', + 'companionRemote.pairing.authFailed' => 'Authentifizierung fehlgeschlagen. Stelle sicher, dass beide Geräte dasselbe Plex-Konto verwenden.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Verbindung fehlgeschlagen: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Möchtest du die Verbindung zur Fernsteuerungssitzung trennen?', 'companionRemote.remote.reconnecting' => 'Verbindung wird wiederhergestellt...', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index ee6ed150..735b0ee9 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -730,6 +730,12 @@ class TranslationsSettingsEn { /// en: 'Show what you're watching on Discord' String get discordRichPresenceDescription => 'Show what you\'re watching on Discord'; + /// en: 'Companion Remote Server' + String get companionRemoteServer => 'Companion Remote Server'; + + /// en: 'Allow mobile devices on your network to control this app' + String get companionRemoteServerDescription => 'Allow mobile devices on your network to control this app'; + /// en: 'Auto Picture-in-Picture' String get autoPip => 'Auto Picture-in-Picture'; @@ -2892,47 +2898,38 @@ class TranslationsCompanionRemoteSessionEn { // Translations - /// en: 'Creating remote session...' - String get creatingSession => 'Creating remote session...'; + /// en: 'Starting remote server...' + String get startingServer => 'Starting remote server...'; - /// en: 'Failed to create remote session:' - String get failedToCreate => 'Failed to create remote session:'; - - /// en: 'No session available' - String get noSession => 'No session available'; - - /// en: 'Scan QR Code' - String get scanQrCode => 'Scan QR Code'; - - /// en: 'Or enter manually' - String get orEnterManually => 'Or enter manually'; + /// en: 'Failed to start remote server:' + String get failedToCreate => 'Failed to start remote server:'; /// en: 'Host Address' String get hostAddress => 'Host Address'; - /// en: 'Session ID' - String get sessionId => 'Session ID'; - - /// en: 'PIN' - String get pin => 'PIN'; - /// en: 'Connected' String get connected => 'Connected'; - /// en: 'Waiting for connection...' - String get waitingForConnection => 'Waiting for connection...'; + /// en: 'Remote server active' + String get serverRunning => 'Remote server active'; + + /// en: 'Remote server stopped' + String get serverStopped => 'Remote server stopped'; + + /// en: 'Mobile devices on your network can discover and connect to this app' + String get serverRunningDescription => 'Mobile devices on your network can discover and connect to this app'; + + /// en: 'Start the server to allow mobile devices to connect' + String get serverStoppedDescription => 'Start the server to allow mobile devices to connect'; /// en: 'Use your mobile device to control this app' String get usePhoneToControl => 'Use your mobile device to control this app'; - /// en: '${label} copied to clipboard' - String copiedToClipboard({required Object label}) => '${label} copied to clipboard'; + /// en: 'Start Server' + String get startServer => 'Start Server'; - /// en: 'Copy to clipboard' - String get copyToClipboard => 'Copy to clipboard'; - - /// en: 'New Session' - String get newSession => 'New Session'; + /// en: 'Stop Server' + String get stopServer => 'Stop Server'; /// en: 'Minimize' String get minimize => 'Minimize'; @@ -2946,53 +2943,35 @@ class TranslationsCompanionRemotePairingEn { // Translations - /// en: 'Scan' - String get scan => 'Scan'; + /// en: 'Connect to Desktop' + String get pairWithDesktop => 'Connect to Desktop'; - /// en: 'Manual' - String get manual => 'Manual'; - - /// en: 'Pair with Desktop' - String get pairWithDesktop => 'Pair with Desktop'; - - /// en: 'Enter the session details shown on your desktop device' - String get enterSessionDetails => 'Enter the session details shown on your desktop device'; + /// en: 'Devices on your network running Plezy with the same Plex account will appear automatically' + String get discoveryDescription => 'Devices on your network running Plezy with the same Plex account will appear automatically'; /// en: '192.168.1.100:48632' String get hostAddressHint => '192.168.1.100:48632'; - /// en: 'Enter 8-character session ID' - String get sessionIdHint => 'Enter 8-character session ID'; - - /// en: 'Enter 6-digit PIN' - String get pinHint => 'Enter 6-digit PIN'; - /// en: 'Connecting...' String get connecting => 'Connecting...'; - /// en: 'Tips' - String get tips => 'Tips'; + /// en: 'Looking for devices...' + String get searchingForDevices => 'Looking for devices...'; - /// en: 'Open Plezy on your desktop and enable Companion Remote from settings or menu' - String get tipDesktop => 'Open Plezy on your desktop and enable Companion Remote from settings or menu'; + /// en: 'No devices found on your network' + String get noDevicesFound => 'No devices found on your network'; - /// en: 'Use the Scan tab to quickly pair by scanning the QR code on your desktop' - String get tipScan => 'Use the Scan tab to quickly pair by scanning the QR code on your desktop'; + /// en: 'Make sure Plezy is open on your desktop and both devices are on the same WiFi network' + String get noDevicesHint => 'Make sure Plezy is open on your desktop and both devices are on the same WiFi network'; - /// en: 'Make sure both devices are on the same WiFi network' - String get tipWifi => 'Make sure both devices are on the same WiFi network'; + /// en: 'Available Devices' + String get availableDevices => 'Available Devices'; - /// en: 'Camera permission is required to scan QR codes. Please grant camera access in your device settings.' - String get cameraPermissionRequired => 'Camera permission is required to scan QR codes.\nPlease grant camera access in your device settings.'; + /// en: 'Manual Connection' + String get manualConnection => 'Manual Connection'; - /// en: 'Could not start camera: ${error}' - String cameraError({required Object error}) => 'Could not start camera: ${error}'; - - /// en: 'Point your camera at the QR code shown on your desktop' - String get scanInstruction => 'Point your camera at the QR code shown on your desktop'; - - /// en: 'Invalid QR code format' - String get invalidQrCode => 'Invalid QR code format'; + /// en: 'Could not initialize secure connection. Make sure you are signed in to a Plex account.' + String get cryptoInitFailed => 'Could not initialize secure connection. Make sure you are signed in to a Plex account.'; /// en: 'Please enter host address' String get validationHostRequired => 'Please enter host address'; @@ -3000,23 +2979,14 @@ class TranslationsCompanionRemotePairingEn { /// en: 'Format must be IP:port (e.g., 192.168.1.100:48632)' String get validationHostFormat => 'Format must be IP:port (e.g., 192.168.1.100:48632)'; - /// en: 'Please enter a session ID' - String get validationSessionIdRequired => 'Please enter a session ID'; + /// en: 'Connection timed out. Make sure both devices are on the same network.' + String get connectionTimedOut => 'Connection timed out. Make sure both devices are on the same network.'; - /// en: 'Session ID must be 8 characters' - String get validationSessionIdLength => 'Session ID must be 8 characters'; + /// en: 'Could not find the device. Make sure Plezy is running on the host.' + String get sessionNotFound => 'Could not find the device. Make sure Plezy is running on the host.'; - /// en: 'Please enter a PIN' - String get validationPinRequired => 'Please enter a PIN'; - - /// en: 'PIN must be 6 digits' - String get validationPinLength => 'PIN must be 6 digits'; - - /// en: 'Connection timed out. Please check the session ID and PIN.' - String get connectionTimedOut => 'Connection timed out. Please check the session ID and PIN.'; - - /// en: 'Could not find the session. Please check your credentials.' - String get sessionNotFound => 'Could not find the session. Please check your credentials.'; + /// en: 'Authentication failed. Make sure both devices are on the same Plex account.' + String get authFailed => 'Authentication failed. Make sure both devices are on the same Plex account.'; /// en: 'Failed to connect: ${error}' String failedToConnect({required Object error}) => 'Failed to connect: ${error}'; @@ -3325,6 +3295,8 @@ extension on Translations { 'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%', 'settings.discordRichPresence' => 'Discord Rich Presence', 'settings.discordRichPresenceDescription' => 'Show what you\'re watching on Discord', + 'settings.companionRemoteServer' => 'Companion Remote Server', + 'settings.companionRemoteServerDescription' => 'Allow mobile devices on your network to control this app', 'settings.autoPip' => 'Auto Picture-in-Picture', 'settings.autoPipDescription' => 'Automatically enter picture-in-picture when leaving the app during playback', 'settings.matchContentFrameRate' => 'Match Content Frame Rate', @@ -3636,10 +3608,10 @@ extension on Translations { 'libraries.groupings.seasons' => 'Seasons', 'libraries.groupings.episodes' => 'Episodes', 'libraries.groupings.folders' => 'Folders', - 'about.title' => 'About', - 'about.openSourceLicenses' => 'Open Source Licenses', _ => null, } ?? switch (path) { + 'about.title' => 'About', + 'about.openSourceLicenses' => 'Open Source Licenses', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'A beautiful Plex client for Flutter', 'about.viewLicensesDescription' => 'View licenses of third-party libraries', @@ -3831,45 +3803,33 @@ extension on Translations { 'companionRemote.remoteControl' => 'Remote Control', 'companionRemote.controlDesktop' => 'Control a desktop device', 'companionRemote.connectedTo' => ({required Object name}) => 'Connected to ${name}', - 'companionRemote.session.creatingSession' => 'Creating remote session...', - 'companionRemote.session.failedToCreate' => 'Failed to create remote session:', - 'companionRemote.session.noSession' => 'No session available', - 'companionRemote.session.scanQrCode' => 'Scan QR Code', - 'companionRemote.session.orEnterManually' => 'Or enter manually', + 'companionRemote.session.startingServer' => 'Starting remote server...', + 'companionRemote.session.failedToCreate' => 'Failed to start remote server:', 'companionRemote.session.hostAddress' => 'Host Address', - 'companionRemote.session.sessionId' => 'Session ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Connected', - 'companionRemote.session.waitingForConnection' => 'Waiting for connection...', + 'companionRemote.session.serverRunning' => 'Remote server active', + 'companionRemote.session.serverStopped' => 'Remote server stopped', + 'companionRemote.session.serverRunningDescription' => 'Mobile devices on your network can discover and connect to this app', + 'companionRemote.session.serverStoppedDescription' => 'Start the server to allow mobile devices to connect', 'companionRemote.session.usePhoneToControl' => 'Use your mobile device to control this app', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copied to clipboard', - 'companionRemote.session.copyToClipboard' => 'Copy to clipboard', - 'companionRemote.session.newSession' => 'New Session', + 'companionRemote.session.startServer' => 'Start Server', + 'companionRemote.session.stopServer' => 'Stop Server', 'companionRemote.session.minimize' => 'Minimize', - 'companionRemote.pairing.scan' => 'Scan', - 'companionRemote.pairing.manual' => 'Manual', - 'companionRemote.pairing.pairWithDesktop' => 'Pair with Desktop', - 'companionRemote.pairing.enterSessionDetails' => 'Enter the session details shown on your desktop device', + 'companionRemote.pairing.pairWithDesktop' => 'Connect to Desktop', + 'companionRemote.pairing.discoveryDescription' => 'Devices on your network running Plezy with the same Plex account will appear automatically', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Enter 8-character session ID', - 'companionRemote.pairing.pinHint' => 'Enter 6-digit PIN', 'companionRemote.pairing.connecting' => 'Connecting...', - 'companionRemote.pairing.tips' => 'Tips', - 'companionRemote.pairing.tipDesktop' => 'Open Plezy on your desktop and enable Companion Remote from settings or menu', - 'companionRemote.pairing.tipScan' => 'Use the Scan tab to quickly pair by scanning the QR code on your desktop', - 'companionRemote.pairing.tipWifi' => 'Make sure both devices are on the same WiFi network', - 'companionRemote.pairing.cameraPermissionRequired' => 'Camera permission is required to scan QR codes.\nPlease grant camera access in your device settings.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Could not start camera: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Point your camera at the QR code shown on your desktop', - 'companionRemote.pairing.invalidQrCode' => 'Invalid QR code format', + 'companionRemote.pairing.searchingForDevices' => 'Looking for devices...', + 'companionRemote.pairing.noDevicesFound' => 'No devices found on your network', + 'companionRemote.pairing.noDevicesHint' => 'Make sure Plezy is open on your desktop and both devices are on the same WiFi network', + 'companionRemote.pairing.availableDevices' => 'Available Devices', + 'companionRemote.pairing.manualConnection' => 'Manual Connection', + 'companionRemote.pairing.cryptoInitFailed' => 'Could not initialize secure connection. Make sure you are signed in to a Plex account.', 'companionRemote.pairing.validationHostRequired' => 'Please enter host address', 'companionRemote.pairing.validationHostFormat' => 'Format must be IP:port (e.g., 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Please enter a session ID', - 'companionRemote.pairing.validationSessionIdLength' => 'Session ID must be 8 characters', - 'companionRemote.pairing.validationPinRequired' => 'Please enter a PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN must be 6 digits', - 'companionRemote.pairing.connectionTimedOut' => 'Connection timed out. Please check the session ID and PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Could not find the session. Please check your credentials.', + 'companionRemote.pairing.connectionTimedOut' => 'Connection timed out. Make sure both devices are on the same network.', + 'companionRemote.pairing.sessionNotFound' => 'Could not find the device. Make sure Plezy is running on the host.', + 'companionRemote.pairing.authFailed' => 'Authentication failed. Make sure both devices are on the same Plex account.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Failed to connect: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Do you want to disconnect from the remote session?', 'companionRemote.remote.reconnecting' => 'Reconnecting...', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 7524cb13..050360b0 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsEs implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Servidor de control remoto'; + @override String get companionRemoteServerDescription => 'Permitir que dispositivos móviles en tu red controlen esta aplicación'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionEs implements TranslationsCompanionRemo final TranslationsEs _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Creando sesión remota...'; - @override String get failedToCreate => 'Error al crear la sesión remota:'; - @override String get noSession => 'No hay sesión disponible'; - @override String get scanQrCode => 'Escanear código QR'; - @override String get orEnterManually => 'O introducir manualmente'; + @override String get startingServer => 'Iniciando servidor remoto...'; + @override String get failedToCreate => 'Error al iniciar el servidor remoto:'; @override String get hostAddress => 'Dirección del host'; - @override String get sessionId => 'ID de sesión'; - @override String get pin => 'PIN'; @override String get connected => 'Conectado'; - @override String get waitingForConnection => 'Esperando conexión...'; - @override String get usePhoneToControl => 'Usa tu dispositivo móvil para controlar esta app'; - @override String copiedToClipboard({required Object label}) => '${label} copiado al portapapeles'; - @override String get copyToClipboard => 'Copiar al portapapeles'; - @override String get newSession => 'Nueva sesión'; + @override String get serverRunning => 'Servidor remoto activo'; + @override String get serverStopped => 'Servidor remoto detenido'; + @override String get serverRunningDescription => 'Los dispositivos móviles en tu red pueden descubrir y conectarse a esta aplicación'; + @override String get serverStoppedDescription => 'Inicia el servidor para permitir que los dispositivos móviles se conecten'; + @override String get usePhoneToControl => 'Usa tu dispositivo móvil para controlar esta aplicación'; + @override String get startServer => 'Iniciar servidor'; + @override String get stopServer => 'Detener servidor'; @override String get minimize => 'Minimizar'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingEs implements TranslationsCompanionRemo final TranslationsEs _root; // ignore: unused_field // Translations - @override String get scan => 'Escanear'; - @override String get manual => 'Manual'; - @override String get pairWithDesktop => 'Emparejar con escritorio'; - @override String get enterSessionDetails => 'Introduce los datos de la sesión que aparecen en tu dispositivo de escritorio'; + @override String get pairWithDesktop => 'Conectar al escritorio'; + @override String get discoveryDescription => 'Los dispositivos en tu red que ejecutan Plezy con la misma cuenta de Plex aparecerán automáticamente'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Introduce ID de sesión de 8 caracteres'; - @override String get pinHint => 'Introduce PIN de 6 dígitos'; @override String get connecting => 'Conectando...'; - @override String get tips => 'Consejos'; - @override String get tipDesktop => 'Abre Plezy en tu escritorio y activa Companion Remote desde los ajustes o el menú'; - @override String get tipScan => 'Usa la pestaña Escanear para emparejar rápidamente escaneando el código QR de tu escritorio'; - @override String get tipWifi => 'Asegúrate de que ambos dispositivos estén en la misma red WiFi'; - @override String get cameraPermissionRequired => 'Se necesita permiso de cámara para escanear códigos QR.\nPor favor, concede acceso a la cámara en los ajustes de tu dispositivo.'; - @override String cameraError({required Object error}) => 'No se pudo iniciar la cámara: ${error}'; - @override String get scanInstruction => 'Apunta tu cámara al código QR que aparece en tu escritorio'; - @override String get invalidQrCode => 'Formato de código QR no válido'; - @override String get validationHostRequired => 'Por favor, introduce la dirección del host'; - @override String get validationHostFormat => 'El formato debe ser IP:puerto (ej., 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Por favor, introduce un ID de sesión'; - @override String get validationSessionIdLength => 'El ID de sesión debe tener 8 caracteres'; - @override String get validationPinRequired => 'Por favor, introduce un PIN'; - @override String get validationPinLength => 'El PIN debe tener 6 dígitos'; - @override String get connectionTimedOut => 'Tiempo de conexión agotado. Verifica el ID de sesión y el PIN.'; - @override String get sessionNotFound => 'No se encontró la sesión. Verifica tus credenciales.'; + @override String get searchingForDevices => 'Buscando dispositivos...'; + @override String get noDevicesFound => 'No se encontraron dispositivos en tu red'; + @override String get noDevicesHint => 'Asegúrate de que Plezy esté abierto en tu escritorio y que ambos dispositivos estén en la misma red WiFi'; + @override String get availableDevices => 'Dispositivos disponibles'; + @override String get manualConnection => 'Conexión manual'; + @override String get cryptoInitFailed => 'No se pudo inicializar la conexión segura. Asegúrate de haber iniciado sesión en una cuenta de Plex.'; + @override String get validationHostRequired => 'Ingresa la dirección del host'; + @override String get validationHostFormat => 'El formato debe ser IP:puerto (ej. 192.168.1.100:48632)'; + @override String get connectionTimedOut => 'Tiempo de conexión agotado. Asegúrate de que ambos dispositivos estén en la misma red.'; + @override String get sessionNotFound => 'No se encontró el dispositivo. Asegúrate de que Plezy esté ejecutándose en el host.'; + @override String get authFailed => 'Autenticación fallida. Asegúrate de que ambos dispositivos usen la misma cuenta de Plex.'; @override String failedToConnect({required Object error}) => 'Error al conectar: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsEs { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Servidor de control remoto', + 'settings.companionRemoteServerDescription' => 'Permitir que dispositivos móviles en tu red controlen esta aplicación', 'search.hint' => 'Buscar películas, series, música...', 'search.tryDifferentTerm' => 'Prueba con un término de búsqueda diferente', 'search.searchYourMedia' => 'Busca en tu contenido', @@ -1881,10 +1873,10 @@ extension on TranslationsEs { 'libraries.groupings.seasons' => 'Temporadas', 'libraries.groupings.episodes' => 'Episodios', 'libraries.groupings.folders' => 'Carpetas', - 'about.title' => 'Acerca de', - 'about.openSourceLicenses' => 'Licencias de Código Abierto', _ => null, } ?? switch (path) { + 'about.title' => 'Acerca de', + 'about.openSourceLicenses' => 'Licencias de Código Abierto', 'about.versionLabel' => ({required Object version}) => 'Versión ${version}', 'about.appDescription' => 'Un cliente de Plex para Flutter', 'about.viewLicensesDescription' => 'Ver licencias de librerías de terceros', @@ -2076,45 +2068,33 @@ extension on TranslationsEs { 'companionRemote.remoteControl' => 'Control remoto', 'companionRemote.controlDesktop' => 'Controlar un dispositivo de escritorio', 'companionRemote.connectedTo' => ({required Object name}) => 'Conectado a ${name}', - 'companionRemote.session.creatingSession' => 'Creando sesión remota...', - 'companionRemote.session.failedToCreate' => 'Error al crear la sesión remota:', - 'companionRemote.session.noSession' => 'No hay sesión disponible', - 'companionRemote.session.scanQrCode' => 'Escanear código QR', - 'companionRemote.session.orEnterManually' => 'O introducir manualmente', + 'companionRemote.session.startingServer' => 'Iniciando servidor remoto...', + 'companionRemote.session.failedToCreate' => 'Error al iniciar el servidor remoto:', 'companionRemote.session.hostAddress' => 'Dirección del host', - 'companionRemote.session.sessionId' => 'ID de sesión', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Conectado', - 'companionRemote.session.waitingForConnection' => 'Esperando conexión...', - 'companionRemote.session.usePhoneToControl' => 'Usa tu dispositivo móvil para controlar esta app', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copiado al portapapeles', - 'companionRemote.session.copyToClipboard' => 'Copiar al portapapeles', - 'companionRemote.session.newSession' => 'Nueva sesión', + 'companionRemote.session.serverRunning' => 'Servidor remoto activo', + 'companionRemote.session.serverStopped' => 'Servidor remoto detenido', + 'companionRemote.session.serverRunningDescription' => 'Los dispositivos móviles en tu red pueden descubrir y conectarse a esta aplicación', + 'companionRemote.session.serverStoppedDescription' => 'Inicia el servidor para permitir que los dispositivos móviles se conecten', + 'companionRemote.session.usePhoneToControl' => 'Usa tu dispositivo móvil para controlar esta aplicación', + 'companionRemote.session.startServer' => 'Iniciar servidor', + 'companionRemote.session.stopServer' => 'Detener servidor', 'companionRemote.session.minimize' => 'Minimizar', - 'companionRemote.pairing.scan' => 'Escanear', - 'companionRemote.pairing.manual' => 'Manual', - 'companionRemote.pairing.pairWithDesktop' => 'Emparejar con escritorio', - 'companionRemote.pairing.enterSessionDetails' => 'Introduce los datos de la sesión que aparecen en tu dispositivo de escritorio', + 'companionRemote.pairing.pairWithDesktop' => 'Conectar al escritorio', + 'companionRemote.pairing.discoveryDescription' => 'Los dispositivos en tu red que ejecutan Plezy con la misma cuenta de Plex aparecerán automáticamente', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Introduce ID de sesión de 8 caracteres', - 'companionRemote.pairing.pinHint' => 'Introduce PIN de 6 dígitos', 'companionRemote.pairing.connecting' => 'Conectando...', - 'companionRemote.pairing.tips' => 'Consejos', - 'companionRemote.pairing.tipDesktop' => 'Abre Plezy en tu escritorio y activa Companion Remote desde los ajustes o el menú', - 'companionRemote.pairing.tipScan' => 'Usa la pestaña Escanear para emparejar rápidamente escaneando el código QR de tu escritorio', - 'companionRemote.pairing.tipWifi' => 'Asegúrate de que ambos dispositivos estén en la misma red WiFi', - 'companionRemote.pairing.cameraPermissionRequired' => 'Se necesita permiso de cámara para escanear códigos QR.\nPor favor, concede acceso a la cámara en los ajustes de tu dispositivo.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'No se pudo iniciar la cámara: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Apunta tu cámara al código QR que aparece en tu escritorio', - 'companionRemote.pairing.invalidQrCode' => 'Formato de código QR no válido', - 'companionRemote.pairing.validationHostRequired' => 'Por favor, introduce la dirección del host', - 'companionRemote.pairing.validationHostFormat' => 'El formato debe ser IP:puerto (ej., 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Por favor, introduce un ID de sesión', - 'companionRemote.pairing.validationSessionIdLength' => 'El ID de sesión debe tener 8 caracteres', - 'companionRemote.pairing.validationPinRequired' => 'Por favor, introduce un PIN', - 'companionRemote.pairing.validationPinLength' => 'El PIN debe tener 6 dígitos', - 'companionRemote.pairing.connectionTimedOut' => 'Tiempo de conexión agotado. Verifica el ID de sesión y el PIN.', - 'companionRemote.pairing.sessionNotFound' => 'No se encontró la sesión. Verifica tus credenciales.', + 'companionRemote.pairing.searchingForDevices' => 'Buscando dispositivos...', + 'companionRemote.pairing.noDevicesFound' => 'No se encontraron dispositivos en tu red', + 'companionRemote.pairing.noDevicesHint' => 'Asegúrate de que Plezy esté abierto en tu escritorio y que ambos dispositivos estén en la misma red WiFi', + 'companionRemote.pairing.availableDevices' => 'Dispositivos disponibles', + 'companionRemote.pairing.manualConnection' => 'Conexión manual', + 'companionRemote.pairing.cryptoInitFailed' => 'No se pudo inicializar la conexión segura. Asegúrate de haber iniciado sesión en una cuenta de Plex.', + 'companionRemote.pairing.validationHostRequired' => 'Ingresa la dirección del host', + 'companionRemote.pairing.validationHostFormat' => 'El formato debe ser IP:puerto (ej. 192.168.1.100:48632)', + 'companionRemote.pairing.connectionTimedOut' => 'Tiempo de conexión agotado. Asegúrate de que ambos dispositivos estén en la misma red.', + 'companionRemote.pairing.sessionNotFound' => 'No se encontró el dispositivo. Asegúrate de que Plezy esté ejecutándose en el host.', + 'companionRemote.pairing.authFailed' => 'Autenticación fallida. Asegúrate de que ambos dispositivos usen la misma cuenta de Plex.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Error al conectar: ${error}', 'companionRemote.remote.disconnectConfirm' => '¿Quieres desconectarte de la sesión remota?', 'companionRemote.remote.reconnecting' => 'Reconectando...', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 7cf23cdb..12405ccd 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Serveur de télécommande'; + @override String get companionRemoteServerDescription => 'Autoriser les appareils mobiles de votre réseau à contrôler cette application'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionFr implements TranslationsCompanionRemo final TranslationsFr _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Création de la session distante...'; - @override String get failedToCreate => 'Échec de la création de la session distante :'; - @override String get noSession => 'Aucune session disponible'; - @override String get scanQrCode => 'Scanner le QR Code'; - @override String get orEnterManually => 'Ou saisir manuellement'; + @override String get startingServer => 'Démarrage du serveur distant...'; + @override String get failedToCreate => 'Échec du démarrage du serveur distant :'; @override String get hostAddress => 'Adresse de l\'hôte'; - @override String get sessionId => 'ID de session'; - @override String get pin => 'PIN'; @override String get connected => 'Connecté'; - @override String get waitingForConnection => 'En attente de connexion...'; + @override String get serverRunning => 'Serveur distant actif'; + @override String get serverStopped => 'Serveur distant arrêté'; + @override String get serverRunningDescription => 'Les appareils mobiles de votre réseau peuvent découvrir et se connecter à cette application'; + @override String get serverStoppedDescription => 'Démarrez le serveur pour permettre aux appareils mobiles de se connecter'; @override String get usePhoneToControl => 'Utilisez votre appareil mobile pour contrôler cette application'; - @override String copiedToClipboard({required Object label}) => '${label} copié dans le presse-papiers'; - @override String get copyToClipboard => 'Copier dans le presse-papiers'; - @override String get newSession => 'Nouvelle session'; + @override String get startServer => 'Démarrer le serveur'; + @override String get stopServer => 'Arrêter le serveur'; @override String get minimize => 'Réduire'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingFr implements TranslationsCompanionRemo final TranslationsFr _root; // ignore: unused_field // Translations - @override String get scan => 'Scanner'; - @override String get manual => 'Manuel'; - @override String get pairWithDesktop => 'Jumeler avec un bureau'; - @override String get enterSessionDetails => 'Saisissez les détails de la session affichés sur votre appareil de bureau'; + @override String get pairWithDesktop => 'Se connecter au bureau'; + @override String get discoveryDescription => 'Les appareils de votre réseau exécutant Plezy avec le même compte Plex apparaîtront automatiquement'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Saisissez l\'ID de session à 8 caractères'; - @override String get pinHint => 'Saisissez le PIN à 6 chiffres'; @override String get connecting => 'Connexion...'; - @override String get tips => 'Conseils'; - @override String get tipDesktop => 'Ouvrez Plezy sur votre bureau et activez Companion Remote depuis les paramètres ou le menu'; - @override String get tipScan => 'Utilisez l\'onglet Scanner pour jumeler rapidement en scannant le QR code sur votre bureau'; - @override String get tipWifi => 'Assurez-vous que les deux appareils sont sur le même réseau WiFi'; - @override String get cameraPermissionRequired => 'L\'autorisation de la caméra est requise pour scanner les QR codes.\nVeuillez accorder l\'accès à la caméra dans les paramètres de votre appareil.'; - @override String cameraError({required Object error}) => 'Impossible de démarrer la caméra : ${error}'; - @override String get scanInstruction => 'Pointez votre caméra vers le QR code affiché sur votre bureau'; - @override String get invalidQrCode => 'Format de QR code invalide'; - @override String get validationHostRequired => 'Veuillez saisir l\'adresse de l\'hôte'; - @override String get validationHostFormat => 'Le format doit être IP:port (ex : 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Veuillez saisir un ID de session'; - @override String get validationSessionIdLength => 'L\'ID de session doit contenir 8 caractères'; - @override String get validationPinRequired => 'Veuillez saisir un PIN'; - @override String get validationPinLength => 'Le PIN doit contenir 6 chiffres'; - @override String get connectionTimedOut => 'Délai de connexion expiré. Veuillez vérifier l\'ID de session et le PIN.'; - @override String get sessionNotFound => 'Session introuvable. Veuillez vérifier vos identifiants.'; + @override String get searchingForDevices => 'Recherche d\'appareils...'; + @override String get noDevicesFound => 'Aucun appareil trouvé sur votre réseau'; + @override String get noDevicesHint => 'Assurez-vous que Plezy est ouvert sur votre bureau et que les deux appareils sont sur le même réseau WiFi'; + @override String get availableDevices => 'Appareils disponibles'; + @override String get manualConnection => 'Connexion manuelle'; + @override String get cryptoInitFailed => 'Impossible d\'initialiser la connexion sécurisée. Assurez-vous d\'être connecté à un compte Plex.'; + @override String get validationHostRequired => 'Veuillez entrer l\'adresse de l\'hôte'; + @override String get validationHostFormat => 'Le format doit être IP:port (ex. 192.168.1.100:48632)'; + @override String get connectionTimedOut => 'Délai de connexion dépassé. Assurez-vous que les deux appareils sont sur le même réseau.'; + @override String get sessionNotFound => 'Appareil introuvable. Assurez-vous que Plezy est en cours d\'exécution sur l\'hôte.'; + @override String get authFailed => 'Échec de l\'authentification. Assurez-vous que les deux appareils utilisent le même compte Plex.'; @override String failedToConnect({required Object error}) => 'Échec de la connexion : ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsFr { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Serveur de télécommande', + 'settings.companionRemoteServerDescription' => 'Autoriser les appareils mobiles de votre réseau à contrôler cette application', 'search.hint' => 'Rechercher des films, des séries, de la musique...', 'search.tryDifferentTerm' => 'Essayez un autre terme de recherche', 'search.searchYourMedia' => 'Rechercher dans vos médias', @@ -1881,10 +1873,10 @@ extension on TranslationsFr { 'libraries.groupings.seasons' => 'Saisons', 'libraries.groupings.episodes' => 'Épisodes', 'libraries.groupings.folders' => 'Dossiers', - 'about.title' => 'À propos', - 'about.openSourceLicenses' => 'Licences Open Source', _ => null, } ?? switch (path) { + 'about.title' => 'À propos', + 'about.openSourceLicenses' => 'Licences Open Source', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'Un magnifique client Plex pour Flutter', 'about.viewLicensesDescription' => 'Afficher les licences des bibliothèques tierces', @@ -2076,45 +2068,33 @@ extension on TranslationsFr { 'companionRemote.remoteControl' => 'Télécommande', 'companionRemote.controlDesktop' => 'Contrôler un appareil de bureau', 'companionRemote.connectedTo' => ({required Object name}) => 'Connecté à ${name}', - 'companionRemote.session.creatingSession' => 'Création de la session distante...', - 'companionRemote.session.failedToCreate' => 'Échec de la création de la session distante :', - 'companionRemote.session.noSession' => 'Aucune session disponible', - 'companionRemote.session.scanQrCode' => 'Scanner le QR Code', - 'companionRemote.session.orEnterManually' => 'Ou saisir manuellement', + 'companionRemote.session.startingServer' => 'Démarrage du serveur distant...', + 'companionRemote.session.failedToCreate' => 'Échec du démarrage du serveur distant :', 'companionRemote.session.hostAddress' => 'Adresse de l\'hôte', - 'companionRemote.session.sessionId' => 'ID de session', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Connecté', - 'companionRemote.session.waitingForConnection' => 'En attente de connexion...', + 'companionRemote.session.serverRunning' => 'Serveur distant actif', + 'companionRemote.session.serverStopped' => 'Serveur distant arrêté', + 'companionRemote.session.serverRunningDescription' => 'Les appareils mobiles de votre réseau peuvent découvrir et se connecter à cette application', + 'companionRemote.session.serverStoppedDescription' => 'Démarrez le serveur pour permettre aux appareils mobiles de se connecter', 'companionRemote.session.usePhoneToControl' => 'Utilisez votre appareil mobile pour contrôler cette application', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copié dans le presse-papiers', - 'companionRemote.session.copyToClipboard' => 'Copier dans le presse-papiers', - 'companionRemote.session.newSession' => 'Nouvelle session', + 'companionRemote.session.startServer' => 'Démarrer le serveur', + 'companionRemote.session.stopServer' => 'Arrêter le serveur', 'companionRemote.session.minimize' => 'Réduire', - 'companionRemote.pairing.scan' => 'Scanner', - 'companionRemote.pairing.manual' => 'Manuel', - 'companionRemote.pairing.pairWithDesktop' => 'Jumeler avec un bureau', - 'companionRemote.pairing.enterSessionDetails' => 'Saisissez les détails de la session affichés sur votre appareil de bureau', + 'companionRemote.pairing.pairWithDesktop' => 'Se connecter au bureau', + 'companionRemote.pairing.discoveryDescription' => 'Les appareils de votre réseau exécutant Plezy avec le même compte Plex apparaîtront automatiquement', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Saisissez l\'ID de session à 8 caractères', - 'companionRemote.pairing.pinHint' => 'Saisissez le PIN à 6 chiffres', 'companionRemote.pairing.connecting' => 'Connexion...', - 'companionRemote.pairing.tips' => 'Conseils', - 'companionRemote.pairing.tipDesktop' => 'Ouvrez Plezy sur votre bureau et activez Companion Remote depuis les paramètres ou le menu', - 'companionRemote.pairing.tipScan' => 'Utilisez l\'onglet Scanner pour jumeler rapidement en scannant le QR code sur votre bureau', - 'companionRemote.pairing.tipWifi' => 'Assurez-vous que les deux appareils sont sur le même réseau WiFi', - 'companionRemote.pairing.cameraPermissionRequired' => 'L\'autorisation de la caméra est requise pour scanner les QR codes.\nVeuillez accorder l\'accès à la caméra dans les paramètres de votre appareil.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Impossible de démarrer la caméra : ${error}', - 'companionRemote.pairing.scanInstruction' => 'Pointez votre caméra vers le QR code affiché sur votre bureau', - 'companionRemote.pairing.invalidQrCode' => 'Format de QR code invalide', - 'companionRemote.pairing.validationHostRequired' => 'Veuillez saisir l\'adresse de l\'hôte', - 'companionRemote.pairing.validationHostFormat' => 'Le format doit être IP:port (ex : 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Veuillez saisir un ID de session', - 'companionRemote.pairing.validationSessionIdLength' => 'L\'ID de session doit contenir 8 caractères', - 'companionRemote.pairing.validationPinRequired' => 'Veuillez saisir un PIN', - 'companionRemote.pairing.validationPinLength' => 'Le PIN doit contenir 6 chiffres', - 'companionRemote.pairing.connectionTimedOut' => 'Délai de connexion expiré. Veuillez vérifier l\'ID de session et le PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Session introuvable. Veuillez vérifier vos identifiants.', + 'companionRemote.pairing.searchingForDevices' => 'Recherche d\'appareils...', + 'companionRemote.pairing.noDevicesFound' => 'Aucun appareil trouvé sur votre réseau', + 'companionRemote.pairing.noDevicesHint' => 'Assurez-vous que Plezy est ouvert sur votre bureau et que les deux appareils sont sur le même réseau WiFi', + 'companionRemote.pairing.availableDevices' => 'Appareils disponibles', + 'companionRemote.pairing.manualConnection' => 'Connexion manuelle', + 'companionRemote.pairing.cryptoInitFailed' => 'Impossible d\'initialiser la connexion sécurisée. Assurez-vous d\'être connecté à un compte Plex.', + 'companionRemote.pairing.validationHostRequired' => 'Veuillez entrer l\'adresse de l\'hôte', + 'companionRemote.pairing.validationHostFormat' => 'Le format doit être IP:port (ex. 192.168.1.100:48632)', + 'companionRemote.pairing.connectionTimedOut' => 'Délai de connexion dépassé. Assurez-vous que les deux appareils sont sur le même réseau.', + 'companionRemote.pairing.sessionNotFound' => 'Appareil introuvable. Assurez-vous que Plezy est en cours d\'exécution sur l\'hôte.', + 'companionRemote.pairing.authFailed' => 'Échec de l\'authentification. Assurez-vous que les deux appareils utilisent le même compte Plex.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Échec de la connexion : ${error}', 'companionRemote.remote.disconnectConfirm' => 'Voulez-vous vous déconnecter de la session distante ?', 'companionRemote.remote.reconnecting' => 'Reconnexion...', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index ac6ade71..05f450da 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Server telecomando'; + @override String get companionRemoteServerDescription => 'Consenti ai dispositivi mobili nella tua rete di controllare questa app'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionIt implements TranslationsCompanionRemo final TranslationsIt _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Creazione sessione remota...'; - @override String get failedToCreate => 'Impossibile creare la sessione remota:'; - @override String get noSession => 'Nessuna sessione disponibile'; - @override String get scanQrCode => 'Scansiona QR Code'; - @override String get orEnterManually => 'Oppure inserisci manualmente'; + @override String get startingServer => 'Avvio del server remoto...'; + @override String get failedToCreate => 'Impossibile avviare il server remoto:'; @override String get hostAddress => 'Indirizzo host'; - @override String get sessionId => 'ID sessione'; - @override String get pin => 'PIN'; @override String get connected => 'Connesso'; - @override String get waitingForConnection => 'In attesa di connessione...'; + @override String get serverRunning => 'Server remoto attivo'; + @override String get serverStopped => 'Server remoto arrestato'; + @override String get serverRunningDescription => 'I dispositivi mobili nella tua rete possono scoprire e connettersi a questa app'; + @override String get serverStoppedDescription => 'Avvia il server per consentire ai dispositivi mobili di connettersi'; @override String get usePhoneToControl => 'Usa il tuo dispositivo mobile per controllare questa app'; - @override String copiedToClipboard({required Object label}) => '${label} copiato negli appunti'; - @override String get copyToClipboard => 'Copia negli appunti'; - @override String get newSession => 'Nuova sessione'; + @override String get startServer => 'Avvia server'; + @override String get stopServer => 'Arresta server'; @override String get minimize => 'Riduci'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingIt implements TranslationsCompanionRemo final TranslationsIt _root; // ignore: unused_field // Translations - @override String get scan => 'Scansiona'; - @override String get manual => 'Manuale'; - @override String get pairWithDesktop => 'Associa con desktop'; - @override String get enterSessionDetails => 'Inserisci i dettagli della sessione mostrati sul tuo dispositivo desktop'; + @override String get pairWithDesktop => 'Connetti al desktop'; + @override String get discoveryDescription => 'I dispositivi nella tua rete che eseguono Plezy con lo stesso account Plex appariranno automaticamente'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Inserisci ID sessione di 8 caratteri'; - @override String get pinHint => 'Inserisci PIN di 6 cifre'; @override String get connecting => 'Connessione...'; - @override String get tips => 'Suggerimenti'; - @override String get tipDesktop => 'Apri Plezy sul tuo desktop e abilita Companion Remote dalle impostazioni o dal menu'; - @override String get tipScan => 'Usa la scheda Scansiona per associare rapidamente scansionando il QR code sul tuo desktop'; - @override String get tipWifi => 'Assicurati che entrambi i dispositivi siano sulla stessa rete WiFi'; - @override String get cameraPermissionRequired => 'L\'autorizzazione della fotocamera è necessaria per scansionare i QR code.\nConcedi l\'accesso alla fotocamera nelle impostazioni del dispositivo.'; - @override String cameraError({required Object error}) => 'Impossibile avviare la fotocamera: ${error}'; - @override String get scanInstruction => 'Punta la fotocamera verso il QR code mostrato sul tuo desktop'; - @override String get invalidQrCode => 'Formato QR code non valido'; + @override String get searchingForDevices => 'Ricerca dispositivi...'; + @override String get noDevicesFound => 'Nessun dispositivo trovato nella tua rete'; + @override String get noDevicesHint => 'Assicurati che Plezy sia aperto sul tuo desktop e che entrambi i dispositivi siano sulla stessa rete WiFi'; + @override String get availableDevices => 'Dispositivi disponibili'; + @override String get manualConnection => 'Connessione manuale'; + @override String get cryptoInitFailed => 'Impossibile inizializzare la connessione sicura. Assicurati di aver effettuato l\'accesso a un account Plex.'; @override String get validationHostRequired => 'Inserisci l\'indirizzo host'; @override String get validationHostFormat => 'Il formato deve essere IP:porta (es. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Inserisci un ID sessione'; - @override String get validationSessionIdLength => 'L\'ID sessione deve essere di 8 caratteri'; - @override String get validationPinRequired => 'Inserisci un PIN'; - @override String get validationPinLength => 'Il PIN deve essere di 6 cifre'; - @override String get connectionTimedOut => 'Connessione scaduta. Verifica l\'ID sessione e il PIN.'; - @override String get sessionNotFound => 'Sessione non trovata. Verifica le tue credenziali.'; + @override String get connectionTimedOut => 'Connessione scaduta. Assicurati che entrambi i dispositivi siano sulla stessa rete.'; + @override String get sessionNotFound => 'Dispositivo non trovato. Assicurati che Plezy sia in esecuzione sull\'host.'; + @override String get authFailed => 'Autenticazione fallita. Assicurati che entrambi i dispositivi utilizzino lo stesso account Plex.'; @override String failedToConnect({required Object error}) => 'Connessione fallita: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsIt { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Server telecomando', + 'settings.companionRemoteServerDescription' => 'Consenti ai dispositivi mobili nella tua rete di controllare questa app', 'search.hint' => 'Cerca film. spettacoli, musica...', 'search.tryDifferentTerm' => 'Prova altri termini di ricerca', 'search.searchYourMedia' => 'Cerca nei tuoi media', @@ -1881,10 +1873,10 @@ extension on TranslationsIt { 'libraries.groupings.seasons' => 'Stagioni', 'libraries.groupings.episodes' => 'Episodi', 'libraries.groupings.folders' => 'Cartelle', - 'about.title' => 'Informazioni', - 'about.openSourceLicenses' => 'Licenze Open Source', _ => null, } ?? switch (path) { + 'about.title' => 'Informazioni', + 'about.openSourceLicenses' => 'Licenze Open Source', 'about.versionLabel' => ({required Object version}) => 'Versione ${version}', 'about.appDescription' => 'Un bellissimo client Plex per Flutter', 'about.viewLicensesDescription' => 'Visualizza le licenze delle librerie di terze parti', @@ -2076,45 +2068,33 @@ extension on TranslationsIt { 'companionRemote.remoteControl' => 'Telecomando', 'companionRemote.controlDesktop' => 'Controlla un dispositivo desktop', 'companionRemote.connectedTo' => ({required Object name}) => 'Connesso a ${name}', - 'companionRemote.session.creatingSession' => 'Creazione sessione remota...', - 'companionRemote.session.failedToCreate' => 'Impossibile creare la sessione remota:', - 'companionRemote.session.noSession' => 'Nessuna sessione disponibile', - 'companionRemote.session.scanQrCode' => 'Scansiona QR Code', - 'companionRemote.session.orEnterManually' => 'Oppure inserisci manualmente', + 'companionRemote.session.startingServer' => 'Avvio del server remoto...', + 'companionRemote.session.failedToCreate' => 'Impossibile avviare il server remoto:', 'companionRemote.session.hostAddress' => 'Indirizzo host', - 'companionRemote.session.sessionId' => 'ID sessione', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Connesso', - 'companionRemote.session.waitingForConnection' => 'In attesa di connessione...', + 'companionRemote.session.serverRunning' => 'Server remoto attivo', + 'companionRemote.session.serverStopped' => 'Server remoto arrestato', + 'companionRemote.session.serverRunningDescription' => 'I dispositivi mobili nella tua rete possono scoprire e connettersi a questa app', + 'companionRemote.session.serverStoppedDescription' => 'Avvia il server per consentire ai dispositivi mobili di connettersi', 'companionRemote.session.usePhoneToControl' => 'Usa il tuo dispositivo mobile per controllare questa app', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copiato negli appunti', - 'companionRemote.session.copyToClipboard' => 'Copia negli appunti', - 'companionRemote.session.newSession' => 'Nuova sessione', + 'companionRemote.session.startServer' => 'Avvia server', + 'companionRemote.session.stopServer' => 'Arresta server', 'companionRemote.session.minimize' => 'Riduci', - 'companionRemote.pairing.scan' => 'Scansiona', - 'companionRemote.pairing.manual' => 'Manuale', - 'companionRemote.pairing.pairWithDesktop' => 'Associa con desktop', - 'companionRemote.pairing.enterSessionDetails' => 'Inserisci i dettagli della sessione mostrati sul tuo dispositivo desktop', + 'companionRemote.pairing.pairWithDesktop' => 'Connetti al desktop', + 'companionRemote.pairing.discoveryDescription' => 'I dispositivi nella tua rete che eseguono Plezy con lo stesso account Plex appariranno automaticamente', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Inserisci ID sessione di 8 caratteri', - 'companionRemote.pairing.pinHint' => 'Inserisci PIN di 6 cifre', 'companionRemote.pairing.connecting' => 'Connessione...', - 'companionRemote.pairing.tips' => 'Suggerimenti', - 'companionRemote.pairing.tipDesktop' => 'Apri Plezy sul tuo desktop e abilita Companion Remote dalle impostazioni o dal menu', - 'companionRemote.pairing.tipScan' => 'Usa la scheda Scansiona per associare rapidamente scansionando il QR code sul tuo desktop', - 'companionRemote.pairing.tipWifi' => 'Assicurati che entrambi i dispositivi siano sulla stessa rete WiFi', - 'companionRemote.pairing.cameraPermissionRequired' => 'L\'autorizzazione della fotocamera è necessaria per scansionare i QR code.\nConcedi l\'accesso alla fotocamera nelle impostazioni del dispositivo.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Impossibile avviare la fotocamera: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Punta la fotocamera verso il QR code mostrato sul tuo desktop', - 'companionRemote.pairing.invalidQrCode' => 'Formato QR code non valido', + 'companionRemote.pairing.searchingForDevices' => 'Ricerca dispositivi...', + 'companionRemote.pairing.noDevicesFound' => 'Nessun dispositivo trovato nella tua rete', + 'companionRemote.pairing.noDevicesHint' => 'Assicurati che Plezy sia aperto sul tuo desktop e che entrambi i dispositivi siano sulla stessa rete WiFi', + 'companionRemote.pairing.availableDevices' => 'Dispositivi disponibili', + 'companionRemote.pairing.manualConnection' => 'Connessione manuale', + 'companionRemote.pairing.cryptoInitFailed' => 'Impossibile inizializzare la connessione sicura. Assicurati di aver effettuato l\'accesso a un account Plex.', 'companionRemote.pairing.validationHostRequired' => 'Inserisci l\'indirizzo host', 'companionRemote.pairing.validationHostFormat' => 'Il formato deve essere IP:porta (es. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Inserisci un ID sessione', - 'companionRemote.pairing.validationSessionIdLength' => 'L\'ID sessione deve essere di 8 caratteri', - 'companionRemote.pairing.validationPinRequired' => 'Inserisci un PIN', - 'companionRemote.pairing.validationPinLength' => 'Il PIN deve essere di 6 cifre', - 'companionRemote.pairing.connectionTimedOut' => 'Connessione scaduta. Verifica l\'ID sessione e il PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Sessione non trovata. Verifica le tue credenziali.', + 'companionRemote.pairing.connectionTimedOut' => 'Connessione scaduta. Assicurati che entrambi i dispositivi siano sulla stessa rete.', + 'companionRemote.pairing.sessionNotFound' => 'Dispositivo non trovato. Assicurati che Plezy sia in esecuzione sull\'host.', + 'companionRemote.pairing.authFailed' => 'Autenticazione fallita. Assicurati che entrambi i dispositivi utilizzino lo stesso account Plex.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Connessione fallita: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Vuoi disconnetterti dalla sessione remota?', 'companionRemote.remote.reconnecting' => 'Riconnessione...', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index bc711bed..662036aa 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsJa implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'コンパニオンリモートサーバー'; + @override String get companionRemoteServerDescription => 'ネットワーク上のモバイルデバイスからこのアプリを操作できるようにする'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionJa implements TranslationsCompanionRemo final TranslationsJa _root; // ignore: unused_field // Translations - @override String get creatingSession => 'リモートセッションを作成中...'; - @override String get failedToCreate => 'リモートセッションの作成に失敗しました:'; - @override String get noSession => '利用可能なセッションがありません'; - @override String get scanQrCode => 'QRコードをスキャン'; - @override String get orEnterManually => 'または手動で入力'; + @override String get startingServer => 'リモートサーバーを起動中...'; + @override String get failedToCreate => 'リモートサーバーの起動に失敗しました:'; @override String get hostAddress => 'ホストアドレス'; - @override String get sessionId => 'セッションID'; - @override String get pin => 'PIN'; @override String get connected => '接続済み'; - @override String get waitingForConnection => '接続を待機中...'; - @override String get usePhoneToControl => 'モバイルデバイスでこのアプリを操作'; - @override String copiedToClipboard({required Object label}) => '${label}をクリップボードにコピーしました'; - @override String get copyToClipboard => 'クリップボードにコピー'; - @override String get newSession => '新しいセッション'; + @override String get serverRunning => 'リモートサーバー稼働中'; + @override String get serverStopped => 'リモートサーバー停止中'; + @override String get serverRunningDescription => 'ネットワーク上のモバイルデバイスがこのアプリを検出して接続できます'; + @override String get serverStoppedDescription => 'モバイルデバイスの接続を許可するにはサーバーを起動してください'; + @override String get usePhoneToControl => 'モバイルデバイスでこのアプリを操作できます'; + @override String get startServer => 'サーバーを起動'; + @override String get stopServer => 'サーバーを停止'; @override String get minimize => '最小化'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingJa implements TranslationsCompanionRemo final TranslationsJa _root; // ignore: unused_field // Translations - @override String get scan => 'スキャン'; - @override String get manual => '手動'; - @override String get pairWithDesktop => 'デスクトップとペアリング'; - @override String get enterSessionDetails => 'デスクトップデバイスに表示されたセッション情報を入力'; + @override String get pairWithDesktop => 'デスクトップに接続'; + @override String get discoveryDescription => '同じPlexアカウントでPlezyを実行しているネットワーク上のデバイスが自動的に表示されます'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => '8文字のセッションIDを入力'; - @override String get pinHint => '6桁のPINを入力'; @override String get connecting => '接続中...'; - @override String get tips => 'ヒント'; - @override String get tipDesktop => 'デスクトップでPlezyを開き、設定またはメニューからコンパニオンリモートを有効にしてください'; - @override String get tipScan => 'スキャンタブを使用して、デスクトップのQRコードをスキャンして素早くペアリング'; - @override String get tipWifi => '両方のデバイスが同じWiFiネットワークに接続されていることを確認'; - @override String get cameraPermissionRequired => 'QRコードをスキャンするにはカメラの権限が必要です。\nデバイスの設定でカメラへのアクセスを許可してください。'; - @override String cameraError({required Object error}) => 'カメラを起動できませんでした: ${error}'; - @override String get scanInstruction => 'デスクトップに表示されたQRコードにカメラを向けてください'; - @override String get invalidQrCode => '無効なQRコード形式'; + @override String get searchingForDevices => 'デバイスを検索中...'; + @override String get noDevicesFound => 'ネットワーク上にデバイスが見つかりません'; + @override String get noDevicesHint => 'デスクトップでPlezyが開いていること、両方のデバイスが同じWiFiネットワーク上にあることを確認してください'; + @override String get availableDevices => '利用可能なデバイス'; + @override String get manualConnection => '手動接続'; + @override String get cryptoInitFailed => '安全な接続を初期化できませんでした。Plexアカウントにサインインしていることを確認してください。'; @override String get validationHostRequired => 'ホストアドレスを入力してください'; @override String get validationHostFormat => '形式はIP:ポートである必要があります(例: 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'セッションIDを入力してください'; - @override String get validationSessionIdLength => 'セッションIDは8文字である必要があります'; - @override String get validationPinRequired => 'PINを入力してください'; - @override String get validationPinLength => 'PINは6桁である必要があります'; - @override String get connectionTimedOut => '接続がタイムアウトしました。セッションIDとPINを確認してください。'; - @override String get sessionNotFound => 'セッションが見つかりませんでした。認証情報を確認してください。'; + @override String get connectionTimedOut => '接続がタイムアウトしました。両方のデバイスが同じネットワーク上にあることを確認してください。'; + @override String get sessionNotFound => 'デバイスが見つかりません。ホストでPlezyが実行されていることを確認してください。'; + @override String get authFailed => '認証に失敗しました。両方のデバイスが同じPlexアカウントを使用していることを確認してください。'; @override String failedToConnect({required Object error}) => '接続に失敗しました: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsJa { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'コンパニオンリモートサーバー', + 'settings.companionRemoteServerDescription' => 'ネットワーク上のモバイルデバイスからこのアプリを操作できるようにする', 'search.hint' => '映画、番組、音楽を検索...', 'search.tryDifferentTerm' => '別の検索語をお試しください', 'search.searchYourMedia' => 'メディアを検索', @@ -1881,10 +1873,10 @@ extension on TranslationsJa { 'libraries.groupings.seasons' => 'シーズン', 'libraries.groupings.episodes' => 'エピソード', 'libraries.groupings.folders' => 'フォルダ', - 'about.title' => 'アプリについて', - 'about.openSourceLicenses' => 'オープンソースライセンス', _ => null, } ?? switch (path) { + 'about.title' => 'アプリについて', + 'about.openSourceLicenses' => 'オープンソースライセンス', 'about.versionLabel' => ({required Object version}) => 'バージョン ${version}', 'about.appDescription' => 'Flutter製の美しいPlexクライアント', 'about.viewLicensesDescription' => 'サードパーティライブラリのライセンスを表示', @@ -2076,45 +2068,33 @@ extension on TranslationsJa { 'companionRemote.remoteControl' => 'リモコン', 'companionRemote.controlDesktop' => 'デスクトップデバイスを操作', 'companionRemote.connectedTo' => ({required Object name}) => '${name}に接続中', - 'companionRemote.session.creatingSession' => 'リモートセッションを作成中...', - 'companionRemote.session.failedToCreate' => 'リモートセッションの作成に失敗しました:', - 'companionRemote.session.noSession' => '利用可能なセッションがありません', - 'companionRemote.session.scanQrCode' => 'QRコードをスキャン', - 'companionRemote.session.orEnterManually' => 'または手動で入力', + 'companionRemote.session.startingServer' => 'リモートサーバーを起動中...', + 'companionRemote.session.failedToCreate' => 'リモートサーバーの起動に失敗しました:', 'companionRemote.session.hostAddress' => 'ホストアドレス', - 'companionRemote.session.sessionId' => 'セッションID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => '接続済み', - 'companionRemote.session.waitingForConnection' => '接続を待機中...', - 'companionRemote.session.usePhoneToControl' => 'モバイルデバイスでこのアプリを操作', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label}をクリップボードにコピーしました', - 'companionRemote.session.copyToClipboard' => 'クリップボードにコピー', - 'companionRemote.session.newSession' => '新しいセッション', + 'companionRemote.session.serverRunning' => 'リモートサーバー稼働中', + 'companionRemote.session.serverStopped' => 'リモートサーバー停止中', + 'companionRemote.session.serverRunningDescription' => 'ネットワーク上のモバイルデバイスがこのアプリを検出して接続できます', + 'companionRemote.session.serverStoppedDescription' => 'モバイルデバイスの接続を許可するにはサーバーを起動してください', + 'companionRemote.session.usePhoneToControl' => 'モバイルデバイスでこのアプリを操作できます', + 'companionRemote.session.startServer' => 'サーバーを起動', + 'companionRemote.session.stopServer' => 'サーバーを停止', 'companionRemote.session.minimize' => '最小化', - 'companionRemote.pairing.scan' => 'スキャン', - 'companionRemote.pairing.manual' => '手動', - 'companionRemote.pairing.pairWithDesktop' => 'デスクトップとペアリング', - 'companionRemote.pairing.enterSessionDetails' => 'デスクトップデバイスに表示されたセッション情報を入力', + 'companionRemote.pairing.pairWithDesktop' => 'デスクトップに接続', + 'companionRemote.pairing.discoveryDescription' => '同じPlexアカウントでPlezyを実行しているネットワーク上のデバイスが自動的に表示されます', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => '8文字のセッションIDを入力', - 'companionRemote.pairing.pinHint' => '6桁のPINを入力', 'companionRemote.pairing.connecting' => '接続中...', - 'companionRemote.pairing.tips' => 'ヒント', - 'companionRemote.pairing.tipDesktop' => 'デスクトップでPlezyを開き、設定またはメニューからコンパニオンリモートを有効にしてください', - 'companionRemote.pairing.tipScan' => 'スキャンタブを使用して、デスクトップのQRコードをスキャンして素早くペアリング', - 'companionRemote.pairing.tipWifi' => '両方のデバイスが同じWiFiネットワークに接続されていることを確認', - 'companionRemote.pairing.cameraPermissionRequired' => 'QRコードをスキャンするにはカメラの権限が必要です。\nデバイスの設定でカメラへのアクセスを許可してください。', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'カメラを起動できませんでした: ${error}', - 'companionRemote.pairing.scanInstruction' => 'デスクトップに表示されたQRコードにカメラを向けてください', - 'companionRemote.pairing.invalidQrCode' => '無効なQRコード形式', + 'companionRemote.pairing.searchingForDevices' => 'デバイスを検索中...', + 'companionRemote.pairing.noDevicesFound' => 'ネットワーク上にデバイスが見つかりません', + 'companionRemote.pairing.noDevicesHint' => 'デスクトップでPlezyが開いていること、両方のデバイスが同じWiFiネットワーク上にあることを確認してください', + 'companionRemote.pairing.availableDevices' => '利用可能なデバイス', + 'companionRemote.pairing.manualConnection' => '手動接続', + 'companionRemote.pairing.cryptoInitFailed' => '安全な接続を初期化できませんでした。Plexアカウントにサインインしていることを確認してください。', 'companionRemote.pairing.validationHostRequired' => 'ホストアドレスを入力してください', 'companionRemote.pairing.validationHostFormat' => '形式はIP:ポートである必要があります(例: 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'セッションIDを入力してください', - 'companionRemote.pairing.validationSessionIdLength' => 'セッションIDは8文字である必要があります', - 'companionRemote.pairing.validationPinRequired' => 'PINを入力してください', - 'companionRemote.pairing.validationPinLength' => 'PINは6桁である必要があります', - 'companionRemote.pairing.connectionTimedOut' => '接続がタイムアウトしました。セッションIDとPINを確認してください。', - 'companionRemote.pairing.sessionNotFound' => 'セッションが見つかりませんでした。認証情報を確認してください。', + 'companionRemote.pairing.connectionTimedOut' => '接続がタイムアウトしました。両方のデバイスが同じネットワーク上にあることを確認してください。', + 'companionRemote.pairing.sessionNotFound' => 'デバイスが見つかりません。ホストでPlezyが実行されていることを確認してください。', + 'companionRemote.pairing.authFailed' => '認証に失敗しました。両方のデバイスが同じPlexアカウントを使用していることを確認してください。', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => '接続に失敗しました: ${error}', 'companionRemote.remote.disconnectConfirm' => 'リモートセッションから切断しますか?', 'companionRemote.remote.reconnecting' => '再接続中...', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 1c16150c..3d7ff58c 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => '컴패니언 리모트 서버'; + @override String get companionRemoteServerDescription => '네트워크의 모바일 기기가 이 앱을 제어할 수 있도록 허용'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionKo implements TranslationsCompanionRemo final TranslationsKo _root; // ignore: unused_field // Translations - @override String get creatingSession => '원격 세션 생성 중...'; - @override String get failedToCreate => '원격 세션 생성 실패:'; - @override String get noSession => '사용 가능한 세션이 없습니다'; - @override String get scanQrCode => 'QR 코드 스캔'; - @override String get orEnterManually => '또는 수동으로 입력'; + @override String get startingServer => '원격 서버 시작 중...'; + @override String get failedToCreate => '원격 서버를 시작하지 못했습니다:'; @override String get hostAddress => '호스트 주소'; - @override String get sessionId => '세션 ID'; - @override String get pin => 'PIN'; @override String get connected => '연결됨'; - @override String get waitingForConnection => '연결 대기 중...'; + @override String get serverRunning => '원격 서버 활성'; + @override String get serverStopped => '원격 서버 중지됨'; + @override String get serverRunningDescription => '네트워크의 모바일 기기가 이 앱을 검색하고 연결할 수 있습니다'; + @override String get serverStoppedDescription => '모바일 기기의 연결을 허용하려면 서버를 시작하세요'; @override String get usePhoneToControl => '모바일 기기로 이 앱을 제어하세요'; - @override String copiedToClipboard({required Object label}) => '${label}이(가) 클립보드에 복사되었습니다'; - @override String get copyToClipboard => '클립보드에 복사'; - @override String get newSession => '새 세션'; + @override String get startServer => '서버 시작'; + @override String get stopServer => '서버 중지'; @override String get minimize => '최소화'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingKo implements TranslationsCompanionRemo final TranslationsKo _root; // ignore: unused_field // Translations - @override String get scan => '스캔'; - @override String get manual => '수동'; - @override String get pairWithDesktop => '데스크톱과 페어링'; - @override String get enterSessionDetails => '데스크톱 기기에 표시된 세션 정보를 입력하세요'; + @override String get pairWithDesktop => '데스크톱에 연결'; + @override String get discoveryDescription => '같은 Plex 계정으로 Plezy를 실행 중인 네트워크의 기기가 자동으로 표시됩니다'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => '8자리 세션 ID 입력'; - @override String get pinHint => '6자리 PIN 입력'; @override String get connecting => '연결 중...'; - @override String get tips => '팁'; - @override String get tipDesktop => '데스크톱에서 Plezy를 열고 설정 또는 메뉴에서 Companion Remote를 활성화하세요'; - @override String get tipScan => '스캔 탭을 사용하여 데스크톱의 QR 코드를 스캔하면 빠르게 페어링할 수 있습니다'; - @override String get tipWifi => '두 기기가 동일한 WiFi 네트워크에 연결되어 있는지 확인하세요'; - @override String get cameraPermissionRequired => 'QR 코드를 스캔하려면 카메라 권한이 필요합니다.\n기기 설정에서 카메라 접근을 허용해 주세요.'; - @override String cameraError({required Object error}) => '카메라를 시작할 수 없습니다: ${error}'; - @override String get scanInstruction => '데스크톱에 표시된 QR 코드에 카메라를 향하세요'; - @override String get invalidQrCode => '유효하지 않은 QR 코드 형식'; + @override String get searchingForDevices => '기기 검색 중...'; + @override String get noDevicesFound => '네트워크에서 기기를 찾을 수 없습니다'; + @override String get noDevicesHint => '데스크톱에서 Plezy가 열려 있고 두 기기가 같은 WiFi 네트워크에 있는지 확인하세요'; + @override String get availableDevices => '사용 가능한 기기'; + @override String get manualConnection => '수동 연결'; + @override String get cryptoInitFailed => '보안 연결을 초기화할 수 없습니다. Plex 계정에 로그인되어 있는지 확인하세요.'; @override String get validationHostRequired => '호스트 주소를 입력하세요'; - @override String get validationHostFormat => 'IP:포트 형식이어야 합니다 (예: 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => '세션 ID를 입력하세요'; - @override String get validationSessionIdLength => '세션 ID는 8자리여야 합니다'; - @override String get validationPinRequired => 'PIN을 입력하세요'; - @override String get validationPinLength => 'PIN은 6자리여야 합니다'; - @override String get connectionTimedOut => '연결 시간이 초과되었습니다. 세션 ID와 PIN을 확인하세요.'; - @override String get sessionNotFound => '세션을 찾을 수 없습니다. 자격 증명을 확인하세요.'; + @override String get validationHostFormat => '형식은 IP:포트여야 합니다 (예: 192.168.1.100:48632)'; + @override String get connectionTimedOut => '연결 시간이 초과되었습니다. 두 기기가 같은 네트워크에 있는지 확인하세요.'; + @override String get sessionNotFound => '기기를 찾을 수 없습니다. 호스트에서 Plezy가 실행 중인지 확인하세요.'; + @override String get authFailed => '인증에 실패했습니다. 두 기기가 같은 Plex 계정을 사용하는지 확인하세요.'; @override String failedToConnect({required Object error}) => '연결 실패: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsKo { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => '컴패니언 리모트 서버', + 'settings.companionRemoteServerDescription' => '네트워크의 모바일 기기가 이 앱을 제어할 수 있도록 허용', 'search.hint' => '영화, 시리즈, 음악 등을 검색하세요...', 'search.tryDifferentTerm' => '다른 검색어를 시도해 보세요', 'search.searchYourMedia' => '미디어 검색', @@ -1881,10 +1873,10 @@ extension on TranslationsKo { 'libraries.groupings.seasons' => '시즌', 'libraries.groupings.episodes' => '화', 'libraries.groupings.folders' => '폴더', - 'about.title' => '소개', - 'about.openSourceLicenses' => '오픈소스 라이선스', _ => null, } ?? switch (path) { + 'about.title' => '소개', + 'about.openSourceLicenses' => '오픈소스 라이선스', 'about.versionLabel' => ({required Object version}) => '버전 ${version}', 'about.appDescription' => '아름다운 Flutter Plex 클라이언트', 'about.viewLicensesDescription' => '타사 라이브러리 라이선스 보기', @@ -2076,45 +2068,33 @@ extension on TranslationsKo { 'companionRemote.remoteControl' => '원격 제어', 'companionRemote.controlDesktop' => '데스크톱 기기 제어', 'companionRemote.connectedTo' => ({required Object name}) => '${name}에 연결됨', - 'companionRemote.session.creatingSession' => '원격 세션 생성 중...', - 'companionRemote.session.failedToCreate' => '원격 세션 생성 실패:', - 'companionRemote.session.noSession' => '사용 가능한 세션이 없습니다', - 'companionRemote.session.scanQrCode' => 'QR 코드 스캔', - 'companionRemote.session.orEnterManually' => '또는 수동으로 입력', + 'companionRemote.session.startingServer' => '원격 서버 시작 중...', + 'companionRemote.session.failedToCreate' => '원격 서버를 시작하지 못했습니다:', 'companionRemote.session.hostAddress' => '호스트 주소', - 'companionRemote.session.sessionId' => '세션 ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => '연결됨', - 'companionRemote.session.waitingForConnection' => '연결 대기 중...', + 'companionRemote.session.serverRunning' => '원격 서버 활성', + 'companionRemote.session.serverStopped' => '원격 서버 중지됨', + 'companionRemote.session.serverRunningDescription' => '네트워크의 모바일 기기가 이 앱을 검색하고 연결할 수 있습니다', + 'companionRemote.session.serverStoppedDescription' => '모바일 기기의 연결을 허용하려면 서버를 시작하세요', 'companionRemote.session.usePhoneToControl' => '모바일 기기로 이 앱을 제어하세요', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label}이(가) 클립보드에 복사되었습니다', - 'companionRemote.session.copyToClipboard' => '클립보드에 복사', - 'companionRemote.session.newSession' => '새 세션', + 'companionRemote.session.startServer' => '서버 시작', + 'companionRemote.session.stopServer' => '서버 중지', 'companionRemote.session.minimize' => '최소화', - 'companionRemote.pairing.scan' => '스캔', - 'companionRemote.pairing.manual' => '수동', - 'companionRemote.pairing.pairWithDesktop' => '데스크톱과 페어링', - 'companionRemote.pairing.enterSessionDetails' => '데스크톱 기기에 표시된 세션 정보를 입력하세요', + 'companionRemote.pairing.pairWithDesktop' => '데스크톱에 연결', + 'companionRemote.pairing.discoveryDescription' => '같은 Plex 계정으로 Plezy를 실행 중인 네트워크의 기기가 자동으로 표시됩니다', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => '8자리 세션 ID 입력', - 'companionRemote.pairing.pinHint' => '6자리 PIN 입력', 'companionRemote.pairing.connecting' => '연결 중...', - 'companionRemote.pairing.tips' => '팁', - 'companionRemote.pairing.tipDesktop' => '데스크톱에서 Plezy를 열고 설정 또는 메뉴에서 Companion Remote를 활성화하세요', - 'companionRemote.pairing.tipScan' => '스캔 탭을 사용하여 데스크톱의 QR 코드를 스캔하면 빠르게 페어링할 수 있습니다', - 'companionRemote.pairing.tipWifi' => '두 기기가 동일한 WiFi 네트워크에 연결되어 있는지 확인하세요', - 'companionRemote.pairing.cameraPermissionRequired' => 'QR 코드를 스캔하려면 카메라 권한이 필요합니다.\n기기 설정에서 카메라 접근을 허용해 주세요.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => '카메라를 시작할 수 없습니다: ${error}', - 'companionRemote.pairing.scanInstruction' => '데스크톱에 표시된 QR 코드에 카메라를 향하세요', - 'companionRemote.pairing.invalidQrCode' => '유효하지 않은 QR 코드 형식', + 'companionRemote.pairing.searchingForDevices' => '기기 검색 중...', + 'companionRemote.pairing.noDevicesFound' => '네트워크에서 기기를 찾을 수 없습니다', + 'companionRemote.pairing.noDevicesHint' => '데스크톱에서 Plezy가 열려 있고 두 기기가 같은 WiFi 네트워크에 있는지 확인하세요', + 'companionRemote.pairing.availableDevices' => '사용 가능한 기기', + 'companionRemote.pairing.manualConnection' => '수동 연결', + 'companionRemote.pairing.cryptoInitFailed' => '보안 연결을 초기화할 수 없습니다. Plex 계정에 로그인되어 있는지 확인하세요.', 'companionRemote.pairing.validationHostRequired' => '호스트 주소를 입력하세요', - 'companionRemote.pairing.validationHostFormat' => 'IP:포트 형식이어야 합니다 (예: 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => '세션 ID를 입력하세요', - 'companionRemote.pairing.validationSessionIdLength' => '세션 ID는 8자리여야 합니다', - 'companionRemote.pairing.validationPinRequired' => 'PIN을 입력하세요', - 'companionRemote.pairing.validationPinLength' => 'PIN은 6자리여야 합니다', - 'companionRemote.pairing.connectionTimedOut' => '연결 시간이 초과되었습니다. 세션 ID와 PIN을 확인하세요.', - 'companionRemote.pairing.sessionNotFound' => '세션을 찾을 수 없습니다. 자격 증명을 확인하세요.', + 'companionRemote.pairing.validationHostFormat' => '형식은 IP:포트여야 합니다 (예: 192.168.1.100:48632)', + 'companionRemote.pairing.connectionTimedOut' => '연결 시간이 초과되었습니다. 두 기기가 같은 네트워크에 있는지 확인하세요.', + 'companionRemote.pairing.sessionNotFound' => '기기를 찾을 수 없습니다. 호스트에서 Plezy가 실행 중인지 확인하세요.', + 'companionRemote.pairing.authFailed' => '인증에 실패했습니다. 두 기기가 같은 Plex 계정을 사용하는지 확인하세요.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => '연결 실패: ${error}', 'companionRemote.remote.disconnectConfirm' => '원격 세션 연결을 해제하시겠습니까?', 'companionRemote.remote.reconnecting' => '재연결 중...', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 55e27534..40c07eec 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsNb implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Companion Remote-server'; + @override String get companionRemoteServerDescription => 'Tillat mobilenheter på nettverket ditt å styre denne appen'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionNb implements TranslationsCompanionRemo final TranslationsNb _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Oppretter fjernøkt...'; - @override String get failedToCreate => 'Kunne ikke opprette fjernøkt:'; - @override String get noSession => 'Ingen økt tilgjengelig'; - @override String get scanQrCode => 'Skann QR-kode'; - @override String get orEnterManually => 'Eller skriv inn manuelt'; + @override String get startingServer => 'Starter fjernserver...'; + @override String get failedToCreate => 'Kunne ikke starte fjernserver:'; @override String get hostAddress => 'Vertsadresse'; - @override String get sessionId => 'Økt-ID'; - @override String get pin => 'PIN'; @override String get connected => 'Tilkoblet'; - @override String get waitingForConnection => 'Venter på tilkobling...'; - @override String get usePhoneToControl => 'Bruk mobilenheten din til å kontrollere denne appen'; - @override String copiedToClipboard({required Object label}) => '${label} kopiert til utklippstavle'; - @override String get copyToClipboard => 'Kopier til utklippstavle'; - @override String get newSession => 'Ny økt'; + @override String get serverRunning => 'Fjernserver aktiv'; + @override String get serverStopped => 'Fjernserver stoppet'; + @override String get serverRunningDescription => 'Mobilenheter på nettverket ditt kan oppdage og koble til denne appen'; + @override String get serverStoppedDescription => 'Start serveren for å la mobilenheter koble til'; + @override String get usePhoneToControl => 'Bruk mobilenheten din til å styre denne appen'; + @override String get startServer => 'Start server'; + @override String get stopServer => 'Stopp server'; @override String get minimize => 'Minimer'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingNb implements TranslationsCompanionRemo final TranslationsNb _root; // ignore: unused_field // Translations - @override String get scan => 'Skann'; - @override String get manual => 'Manuell'; - @override String get pairWithDesktop => 'Koble til stasjonær'; - @override String get enterSessionDetails => 'Skriv inn øktdetaljene som vises på den stasjonære enheten din'; + @override String get pairWithDesktop => 'Koble til datamaskin'; + @override String get discoveryDescription => 'Enheter på nettverket ditt som kjører Plezy med samme Plex-konto vil vises automatisk'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Skriv inn 8-tegns økt-ID'; - @override String get pinHint => 'Skriv inn 6-sifret PIN'; @override String get connecting => 'Kobler til...'; - @override String get tips => 'Tips'; - @override String get tipDesktop => 'Åpne Plezy på datamaskinen din og aktiver Følgesvenn-fjernkontroll fra innstillinger eller meny'; - @override String get tipScan => 'Bruk Skann-fanen for å raskt koble til ved å skanne QR-koden på datamaskinen din'; - @override String get tipWifi => 'Sørg for at begge enhetene er på samme WiFi-nettverk'; - @override String get cameraPermissionRequired => 'Kameratillatelse kreves for å skanne QR-koder.\nVennligst gi kameratilgang i enhetsinnstillingene.'; - @override String cameraError({required Object error}) => 'Kunne ikke starte kamera: ${error}'; - @override String get scanInstruction => 'Pek kameraet mot QR-koden som vises på datamaskinen din'; - @override String get invalidQrCode => 'Ugyldig QR-kodeformat'; - @override String get validationHostRequired => 'Vennligst skriv inn vertsadresse'; - @override String get validationHostFormat => 'Formatet må være IP:port (f.eks. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Vennligst skriv inn en økt-ID'; - @override String get validationSessionIdLength => 'Økt-ID må være 8 tegn'; - @override String get validationPinRequired => 'Vennligst skriv inn en PIN'; - @override String get validationPinLength => 'PIN må være 6 sifre'; - @override String get connectionTimedOut => 'Tilkoblingen ble tidsavbrutt. Sjekk økt-ID og PIN.'; - @override String get sessionNotFound => 'Kunne ikke finne økten. Sjekk legitimasjonen din.'; + @override String get searchingForDevices => 'Søker etter enheter...'; + @override String get noDevicesFound => 'Ingen enheter funnet på nettverket ditt'; + @override String get noDevicesHint => 'Sørg for at Plezy er åpen på datamaskinen din og at begge enhetene er på samme WiFi-nettverk'; + @override String get availableDevices => 'Tilgjengelige enheter'; + @override String get manualConnection => 'Manuell tilkobling'; + @override String get cryptoInitFailed => 'Kunne ikke initialisere sikker tilkobling. Sørg for at du er logget inn på en Plex-konto.'; + @override String get validationHostRequired => 'Vennligst oppgi vertsadresse'; + @override String get validationHostFormat => 'Format må være IP:port (f.eks. 192.168.1.100:48632)'; + @override String get connectionTimedOut => 'Tilkoblingen ble tidsavbrutt. Sørg for at begge enhetene er på samme nettverk.'; + @override String get sessionNotFound => 'Kunne ikke finne enheten. Sørg for at Plezy kjører på verten.'; + @override String get authFailed => 'Autentisering mislyktes. Sørg for at begge enhetene bruker samme Plex-konto.'; @override String failedToConnect({required Object error}) => 'Kunne ikke koble til: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsNb { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Companion Remote-server', + 'settings.companionRemoteServerDescription' => 'Tillat mobilenheter på nettverket ditt å styre denne appen', 'search.hint' => 'Søk i filmer, serier, musikk...', 'search.tryDifferentTerm' => 'Prøv et annet søkeord', 'search.searchYourMedia' => 'Søk i mediene dine', @@ -1881,10 +1873,10 @@ extension on TranslationsNb { 'libraries.groupings.seasons' => 'Sesonger', 'libraries.groupings.episodes' => 'Episoder', 'libraries.groupings.folders' => 'Mapper', - 'about.title' => 'Om', - 'about.openSourceLicenses' => 'Åpen kildekode-lisenser', _ => null, } ?? switch (path) { + 'about.title' => 'Om', + 'about.openSourceLicenses' => 'Åpen kildekode-lisenser', 'about.versionLabel' => ({required Object version}) => 'Versjon ${version}', 'about.appDescription' => 'En vakker Plex-klient for Flutter', 'about.viewLicensesDescription' => 'Vis lisenser for tredjepartsbiblioteker', @@ -2076,45 +2068,33 @@ extension on TranslationsNb { 'companionRemote.remoteControl' => 'Fjernkontroll', 'companionRemote.controlDesktop' => 'Kontroller en stasjonær enhet', 'companionRemote.connectedTo' => ({required Object name}) => 'Tilkoblet ${name}', - 'companionRemote.session.creatingSession' => 'Oppretter fjernøkt...', - 'companionRemote.session.failedToCreate' => 'Kunne ikke opprette fjernøkt:', - 'companionRemote.session.noSession' => 'Ingen økt tilgjengelig', - 'companionRemote.session.scanQrCode' => 'Skann QR-kode', - 'companionRemote.session.orEnterManually' => 'Eller skriv inn manuelt', + 'companionRemote.session.startingServer' => 'Starter fjernserver...', + 'companionRemote.session.failedToCreate' => 'Kunne ikke starte fjernserver:', 'companionRemote.session.hostAddress' => 'Vertsadresse', - 'companionRemote.session.sessionId' => 'Økt-ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Tilkoblet', - 'companionRemote.session.waitingForConnection' => 'Venter på tilkobling...', - 'companionRemote.session.usePhoneToControl' => 'Bruk mobilenheten din til å kontrollere denne appen', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} kopiert til utklippstavle', - 'companionRemote.session.copyToClipboard' => 'Kopier til utklippstavle', - 'companionRemote.session.newSession' => 'Ny økt', + 'companionRemote.session.serverRunning' => 'Fjernserver aktiv', + 'companionRemote.session.serverStopped' => 'Fjernserver stoppet', + 'companionRemote.session.serverRunningDescription' => 'Mobilenheter på nettverket ditt kan oppdage og koble til denne appen', + 'companionRemote.session.serverStoppedDescription' => 'Start serveren for å la mobilenheter koble til', + 'companionRemote.session.usePhoneToControl' => 'Bruk mobilenheten din til å styre denne appen', + 'companionRemote.session.startServer' => 'Start server', + 'companionRemote.session.stopServer' => 'Stopp server', 'companionRemote.session.minimize' => 'Minimer', - 'companionRemote.pairing.scan' => 'Skann', - 'companionRemote.pairing.manual' => 'Manuell', - 'companionRemote.pairing.pairWithDesktop' => 'Koble til stasjonær', - 'companionRemote.pairing.enterSessionDetails' => 'Skriv inn øktdetaljene som vises på den stasjonære enheten din', + 'companionRemote.pairing.pairWithDesktop' => 'Koble til datamaskin', + 'companionRemote.pairing.discoveryDescription' => 'Enheter på nettverket ditt som kjører Plezy med samme Plex-konto vil vises automatisk', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Skriv inn 8-tegns økt-ID', - 'companionRemote.pairing.pinHint' => 'Skriv inn 6-sifret PIN', 'companionRemote.pairing.connecting' => 'Kobler til...', - 'companionRemote.pairing.tips' => 'Tips', - 'companionRemote.pairing.tipDesktop' => 'Åpne Plezy på datamaskinen din og aktiver Følgesvenn-fjernkontroll fra innstillinger eller meny', - 'companionRemote.pairing.tipScan' => 'Bruk Skann-fanen for å raskt koble til ved å skanne QR-koden på datamaskinen din', - 'companionRemote.pairing.tipWifi' => 'Sørg for at begge enhetene er på samme WiFi-nettverk', - 'companionRemote.pairing.cameraPermissionRequired' => 'Kameratillatelse kreves for å skanne QR-koder.\nVennligst gi kameratilgang i enhetsinnstillingene.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kunne ikke starte kamera: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Pek kameraet mot QR-koden som vises på datamaskinen din', - 'companionRemote.pairing.invalidQrCode' => 'Ugyldig QR-kodeformat', - 'companionRemote.pairing.validationHostRequired' => 'Vennligst skriv inn vertsadresse', - 'companionRemote.pairing.validationHostFormat' => 'Formatet må være IP:port (f.eks. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Vennligst skriv inn en økt-ID', - 'companionRemote.pairing.validationSessionIdLength' => 'Økt-ID må være 8 tegn', - 'companionRemote.pairing.validationPinRequired' => 'Vennligst skriv inn en PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN må være 6 sifre', - 'companionRemote.pairing.connectionTimedOut' => 'Tilkoblingen ble tidsavbrutt. Sjekk økt-ID og PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finne økten. Sjekk legitimasjonen din.', + 'companionRemote.pairing.searchingForDevices' => 'Søker etter enheter...', + 'companionRemote.pairing.noDevicesFound' => 'Ingen enheter funnet på nettverket ditt', + 'companionRemote.pairing.noDevicesHint' => 'Sørg for at Plezy er åpen på datamaskinen din og at begge enhetene er på samme WiFi-nettverk', + 'companionRemote.pairing.availableDevices' => 'Tilgjengelige enheter', + 'companionRemote.pairing.manualConnection' => 'Manuell tilkobling', + 'companionRemote.pairing.cryptoInitFailed' => 'Kunne ikke initialisere sikker tilkobling. Sørg for at du er logget inn på en Plex-konto.', + 'companionRemote.pairing.validationHostRequired' => 'Vennligst oppgi vertsadresse', + 'companionRemote.pairing.validationHostFormat' => 'Format må være IP:port (f.eks. 192.168.1.100:48632)', + 'companionRemote.pairing.connectionTimedOut' => 'Tilkoblingen ble tidsavbrutt. Sørg for at begge enhetene er på samme nettverk.', + 'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finne enheten. Sørg for at Plezy kjører på verten.', + 'companionRemote.pairing.authFailed' => 'Autentisering mislyktes. Sørg for at begge enhetene bruker samme Plex-konto.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke koble til: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Vil du koble fra fjernøkten?', 'companionRemote.remote.reconnecting' => 'Kobler til på nytt...', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index def3d1ef..44ea6adf 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Companion Remote-server'; + @override String get companionRemoteServerDescription => 'Sta mobiele apparaten op je netwerk toe om deze app te bedienen'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionNl implements TranslationsCompanionRemo final TranslationsNl _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Externe sessie aanmaken...'; - @override String get failedToCreate => 'Kan externe sessie niet aanmaken:'; - @override String get noSession => 'Geen sessie beschikbaar'; - @override String get scanQrCode => 'Scan QR-code'; - @override String get orEnterManually => 'Of voer handmatig in'; + @override String get startingServer => 'Externe server starten...'; + @override String get failedToCreate => 'Kan externe server niet starten:'; @override String get hostAddress => 'Hostadres'; - @override String get sessionId => 'Sessie-ID'; - @override String get pin => 'PIN'; @override String get connected => 'Verbonden'; - @override String get waitingForConnection => 'Wachten op verbinding...'; + @override String get serverRunning => 'Externe server actief'; + @override String get serverStopped => 'Externe server gestopt'; + @override String get serverRunningDescription => 'Mobiele apparaten op je netwerk kunnen deze app ontdekken en ermee verbinden'; + @override String get serverStoppedDescription => 'Start de server om mobiele apparaten te laten verbinden'; @override String get usePhoneToControl => 'Gebruik je mobiele apparaat om deze app te bedienen'; - @override String copiedToClipboard({required Object label}) => '${label} gekopieerd naar klembord'; - @override String get copyToClipboard => 'Kopieer naar klembord'; - @override String get newSession => 'Nieuwe sessie'; + @override String get startServer => 'Server starten'; + @override String get stopServer => 'Server stoppen'; @override String get minimize => 'Minimaliseren'; } @@ -1298,31 +1297,22 @@ class _TranslationsCompanionRemotePairingNl implements TranslationsCompanionRemo final TranslationsNl _root; // ignore: unused_field // Translations - @override String get scan => 'Scannen'; - @override String get manual => 'Handmatig'; - @override String get pairWithDesktop => 'Koppelen met desktop'; - @override String get enterSessionDetails => 'Voer de sessiegegevens in die op je desktop-apparaat worden getoond'; + @override String get pairWithDesktop => 'Verbinden met desktop'; + @override String get discoveryDescription => 'Apparaten op je netwerk die Plezy gebruiken met hetzelfde Plex-account verschijnen automatisch'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Voer 8-tekens sessie-ID in'; - @override String get pinHint => 'Voer 6-cijferige PIN in'; @override String get connecting => 'Verbinden...'; - @override String get tips => 'Tips'; - @override String get tipDesktop => 'Open Plezy op je desktop en schakel Companion Remote in via instellingen of menu'; - @override String get tipScan => 'Gebruik het tabblad Scannen om snel te koppelen door de QR-code op je desktop te scannen'; - @override String get tipWifi => 'Zorg ervoor dat beide apparaten op hetzelfde WiFi-netwerk zitten'; - @override String get cameraPermissionRequired => 'Cameratoestemming is vereist om QR-codes te scannen.\nGeef cameratoegang in je apparaatinstellingen.'; - @override String cameraError({required Object error}) => 'Kan camera niet starten: ${error}'; - @override String get scanInstruction => 'Richt je camera op de QR-code die op je desktop wordt getoond'; - @override String get invalidQrCode => 'Ongeldig QR-codeformaat'; - @override String get validationHostRequired => 'Voer een hostadres in'; + @override String get searchingForDevices => 'Apparaten zoeken...'; + @override String get noDevicesFound => 'Geen apparaten gevonden op je netwerk'; + @override String get noDevicesHint => 'Zorg ervoor dat Plezy geopend is op je desktop en dat beide apparaten op hetzelfde WiFi-netwerk zitten'; + @override String get availableDevices => 'Beschikbare apparaten'; + @override String get manualConnection => 'Handmatige verbinding'; + @override String get cryptoInitFailed => 'Kan beveiligde verbinding niet initialiseren. Zorg ervoor dat je bent ingelogd bij een Plex-account.'; + @override String get validationHostRequired => 'Voer het hostadres in'; @override String get validationHostFormat => 'Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Voer een sessie-ID in'; - @override String get validationSessionIdLength => 'Sessie-ID moet 8 tekens zijn'; - @override String get validationPinRequired => 'Voer een PIN in'; - @override String get validationPinLength => 'PIN moet 6 cijfers zijn'; - @override String get connectionTimedOut => 'Verbinding verlopen. Controleer de sessie-ID en PIN.'; - @override String get sessionNotFound => 'Kan de sessie niet vinden. Controleer je gegevens.'; - @override String failedToConnect({required Object error}) => 'Verbinden mislukt: ${error}'; + @override String get connectionTimedOut => 'Verbinding verlopen. Zorg ervoor dat beide apparaten op hetzelfde netwerk zitten.'; + @override String get sessionNotFound => 'Apparaat niet gevonden. Zorg ervoor dat Plezy draait op de host.'; + @override String get authFailed => 'Authenticatie mislukt. Zorg ervoor dat beide apparaten hetzelfde Plex-account gebruiken.'; + @override String failedToConnect({required Object error}) => 'Kan niet verbinden: ${error}'; } // Path: companionRemote.remote @@ -1600,6 +1590,8 @@ extension on TranslationsNl { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Companion Remote-server', + 'settings.companionRemoteServerDescription' => 'Sta mobiele apparaten op je netwerk toe om deze app te bedienen', 'search.hint' => 'Zoek films, series, muziek...', 'search.tryDifferentTerm' => 'Probeer een andere zoekterm', 'search.searchYourMedia' => 'Zoek in je media', @@ -1881,10 +1873,10 @@ extension on TranslationsNl { 'libraries.groupings.seasons' => 'Seizoenen', 'libraries.groupings.episodes' => 'Afleveringen', 'libraries.groupings.folders' => 'Mappen', - 'about.title' => 'Over', - 'about.openSourceLicenses' => 'Open Source licenties', _ => null, } ?? switch (path) { + 'about.title' => 'Over', + 'about.openSourceLicenses' => 'Open Source licenties', 'about.versionLabel' => ({required Object version}) => 'Versie ${version}', 'about.appDescription' => 'Een mooie Plex client voor Flutter', 'about.viewLicensesDescription' => 'Bekijk licenties van third-party bibliotheken', @@ -2076,46 +2068,34 @@ extension on TranslationsNl { 'companionRemote.remoteControl' => 'Afstandsbediening', 'companionRemote.controlDesktop' => 'Bedien een desktop-apparaat', 'companionRemote.connectedTo' => ({required Object name}) => 'Verbonden met ${name}', - 'companionRemote.session.creatingSession' => 'Externe sessie aanmaken...', - 'companionRemote.session.failedToCreate' => 'Kan externe sessie niet aanmaken:', - 'companionRemote.session.noSession' => 'Geen sessie beschikbaar', - 'companionRemote.session.scanQrCode' => 'Scan QR-code', - 'companionRemote.session.orEnterManually' => 'Of voer handmatig in', + 'companionRemote.session.startingServer' => 'Externe server starten...', + 'companionRemote.session.failedToCreate' => 'Kan externe server niet starten:', 'companionRemote.session.hostAddress' => 'Hostadres', - 'companionRemote.session.sessionId' => 'Sessie-ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Verbonden', - 'companionRemote.session.waitingForConnection' => 'Wachten op verbinding...', + 'companionRemote.session.serverRunning' => 'Externe server actief', + 'companionRemote.session.serverStopped' => 'Externe server gestopt', + 'companionRemote.session.serverRunningDescription' => 'Mobiele apparaten op je netwerk kunnen deze app ontdekken en ermee verbinden', + 'companionRemote.session.serverStoppedDescription' => 'Start de server om mobiele apparaten te laten verbinden', 'companionRemote.session.usePhoneToControl' => 'Gebruik je mobiele apparaat om deze app te bedienen', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} gekopieerd naar klembord', - 'companionRemote.session.copyToClipboard' => 'Kopieer naar klembord', - 'companionRemote.session.newSession' => 'Nieuwe sessie', + 'companionRemote.session.startServer' => 'Server starten', + 'companionRemote.session.stopServer' => 'Server stoppen', 'companionRemote.session.minimize' => 'Minimaliseren', - 'companionRemote.pairing.scan' => 'Scannen', - 'companionRemote.pairing.manual' => 'Handmatig', - 'companionRemote.pairing.pairWithDesktop' => 'Koppelen met desktop', - 'companionRemote.pairing.enterSessionDetails' => 'Voer de sessiegegevens in die op je desktop-apparaat worden getoond', + 'companionRemote.pairing.pairWithDesktop' => 'Verbinden met desktop', + 'companionRemote.pairing.discoveryDescription' => 'Apparaten op je netwerk die Plezy gebruiken met hetzelfde Plex-account verschijnen automatisch', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Voer 8-tekens sessie-ID in', - 'companionRemote.pairing.pinHint' => 'Voer 6-cijferige PIN in', 'companionRemote.pairing.connecting' => 'Verbinden...', - 'companionRemote.pairing.tips' => 'Tips', - 'companionRemote.pairing.tipDesktop' => 'Open Plezy op je desktop en schakel Companion Remote in via instellingen of menu', - 'companionRemote.pairing.tipScan' => 'Gebruik het tabblad Scannen om snel te koppelen door de QR-code op je desktop te scannen', - 'companionRemote.pairing.tipWifi' => 'Zorg ervoor dat beide apparaten op hetzelfde WiFi-netwerk zitten', - 'companionRemote.pairing.cameraPermissionRequired' => 'Cameratoestemming is vereist om QR-codes te scannen.\nGeef cameratoegang in je apparaatinstellingen.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kan camera niet starten: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Richt je camera op de QR-code die op je desktop wordt getoond', - 'companionRemote.pairing.invalidQrCode' => 'Ongeldig QR-codeformaat', - 'companionRemote.pairing.validationHostRequired' => 'Voer een hostadres in', + 'companionRemote.pairing.searchingForDevices' => 'Apparaten zoeken...', + 'companionRemote.pairing.noDevicesFound' => 'Geen apparaten gevonden op je netwerk', + 'companionRemote.pairing.noDevicesHint' => 'Zorg ervoor dat Plezy geopend is op je desktop en dat beide apparaten op hetzelfde WiFi-netwerk zitten', + 'companionRemote.pairing.availableDevices' => 'Beschikbare apparaten', + 'companionRemote.pairing.manualConnection' => 'Handmatige verbinding', + 'companionRemote.pairing.cryptoInitFailed' => 'Kan beveiligde verbinding niet initialiseren. Zorg ervoor dat je bent ingelogd bij een Plex-account.', + 'companionRemote.pairing.validationHostRequired' => 'Voer het hostadres in', 'companionRemote.pairing.validationHostFormat' => 'Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Voer een sessie-ID in', - 'companionRemote.pairing.validationSessionIdLength' => 'Sessie-ID moet 8 tekens zijn', - 'companionRemote.pairing.validationPinRequired' => 'Voer een PIN in', - 'companionRemote.pairing.validationPinLength' => 'PIN moet 6 cijfers zijn', - 'companionRemote.pairing.connectionTimedOut' => 'Verbinding verlopen. Controleer de sessie-ID en PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Kan de sessie niet vinden. Controleer je gegevens.', - 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Verbinden mislukt: ${error}', + 'companionRemote.pairing.connectionTimedOut' => 'Verbinding verlopen. Zorg ervoor dat beide apparaten op hetzelfde netwerk zitten.', + 'companionRemote.pairing.sessionNotFound' => 'Apparaat niet gevonden. Zorg ervoor dat Plezy draait op de host.', + 'companionRemote.pairing.authFailed' => 'Authenticatie mislukt. Zorg ervoor dat beide apparaten hetzelfde Plex-account gebruiken.', + 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kan niet verbinden: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Wil je de verbinding met de externe sessie verbreken?', 'companionRemote.remote.reconnecting' => 'Opnieuw verbinden...', 'companionRemote.remote.attemptOf' => ({required Object current}) => 'Poging ${current} van 5', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index d19ab61f..240cc549 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsPl implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Serwer zdalnego sterowania'; + @override String get companionRemoteServerDescription => 'Pozwól urządzeniom mobilnym w sieci sterować tą aplikacją'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionPl implements TranslationsCompanionRemo final TranslationsPl _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Tworzenie sesji zdalnej...'; - @override String get failedToCreate => 'Nie udało się utworzyć sesji zdalnej:'; - @override String get noSession => 'Brak dostępnej sesji'; - @override String get scanQrCode => 'Zeskanuj kod QR'; - @override String get orEnterManually => 'Lub wprowadź ręcznie'; + @override String get startingServer => 'Uruchamianie serwera zdalnego...'; + @override String get failedToCreate => 'Nie udało się uruchomić serwera zdalnego:'; @override String get hostAddress => 'Adres hosta'; - @override String get sessionId => 'ID sesji'; - @override String get pin => 'PIN'; @override String get connected => 'Połączono'; - @override String get waitingForConnection => 'Oczekiwanie na połączenie...'; - @override String get usePhoneToControl => 'Użyj urządzenia mobilnego do sterowania tą aplikacją'; - @override String copiedToClipboard({required Object label}) => '${label} skopiowane do schowka'; - @override String get copyToClipboard => 'Kopiuj do schowka'; - @override String get newSession => 'Nowa sesja'; + @override String get serverRunning => 'Serwer zdalny aktywny'; + @override String get serverStopped => 'Serwer zdalny zatrzymany'; + @override String get serverRunningDescription => 'Urządzenia mobilne w sieci mogą wykryć tę aplikację i się z nią połączyć'; + @override String get serverStoppedDescription => 'Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych'; + @override String get usePhoneToControl => 'Użyj urządzenia mobilnego, aby sterować tą aplikacją'; + @override String get startServer => 'Uruchom serwer'; + @override String get stopServer => 'Zatrzymaj serwer'; @override String get minimize => 'Minimalizuj'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingPl implements TranslationsCompanionRemo final TranslationsPl _root; // ignore: unused_field // Translations - @override String get scan => 'Skanuj'; - @override String get manual => 'Ręcznie'; - @override String get pairWithDesktop => 'Sparuj z komputerem'; - @override String get enterSessionDetails => 'Wprowadź dane sesji wyświetlone na urządzeniu desktop'; + @override String get pairWithDesktop => 'Połącz z komputerem'; + @override String get discoveryDescription => 'Urządzenia w sieci z Plezy na tym samym koncie Plex pojawią się automatycznie'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Wprowadź 8-znakowe ID sesji'; - @override String get pinHint => 'Wprowadź 6-cyfrowy PIN'; @override String get connecting => 'Łączenie...'; - @override String get tips => 'Wskazówki'; - @override String get tipDesktop => 'Otwórz Plezy na komputerze i włącz Pilota zdalnego w ustawieniach lub menu'; - @override String get tipScan => 'Użyj zakładki Skanuj, aby szybko sparować skanując kod QR na komputerze'; - @override String get tipWifi => 'Upewnij się, że oba urządzenia są w tej samej sieci WiFi'; - @override String get cameraPermissionRequired => 'Uprawnienie do kamery jest wymagane do skanowania kodów QR.\nPrzyznaj dostęp do kamery w ustawieniach urządzenia.'; - @override String cameraError({required Object error}) => 'Nie udało się uruchomić kamery: ${error}'; - @override String get scanInstruction => 'Skieruj kamerę na kod QR wyświetlony na komputerze'; - @override String get invalidQrCode => 'Nieprawidłowy format kodu QR'; + @override String get searchingForDevices => 'Szukanie urządzeń...'; + @override String get noDevicesFound => 'Nie znaleziono urządzeń w sieci'; + @override String get noDevicesHint => 'Upewnij się, że Plezy jest otwarte na komputerze i oba urządzenia są w tej samej sieci WiFi'; + @override String get availableDevices => 'Dostępne urządzenia'; + @override String get manualConnection => 'Połączenie ręczne'; + @override String get cryptoInitFailed => 'Nie udało się zainicjować bezpiecznego połączenia. Upewnij się, że jesteś zalogowany na konto Plex.'; @override String get validationHostRequired => 'Wprowadź adres hosta'; @override String get validationHostFormat => 'Format musi być IP:port (np. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Wprowadź ID sesji'; - @override String get validationSessionIdLength => 'ID sesji musi mieć 8 znaków'; - @override String get validationPinRequired => 'Wprowadź PIN'; - @override String get validationPinLength => 'PIN musi mieć 6 cyfr'; - @override String get connectionTimedOut => 'Upłynął czas połączenia. Sprawdź ID sesji i PIN.'; - @override String get sessionNotFound => 'Nie znaleziono sesji. Sprawdź swoje dane.'; + @override String get connectionTimedOut => 'Przekroczono limit czasu połączenia. Upewnij się, że oba urządzenia są w tej samej sieci.'; + @override String get sessionNotFound => 'Nie znaleziono urządzenia. Upewnij się, że Plezy działa na hoście.'; + @override String get authFailed => 'Uwierzytelnianie nie powiodło się. Upewnij się, że oba urządzenia używają tego samego konta Plex.'; @override String failedToConnect({required Object error}) => 'Nie udało się połączyć: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsPl { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Serwer zdalnego sterowania', + 'settings.companionRemoteServerDescription' => 'Pozwól urządzeniom mobilnym w sieci sterować tą aplikacją', 'search.hint' => 'Szukaj filmów, seriali, muzyki...', 'search.tryDifferentTerm' => 'Spróbuj innego wyszukiwania', 'search.searchYourMedia' => 'Przeszukaj swoje media', @@ -1881,10 +1873,10 @@ extension on TranslationsPl { 'libraries.groupings.seasons' => 'Sezony', 'libraries.groupings.episodes' => 'Odcinki', 'libraries.groupings.folders' => 'Foldery', - 'about.title' => 'O aplikacji', - 'about.openSourceLicenses' => 'Licencje open source', _ => null, } ?? switch (path) { + 'about.title' => 'O aplikacji', + 'about.openSourceLicenses' => 'Licencje open source', 'about.versionLabel' => ({required Object version}) => 'Wersja ${version}', 'about.appDescription' => 'Piękny klient Plex na Flutter', 'about.viewLicensesDescription' => 'Zobacz licencje bibliotek zewnętrznych', @@ -2076,45 +2068,33 @@ extension on TranslationsPl { 'companionRemote.remoteControl' => 'Pilot zdalny', 'companionRemote.controlDesktop' => 'Steruj urządzeniem desktop', 'companionRemote.connectedTo' => ({required Object name}) => 'Połączono z ${name}', - 'companionRemote.session.creatingSession' => 'Tworzenie sesji zdalnej...', - 'companionRemote.session.failedToCreate' => 'Nie udało się utworzyć sesji zdalnej:', - 'companionRemote.session.noSession' => 'Brak dostępnej sesji', - 'companionRemote.session.scanQrCode' => 'Zeskanuj kod QR', - 'companionRemote.session.orEnterManually' => 'Lub wprowadź ręcznie', + 'companionRemote.session.startingServer' => 'Uruchamianie serwera zdalnego...', + 'companionRemote.session.failedToCreate' => 'Nie udało się uruchomić serwera zdalnego:', 'companionRemote.session.hostAddress' => 'Adres hosta', - 'companionRemote.session.sessionId' => 'ID sesji', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Połączono', - 'companionRemote.session.waitingForConnection' => 'Oczekiwanie na połączenie...', - 'companionRemote.session.usePhoneToControl' => 'Użyj urządzenia mobilnego do sterowania tą aplikacją', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} skopiowane do schowka', - 'companionRemote.session.copyToClipboard' => 'Kopiuj do schowka', - 'companionRemote.session.newSession' => 'Nowa sesja', + 'companionRemote.session.serverRunning' => 'Serwer zdalny aktywny', + 'companionRemote.session.serverStopped' => 'Serwer zdalny zatrzymany', + 'companionRemote.session.serverRunningDescription' => 'Urządzenia mobilne w sieci mogą wykryć tę aplikację i się z nią połączyć', + 'companionRemote.session.serverStoppedDescription' => 'Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych', + 'companionRemote.session.usePhoneToControl' => 'Użyj urządzenia mobilnego, aby sterować tą aplikacją', + 'companionRemote.session.startServer' => 'Uruchom serwer', + 'companionRemote.session.stopServer' => 'Zatrzymaj serwer', 'companionRemote.session.minimize' => 'Minimalizuj', - 'companionRemote.pairing.scan' => 'Skanuj', - 'companionRemote.pairing.manual' => 'Ręcznie', - 'companionRemote.pairing.pairWithDesktop' => 'Sparuj z komputerem', - 'companionRemote.pairing.enterSessionDetails' => 'Wprowadź dane sesji wyświetlone na urządzeniu desktop', + 'companionRemote.pairing.pairWithDesktop' => 'Połącz z komputerem', + 'companionRemote.pairing.discoveryDescription' => 'Urządzenia w sieci z Plezy na tym samym koncie Plex pojawią się automatycznie', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Wprowadź 8-znakowe ID sesji', - 'companionRemote.pairing.pinHint' => 'Wprowadź 6-cyfrowy PIN', 'companionRemote.pairing.connecting' => 'Łączenie...', - 'companionRemote.pairing.tips' => 'Wskazówki', - 'companionRemote.pairing.tipDesktop' => 'Otwórz Plezy na komputerze i włącz Pilota zdalnego w ustawieniach lub menu', - 'companionRemote.pairing.tipScan' => 'Użyj zakładki Skanuj, aby szybko sparować skanując kod QR na komputerze', - 'companionRemote.pairing.tipWifi' => 'Upewnij się, że oba urządzenia są w tej samej sieci WiFi', - 'companionRemote.pairing.cameraPermissionRequired' => 'Uprawnienie do kamery jest wymagane do skanowania kodów QR.\nPrzyznaj dostęp do kamery w ustawieniach urządzenia.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Nie udało się uruchomić kamery: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Skieruj kamerę na kod QR wyświetlony na komputerze', - 'companionRemote.pairing.invalidQrCode' => 'Nieprawidłowy format kodu QR', + 'companionRemote.pairing.searchingForDevices' => 'Szukanie urządzeń...', + 'companionRemote.pairing.noDevicesFound' => 'Nie znaleziono urządzeń w sieci', + 'companionRemote.pairing.noDevicesHint' => 'Upewnij się, że Plezy jest otwarte na komputerze i oba urządzenia są w tej samej sieci WiFi', + 'companionRemote.pairing.availableDevices' => 'Dostępne urządzenia', + 'companionRemote.pairing.manualConnection' => 'Połączenie ręczne', + 'companionRemote.pairing.cryptoInitFailed' => 'Nie udało się zainicjować bezpiecznego połączenia. Upewnij się, że jesteś zalogowany na konto Plex.', 'companionRemote.pairing.validationHostRequired' => 'Wprowadź adres hosta', 'companionRemote.pairing.validationHostFormat' => 'Format musi być IP:port (np. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Wprowadź ID sesji', - 'companionRemote.pairing.validationSessionIdLength' => 'ID sesji musi mieć 8 znaków', - 'companionRemote.pairing.validationPinRequired' => 'Wprowadź PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN musi mieć 6 cyfr', - 'companionRemote.pairing.connectionTimedOut' => 'Upłynął czas połączenia. Sprawdź ID sesji i PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Nie znaleziono sesji. Sprawdź swoje dane.', + 'companionRemote.pairing.connectionTimedOut' => 'Przekroczono limit czasu połączenia. Upewnij się, że oba urządzenia są w tej samej sieci.', + 'companionRemote.pairing.sessionNotFound' => 'Nie znaleziono urządzenia. Upewnij się, że Plezy działa na hoście.', + 'companionRemote.pairing.authFailed' => 'Uwierzytelnianie nie powiodło się. Upewnij się, że oba urządzenia używają tego samego konta Plex.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Nie udało się połączyć: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Czy chcesz się rozłączyć od sesji zdalnej?', 'companionRemote.remote.reconnecting' => 'Ponowne łączenie...', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 7e3ca7d7..bdd406e9 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsPt implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Servidor de controlo remoto'; + @override String get companionRemoteServerDescription => 'Permitir que dispositivos móveis na sua rede controlem esta aplicação'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionPt implements TranslationsCompanionRemo final TranslationsPt _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Criando sessão remota...'; - @override String get failedToCreate => 'Falha ao criar sessão remota:'; - @override String get noSession => 'Nenhuma sessão disponível'; - @override String get scanQrCode => 'Escanear QR Code'; - @override String get orEnterManually => 'Ou insira manualmente'; - @override String get hostAddress => 'Endereço do Host'; - @override String get sessionId => 'ID da Sessão'; - @override String get pin => 'PIN'; + @override String get startingServer => 'A iniciar servidor remoto...'; + @override String get failedToCreate => 'Falha ao iniciar o servidor remoto:'; + @override String get hostAddress => 'Endereço do host'; @override String get connected => 'Conectado'; - @override String get waitingForConnection => 'Aguardando conexão...'; - @override String get usePhoneToControl => 'Use seu dispositivo móvel para controlar este app'; - @override String copiedToClipboard({required Object label}) => '${label} copiado para a área de transferência'; - @override String get copyToClipboard => 'Copiar para área de transferência'; - @override String get newSession => 'Nova Sessão'; + @override String get serverRunning => 'Servidor remoto ativo'; + @override String get serverStopped => 'Servidor remoto parado'; + @override String get serverRunningDescription => 'Dispositivos móveis na sua rede podem descobrir e conectar-se a esta aplicação'; + @override String get serverStoppedDescription => 'Inicie o servidor para permitir que dispositivos móveis se conectem'; + @override String get usePhoneToControl => 'Use o seu dispositivo móvel para controlar esta aplicação'; + @override String get startServer => 'Iniciar servidor'; + @override String get stopServer => 'Parar servidor'; @override String get minimize => 'Minimizar'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingPt implements TranslationsCompanionRemo final TranslationsPt _root; // ignore: unused_field // Translations - @override String get scan => 'Escanear'; - @override String get manual => 'Manual'; - @override String get pairWithDesktop => 'Parear com Desktop'; - @override String get enterSessionDetails => 'Insira os detalhes da sessão exibidos no seu dispositivo desktop'; + @override String get pairWithDesktop => 'Conectar ao desktop'; + @override String get discoveryDescription => 'Dispositivos na sua rede a executar Plezy com a mesma conta Plex aparecerão automaticamente'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Insira o ID da sessão de 8 caracteres'; - @override String get pinHint => 'Insira o PIN de 6 dígitos'; - @override String get connecting => 'Conectando...'; - @override String get tips => 'Dicas'; - @override String get tipDesktop => 'Abra o Plezy no desktop e ative o Controle Remoto nas configurações ou menu'; - @override String get tipScan => 'Use a aba Escanear para parear rapidamente escaneando o QR code no seu desktop'; - @override String get tipWifi => 'Certifique-se de que ambos os dispositivos estão na mesma rede WiFi'; - @override String get cameraPermissionRequired => 'A permissão da câmera é necessária para escanear QR codes.\nConceda acesso à câmera nas configurações do dispositivo.'; - @override String cameraError({required Object error}) => 'Não foi possível iniciar a câmera: ${error}'; - @override String get scanInstruction => 'Aponte sua câmera para o QR code exibido no seu desktop'; - @override String get invalidQrCode => 'Formato de QR code inválido'; - @override String get validationHostRequired => 'Insira o endereço do host'; - @override String get validationHostFormat => 'O formato deve ser IP:porta (ex: 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Insira um ID de sessão'; - @override String get validationSessionIdLength => 'O ID da sessão deve ter 8 caracteres'; - @override String get validationPinRequired => 'Insira um PIN'; - @override String get validationPinLength => 'O PIN deve ter 6 dígitos'; - @override String get connectionTimedOut => 'Tempo de conexão esgotado. Verifique o ID da sessão e o PIN.'; - @override String get sessionNotFound => 'Sessão não encontrada. Verifique suas credenciais.'; + @override String get connecting => 'A conectar...'; + @override String get searchingForDevices => 'A procurar dispositivos...'; + @override String get noDevicesFound => 'Nenhum dispositivo encontrado na sua rede'; + @override String get noDevicesHint => 'Certifique-se de que o Plezy está aberto no seu desktop e que ambos os dispositivos estão na mesma rede WiFi'; + @override String get availableDevices => 'Dispositivos disponíveis'; + @override String get manualConnection => 'Conexão manual'; + @override String get cryptoInitFailed => 'Não foi possível inicializar a conexão segura. Certifique-se de que está conectado a uma conta Plex.'; + @override String get validationHostRequired => 'Introduza o endereço do host'; + @override String get validationHostFormat => 'O formato deve ser IP:porta (ex. 192.168.1.100:48632)'; + @override String get connectionTimedOut => 'Tempo de conexão esgotado. Certifique-se de que ambos os dispositivos estão na mesma rede.'; + @override String get sessionNotFound => 'Dispositivo não encontrado. Certifique-se de que o Plezy está em execução no host.'; + @override String get authFailed => 'Autenticação falhou. Certifique-se de que ambos os dispositivos usam a mesma conta Plex.'; @override String failedToConnect({required Object error}) => 'Falha ao conectar: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsPt { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Servidor de controlo remoto', + 'settings.companionRemoteServerDescription' => 'Permitir que dispositivos móveis na sua rede controlem esta aplicação', 'search.hint' => 'Buscar filmes, séries, músicas...', 'search.tryDifferentTerm' => 'Tente um termo de busca diferente', 'search.searchYourMedia' => 'Buscar suas mídias', @@ -1881,10 +1873,10 @@ extension on TranslationsPt { 'libraries.groupings.seasons' => 'Temporadas', 'libraries.groupings.episodes' => 'Episódios', 'libraries.groupings.folders' => 'Pastas', - 'about.title' => 'Sobre', - 'about.openSourceLicenses' => 'Licenças Open Source', _ => null, } ?? switch (path) { + 'about.title' => 'Sobre', + 'about.openSourceLicenses' => 'Licenças Open Source', 'about.versionLabel' => ({required Object version}) => 'Versão ${version}', 'about.appDescription' => 'Um belo cliente Plex para Flutter', 'about.viewLicensesDescription' => 'Ver licenças de bibliotecas de terceiros', @@ -2076,45 +2068,33 @@ extension on TranslationsPt { 'companionRemote.remoteControl' => 'Controle Remoto', 'companionRemote.controlDesktop' => 'Controlar um dispositivo desktop', 'companionRemote.connectedTo' => ({required Object name}) => 'Conectado a ${name}', - 'companionRemote.session.creatingSession' => 'Criando sessão remota...', - 'companionRemote.session.failedToCreate' => 'Falha ao criar sessão remota:', - 'companionRemote.session.noSession' => 'Nenhuma sessão disponível', - 'companionRemote.session.scanQrCode' => 'Escanear QR Code', - 'companionRemote.session.orEnterManually' => 'Ou insira manualmente', - 'companionRemote.session.hostAddress' => 'Endereço do Host', - 'companionRemote.session.sessionId' => 'ID da Sessão', - 'companionRemote.session.pin' => 'PIN', + 'companionRemote.session.startingServer' => 'A iniciar servidor remoto...', + 'companionRemote.session.failedToCreate' => 'Falha ao iniciar o servidor remoto:', + 'companionRemote.session.hostAddress' => 'Endereço do host', 'companionRemote.session.connected' => 'Conectado', - 'companionRemote.session.waitingForConnection' => 'Aguardando conexão...', - 'companionRemote.session.usePhoneToControl' => 'Use seu dispositivo móvel para controlar este app', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copiado para a área de transferência', - 'companionRemote.session.copyToClipboard' => 'Copiar para área de transferência', - 'companionRemote.session.newSession' => 'Nova Sessão', + 'companionRemote.session.serverRunning' => 'Servidor remoto ativo', + 'companionRemote.session.serverStopped' => 'Servidor remoto parado', + 'companionRemote.session.serverRunningDescription' => 'Dispositivos móveis na sua rede podem descobrir e conectar-se a esta aplicação', + 'companionRemote.session.serverStoppedDescription' => 'Inicie o servidor para permitir que dispositivos móveis se conectem', + 'companionRemote.session.usePhoneToControl' => 'Use o seu dispositivo móvel para controlar esta aplicação', + 'companionRemote.session.startServer' => 'Iniciar servidor', + 'companionRemote.session.stopServer' => 'Parar servidor', 'companionRemote.session.minimize' => 'Minimizar', - 'companionRemote.pairing.scan' => 'Escanear', - 'companionRemote.pairing.manual' => 'Manual', - 'companionRemote.pairing.pairWithDesktop' => 'Parear com Desktop', - 'companionRemote.pairing.enterSessionDetails' => 'Insira os detalhes da sessão exibidos no seu dispositivo desktop', + 'companionRemote.pairing.pairWithDesktop' => 'Conectar ao desktop', + 'companionRemote.pairing.discoveryDescription' => 'Dispositivos na sua rede a executar Plezy com a mesma conta Plex aparecerão automaticamente', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Insira o ID da sessão de 8 caracteres', - 'companionRemote.pairing.pinHint' => 'Insira o PIN de 6 dígitos', - 'companionRemote.pairing.connecting' => 'Conectando...', - 'companionRemote.pairing.tips' => 'Dicas', - 'companionRemote.pairing.tipDesktop' => 'Abra o Plezy no desktop e ative o Controle Remoto nas configurações ou menu', - 'companionRemote.pairing.tipScan' => 'Use a aba Escanear para parear rapidamente escaneando o QR code no seu desktop', - 'companionRemote.pairing.tipWifi' => 'Certifique-se de que ambos os dispositivos estão na mesma rede WiFi', - 'companionRemote.pairing.cameraPermissionRequired' => 'A permissão da câmera é necessária para escanear QR codes.\nConceda acesso à câmera nas configurações do dispositivo.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Não foi possível iniciar a câmera: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Aponte sua câmera para o QR code exibido no seu desktop', - 'companionRemote.pairing.invalidQrCode' => 'Formato de QR code inválido', - 'companionRemote.pairing.validationHostRequired' => 'Insira o endereço do host', - 'companionRemote.pairing.validationHostFormat' => 'O formato deve ser IP:porta (ex: 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Insira um ID de sessão', - 'companionRemote.pairing.validationSessionIdLength' => 'O ID da sessão deve ter 8 caracteres', - 'companionRemote.pairing.validationPinRequired' => 'Insira um PIN', - 'companionRemote.pairing.validationPinLength' => 'O PIN deve ter 6 dígitos', - 'companionRemote.pairing.connectionTimedOut' => 'Tempo de conexão esgotado. Verifique o ID da sessão e o PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Sessão não encontrada. Verifique suas credenciais.', + 'companionRemote.pairing.connecting' => 'A conectar...', + 'companionRemote.pairing.searchingForDevices' => 'A procurar dispositivos...', + 'companionRemote.pairing.noDevicesFound' => 'Nenhum dispositivo encontrado na sua rede', + 'companionRemote.pairing.noDevicesHint' => 'Certifique-se de que o Plezy está aberto no seu desktop e que ambos os dispositivos estão na mesma rede WiFi', + 'companionRemote.pairing.availableDevices' => 'Dispositivos disponíveis', + 'companionRemote.pairing.manualConnection' => 'Conexão manual', + 'companionRemote.pairing.cryptoInitFailed' => 'Não foi possível inicializar a conexão segura. Certifique-se de que está conectado a uma conta Plex.', + 'companionRemote.pairing.validationHostRequired' => 'Introduza o endereço do host', + 'companionRemote.pairing.validationHostFormat' => 'O formato deve ser IP:porta (ex. 192.168.1.100:48632)', + 'companionRemote.pairing.connectionTimedOut' => 'Tempo de conexão esgotado. Certifique-se de que ambos os dispositivos estão na mesma rede.', + 'companionRemote.pairing.sessionNotFound' => 'Dispositivo não encontrado. Certifique-se de que o Plezy está em execução no host.', + 'companionRemote.pairing.authFailed' => 'Autenticação falhou. Certifique-se de que ambos os dispositivos usam a mesma conta Plex.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Falha ao conectar: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Deseja desconectar da sessão remota?', 'companionRemote.remote.reconnecting' => 'Reconectando...', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index 5be4dbb9..60154420 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsRu implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Сервер удалённого управления'; + @override String get companionRemoteServerDescription => 'Разрешить мобильным устройствам в сети управлять этим приложением'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionRu implements TranslationsCompanionRemo final TranslationsRu _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Создание удалённой сессии...'; - @override String get failedToCreate => 'Не удалось создать удалённую сессию:'; - @override String get noSession => 'Нет доступных сессий'; - @override String get scanQrCode => 'Сканировать QR-код'; - @override String get orEnterManually => 'Или введите вручную'; + @override String get startingServer => 'Запуск удалённого сервера...'; + @override String get failedToCreate => 'Не удалось запустить удалённый сервер:'; @override String get hostAddress => 'Адрес хоста'; - @override String get sessionId => 'ID сессии'; - @override String get pin => 'PIN'; @override String get connected => 'Подключено'; - @override String get waitingForConnection => 'Ожидание подключения...'; + @override String get serverRunning => 'Удалённый сервер активен'; + @override String get serverStopped => 'Удалённый сервер остановлен'; + @override String get serverRunningDescription => 'Мобильные устройства в вашей сети могут обнаруживать это приложение и подключаться к нему'; + @override String get serverStoppedDescription => 'Запустите сервер, чтобы разрешить подключение мобильных устройств'; @override String get usePhoneToControl => 'Используйте мобильное устройство для управления этим приложением'; - @override String copiedToClipboard({required Object label}) => '${label} скопировано в буфер обмена'; - @override String get copyToClipboard => 'Скопировать в буфер обмена'; - @override String get newSession => 'Новая сессия'; + @override String get startServer => 'Запустить сервер'; + @override String get stopServer => 'Остановить сервер'; @override String get minimize => 'Свернуть'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingRu implements TranslationsCompanionRemo final TranslationsRu _root; // ignore: unused_field // Translations - @override String get scan => 'Сканировать'; - @override String get manual => 'Вручную'; - @override String get pairWithDesktop => 'Сопряжение с ПК'; - @override String get enterSessionDetails => 'Введите данные сессии, показанные на десктопном устройстве'; + @override String get pairWithDesktop => 'Подключиться к компьютеру'; + @override String get discoveryDescription => 'Устройства в вашей сети с Plezy на том же аккаунте Plex появятся автоматически'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Введите 8-символьный ID сессии'; - @override String get pinHint => 'Введите 6-значный PIN'; @override String get connecting => 'Подключение...'; - @override String get tips => 'Советы'; - @override String get tipDesktop => 'Откройте Plezy на десктопе и включите пульт управления в настройках или меню'; - @override String get tipScan => 'Используйте вкладку «Сканировать» для быстрого сопряжения через QR-код на десктопе'; - @override String get tipWifi => 'Убедитесь, что оба устройства находятся в одной WiFi-сети'; - @override String get cameraPermissionRequired => 'Для сканирования QR-кодов требуется разрешение на камеру.\nПредоставьте доступ к камере в настройках устройства.'; - @override String cameraError({required Object error}) => 'Не удалось запустить камеру: ${error}'; - @override String get scanInstruction => 'Наведите камеру на QR-код, показанный на десктопе'; - @override String get invalidQrCode => 'Недопустимый формат QR-кода'; + @override String get searchingForDevices => 'Поиск устройств...'; + @override String get noDevicesFound => 'Устройства в вашей сети не найдены'; + @override String get noDevicesHint => 'Убедитесь, что Plezy открыт на вашем компьютере и оба устройства находятся в одной сети WiFi'; + @override String get availableDevices => 'Доступные устройства'; + @override String get manualConnection => 'Ручное подключение'; + @override String get cryptoInitFailed => 'Не удалось инициализировать безопасное соединение. Убедитесь, что вы вошли в аккаунт Plex.'; @override String get validationHostRequired => 'Введите адрес хоста'; @override String get validationHostFormat => 'Формат должен быть IP:порт (например, 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Введите ID сессии'; - @override String get validationSessionIdLength => 'ID сессии должен содержать 8 символов'; - @override String get validationPinRequired => 'Введите PIN'; - @override String get validationPinLength => 'PIN должен содержать 6 цифр'; - @override String get connectionTimedOut => 'Время подключения истекло. Проверьте ID сессии и PIN.'; - @override String get sessionNotFound => 'Сессия не найдена. Проверьте ваши учётные данные.'; + @override String get connectionTimedOut => 'Время подключения истекло. Убедитесь, что оба устройства находятся в одной сети.'; + @override String get sessionNotFound => 'Устройство не найдено. Убедитесь, что Plezy запущен на хосте.'; + @override String get authFailed => 'Ошибка аутентификации. Убедитесь, что оба устройства используют один и тот же аккаунт Plex.'; @override String failedToConnect({required Object error}) => 'Не удалось подключиться: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsRu { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Сервер удалённого управления', + 'settings.companionRemoteServerDescription' => 'Разрешить мобильным устройствам в сети управлять этим приложением', 'search.hint' => 'Поиск фильмов, сериалов, музыки...', 'search.tryDifferentTerm' => 'Попробуйте другой запрос', 'search.searchYourMedia' => 'Поиск в вашей медиатеке', @@ -1881,10 +1873,10 @@ extension on TranslationsRu { 'libraries.groupings.seasons' => 'Сезоны', 'libraries.groupings.episodes' => 'Эпизоды', 'libraries.groupings.folders' => 'Папки', - 'about.title' => 'О приложении', - 'about.openSourceLicenses' => 'Лицензии открытого ПО', _ => null, } ?? switch (path) { + 'about.title' => 'О приложении', + 'about.openSourceLicenses' => 'Лицензии открытого ПО', 'about.versionLabel' => ({required Object version}) => 'Версия ${version}', 'about.appDescription' => 'Красивый клиент Plex на Flutter', 'about.viewLicensesDescription' => 'Просмотр лицензий сторонних библиотек', @@ -2076,45 +2068,33 @@ extension on TranslationsRu { 'companionRemote.remoteControl' => 'Пульт управления', 'companionRemote.controlDesktop' => 'Управлять десктопным устройством', 'companionRemote.connectedTo' => ({required Object name}) => 'Подключено к ${name}', - 'companionRemote.session.creatingSession' => 'Создание удалённой сессии...', - 'companionRemote.session.failedToCreate' => 'Не удалось создать удалённую сессию:', - 'companionRemote.session.noSession' => 'Нет доступных сессий', - 'companionRemote.session.scanQrCode' => 'Сканировать QR-код', - 'companionRemote.session.orEnterManually' => 'Или введите вручную', + 'companionRemote.session.startingServer' => 'Запуск удалённого сервера...', + 'companionRemote.session.failedToCreate' => 'Не удалось запустить удалённый сервер:', 'companionRemote.session.hostAddress' => 'Адрес хоста', - 'companionRemote.session.sessionId' => 'ID сессии', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Подключено', - 'companionRemote.session.waitingForConnection' => 'Ожидание подключения...', + 'companionRemote.session.serverRunning' => 'Удалённый сервер активен', + 'companionRemote.session.serverStopped' => 'Удалённый сервер остановлен', + 'companionRemote.session.serverRunningDescription' => 'Мобильные устройства в вашей сети могут обнаруживать это приложение и подключаться к нему', + 'companionRemote.session.serverStoppedDescription' => 'Запустите сервер, чтобы разрешить подключение мобильных устройств', 'companionRemote.session.usePhoneToControl' => 'Используйте мобильное устройство для управления этим приложением', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} скопировано в буфер обмена', - 'companionRemote.session.copyToClipboard' => 'Скопировать в буфер обмена', - 'companionRemote.session.newSession' => 'Новая сессия', + 'companionRemote.session.startServer' => 'Запустить сервер', + 'companionRemote.session.stopServer' => 'Остановить сервер', 'companionRemote.session.minimize' => 'Свернуть', - 'companionRemote.pairing.scan' => 'Сканировать', - 'companionRemote.pairing.manual' => 'Вручную', - 'companionRemote.pairing.pairWithDesktop' => 'Сопряжение с ПК', - 'companionRemote.pairing.enterSessionDetails' => 'Введите данные сессии, показанные на десктопном устройстве', + 'companionRemote.pairing.pairWithDesktop' => 'Подключиться к компьютеру', + 'companionRemote.pairing.discoveryDescription' => 'Устройства в вашей сети с Plezy на том же аккаунте Plex появятся автоматически', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Введите 8-символьный ID сессии', - 'companionRemote.pairing.pinHint' => 'Введите 6-значный PIN', 'companionRemote.pairing.connecting' => 'Подключение...', - 'companionRemote.pairing.tips' => 'Советы', - 'companionRemote.pairing.tipDesktop' => 'Откройте Plezy на десктопе и включите пульт управления в настройках или меню', - 'companionRemote.pairing.tipScan' => 'Используйте вкладку «Сканировать» для быстрого сопряжения через QR-код на десктопе', - 'companionRemote.pairing.tipWifi' => 'Убедитесь, что оба устройства находятся в одной WiFi-сети', - 'companionRemote.pairing.cameraPermissionRequired' => 'Для сканирования QR-кодов требуется разрешение на камеру.\nПредоставьте доступ к камере в настройках устройства.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Не удалось запустить камеру: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Наведите камеру на QR-код, показанный на десктопе', - 'companionRemote.pairing.invalidQrCode' => 'Недопустимый формат QR-кода', + 'companionRemote.pairing.searchingForDevices' => 'Поиск устройств...', + 'companionRemote.pairing.noDevicesFound' => 'Устройства в вашей сети не найдены', + 'companionRemote.pairing.noDevicesHint' => 'Убедитесь, что Plezy открыт на вашем компьютере и оба устройства находятся в одной сети WiFi', + 'companionRemote.pairing.availableDevices' => 'Доступные устройства', + 'companionRemote.pairing.manualConnection' => 'Ручное подключение', + 'companionRemote.pairing.cryptoInitFailed' => 'Не удалось инициализировать безопасное соединение. Убедитесь, что вы вошли в аккаунт Plex.', 'companionRemote.pairing.validationHostRequired' => 'Введите адрес хоста', 'companionRemote.pairing.validationHostFormat' => 'Формат должен быть IP:порт (например, 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Введите ID сессии', - 'companionRemote.pairing.validationSessionIdLength' => 'ID сессии должен содержать 8 символов', - 'companionRemote.pairing.validationPinRequired' => 'Введите PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN должен содержать 6 цифр', - 'companionRemote.pairing.connectionTimedOut' => 'Время подключения истекло. Проверьте ID сессии и PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Сессия не найдена. Проверьте ваши учётные данные.', + 'companionRemote.pairing.connectionTimedOut' => 'Время подключения истекло. Убедитесь, что оба устройства находятся в одной сети.', + 'companionRemote.pairing.sessionNotFound' => 'Устройство не найдено. Убедитесь, что Plezy запущен на хосте.', + 'companionRemote.pairing.authFailed' => 'Ошибка аутентификации. Убедитесь, что оба устройства используют один и тот же аккаунт Plex.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Не удалось подключиться: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Отключиться от удалённой сессии?', 'companionRemote.remote.reconnecting' => 'Переподключение...', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index f81bc467..170318f7 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => 'Companion Remote-server'; + @override String get companionRemoteServerDescription => 'Tillåt mobila enheter i ditt nätverk att styra denna app'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionSv implements TranslationsCompanionRemo final TranslationsSv _root; // ignore: unused_field // Translations - @override String get creatingSession => 'Skapar fjärrsession...'; - @override String get failedToCreate => 'Kunde inte skapa fjärrsession:'; - @override String get noSession => 'Ingen session tillgänglig'; - @override String get scanQrCode => 'Skanna QR-kod'; - @override String get orEnterManually => 'Eller ange manuellt'; + @override String get startingServer => 'Startar fjärrserver...'; + @override String get failedToCreate => 'Kunde inte starta fjärrserver:'; @override String get hostAddress => 'Värdadress'; - @override String get sessionId => 'Sessions-ID'; - @override String get pin => 'PIN'; @override String get connected => 'Ansluten'; - @override String get waitingForConnection => 'Väntar på anslutning...'; + @override String get serverRunning => 'Fjärrserver aktiv'; + @override String get serverStopped => 'Fjärrserver stoppad'; + @override String get serverRunningDescription => 'Mobila enheter i ditt nätverk kan upptäcka och ansluta till denna app'; + @override String get serverStoppedDescription => 'Starta servern för att tillåta mobila enheter att ansluta'; @override String get usePhoneToControl => 'Använd din mobila enhet för att styra denna app'; - @override String copiedToClipboard({required Object label}) => '${label} kopierad till urklipp'; - @override String get copyToClipboard => 'Kopiera till urklipp'; - @override String get newSession => 'Ny session'; + @override String get startServer => 'Starta server'; + @override String get stopServer => 'Stoppa server'; @override String get minimize => 'Minimera'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingSv implements TranslationsCompanionRemo final TranslationsSv _root; // ignore: unused_field // Translations - @override String get scan => 'Skanna'; - @override String get manual => 'Manuell'; - @override String get pairWithDesktop => 'Parkoppla med dator'; - @override String get enterSessionDetails => 'Ange sessionsuppgifterna som visas på din datorenhet'; + @override String get pairWithDesktop => 'Anslut till dator'; + @override String get discoveryDescription => 'Enheter i ditt nätverk som kör Plezy med samma Plex-konto visas automatiskt'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => 'Ange 8-teckens sessions-ID'; - @override String get pinHint => 'Ange 6-siffrig PIN'; @override String get connecting => 'Ansluter...'; - @override String get tips => 'Tips'; - @override String get tipDesktop => 'Öppna Plezy på din dator och aktivera Companion Remote via inställningar eller meny'; - @override String get tipScan => 'Använd fliken Skanna för att snabbt parkoppla genom att skanna QR-koden på din dator'; - @override String get tipWifi => 'Se till att båda enheterna är på samma WiFi-nätverk'; - @override String get cameraPermissionRequired => 'Kamerabehörighet krävs för att skanna QR-koder.\nVänligen ge kameraåtkomst i enhetsinställningarna.'; - @override String cameraError({required Object error}) => 'Kunde inte starta kameran: ${error}'; - @override String get scanInstruction => 'Rikta kameran mot QR-koden som visas på din dator'; - @override String get invalidQrCode => 'Ogiltigt QR-kodformat'; - @override String get validationHostRequired => 'Ange en värdadress'; + @override String get searchingForDevices => 'Söker efter enheter...'; + @override String get noDevicesFound => 'Inga enheter hittades i ditt nätverk'; + @override String get noDevicesHint => 'Se till att Plezy är öppet på din dator och att båda enheterna är på samma WiFi-nätverk'; + @override String get availableDevices => 'Tillgängliga enheter'; + @override String get manualConnection => 'Manuell anslutning'; + @override String get cryptoInitFailed => 'Kunde inte initiera säker anslutning. Se till att du är inloggad på ett Plex-konto.'; + @override String get validationHostRequired => 'Ange värdadress'; @override String get validationHostFormat => 'Format måste vara IP:port (t.ex. 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => 'Ange ett sessions-ID'; - @override String get validationSessionIdLength => 'Sessions-ID måste vara 8 tecken'; - @override String get validationPinRequired => 'Ange en PIN'; - @override String get validationPinLength => 'PIN måste vara 6 siffror'; - @override String get connectionTimedOut => 'Anslutningen tog för lång tid. Kontrollera sessions-ID och PIN.'; - @override String get sessionNotFound => 'Kunde inte hitta sessionen. Kontrollera dina uppgifter.'; + @override String get connectionTimedOut => 'Anslutningen tog för lång tid. Se till att båda enheterna är på samma nätverk.'; + @override String get sessionNotFound => 'Kunde inte hitta enheten. Se till att Plezy körs på värden.'; + @override String get authFailed => 'Autentisering misslyckades. Se till att båda enheterna använder samma Plex-konto.'; @override String failedToConnect({required Object error}) => 'Kunde inte ansluta: ${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsSv { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => 'Companion Remote-server', + 'settings.companionRemoteServerDescription' => 'Tillåt mobila enheter i ditt nätverk att styra denna app', 'search.hint' => 'Sök filmer, serier, musik...', 'search.tryDifferentTerm' => 'Prova en annan sökterm', 'search.searchYourMedia' => 'Sök i dina media', @@ -1881,10 +1873,10 @@ extension on TranslationsSv { 'libraries.groupings.seasons' => 'Säsonger', 'libraries.groupings.episodes' => 'Avsnitt', 'libraries.groupings.folders' => 'Mappar', - 'about.title' => 'Om', - 'about.openSourceLicenses' => 'Öppen källkod-licenser', _ => null, } ?? switch (path) { + 'about.title' => 'Om', + 'about.openSourceLicenses' => 'Öppen källkod-licenser', 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'En vacker Plex-klient för Flutter', 'about.viewLicensesDescription' => 'Visa licenser för tredjepartsbibliotek', @@ -2076,45 +2068,33 @@ extension on TranslationsSv { 'companionRemote.remoteControl' => 'Fjärrkontroll', 'companionRemote.controlDesktop' => 'Styr en datorenhet', 'companionRemote.connectedTo' => ({required Object name}) => 'Ansluten till ${name}', - 'companionRemote.session.creatingSession' => 'Skapar fjärrsession...', - 'companionRemote.session.failedToCreate' => 'Kunde inte skapa fjärrsession:', - 'companionRemote.session.noSession' => 'Ingen session tillgänglig', - 'companionRemote.session.scanQrCode' => 'Skanna QR-kod', - 'companionRemote.session.orEnterManually' => 'Eller ange manuellt', + 'companionRemote.session.startingServer' => 'Startar fjärrserver...', + 'companionRemote.session.failedToCreate' => 'Kunde inte starta fjärrserver:', 'companionRemote.session.hostAddress' => 'Värdadress', - 'companionRemote.session.sessionId' => 'Sessions-ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => 'Ansluten', - 'companionRemote.session.waitingForConnection' => 'Väntar på anslutning...', + 'companionRemote.session.serverRunning' => 'Fjärrserver aktiv', + 'companionRemote.session.serverStopped' => 'Fjärrserver stoppad', + 'companionRemote.session.serverRunningDescription' => 'Mobila enheter i ditt nätverk kan upptäcka och ansluta till denna app', + 'companionRemote.session.serverStoppedDescription' => 'Starta servern för att tillåta mobila enheter att ansluta', 'companionRemote.session.usePhoneToControl' => 'Använd din mobila enhet för att styra denna app', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} kopierad till urklipp', - 'companionRemote.session.copyToClipboard' => 'Kopiera till urklipp', - 'companionRemote.session.newSession' => 'Ny session', + 'companionRemote.session.startServer' => 'Starta server', + 'companionRemote.session.stopServer' => 'Stoppa server', 'companionRemote.session.minimize' => 'Minimera', - 'companionRemote.pairing.scan' => 'Skanna', - 'companionRemote.pairing.manual' => 'Manuell', - 'companionRemote.pairing.pairWithDesktop' => 'Parkoppla med dator', - 'companionRemote.pairing.enterSessionDetails' => 'Ange sessionsuppgifterna som visas på din datorenhet', + 'companionRemote.pairing.pairWithDesktop' => 'Anslut till dator', + 'companionRemote.pairing.discoveryDescription' => 'Enheter i ditt nätverk som kör Plezy med samma Plex-konto visas automatiskt', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => 'Ange 8-teckens sessions-ID', - 'companionRemote.pairing.pinHint' => 'Ange 6-siffrig PIN', 'companionRemote.pairing.connecting' => 'Ansluter...', - 'companionRemote.pairing.tips' => 'Tips', - 'companionRemote.pairing.tipDesktop' => 'Öppna Plezy på din dator och aktivera Companion Remote via inställningar eller meny', - 'companionRemote.pairing.tipScan' => 'Använd fliken Skanna för att snabbt parkoppla genom att skanna QR-koden på din dator', - 'companionRemote.pairing.tipWifi' => 'Se till att båda enheterna är på samma WiFi-nätverk', - 'companionRemote.pairing.cameraPermissionRequired' => 'Kamerabehörighet krävs för att skanna QR-koder.\nVänligen ge kameraåtkomst i enhetsinställningarna.', - 'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kunde inte starta kameran: ${error}', - 'companionRemote.pairing.scanInstruction' => 'Rikta kameran mot QR-koden som visas på din dator', - 'companionRemote.pairing.invalidQrCode' => 'Ogiltigt QR-kodformat', - 'companionRemote.pairing.validationHostRequired' => 'Ange en värdadress', + 'companionRemote.pairing.searchingForDevices' => 'Söker efter enheter...', + 'companionRemote.pairing.noDevicesFound' => 'Inga enheter hittades i ditt nätverk', + 'companionRemote.pairing.noDevicesHint' => 'Se till att Plezy är öppet på din dator och att båda enheterna är på samma WiFi-nätverk', + 'companionRemote.pairing.availableDevices' => 'Tillgängliga enheter', + 'companionRemote.pairing.manualConnection' => 'Manuell anslutning', + 'companionRemote.pairing.cryptoInitFailed' => 'Kunde inte initiera säker anslutning. Se till att du är inloggad på ett Plex-konto.', + 'companionRemote.pairing.validationHostRequired' => 'Ange värdadress', 'companionRemote.pairing.validationHostFormat' => 'Format måste vara IP:port (t.ex. 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => 'Ange ett sessions-ID', - 'companionRemote.pairing.validationSessionIdLength' => 'Sessions-ID måste vara 8 tecken', - 'companionRemote.pairing.validationPinRequired' => 'Ange en PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN måste vara 6 siffror', - 'companionRemote.pairing.connectionTimedOut' => 'Anslutningen tog för lång tid. Kontrollera sessions-ID och PIN.', - 'companionRemote.pairing.sessionNotFound' => 'Kunde inte hitta sessionen. Kontrollera dina uppgifter.', + 'companionRemote.pairing.connectionTimedOut' => 'Anslutningen tog för lång tid. Se till att båda enheterna är på samma nätverk.', + 'companionRemote.pairing.sessionNotFound' => 'Kunde inte hitta enheten. Se till att Plezy körs på värden.', + 'companionRemote.pairing.authFailed' => 'Autentisering misslyckades. Se till att båda enheterna använder samma Plex-konto.', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunde inte ansluta: ${error}', 'companionRemote.remote.disconnectConfirm' => 'Vill du koppla från fjärrsessionen?', 'companionRemote.remote.reconnecting' => 'Återansluter...', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index f11acc3c..8ec486ea 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -358,6 +358,8 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn { @override String get subtitlesAndConfig => 'Subtitles & Configuration'; @override String get seekAndTiming => 'Seek & Timing'; @override String get behavior => 'Behavior'; + @override String get companionRemoteServer => '配套遥控服务器'; + @override String get companionRemoteServerDescription => '允许网络上的移动设备控制此应用'; } // Path: search @@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionZh implements TranslationsCompanionRemo final TranslationsZh _root; // ignore: unused_field // Translations - @override String get creatingSession => '正在创建远程会话...'; - @override String get failedToCreate => '创建远程会话失败:'; - @override String get noSession => '没有可用的会话'; - @override String get scanQrCode => '扫描 QR 码'; - @override String get orEnterManually => '或手动输入'; + @override String get startingServer => '正在启动远程服务器...'; + @override String get failedToCreate => '启动远程服务器失败:'; @override String get hostAddress => '主机地址'; - @override String get sessionId => '会话 ID'; - @override String get pin => 'PIN'; @override String get connected => '已连接'; - @override String get waitingForConnection => '等待连接中...'; + @override String get serverRunning => '远程服务器已启动'; + @override String get serverStopped => '远程服务器已停止'; + @override String get serverRunningDescription => '网络上的移动设备可以发现并连接到此应用'; + @override String get serverStoppedDescription => '启动服务器以允许移动设备连接'; @override String get usePhoneToControl => '使用移动设备控制此应用'; - @override String copiedToClipboard({required Object label}) => '${label}已复制到剪贴板'; - @override String get copyToClipboard => '复制到剪贴板'; - @override String get newSession => '新建会话'; + @override String get startServer => '启动服务器'; + @override String get stopServer => '停止服务器'; @override String get minimize => '最小化'; } @@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingZh implements TranslationsCompanionRemo final TranslationsZh _root; // ignore: unused_field // Translations - @override String get scan => '扫描'; - @override String get manual => '手动'; - @override String get pairWithDesktop => '与桌面配对'; - @override String get enterSessionDetails => '输入桌面设备上显示的会话信息'; + @override String get pairWithDesktop => '连接到桌面'; + @override String get discoveryDescription => '网络上使用相同Plex账户运行Plezy的设备将自动显示'; @override String get hostAddressHint => '192.168.1.100:48632'; - @override String get sessionIdHint => '输入8位会话 ID'; - @override String get pinHint => '输入6位 PIN'; - @override String get connecting => '连接中...'; - @override String get tips => '提示'; - @override String get tipDesktop => '在桌面上打开 Plezy,并从设置或菜单中启用 Companion Remote'; - @override String get tipScan => '使用扫描选项卡扫描桌面上的 QR 码以快速配对'; - @override String get tipWifi => '请确保两台设备连接到同一个 WiFi 网络'; - @override String get cameraPermissionRequired => '扫描 QR 码需要相机权限。\n请在设备设置中授予相机访问权限。'; - @override String cameraError({required Object error}) => '无法启动相机:${error}'; - @override String get scanInstruction => '将相机对准桌面上显示的 QR 码'; - @override String get invalidQrCode => '无效的 QR 码格式'; + @override String get connecting => '正在连接...'; + @override String get searchingForDevices => '正在搜索设备...'; + @override String get noDevicesFound => '未在网络上找到设备'; + @override String get noDevicesHint => '请确保桌面上已打开Plezy,且两台设备在同一WiFi网络上'; + @override String get availableDevices => '可用设备'; + @override String get manualConnection => '手动连接'; + @override String get cryptoInitFailed => '无法初始化安全连接。请确保已登录Plex账户。'; @override String get validationHostRequired => '请输入主机地址'; - @override String get validationHostFormat => '格式必须为 IP:端口(例如 192.168.1.100:48632)'; - @override String get validationSessionIdRequired => '请输入会话 ID'; - @override String get validationSessionIdLength => '会话 ID 必须为8个字符'; - @override String get validationPinRequired => '请输入 PIN'; - @override String get validationPinLength => 'PIN 必须为6位数字'; - @override String get connectionTimedOut => '连接超时。请检查会话 ID 和 PIN。'; - @override String get sessionNotFound => '找不到会话。请检查您的凭据。'; + @override String get validationHostFormat => '格式必须为IP:端口(例如 192.168.1.100:48632)'; + @override String get connectionTimedOut => '连接超时。请确保两台设备在同一网络上。'; + @override String get sessionNotFound => '未找到设备。请确保Plezy正在主机上运行。'; + @override String get authFailed => '认证失败。请确保两台设备使用相同的Plex账户。'; @override String failedToConnect({required Object error}) => '连接失败:${error}'; } @@ -1600,6 +1590,8 @@ extension on TranslationsZh { 'settings.subtitlesAndConfig' => 'Subtitles & Configuration', 'settings.seekAndTiming' => 'Seek & Timing', 'settings.behavior' => 'Behavior', + 'settings.companionRemoteServer' => '配套遥控服务器', + 'settings.companionRemoteServerDescription' => '允许网络上的移动设备控制此应用', 'search.hint' => '搜索电影、系列、音乐...', 'search.tryDifferentTerm' => '尝试不同的搜索词', 'search.searchYourMedia' => '搜索媒体', @@ -1881,10 +1873,10 @@ extension on TranslationsZh { 'libraries.groupings.seasons' => '季', 'libraries.groupings.episodes' => '集', 'libraries.groupings.folders' => '文件夹', - 'about.title' => '关于', - 'about.openSourceLicenses' => '开源许可证', _ => null, } ?? switch (path) { + 'about.title' => '关于', + 'about.openSourceLicenses' => '开源许可证', 'about.versionLabel' => ({required Object version}) => '版本 ${version}', 'about.appDescription' => '一款精美的 Flutter Plex 客户端', 'about.viewLicensesDescription' => '查看第三方库的许可证', @@ -2076,45 +2068,33 @@ extension on TranslationsZh { 'companionRemote.remoteControl' => '远程控制', 'companionRemote.controlDesktop' => '控制桌面设备', 'companionRemote.connectedTo' => ({required Object name}) => '已连接到 ${name}', - 'companionRemote.session.creatingSession' => '正在创建远程会话...', - 'companionRemote.session.failedToCreate' => '创建远程会话失败:', - 'companionRemote.session.noSession' => '没有可用的会话', - 'companionRemote.session.scanQrCode' => '扫描 QR 码', - 'companionRemote.session.orEnterManually' => '或手动输入', + 'companionRemote.session.startingServer' => '正在启动远程服务器...', + 'companionRemote.session.failedToCreate' => '启动远程服务器失败:', 'companionRemote.session.hostAddress' => '主机地址', - 'companionRemote.session.sessionId' => '会话 ID', - 'companionRemote.session.pin' => 'PIN', 'companionRemote.session.connected' => '已连接', - 'companionRemote.session.waitingForConnection' => '等待连接中...', + 'companionRemote.session.serverRunning' => '远程服务器已启动', + 'companionRemote.session.serverStopped' => '远程服务器已停止', + 'companionRemote.session.serverRunningDescription' => '网络上的移动设备可以发现并连接到此应用', + 'companionRemote.session.serverStoppedDescription' => '启动服务器以允许移动设备连接', 'companionRemote.session.usePhoneToControl' => '使用移动设备控制此应用', - 'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label}已复制到剪贴板', - 'companionRemote.session.copyToClipboard' => '复制到剪贴板', - 'companionRemote.session.newSession' => '新建会话', + 'companionRemote.session.startServer' => '启动服务器', + 'companionRemote.session.stopServer' => '停止服务器', 'companionRemote.session.minimize' => '最小化', - 'companionRemote.pairing.scan' => '扫描', - 'companionRemote.pairing.manual' => '手动', - 'companionRemote.pairing.pairWithDesktop' => '与桌面配对', - 'companionRemote.pairing.enterSessionDetails' => '输入桌面设备上显示的会话信息', + 'companionRemote.pairing.pairWithDesktop' => '连接到桌面', + 'companionRemote.pairing.discoveryDescription' => '网络上使用相同Plex账户运行Plezy的设备将自动显示', 'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632', - 'companionRemote.pairing.sessionIdHint' => '输入8位会话 ID', - 'companionRemote.pairing.pinHint' => '输入6位 PIN', - 'companionRemote.pairing.connecting' => '连接中...', - 'companionRemote.pairing.tips' => '提示', - 'companionRemote.pairing.tipDesktop' => '在桌面上打开 Plezy,并从设置或菜单中启用 Companion Remote', - 'companionRemote.pairing.tipScan' => '使用扫描选项卡扫描桌面上的 QR 码以快速配对', - 'companionRemote.pairing.tipWifi' => '请确保两台设备连接到同一个 WiFi 网络', - 'companionRemote.pairing.cameraPermissionRequired' => '扫描 QR 码需要相机权限。\n请在设备设置中授予相机访问权限。', - 'companionRemote.pairing.cameraError' => ({required Object error}) => '无法启动相机:${error}', - 'companionRemote.pairing.scanInstruction' => '将相机对准桌面上显示的 QR 码', - 'companionRemote.pairing.invalidQrCode' => '无效的 QR 码格式', + 'companionRemote.pairing.connecting' => '正在连接...', + 'companionRemote.pairing.searchingForDevices' => '正在搜索设备...', + 'companionRemote.pairing.noDevicesFound' => '未在网络上找到设备', + 'companionRemote.pairing.noDevicesHint' => '请确保桌面上已打开Plezy,且两台设备在同一WiFi网络上', + 'companionRemote.pairing.availableDevices' => '可用设备', + 'companionRemote.pairing.manualConnection' => '手动连接', + 'companionRemote.pairing.cryptoInitFailed' => '无法初始化安全连接。请确保已登录Plex账户。', 'companionRemote.pairing.validationHostRequired' => '请输入主机地址', - 'companionRemote.pairing.validationHostFormat' => '格式必须为 IP:端口(例如 192.168.1.100:48632)', - 'companionRemote.pairing.validationSessionIdRequired' => '请输入会话 ID', - 'companionRemote.pairing.validationSessionIdLength' => '会话 ID 必须为8个字符', - 'companionRemote.pairing.validationPinRequired' => '请输入 PIN', - 'companionRemote.pairing.validationPinLength' => 'PIN 必须为6位数字', - 'companionRemote.pairing.connectionTimedOut' => '连接超时。请检查会话 ID 和 PIN。', - 'companionRemote.pairing.sessionNotFound' => '找不到会话。请检查您的凭据。', + 'companionRemote.pairing.validationHostFormat' => '格式必须为IP:端口(例如 192.168.1.100:48632)', + 'companionRemote.pairing.connectionTimedOut' => '连接超时。请确保两台设备在同一网络上。', + 'companionRemote.pairing.sessionNotFound' => '未找到设备。请确保Plezy正在主机上运行。', + 'companionRemote.pairing.authFailed' => '认证失败。请确保两台设备使用相同的Plex账户。', 'companionRemote.pairing.failedToConnect' => ({required Object error}) => '连接失败:${error}', 'companionRemote.remote.disconnectConfirm' => '是否要断开远程会话的连接?', 'companionRemote.remote.reconnecting' => '重新连接中...', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 5e9f974a..7197cad2 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "Companion Remote-server", + "companionRemoteServerDescription": "Tillåt mobila enheter i ditt nätverk att styra denna app" }, "search": { "hint": "Sök filmer, serier, musik...", @@ -778,47 +780,35 @@ "controlDesktop": "Styr en datorenhet", "connectedTo": "Ansluten till ${name}", "session": { - "creatingSession": "Skapar fjärrsession...", - "failedToCreate": "Kunde inte skapa fjärrsession:", - "noSession": "Ingen session tillgänglig", - "scanQrCode": "Skanna QR-kod", - "orEnterManually": "Eller ange manuellt", + "startingServer": "Startar fjärrserver...", + "failedToCreate": "Kunde inte starta fjärrserver:", "hostAddress": "Värdadress", - "sessionId": "Sessions-ID", - "pin": "PIN", "connected": "Ansluten", - "waitingForConnection": "Väntar på anslutning...", + "serverRunning": "Fjärrserver aktiv", + "serverStopped": "Fjärrserver stoppad", + "serverRunningDescription": "Mobila enheter i ditt nätverk kan upptäcka och ansluta till denna app", + "serverStoppedDescription": "Starta servern för att tillåta mobila enheter att ansluta", "usePhoneToControl": "Använd din mobila enhet för att styra denna app", - "copiedToClipboard": "${label} kopierad till urklipp", - "copyToClipboard": "Kopiera till urklipp", - "newSession": "Ny session", + "startServer": "Starta server", + "stopServer": "Stoppa server", "minimize": "Minimera" }, "pairing": { - "scan": "Skanna", - "manual": "Manuell", - "pairWithDesktop": "Parkoppla med dator", - "enterSessionDetails": "Ange sessionsuppgifterna som visas på din datorenhet", + "pairWithDesktop": "Anslut till dator", + "discoveryDescription": "Enheter i ditt nätverk som kör Plezy med samma Plex-konto visas automatiskt", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "Ange 8-teckens sessions-ID", - "pinHint": "Ange 6-siffrig PIN", "connecting": "Ansluter...", - "tips": "Tips", - "tipDesktop": "Öppna Plezy på din dator och aktivera Companion Remote via inställningar eller meny", - "tipScan": "Använd fliken Skanna för att snabbt parkoppla genom att skanna QR-koden på din dator", - "tipWifi": "Se till att båda enheterna är på samma WiFi-nätverk", - "cameraPermissionRequired": "Kamerabehörighet krävs för att skanna QR-koder.\nVänligen ge kameraåtkomst i enhetsinställningarna.", - "cameraError": "Kunde inte starta kameran: ${error}", - "scanInstruction": "Rikta kameran mot QR-koden som visas på din dator", - "invalidQrCode": "Ogiltigt QR-kodformat", - "validationHostRequired": "Ange en värdadress", + "searchingForDevices": "Söker efter enheter...", + "noDevicesFound": "Inga enheter hittades i ditt nätverk", + "noDevicesHint": "Se till att Plezy är öppet på din dator och att båda enheterna är på samma WiFi-nätverk", + "availableDevices": "Tillgängliga enheter", + "manualConnection": "Manuell anslutning", + "cryptoInitFailed": "Kunde inte initiera säker anslutning. Se till att du är inloggad på ett Plex-konto.", + "validationHostRequired": "Ange värdadress", "validationHostFormat": "Format måste vara IP:port (t.ex. 192.168.1.100:48632)", - "validationSessionIdRequired": "Ange ett sessions-ID", - "validationSessionIdLength": "Sessions-ID måste vara 8 tecken", - "validationPinRequired": "Ange en PIN", - "validationPinLength": "PIN måste vara 6 siffror", - "connectionTimedOut": "Anslutningen tog för lång tid. Kontrollera sessions-ID och PIN.", - "sessionNotFound": "Kunde inte hitta sessionen. Kontrollera dina uppgifter.", + "connectionTimedOut": "Anslutningen tog för lång tid. Se till att båda enheterna är på samma nätverk.", + "sessionNotFound": "Kunde inte hitta enheten. Se till att Plezy körs på värden.", + "authFailed": "Autentisering misslyckades. Se till att båda enheterna använder samma Plex-konto.", "failedToConnect": "Kunde inte ansluta: ${error}" }, "remote": { diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index ac36714c..2f860034 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -238,7 +238,9 @@ "player": "Player", "subtitlesAndConfig": "Subtitles & Configuration", "seekAndTiming": "Seek & Timing", - "behavior": "Behavior" + "behavior": "Behavior", + "companionRemoteServer": "配套遥控服务器", + "companionRemoteServerDescription": "允许网络上的移动设备控制此应用" }, "search": { "hint": "搜索电影、系列、音乐...", @@ -778,47 +780,35 @@ "controlDesktop": "控制桌面设备", "connectedTo": "已连接到 ${name}", "session": { - "creatingSession": "正在创建远程会话...", - "failedToCreate": "创建远程会话失败:", - "noSession": "没有可用的会话", - "scanQrCode": "扫描 QR 码", - "orEnterManually": "或手动输入", + "startingServer": "正在启动远程服务器...", + "failedToCreate": "启动远程服务器失败:", "hostAddress": "主机地址", - "sessionId": "会话 ID", - "pin": "PIN", "connected": "已连接", - "waitingForConnection": "等待连接中...", + "serverRunning": "远程服务器已启动", + "serverStopped": "远程服务器已停止", + "serverRunningDescription": "网络上的移动设备可以发现并连接到此应用", + "serverStoppedDescription": "启动服务器以允许移动设备连接", "usePhoneToControl": "使用移动设备控制此应用", - "copiedToClipboard": "${label}已复制到剪贴板", - "copyToClipboard": "复制到剪贴板", - "newSession": "新建会话", + "startServer": "启动服务器", + "stopServer": "停止服务器", "minimize": "最小化" }, "pairing": { - "scan": "扫描", - "manual": "手动", - "pairWithDesktop": "与桌面配对", - "enterSessionDetails": "输入桌面设备上显示的会话信息", + "pairWithDesktop": "连接到桌面", + "discoveryDescription": "网络上使用相同Plex账户运行Plezy的设备将自动显示", "hostAddressHint": "192.168.1.100:48632", - "sessionIdHint": "输入8位会话 ID", - "pinHint": "输入6位 PIN", - "connecting": "连接中...", - "tips": "提示", - "tipDesktop": "在桌面上打开 Plezy,并从设置或菜单中启用 Companion Remote", - "tipScan": "使用扫描选项卡扫描桌面上的 QR 码以快速配对", - "tipWifi": "请确保两台设备连接到同一个 WiFi 网络", - "cameraPermissionRequired": "扫描 QR 码需要相机权限。\n请在设备设置中授予相机访问权限。", - "cameraError": "无法启动相机:${error}", - "scanInstruction": "将相机对准桌面上显示的 QR 码", - "invalidQrCode": "无效的 QR 码格式", + "connecting": "正在连接...", + "searchingForDevices": "正在搜索设备...", + "noDevicesFound": "未在网络上找到设备", + "noDevicesHint": "请确保桌面上已打开Plezy,且两台设备在同一WiFi网络上", + "availableDevices": "可用设备", + "manualConnection": "手动连接", + "cryptoInitFailed": "无法初始化安全连接。请确保已登录Plex账户。", "validationHostRequired": "请输入主机地址", - "validationHostFormat": "格式必须为 IP:端口(例如 192.168.1.100:48632)", - "validationSessionIdRequired": "请输入会话 ID", - "validationSessionIdLength": "会话 ID 必须为8个字符", - "validationPinRequired": "请输入 PIN", - "validationPinLength": "PIN 必须为6位数字", - "connectionTimedOut": "连接超时。请检查会话 ID 和 PIN。", - "sessionNotFound": "找不到会话。请检查您的凭据。", + "validationHostFormat": "格式必须为IP:端口(例如 192.168.1.100:48632)", + "connectionTimedOut": "连接超时。请确保两台设备在同一网络上。", + "sessionNotFound": "未找到设备。请确保Plezy正在主机上运行。", + "authFailed": "认证失败。请确保两台设备使用相同的Plex账户。", "failedToConnect": "连接失败:${error}" }, "remote": { diff --git a/lib/models/companion_remote/remote_session.dart b/lib/models/companion_remote/remote_session.dart index e7b98c4b..24f55452 100644 --- a/lib/models/companion_remote/remote_session.dart +++ b/lib/models/companion_remote/remote_session.dart @@ -56,8 +56,6 @@ class RemoteDevice { @JsonSerializable() class RemoteSession { - final String sessionId; - final String pin; @JsonKey(unknownEnumValue: RemoteSessionRole.remote) final RemoteSessionRole role; @JsonKey(unknownEnumValue: RemoteSessionStatus.disconnected) @@ -67,8 +65,6 @@ class RemoteSession { final String? errorMessage; RemoteSession({ - required this.sessionId, - required this.pin, required this.role, this.status = RemoteSessionStatus.disconnected, this.connectedDevice, @@ -85,8 +81,6 @@ class RemoteSession { Map toJson() => _$RemoteSessionToJson(this); RemoteSession copyWith({ - String? sessionId, - String? pin, RemoteSessionRole? role, RemoteSessionStatus? status, RemoteDevice? connectedDevice, @@ -96,8 +90,6 @@ class RemoteSession { bool clearErrorMessage = false, }) { return RemoteSession( - sessionId: sessionId ?? this.sessionId, - pin: pin ?? this.pin, role: role ?? this.role, status: status ?? this.status, connectedDevice: clearConnectedDevice ? null : (connectedDevice ?? this.connectedDevice), diff --git a/lib/models/companion_remote/remote_session.g.dart b/lib/models/companion_remote/remote_session.g.dart index 113f6e99..ee6f3c4e 100644 --- a/lib/models/companion_remote/remote_session.g.dart +++ b/lib/models/companion_remote/remote_session.g.dart @@ -29,8 +29,6 @@ Map _$RemoteDeviceToJson(RemoteDevice instance) => RemoteSession _$RemoteSessionFromJson(Map json) => RemoteSession( - sessionId: json['sessionId'] as String, - pin: json['pin'] as String, role: $enumDecode( _$RemoteSessionRoleEnumMap, json['role'], @@ -56,8 +54,6 @@ RemoteSession _$RemoteSessionFromJson(Map json) => Map _$RemoteSessionToJson(RemoteSession instance) => { - 'sessionId': instance.sessionId, - 'pin': instance.pin, 'role': _$RemoteSessionRoleEnumMap[instance.role]!, 'status': _$RemoteSessionStatusEnumMap[instance.status]!, 'connectedDevice': instance.connectedDevice, diff --git a/lib/providers/companion_remote_provider.dart b/lib/providers/companion_remote_provider.dart index efad669c..ddc05718 100644 --- a/lib/providers/companion_remote_provider.dart +++ b/lib/providers/companion_remote_provider.dart @@ -6,14 +6,21 @@ import 'package:device_info_plus/device_info_plus.dart'; import '../models/companion_remote/remote_command.dart'; import '../models/companion_remote/remote_session.dart'; +import '../models/plex_home.dart'; import '../services/companion_remote/companion_remote_peer_service.dart'; +import '../services/companion_remote/lan_discovery_service.dart'; +import '../services/companion_remote/remote_auth_service.dart'; +import '../services/storage_service.dart'; import '../utils/app_logger.dart'; +export '../services/companion_remote/lan_discovery_service.dart' show DiscoveredHost; + typedef CommandReceivedCallback = void Function(RemoteCommand command); class CompanionRemoteProvider with ChangeNotifier { RemoteSession? _session; CompanionRemotePeerService? _peerService; + LanDiscoveryService? _discoveryService; String _deviceName = 'Unknown Device'; String _platform = 'unknown'; bool _isPlayerActive = false; @@ -23,9 +30,17 @@ class CompanionRemoteProvider with ChangeNotifier { Timer? _reconnectTimer; int _reconnectAttempts = 0; bool _intentionalDisconnect = false; - String? _lastSessionId; - String? _lastPin; - String? _lastHostAddress; + + // Reconnection context (only hostAddresses and hostClientId are connection-specific) + List? _lastHostAddresses; + String? _lastHostClientId; + + // Crypto context (derived in memory, never persisted) + List? _homeSecret; + List? _discoveryKey; + String? _clientIdentifier; + String? _userUUID; + List? _homeUserUUIDs; int get reconnectAttempts => _reconnectAttempts; @@ -43,10 +58,9 @@ class CompanionRemoteProvider with ChangeNotifier { bool get isConnected => _session?.isConnected ?? false; RemoteSession? get session => _session; RemoteSessionStatus get status => _session?.status ?? RemoteSessionStatus.disconnected; - String? get sessionId => _session?.sessionId; - String? get pin => _session?.pin; RemoteDevice? get connectedDevice => _session?.connectedDevice; bool get isPlayerActive => _isPlayerActive; + bool get isHostServerRunning => _peerService?.isServerRunning ?? false; CompanionRemoteProvider() { _initializeDeviceInfo(); @@ -86,6 +100,219 @@ class CompanionRemoteProvider with ChangeNotifier { notifyListeners(); } + /// Initialize crypto context from Plex home data. + /// Must be called before startHostServer or connectToDiscoveredHost. + Future initializeCrypto(PlexHome? home, StorageService storage) async { + if (home == null || home.adminUser == null) { + appLogger.w('CompanionRemote: Cannot init crypto — no home data'); + return false; + } + + try { + final auth = RemoteAuthService.instance; + _homeSecret = await auth.deriveHomeSecretFromHome(home); + _discoveryKey = await auth.deriveDiscoveryKey(_homeSecret!); + _clientIdentifier = storage.getClientIdentifier(); + _userUUID = storage.getCurrentUserUUID(); + _homeUserUUIDs = home.users.map((u) => u.uuid).toList(); + + appLogger.d('CompanionRemote: Crypto context initialized'); + return true; + } catch (e) { + appLogger.e('CompanionRemote: Failed to init crypto', error: e); + return false; + } + } + + bool get isCryptoReady => _homeSecret != null && _discoveryKey != null && _clientIdentifier != null; + + /// Convenience: ensure crypto is initialized using providers from context. + /// Returns true if crypto is ready (already initialized or just initialized). + Future ensureCryptoReady(PlexHome? home) async { + if (isCryptoReady) return true; + final storage = await StorageService.getInstance(); + return initializeCrypto(home, storage); + } + + // ── Host Server ── + + /// Start the host server and begin LAN broadcasting. Idempotent. + Future startHostServer() async { + if (_peerService?.isServerRunning == true) return; + if (!isCryptoReady) { + appLogger.w('CompanionRemote: Cannot start host — crypto not initialized'); + return; + } + + appLogger.d('CompanionRemote: Starting host server'); + + _peerService ??= CompanionRemotePeerService(); + _setupPeerServiceListeners(); + + try { + final result = await _peerService!.createSession( + _deviceName, + _platform, + _homeSecret!, + _clientIdentifier!, + _homeUserUUIDs!, + ); + + _session = RemoteSession( + role: RemoteSessionRole.host, + status: RemoteSessionStatus.connected, + ); + notifyListeners(); + + // Start LAN discovery broadcasting + _discoveryService ??= LanDiscoveryService(); + final localIps = result.addresses.map((a) => a.split(':').first).toList(); + await _discoveryService!.startBroadcasting( + discoveryKey: _discoveryKey!, + deviceName: _deviceName, + platform: _platform, + clientId: _clientIdentifier!, + wsPort: result.port, + ips: localIps, + ); + + appLogger.d('CompanionRemote: Host server running, broadcasting on LAN'); + } catch (e) { + appLogger.e('CompanionRemote: Failed to start host server', error: e); + _session = RemoteSession( + role: RemoteSessionRole.host, + status: RemoteSessionStatus.error, + errorMessage: e.toString(), + ); + notifyListeners(); + } + } + + /// Stop the host server and LAN broadcasting. + Future stopHostServer() async { + _intentionalDisconnect = true; + _discoveryService?.stopBroadcasting(); + + if (_peerService != null) { + await _peerService!.disconnect(); + _peerService = null; + } + _cleanupSubscriptions(); + + _session = null; + _isPlayerActive = false; + _intentionalDisconnect = false; + notifyListeners(); + } + + // ── Client: Discovery ── + + /// Start listening for host beacons. Returns a stream of discovered hosts. + Stream>? discoverHosts() { + if (!isCryptoReady) { + appLogger.w('CompanionRemote: Cannot discover — crypto not initialized'); + return null; + } + + _discoveryService ??= LanDiscoveryService(); + return _discoveryService!.startListening(discoveryKey: _discoveryKey!); + } + + /// Stop listening for host beacons. + void stopDiscovery() { + _discoveryService?.stopListening(); + } + + /// Connect to a discovered host as a remote client. + Future connectToDiscoveredHost(DiscoveredHost host) async { + if (!isCryptoReady) { + throw StateError('Crypto not initialized'); + } + + await leaveSession(); + + _lastHostAddresses = host.addresses; + _lastHostClientId = host.clientId; + + appLogger.d('CompanionRemote: Connecting to ${host.name} at ${host.addresses}'); + + _peerService = CompanionRemotePeerService(); + _setupPeerServiceListeners(); + + _session = RemoteSession( + role: RemoteSessionRole.remote, + status: RemoteSessionStatus.connecting, + ); + notifyListeners(); + + try { + final winner = await _peerService!.joinSessionRacing( + _deviceName, + _platform, + host.addresses, + _homeSecret!, + host.clientId, + _userUUID!, + _clientIdentifier!, + ); + _lastHostAddresses = [winner]; + + _session = _session?.copyWith(status: RemoteSessionStatus.connected); + notifyListeners(); + appLogger.d('CompanionRemote: Connected to ${host.name} via $winner'); + } catch (e) { + appLogger.e('CompanionRemote: Failed to connect to host', error: e); + _session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString()); + notifyListeners(); + rethrow; + } + } + + /// Connect to a host by manual IP:port entry. + Future connectToManualHost(String hostAddress) async { + if (!isCryptoReady) { + throw StateError('Crypto not initialized'); + } + + await leaveSession(); + + _lastHostAddresses = [hostAddress]; + _lastHostClientId = ''; + + appLogger.d('CompanionRemote: Connecting to manual host $hostAddress'); + + _peerService = CompanionRemotePeerService(); + _setupPeerServiceListeners(); + + _session = RemoteSession( + role: RemoteSessionRole.remote, + status: RemoteSessionStatus.connecting, + ); + notifyListeners(); + + try { + await _peerService!.joinSession( + _deviceName, + _platform, + hostAddress, + _homeSecret!, + '', // Empty hostClientId — accept any host in same home + _userUUID!, + _clientIdentifier!, + ); + + _session = _session?.copyWith(status: RemoteSessionStatus.connected); + notifyListeners(); + } catch (e) { + appLogger.e('CompanionRemote: Failed to connect to manual host', error: e); + _session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString()); + notifyListeners(); + rethrow; + } + } + + // ── Peer service listeners ── + void _setupPeerServiceListeners() { _commandSubscription = _peerService!.onCommandReceived.listen( (command) { @@ -118,7 +345,6 @@ class CompanionRemoteProvider with ChangeNotifier { _session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true); notifyListeners(); } else if (isHost) { - // Host keeps the server running — the client will reconnect on its own _session = _session?.copyWith( status: RemoteSessionStatus.reconnecting, clearConnectedDevice: true, @@ -183,93 +409,7 @@ class CompanionRemoteProvider with ChangeNotifier { _statusSubscription = null; } - Future<({String sessionId, String pin, List addresses})> createSession() async { - await leaveSession(); - - appLogger.d('CompanionRemote: Creating session as host'); - - _peerService = CompanionRemotePeerService(); - _setupPeerServiceListeners(); - - try { - final result = await _peerService!.createSession(_deviceName, _platform); - - _session = RemoteSession( - sessionId: result.sessionId, - pin: result.pin, - role: RemoteSessionRole.host, - status: RemoteSessionStatus.connected, - ); - - notifyListeners(); - appLogger.d( - 'CompanionRemote: Session created - ID: ${result.sessionId}, PIN: ${result.pin}, Addresses: ${result.addresses}', - ); - - return result; - } catch (e) { - appLogger.e('CompanionRemote: Failed to create session', error: e); - _session = RemoteSession( - sessionId: '', - pin: '', - role: RemoteSessionRole.host, - status: RemoteSessionStatus.error, - errorMessage: e.toString(), - ); - notifyListeners(); - rethrow; - } - } - - Future joinSession(String sessionId, String pin, String hostAddress) async { - await joinSessionMulti(sessionId, pin, [hostAddress]); - } - - Future joinSessionMulti(String sessionId, String pin, List hostAddresses) async { - if (hostAddresses.isEmpty) { - throw ArgumentError('hostAddresses must not be empty'); - } - - await leaveSession(); - - _lastSessionId = sessionId; - _lastPin = pin; - // Store first address as fallback for reconnection; will be updated with winner - _lastHostAddress = hostAddresses.first; - - appLogger.d('CompanionRemote: Joining session - ID: $sessionId, Hosts: $hostAddresses'); - - _peerService = CompanionRemotePeerService(); - _setupPeerServiceListeners(); - - _session = RemoteSession( - sessionId: sessionId, - pin: pin, - role: RemoteSessionRole.remote, - status: RemoteSessionStatus.connecting, - ); - notifyListeners(); - - try { - final winner = await _peerService!.joinSessionRacing( - sessionId, - pin, - _deviceName, - _platform, - hostAddresses, - ); - _lastHostAddress = winner; - - _session = _session?.copyWith(status: RemoteSessionStatus.connected); - notifyListeners(); - appLogger.d('CompanionRemote: Successfully joined session via $winner'); - } catch (e) { - appLogger.e('CompanionRemote: Failed to join session', error: e); - _session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString()); - notifyListeners(); - rethrow; - } - } + // ── Commands ── void sendCommand(RemoteCommandType type, {Map? data}) { if (_peerService == null || !isConnected) { @@ -281,6 +421,8 @@ class CompanionRemoteProvider with ChangeNotifier { _peerService!.sendCommand(RemoteCommand(type: type, data: data)); } + // ── Reconnection ── + void _scheduleReconnect() { if (_reconnectAttempts >= _maxReconnectAttempts) { appLogger.w('CompanionRemote: Max reconnect attempts reached'); @@ -293,7 +435,7 @@ class CompanionRemoteProvider with ChangeNotifier { return; } - final delay = Duration(seconds: 1 << _reconnectAttempts); // 1s, 2s, 4s, 8s, 16s + final delay = Duration(seconds: 1 << _reconnectAttempts); _reconnectAttempts++; appLogger.d('CompanionRemote: Reconnect attempt $_reconnectAttempts in ${delay.inSeconds}s'); @@ -302,8 +444,8 @@ class CompanionRemoteProvider with ChangeNotifier { } Future _attemptReconnect() async { - if (_lastSessionId == null || _lastPin == null || _lastHostAddress == null) { - appLogger.w('CompanionRemote: No stored credentials for reconnect'); + if (_lastHostAddresses == null || !isCryptoReady) { + appLogger.w('CompanionRemote: No stored context for reconnect'); _session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: 'Connection lost'); notifyListeners(); return; @@ -311,7 +453,6 @@ class CompanionRemoteProvider with ChangeNotifier { try { appLogger.d('CompanionRemote: Attempting reconnect...'); - // Clean up old peer service without triggering intentional disconnect _cleanupSubscriptions(); try { await _peerService?.disconnect(); @@ -320,7 +461,15 @@ class CompanionRemoteProvider with ChangeNotifier { _setupPeerServiceListeners(); } - await _peerService!.joinSession(_lastSessionId!, _lastPin!, _deviceName, _platform, _lastHostAddress!); + await _peerService!.joinSession( + _deviceName, + _platform, + _lastHostAddresses!.first, + _homeSecret!, + _lastHostClientId ?? '', + _userUUID!, + _clientIdentifier!, + ); _session = _session?.copyWith(status: RemoteSessionStatus.connected, clearErrorMessage: true); _reconnectAttempts = 0; @@ -334,14 +483,12 @@ class CompanionRemoteProvider with ChangeNotifier { } } - /// Immediately retry reconnection, skipping the backoff wait void retryReconnectNow() { _reconnectTimer?.cancel(); _reconnectAttempts = 0; _attemptReconnect(); } - /// Cancel ongoing reconnection attempts void cancelReconnect() { _reconnectTimer?.cancel(); _reconnectAttempts = 0; @@ -354,7 +501,8 @@ class CompanionRemoteProvider with ChangeNotifier { _reconnectTimer?.cancel(); _reconnectAttempts = 0; - if (_peerService != null) { + // Don't stop the host server when leaving — only stop discovery listening + if (_peerService != null && !isHost) { appLogger.d('CompanionRemote: Leaving session'); await _peerService!.disconnect(); _peerService = null; @@ -362,7 +510,9 @@ class CompanionRemoteProvider with ChangeNotifier { _cleanupSubscriptions(); - _session = null; + if (!isHost) { + _session = null; + } _isPlayerActive = false; _intentionalDisconnect = false; notifyListeners(); @@ -371,7 +521,9 @@ class CompanionRemoteProvider with ChangeNotifier { @override void dispose() { _reconnectTimer?.cancel(); - leaveSession(); + _discoveryService?.dispose(); + _peerService?.dispose(); + RemoteAuthService.instance.clearCache(); super.dispose(); } } diff --git a/lib/screens/companion_remote/pairing_screen.dart b/lib/screens/companion_remote/pairing_screen.dart index 23855234..bc33e2f4 100644 --- a/lib/screens/companion_remote/pairing_screen.dart +++ b/lib/screens/companion_remote/pairing_screen.dart @@ -1,12 +1,11 @@ -import 'dart:io'; +import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:provider/provider.dart'; import '../../i18n/strings.g.dart'; import '../../providers/companion_remote_provider.dart'; +import '../../providers/user_profile_provider.dart'; import '../../utils/app_logger.dart'; class PairingScreen extends StatefulWidget { @@ -18,58 +17,97 @@ class PairingScreen extends StatefulWidget { class _PairingScreenState extends State { final _hostAddressController = TextEditingController(); - final _sessionIdController = TextEditingController(); - final _pinController = TextEditingController(); final _formKey = GlobalKey(); bool _isConnecting = false; String? _errorMessage; - int _selectedTab = 0; + bool _showManualEntry = false; + bool _cryptoReady = false; - // QR scanner state - MobileScannerController? _scannerController; - String? _lastScannedCode; - bool _hasCamera = false; - - bool get _canScan => (Platform.isAndroid || Platform.isIOS) && _hasCamera; - - // Tab indices: scannable gets Scan (0) + Manual (1), otherwise Manual (0) - int get _scanTabIndex => _canScan ? 0 : -1; - int get _manualTabIndex => _canScan ? 1 : 0; + StreamSubscription>? _discoverySubscription; + List _hosts = []; + bool _isSearching = true; + Timer? _searchTimeout; @override void initState() { super.initState(); - _checkCamera(); + _initCryptoAndDiscover(); } - Future _checkCamera() async { - if (Platform.isIOS) { - setState(() => _hasCamera = true); - return; - } - if (!Platform.isAndroid) return; - try { - const channel = MethodChannel('com.plezy/theme'); - final result = await channel.invokeMethod('hasCamera'); - if (mounted) setState(() => _hasCamera = result ?? false); - } catch (e) { - debugPrint('Camera check failed: $e'); + Future _initCryptoAndDiscover() async { + final provider = context.read(); + final home = context.read().home; + await provider.ensureCryptoReady(home); + if (!mounted) return; + + if (provider.isCryptoReady) { + setState(() => _cryptoReady = true); + _startDiscovery(); + } else { + setState(() { + _cryptoReady = false; + _isSearching = false; + _errorMessage = t.companionRemote.pairing.cryptoInitFailed; + }); } } + void _startDiscovery() { + final provider = context.read(); + final stream = provider.discoverHosts(); + if (stream == null) return; + + _discoverySubscription = stream.listen((hosts) { + if (mounted) { + setState(() { + _hosts = hosts; + if (hosts.isNotEmpty) _isSearching = false; + }); + } + }); + + // Show "no devices found" after 10 seconds of no results + _searchTimeout = Timer(const Duration(seconds: 10), () { + if (mounted && _hosts.isEmpty) { + setState(() => _isSearching = false); + } + }); + } + @override void dispose() { _hostAddressController.dispose(); - _sessionIdController.dispose(); - _pinController.dispose(); - _scannerController?.dispose(); + _discoverySubscription?.cancel(); + _searchTimeout?.cancel(); + context.read().stopDiscovery(); super.dispose(); } - Future _connect() async { - if (!_formKey.currentState!.validate()) { - return; + Future _connectToHost(DiscoveredHost host) async { + setState(() { + _isConnecting = true; + _errorMessage = null; + }); + + try { + final provider = context.read(); + await provider.connectToDiscoveredHost(host); + + if (mounted) { + Navigator.of(context).pop(); + } + } catch (e) { + appLogger.e('Failed to connect to host', error: e); + if (!mounted) return; + setState(() { + _isConnecting = false; + _errorMessage = _parseErrorMessage(e.toString()); + }); } + } + + Future _connectManual() async { + if (!_formKey.currentState!.validate()) return; setState(() { _isConnecting = true; @@ -78,17 +116,13 @@ class _PairingScreenState extends State { try { final provider = context.read(); - await provider.joinSession( - _sessionIdController.text.trim().toUpperCase(), - _pinController.text.trim(), - _hostAddressController.text.trim(), - ); + await provider.connectToManualHost(_hostAddressController.text.trim()); if (mounted) { Navigator.of(context).pop(); } } catch (e) { - appLogger.e('Failed to join remote session', error: e); + appLogger.e('Failed to connect to manual host', error: e); if (!mounted) return; setState(() { _isConnecting = false; @@ -102,73 +136,22 @@ class _PairingScreenState extends State { return t.companionRemote.pairing.connectionTimedOut; } else if (error.contains('Failed to connect')) { return t.companionRemote.pairing.sessionNotFound; + } else if (error.contains('Authentication failed')) { + return t.companionRemote.pairing.authFailed; } return t.companionRemote.pairing.failedToConnect(error: error.replaceAll('Exception: ', '')); } - void _handleQrCode(String data) { - // Debounce: don't process the same code twice - if (data == _lastScannedCode) return; - _lastScannedCode = data; - - // Strip URL wrapper if present (e.g. "https://plezy.app/scan#ip1,ip2|port|sid|pin") - final payload = data.contains('#') ? data.split('#').last : data; - final parts = payload.split('|'); - if (parts.length == 4) { - final ipsField = parts.first; - final port = parts[1]; - final sessionId = parts[2]; - final pin = parts[3]; - - // Support comma-separated IPs (multi-NIC) or single IP (legacy) - final ips = ipsField.split(','); - final hostAddresses = ips.map((ip) => '$ip:$port').toList(); - - _scannerController?.stop(); - setState(() { - _errorMessage = null; - _isConnecting = true; - }); - - _connectWithCredentialsMulti(sessionId, pin, hostAddresses); - } else { - setState(() { - _errorMessage = t.companionRemote.pairing.invalidQrCode; - }); - } - } - - Future _connectWithCredentialsMulti(String sessionId, String pin, List hostAddresses) async { - try { - final provider = context.read(); - await provider.joinSessionMulti( - sessionId.trim().toUpperCase(), - pin.trim(), - hostAddresses.map((a) => a.trim()).toList(), - ); - - if (mounted) { - Navigator.of(context).pop(); - } - } catch (e) { - appLogger.e('Failed to join remote session', error: e); - if (!mounted) return; - _lastScannedCode = null; // Allow re-scanning - setState(() { - _isConnecting = false; - _errorMessage = _parseErrorMessage(e.toString()); - }); - _scannerController?.start(); - } - } - - Future _pasteFromClipboard(TextEditingController controller) async { - final data = await Clipboard.getData(Clipboard.kTextPlain); - if (!mounted) return; - if (data?.text != null) { - setState(() { - controller.text = data!.text!; - }); + IconData _platformIcon(String platform) { + switch (platform.toLowerCase()) { + case 'macos': + return Icons.desktop_mac; + case 'windows': + return Icons.desktop_windows; + case 'linux': + return Icons.computer; + default: + return Icons.devices; } } @@ -178,290 +161,218 @@ class _PairingScreenState extends State { appBar: AppBar( title: Text(t.companionRemote.connectToDevice), ), - body: Column( + body: _buildBody(), + ); + } + + Widget _buildBody() { + return SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (_canScan) - SegmentedButton( - segments: [ - ButtonSegment( - value: _scanTabIndex, - label: Text(t.companionRemote.pairing.scan), - icon: const Icon(Icons.qr_code_scanner), + Icon(Icons.devices, size: 64, color: Theme.of(context).colorScheme.primary), + const SizedBox(height: 16), + Text( + t.companionRemote.pairing.pairWithDesktop, + style: Theme.of(context).textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + t.companionRemote.pairing.discoveryDescription, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + + if (_errorMessage != null) ...[ + Card( + color: Theme.of(context).colorScheme.errorContainer, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer), + const SizedBox(width: 12), + Expanded( + child: Text( + _errorMessage!, + style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer), + ), + ), + ], ), - ButtonSegment( - value: _manualTabIndex, - label: Text(t.companionRemote.pairing.manual), - icon: const Icon(Icons.keyboard), - ), - ], - selected: {_selectedTab}, - onSelectionChanged: (Set selection) { - setState(() { - _selectedTab = selection.first; - }); - }, + ), ), - Expanded(child: _buildTabContent()), + const SizedBox(height: 16), + ], + + // Discovered hosts + _buildDiscoverySection(), + + const SizedBox(height: 24), + const Divider(), + const SizedBox(height: 16), + + // Manual entry fallback + _buildManualEntrySection(), ], ), ); } - Widget _buildTabContent() { - if (_selectedTab == _scanTabIndex && _canScan) return _buildScanTab(); - return _buildManualEntryTab(); - } - - Widget _buildScanTab() { - return Column( - children: [ - Expanded( - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(24.0), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: MobileScanner( - controller: _scannerController ??= MobileScannerController(), - onDetect: (capture) { - final barcode = capture.barcodes.firstOrNull; - if (barcode?.rawValue != null) { - _handleQrCode(barcode!.rawValue!); - } - }, - errorBuilder: (context, error) { - return Center( - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.no_photography, size: 64, color: Colors.grey), - const SizedBox(height: 16), - Text( - error.errorCode == MobileScannerErrorCode.permissionDenied - ? t.companionRemote.pairing.cameraPermissionRequired - : t.companionRemote.pairing.cameraError( - error: error.errorDetails?.message ?? error.errorCode.name, - ), - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - ), - ); - }, - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24), + Widget _buildDiscoverySection() { + if (!_cryptoReady) { + return Card( + child: Padding( + padding: const EdgeInsets.all(24.0), child: Column( children: [ + const Icon(Icons.warning_amber, size: 48, color: Colors.orange), + const SizedBox(height: 12), Text( - t.companionRemote.pairing.scanInstruction, - style: Theme.of(context).textTheme.bodyMedium, + t.companionRemote.pairing.cryptoInitFailed, textAlign: TextAlign.center, ), - if (_errorMessage != null) ...[ - const SizedBox(height: 12), - Card( - color: Theme.of(context).colorScheme.errorContainer, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer), - const SizedBox(width: 12), - Expanded( - child: Text( - _errorMessage!, - style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer), - ), - ), - ], - ), - ), - ), - ], ], ), ), + ); + } + + if (_isSearching && _hosts.isEmpty) { + return Card( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + const SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator(strokeWidth: 3), + ), + const SizedBox(height: 16), + Text( + t.companionRemote.pairing.searchingForDevices, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + ); + } + + if (_hosts.isEmpty) { + return Card( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + const Icon(Icons.devices_other, size: 48, color: Colors.grey), + const SizedBox(height: 12), + Text( + t.companionRemote.pairing.noDevicesFound, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + t.companionRemote.pairing.noDevicesHint, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.companionRemote.pairing.availableDevices, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + ..._hosts.map((host) => Card( + child: ListTile( + leading: Icon(_platformIcon(host.platform), size: 32), + title: Text(host.name), + subtitle: Text(host.platform), + trailing: _isConnecting + ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2)) + : const Icon(Icons.arrow_forward), + onTap: _isConnecting ? null : () => _connectToHost(host), + ), + )), ], ); } - Widget _buildManualEntryTab() { - return SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Icon(Icons.keyboard, size: 64, color: Theme.of(context).colorScheme.primary), - const SizedBox(height: 24), - Text( - t.companionRemote.pairing.pairWithDesktop, - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - t.companionRemote.pairing.enterSessionDetails, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 32), - if (_errorMessage != null) ...[ - Card( - color: Theme.of(context).colorScheme.errorContainer, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer), - const SizedBox(width: 12), - Expanded( - child: Text( - _errorMessage!, - style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer), - ), - ), - ], + Widget _buildManualEntrySection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () => setState(() => _showManualEntry = !_showManualEntry), + child: Row( + children: [ + Icon( + _showManualEntry ? Icons.expand_less : Icons.expand_more, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 8), + Text( + t.companionRemote.pairing.manualConnection, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + if (_showManualEntry) ...[ + const SizedBox(height: 16), + Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + controller: _hostAddressController, + decoration: InputDecoration( + labelText: t.companionRemote.session.hostAddress, + hintText: t.companionRemote.pairing.hostAddressHint, + border: const OutlineInputBorder(), + prefixIcon: const Icon(Icons.computer), ), + validator: (value) { + if (value == null || value.isEmpty) { + return t.companionRemote.pairing.validationHostRequired; + } + final parts = value.split(':'); + if (parts.length != 2) { + return t.companionRemote.pairing.validationHostFormat; + } + return null; + }, + enabled: !_isConnecting, ), - ), - const SizedBox(height: 16), - ], - TextFormField( - controller: _hostAddressController, - decoration: InputDecoration( - labelText: t.companionRemote.session.hostAddress, - hintText: t.companionRemote.pairing.hostAddressHint, - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.computer), - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () => _pasteFromClipboard(_hostAddressController), - tooltip: t.common.paste, + const SizedBox(height: 16), + FilledButton.icon( + onPressed: _isConnecting ? null : _connectManual, + icon: _isConnecting + ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) + : const Icon(Icons.link), + label: Text(_isConnecting ? t.companionRemote.pairing.connecting : t.common.connect), ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return t.companionRemote.pairing.validationHostRequired; - } - // Validate IP:port format - final parts = value.split(':'); - if (parts.length != 2) { - return t.companionRemote.pairing.validationHostFormat; - } - return null; - }, - enabled: !_isConnecting, - ), - const SizedBox(height: 16), - TextFormField( - controller: _sessionIdController, - decoration: InputDecoration( - labelText: t.companionRemote.session.sessionId, - hintText: t.companionRemote.pairing.sessionIdHint, - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.vpn_key), - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () => _pasteFromClipboard(_sessionIdController), - tooltip: t.common.paste, - ), - ), - textCapitalization: TextCapitalization.characters, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), - LengthLimitingTextInputFormatter(8), - TextInputFormatter.withFunction((oldValue, newValue) { - return newValue.copyWith(text: newValue.text.toUpperCase()); - }), ], - validator: (value) { - if (value == null || value.isEmpty) { - return t.companionRemote.pairing.validationSessionIdRequired; - } - if (value.length != 8) { - return t.companionRemote.pairing.validationSessionIdLength; - } - return null; - }, - enabled: !_isConnecting, ), - const SizedBox(height: 16), - TextFormField( - controller: _pinController, - decoration: InputDecoration( - labelText: t.companionRemote.session.pin, - hintText: t.companionRemote.pairing.pinHint, - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.lock), - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () => _pasteFromClipboard(_pinController), - tooltip: t.common.paste, - ), - ), - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)], - validator: (value) { - if (value == null || value.isEmpty) { - return t.companionRemote.pairing.validationPinRequired; - } - if (value.length != 6) { - return t.companionRemote.pairing.validationPinLength; - } - return null; - }, - enabled: !_isConnecting, - ), - const SizedBox(height: 24), - FilledButton.icon( - onPressed: _isConnecting ? null : _connect, - icon: _isConnecting - ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) - : const Icon(Icons.link), - label: Text(_isConnecting ? t.companionRemote.pairing.connecting : t.common.connect), - ), - const SizedBox(height: 32), - const Divider(), - const SizedBox(height: 16), - Text(t.companionRemote.pairing.tips, style: Theme.of(context).textTheme.titleMedium), - const SizedBox(height: 8), - _buildTipCard(context, Icons.computer, t.companionRemote.pairing.tipDesktop), - if (_canScan) ...[ - const SizedBox(height: 8), - _buildTipCard(context, Icons.qr_code, t.companionRemote.pairing.tipScan), - ], - const SizedBox(height: 8), - _buildTipCard(context, Icons.wifi, t.companionRemote.pairing.tipWifi), - ], - ), - ), - ); - } - - Widget _buildTipCard(BuildContext context, IconData icon, String text) { - return Card( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary), - const SizedBox(width: 12), - Expanded(child: Text(text, style: Theme.of(context).textTheme.bodySmall)), - ], - ), - ), + ), + ], + ], ); } } diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 9c890beb..bb4542f6 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -436,6 +436,9 @@ class _MainScreenState extends State with RouteAware, WindowListener } }; + // Auto-start host server if setting enabled + _autoStartCompanionRemoteServer(companionRemote); + final receiver = CompanionRemoteReceiver.instance; receiver.onTabNext = () { @@ -466,6 +469,21 @@ class _MainScreenState extends State with RouteAware, WindowListener }; } + Future _autoStartCompanionRemoteServer(CompanionRemoteProvider companionRemote) async { + try { + final settings = await SettingsService.getInstance(); + if (!settings.getEnableCompanionRemoteServer()) return; + if (!mounted) return; + + final home = context.read().home; + if (await companionRemote.ensureCryptoReady(home)) { + await companionRemote.startHostServer(); + } + } catch (e) { + appLogger.e('CompanionRemote: Failed to auto-start server', error: e); + } + } + @override void dispose() { WidgetsBinding.instance.removeObserver(this); diff --git a/lib/screens/settings/playback_settings_screen.dart b/lib/screens/settings/playback_settings_screen.dart index 228e6f5d..703be53a 100644 --- a/lib/screens/settings/playback_settings_screen.dart +++ b/lib/screens/settings/playback_settings_screen.dart @@ -45,6 +45,7 @@ class _PlaybackSettingsScreenState extends State { String _creditsPattern = settings.SettingsService.defaultCreditsPattern; int _maxVolume = 100; bool _enableDiscordRPC = false; + bool _enableCompanionRemoteServer = false; bool _autoPip = true; bool _matchContentFrameRate = false; bool _matchRefreshRate = false; @@ -84,6 +85,7 @@ class _PlaybackSettingsScreenState extends State { _creditsPattern = _settingsService.getCreditsPattern(); _maxVolume = _settingsService.getMaxVolume(); _enableDiscordRPC = _settingsService.getEnableDiscordRPC(); + _enableCompanionRemoteServer = _settingsService.getEnableCompanionRemoteServer(); _autoPip = _settingsService.getAutoPip(); _matchContentFrameRate = _settingsService.getMatchContentFrameRate(); _matchRefreshRate = _settingsService.getMatchRefreshRate(); @@ -142,6 +144,7 @@ class _PlaybackSettingsScreenState extends State { // --- Behavior --- SettingsSectionHeader(t.settings.behavior), if (DiscordRPCService.isAvailable) _buildDiscordRPC(), + if (PlatformDetector.shouldActAsRemoteHost(context)) _buildCompanionRemoteServer(), _buildRememberTrackSelections(), if (!isMobile) _buildClickVideoTogglesPlayback(), @@ -494,6 +497,19 @@ class _PlaybackSettingsScreenState extends State { ); } + Widget _buildCompanionRemoteServer() { + return SwitchListTile( + secondary: const AppIcon(Symbols.phone_android_rounded, fill: 1), + title: Text(t.settings.companionRemoteServer), + subtitle: Text(t.settings.companionRemoteServerDescription), + value: _enableCompanionRemoteServer, + onChanged: (value) async { + setState(() => _enableCompanionRemoteServer = value); + await _settingsService.setEnableCompanionRemoteServer(value); + }, + ); + } + Widget _buildRememberTrackSelections() { return SwitchListTile( secondary: const AppIcon(Symbols.bookmark_rounded, fill: 1), diff --git a/lib/services/companion_remote/companion_remote_peer_service.dart b/lib/services/companion_remote/companion_remote_peer_service.dart index 93a0b39b..0cf262a7 100644 --- a/lib/services/companion_remote/companion_remote_peer_service.dart +++ b/lib/services/companion_remote/companion_remote_peer_service.dart @@ -10,6 +10,7 @@ import '../../models/companion_remote/remote_command.dart'; import '../../models/companion_remote/remote_session.dart'; import '../../utils/app_logger.dart'; import '../base_peer_service.dart'; +import 'remote_auth_service.dart'; // Re-export so callers that import from here get the types. export '../base_peer_service.dart' show PeerError, PeerErrorType; @@ -27,12 +28,16 @@ class CompanionRemotePeerService with KeepaliveMixin { // Client-side (remote) fields IOWebSocketChannel? _channel; - String? _sessionId; - String? _pin; String? _myPeerId; String? _hostAddress; // Format: "ip:port" RemoteSessionRole? _role; + // Encrypted channel state + List? _sessionEncKey; + int _sendCounter = 0; + int _recvCounter = 0; + bool _isAuthenticated = false; + final _commandReceivedController = StreamController.broadcast(); final _deviceConnectedController = StreamController.broadcast(); final _deviceDisconnectedController = StreamController.broadcast(); @@ -45,9 +50,9 @@ class CompanionRemotePeerService with KeepaliveMixin { @override Duration get pongTimeout => Duration.zero; // No pong timeout; host just replies inline - // Auth rate limiting - int _failedAuthAttempts = 0; - DateTime? _authLockoutUntil; + // Auth rate limiting (per source IP) + final Map _failedAuthAttempts = {}; + final Map _authLockouts = {}; static const int _maxFailedAuthAttempts = 5; static const Duration _authLockoutDuration = Duration(seconds: 30); @@ -57,25 +62,12 @@ class CompanionRemotePeerService with KeepaliveMixin { Stream get onError => _errorController.stream; Stream get onConnectionStateChanged => _connectionStateController.stream; - String? get sessionId => _sessionId; - String? get pin => _pin; String? get myPeerId => _myPeerId; String? get hostAddress => _hostAddress; RemoteSessionRole? get role => _role; bool get isHost => _role == RemoteSessionRole.host; bool get isConnected => _clientSocket != null || (_channel != null && _channel?.closeCode == null); - String _generateSessionId() { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - final random = Random.secure(); - return List.generate(8, (index) => chars[random.nextInt(chars.length)]).join(); - } - - String _generatePin() { - final random = Random.secure(); - return List.generate(6, (index) => random.nextInt(10).toString()).join(); - } - Future> _getAllLocalIpAddresses() async { try { final interfaces = await NetworkInterface.list(type: InternetAddressType.IPv4); @@ -109,21 +101,22 @@ class CompanionRemotePeerService with KeepaliveMixin { } } - Future<({String sessionId, String pin, List addresses})> createSession( + /// Create a host session — starts WebSocket server, returns local addresses and port. + Future<({List addresses, int port})> createSession( String deviceName, String platform, + List homeSecret, + String clientIdentifier, + List homeUserUUIDs, ) async { if (_server != null) { await disconnect(); } _role = RemoteSessionRole.host; - _sessionId = _generateSessionId(); - _pin = _generatePin(); - _myPeerId = 'host-$_sessionId'; + _myPeerId = 'host'; try { - // Try preferred port first, fallback to OS-assigned port const int preferredPort = 48632; try { @@ -141,12 +134,20 @@ class CompanionRemotePeerService with KeepaliveMixin { appLogger.d('CompanionRemote: Host server started, addresses: $addresses'); - // Listen for WebSocket connections _server!.listen((HttpRequest request) async { if (request.uri.path == '/ws') { try { final socket = await WebSocketTransformer.upgrade(request); - _handleNewWebSocketConnection(socket, deviceName, platform); + final sourceIp = request.connectionInfo?.remoteAddress.address ?? 'unknown'; + _handleNewWebSocketConnection( + socket, + deviceName, + platform, + homeSecret, + clientIdentifier, + homeUserUUIDs, + sourceIp, + ); } catch (e) { appLogger.e('CompanionRemote: Failed to upgrade WebSocket', error: e); } @@ -158,7 +159,7 @@ class CompanionRemotePeerService with KeepaliveMixin { _connectionStateController.add(RemoteSessionStatus.connected); - return (sessionId: _sessionId!, pin: _pin!, addresses: addresses); + return (addresses: addresses, port: port); } catch (e) { appLogger.e('CompanionRemote: Failed to create server', error: e); _errorController.add( @@ -172,11 +173,36 @@ class CompanionRemotePeerService with KeepaliveMixin { } } - void _handleNewWebSocketConnection(WebSocket socket, String hostDeviceName, String hostPlatform) { - appLogger.d('CompanionRemote: New WebSocket connection'); + void _handleNewWebSocketConnection( + WebSocket socket, + String hostDeviceName, + String hostPlatform, + List homeSecret, + String hostClientId, + List homeUserUUIDs, + String sourceIp, + ) { + appLogger.d('CompanionRemote: New WebSocket connection from $sourceIp'); bool isAuthenticated = false; Timer? authTimeout; + final auth = RemoteAuthService.instance; + final hostNonce = auth.generateNonce(); + + // Check rate limiting + final lockout = _authLockouts[sourceIp]; + if (lockout != null && DateTime.now().isBefore(lockout)) { + appLogger.w('CompanionRemote: Connection from $sourceIp rejected (rate limited)'); + socket.close(4005, 'Rate limited'); + return; + } + + // Send challenge: hostNonce + hostClientId + socket.add(jsonEncode({ + 'type': 'challenge', + 'nonce': base64Encode(hostNonce), + 'hostClientId': hostClientId, + })); // Authentication timeout authTimeout = Timer(const Duration(seconds: 10), () { @@ -187,77 +213,107 @@ class CompanionRemotePeerService with KeepaliveMixin { }); socket.listen( - (data) { + (data) async { try { - final json = jsonDecode(data as String) as Map; - if (!isAuthenticated) { - // First message must be authentication - if (json['type'] == 'auth') { - // Rate limiting: reject if locked out - if (_authLockoutUntil != null && DateTime.now().isBefore(_authLockoutUntil!)) { - appLogger.w('CompanionRemote: Auth attempt rejected (rate limited)'); - socket.add(jsonEncode({'type': 'authFailed', 'message': 'Too many attempts. Try again later.'})); - socket.close(4005, 'Rate limited'); - return; - } + final json = jsonDecode(data as String) as Map; - final sessionId = json['sessionId'] as String?; - final pin = json['pin'] as String?; + if (json['type'] == 'auth') { + final authTag = json['authTag'] as String?; + final clientNonceB64 = json['clientNonce'] as String?; + final userUUID = json['userUUID'] as String?; + final clientIdentifier = json['clientIdentifier'] as String?; final deviceName = json['deviceName'] as String?; final platform = json['platform'] as String?; - if (sessionId == _sessionId && pin == _pin) { - _failedAuthAttempts = 0; - isAuthenticated = true; - authTimeout?.cancel(); - - // Close existing client if present - if (_clientSocket != null) { - appLogger.d('CompanionRemote: Replacing existing client connection'); - _clientSocket!.close(4004, 'Replaced by new connection'); - } - - _clientSocket = socket; - - appLogger.d('CompanionRemote: Client authenticated: $deviceName ($platform)'); - - // Send auth success - socket.add(jsonEncode({'type': 'authSuccess'})); - - // Notify connection - final device = RemoteDevice( - id: 'remote-client', - name: deviceName ?? 'Unknown Device', - platform: platform ?? 'unknown', - ); - _deviceConnectedController.add(device); - _connectionStateController.add(RemoteSessionStatus.connected); - - // Send device info - sendDeviceInfo(hostDeviceName, hostPlatform); - - // Note: Client sends keepalive pings, host only responds with pongs - } else { - _failedAuthAttempts++; - if (_failedAuthAttempts >= _maxFailedAuthAttempts) { - _authLockoutUntil = DateTime.now().add(_authLockoutDuration); - appLogger.w( - 'CompanionRemote: Too many failed auth attempts, locked out for ${_authLockoutDuration.inSeconds}s', - ); - } - appLogger.w( - 'CompanionRemote: Invalid credentials (attempt $_failedAuthAttempts/$_maxFailedAuthAttempts)', - ); - socket.add(jsonEncode({'type': 'authFailed', 'message': 'Invalid session ID or PIN'})); - socket.close(4003, 'Invalid credentials'); + if (authTag == null || + clientNonceB64 == null || + userUUID == null || + clientIdentifier == null || + deviceName == null || + platform == null) { + socket.add(jsonEncode({'type': 'authFailed'})); + socket.close(4003, 'Authentication failed'); + return; } + + final clientNonce = base64Decode(clientNonceB64); + + // Verify userUUID is in home users list + if (!homeUserUUIDs.contains(userUUID)) { + _recordFailedAuth(sourceIp); + appLogger.w('CompanionRemote: Auth failed — unknown user'); + socket.add(jsonEncode({'type': 'authFailed'})); + socket.close(4003, 'Authentication failed'); + return; + } + + // Verify auth tag + final valid = auth.verifyAuthTag( + authTag: authTag, + homeSecret: homeSecret, + hostNonce: hostNonce, + clientNonce: clientNonce, + hostClientId: hostClientId, + userUUID: userUUID, + clientIdentifier: clientIdentifier, + deviceName: deviceName, + platform: platform, + ); + + if (!valid) { + _recordFailedAuth(sourceIp); + appLogger.w('CompanionRemote: Auth failed — invalid auth tag'); + socket.add(jsonEncode({'type': 'authFailed'})); + socket.close(4003, 'Authentication failed'); + return; + } + + // Auth success — derive per-session encryption key + _failedAuthAttempts.remove(sourceIp); + isAuthenticated = true; + authTimeout?.cancel(); + + final sessionEncKey = await auth.deriveSessionEncKey(homeSecret, hostNonce, clientNonce); + + // Close existing client if present + if (_clientSocket != null) { + appLogger.d('CompanionRemote: Replacing existing client connection'); + _clientSocket!.close(4004, 'Replaced by new connection'); + } + + _clientSocket = socket; + _sessionEncKey = sessionEncKey; + _sendCounter = 0; + _recvCounter = 0; + _isAuthenticated = true; + + appLogger.d('CompanionRemote: Client authenticated: $deviceName ($platform)'); + + // Send encrypted authSuccess + await _sendEncryptedToSocket(socket, jsonEncode({'type': 'authSuccess'})); + + // Notify connection + final device = RemoteDevice( + id: 'remote-client', + name: deviceName, + platform: platform, + ); + _deviceConnectedController.add(device); + _connectionStateController.add(RemoteSessionStatus.connected); + + // Send device info + sendDeviceInfo(hostDeviceName, hostPlatform); } else { appLogger.w('CompanionRemote: Expected auth, got ${json['type']}'); socket.close(4002, 'Authentication required'); } } else { - // Handle regular commands + // Encrypted command — data is binary + final decrypted = await _decryptIncoming(data); + if (decrypted == null) return; + + final json = jsonDecode(decrypted) as Map; final command = RemoteCommand.fromJson(json); appLogger.d('CompanionRemote: Received command: ${command.type}'); @@ -280,6 +336,8 @@ class CompanionRemotePeerService with KeepaliveMixin { appLogger.d('CompanionRemote: WebSocket connection closed'); if (isAuthenticated) { _clientSocket = null; + _sessionEncKey = null; + _isAuthenticated = false; _deviceDisconnectedController.add(null); _connectionStateController.add(RemoteSessionStatus.disconnected); stopKeepalive(); @@ -299,18 +357,35 @@ class CompanionRemotePeerService with KeepaliveMixin { ); } - Future joinSession(String sessionId, String pin, String deviceName, String platform, String hostAddress) async { + void _recordFailedAuth(String sourceIp) { + final attempts = (_failedAuthAttempts[sourceIp] ?? 0) + 1; + _failedAuthAttempts[sourceIp] = attempts; + if (attempts >= _maxFailedAuthAttempts) { + _authLockouts[sourceIp] = DateTime.now().add(_authLockoutDuration); + appLogger.w('CompanionRemote: IP $sourceIp locked out for ${_authLockoutDuration.inSeconds}s'); + } + } + + /// Join a host session as a remote client. + Future joinSession( + String deviceName, + String platform, + String hostAddress, + List homeSecret, + String hostClientId, + String userUUID, + String clientIdentifier, + ) async { if (_channel != null) { await disconnect(); } _role = RemoteSessionRole.remote; - _sessionId = sessionId.toUpperCase(); - _pin = pin; _hostAddress = hostAddress; _myPeerId = 'remote-${Random.secure().nextInt(99999)}'; final completer = Completer(); + final auth = RemoteAuthService.instance; try { final url = 'ws://$hostAddress/ws'; @@ -321,51 +396,19 @@ class CompanionRemotePeerService with KeepaliveMixin { _channel = IOWebSocketChannel.connect(Uri.parse(url)); await _channel!.ready; - // Send authentication message - final authMessage = jsonEncode({ - 'type': 'auth', - 'sessionId': _sessionId, - 'pin': _pin, - 'deviceName': deviceName, - 'platform': platform, - }); - _channel!.sink.add(authMessage); + List? hostNonce; + List? clientNonce; + String? receivedHostClientId; - // Listen for messages _channel!.stream.listen( - (data) { + (data) async { try { - final json = jsonDecode(data as String) as Map; - final messageType = json['type'] as String?; + if (_isAuthenticated) { + // Post-auth: all messages are encrypted binary + final decrypted = await _decryptIncoming(data); + if (decrypted == null) return; - if (messageType == 'authSuccess') { - appLogger.d('CompanionRemote: Authentication successful'); - - if (!completer.isCompleted) { - completer.complete(); - } - - final device = RemoteDevice(id: 'host', name: 'Desktop', platform: 'desktop'); - _deviceConnectedController.add(device); - _connectionStateController.add(RemoteSessionStatus.connected); - - // Send device info - sendDeviceInfo(deviceName, platform); - - // Start ping timer - startKeepalive(); - } else if (messageType == 'authFailed') { - final message = json['message'] as String? ?? 'Authentication failed'; - appLogger.w('CompanionRemote: $message'); - - if (!completer.isCompleted) { - completer.completeError(RemotePeerError(type: RemotePeerErrorType.authFailed, message: message)); - } - - _errorController.add(RemotePeerError(type: RemotePeerErrorType.authFailed, message: message)); - _connectionStateController.add(RemoteSessionStatus.error); - } else { - // Regular command + final json = jsonDecode(decrypted) as Map; final command = RemoteCommand.fromJson(json); appLogger.d('CompanionRemote: Received command: ${command.type}'); @@ -378,6 +421,89 @@ class CompanionRemotePeerService with KeepaliveMixin { if (command.type == RemoteCommandType.ping) { _sendPong(); } + } else if (_sessionEncKey != null) { + // Keys derived, waiting for encrypted authSuccess + final decrypted = await _decryptIncoming(data); + if (decrypted == null) return; + + final json = jsonDecode(decrypted) as Map; + if (json['type'] == 'authSuccess') { + _isAuthenticated = true; + appLogger.d('CompanionRemote: Authentication successful'); + + if (!completer.isCompleted) { + completer.complete(); + } + + final device = RemoteDevice(id: 'host', name: 'Desktop', platform: 'desktop'); + _deviceConnectedController.add(device); + _connectionStateController.add(RemoteSessionStatus.connected); + + sendDeviceInfo(deviceName, platform); + startKeepalive(); + } else if (json['type'] == 'authFailed') { + if (!completer.isCompleted) { + completer.completeError( + const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Authentication failed'), + ); + } + } + } else { + // Pre-auth: plaintext handshake + final json = jsonDecode(data as String) as Map; + final messageType = json['type'] as String?; + + if (messageType == 'challenge') { + hostNonce = base64Decode(json['nonce'] as String); + receivedHostClientId = json['hostClientId'] as String; + clientNonce = auth.generateNonce(); + + if (hostClientId.isNotEmpty && receivedHostClientId != hostClientId) { + appLogger.w('CompanionRemote: Host client ID mismatch'); + if (!completer.isCompleted) { + completer.completeError( + const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Host identity mismatch'), + ); + } + return; + } + + final authTag = auth.computeAuthTag( + homeSecret: homeSecret, + hostNonce: hostNonce!, + clientNonce: clientNonce!, + hostClientId: receivedHostClientId!, + userUUID: userUUID, + clientIdentifier: clientIdentifier, + deviceName: deviceName, + platform: platform, + ); + + _channel!.sink.add(jsonEncode({ + 'type': 'auth', + 'clientNonce': base64Encode(clientNonce!), + 'userUUID': userUUID, + 'clientIdentifier': clientIdentifier, + 'deviceName': deviceName, + 'platform': platform, + 'authTag': authTag, + })); + + _sessionEncKey = await auth.deriveSessionEncKey(homeSecret, hostNonce!, clientNonce!); + _sendCounter = 0; + _recvCounter = 0; + } else if (messageType == 'authFailed') { + appLogger.w('CompanionRemote: Authentication failed'); + if (!completer.isCompleted) { + completer.completeError( + const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Authentication failed'), + ); + } + _errorController.add( + const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Authentication failed'), + ); + _connectionStateController.add(RemoteSessionStatus.error); + } } } catch (e) { appLogger.e('CompanionRemote: Failed to parse message', error: e); @@ -387,8 +513,9 @@ class CompanionRemotePeerService with KeepaliveMixin { appLogger.d('CompanionRemote: Connection closed'); _deviceDisconnectedController.add(null); _connectionStateController.add(RemoteSessionStatus.disconnected); + _isAuthenticated = false; + _sessionEncKey = null; stopKeepalive(); - // Reconnection is handled by CompanionRemoteProvider }, onError: (error) { appLogger.e('CompanionRemote: Connection error', error: error); @@ -422,7 +549,6 @@ class CompanionRemotePeerService with KeepaliveMixin { return completer.future.timeout( const Duration(seconds: 15), onTimeout: () async { - // Clean up channel on timeout if (_channel != null) { try { await _channel!.sink.close(); @@ -435,21 +561,23 @@ class CompanionRemotePeerService with KeepaliveMixin { } /// Race WebSocket connections to multiple host addresses in parallel. - /// Returns the winning address and sets up a proper managed connection via [joinSession]. Future joinSessionRacing( - String sessionId, - String pin, String deviceName, String platform, List hostAddresses, + List homeSecret, + String hostClientId, + String userUUID, + String clientIdentifier, ) async { if (hostAddresses.length == 1) { - await joinSession(sessionId, pin, deviceName, platform, hostAddresses.first); + await joinSession(deviceName, platform, hostAddresses.first, homeSecret, hostClientId, userUUID, clientIdentifier); return hostAddresses.first; } appLogger.d('CompanionRemote: Racing connections to ${hostAddresses.length} addresses'); + // Race: try to connect to all addresses, first one to get a challenge wins final completer = Completer(); final channels = []; final subs = []; @@ -471,20 +599,12 @@ class CompanionRemotePeerService with KeepaliveMixin { final channel = IOWebSocketChannel.connect(Uri.parse(url), connectTimeout: const Duration(seconds: 5)); channels.add(channel); - // Send auth immediately - channel.sink.add(jsonEncode({ - 'type': 'auth', - 'sessionId': sessionId.toUpperCase(), - 'pin': pin, - 'deviceName': deviceName, - 'platform': platform, - })); - final sub = channel.stream.listen( (data) { try { final json = jsonDecode(data as String) as Map; - if (json['type'] == 'authSuccess' && !completer.isCompleted) { + // First address to send us a challenge wins the race + if (json['type'] == 'challenge' && !completer.isCompleted) { appLogger.d('CompanionRemote: Race winner: $address'); completer.complete(address); } @@ -507,11 +627,12 @@ class CompanionRemotePeerService with KeepaliveMixin { } try { - final winner = await completer.future.namedTimeout(const Duration(seconds: 10), operation: 'CompanionRemote race connect'); + final winner = + await completer.future.namedTimeout(const Duration(seconds: 10), operation: 'CompanionRemote race connect'); cleanup(); - // Now set up the proper managed connection on the winning address - await joinSession(sessionId, pin, deviceName, platform, winner); + // Set up the proper managed connection on the winning address + await joinSession(deviceName, platform, winner, homeSecret, hostClientId, userUUID, clientIdentifier); return winner; } on TimeoutException { cleanup(); @@ -522,6 +643,49 @@ class CompanionRemotePeerService with KeepaliveMixin { } } + // ── Encrypted send/receive ── + + // Serializes async sends to prevent counter interleaving + Future? _sendChain; + + Future> _encryptOutgoing(String plaintext) async { + final encrypted = await RemoteAuthService.instance.encrypt( + _sessionEncKey!, + utf8.encode(plaintext), + isHost: _role == RemoteSessionRole.host, + counter: _sendCounter, + ); + _sendCounter++; + return encrypted; + } + + Future _sendEncryptedToSocket(WebSocket socket, String plaintext) async { + if (_sessionEncKey == null) return; + final encrypted = await _encryptOutgoing(plaintext); + socket.add(encrypted); + } + + Future _decryptIncoming(dynamic data) async { + if (_sessionEncKey == null) return null; + try { + final auth = RemoteAuthService.instance; + final bytes = data is List ? data : utf8.encode(data as String); + final decrypted = await auth.decrypt( + bytes, + _sessionEncKey!, + fromHost: _role == RemoteSessionRole.remote, // If we're remote, incoming is from host + expectedCounter: _recvCounter, + ); + _recvCounter++; + return utf8.decode(decrypted); + } catch (e) { + appLogger.e('CompanionRemote: Decryption failed (counter=$_recvCounter)', error: e); + return null; + } + } + + // ── Commands ── + @override void sendPing() { if (isConnected) { @@ -559,28 +723,34 @@ class CompanionRemotePeerService with KeepaliveMixin { } void sendCommand(RemoteCommand command) { - try { - final json = jsonEncode(command.toJson()); - - if (_role == RemoteSessionRole.host && _clientSocket != null) { - _clientSocket!.add(json); - appLogger.d('CompanionRemote: Sent command (host): ${command.type}'); - } else if (_role == RemoteSessionRole.remote && _channel != null) { - _channel!.sink.add(json); - appLogger.d('CompanionRemote: Sent command (remote): ${command.type}'); - } else { - appLogger.w('CompanionRemote: No connection to send command'); - } - } catch (e) { - appLogger.e('CompanionRemote: Failed to send command', error: e); - _errorController.add( - RemotePeerError( - type: RemotePeerErrorType.dataChannelError, - message: 'Failed to send command: $e', - originalError: e, - ), - ); + if (_sessionEncKey == null || !_isAuthenticated) { + appLogger.w('CompanionRemote: No connection to send command'); + return; } + + // Chain sends to prevent counter interleaving from concurrent async encrypts + _sendChain = (_sendChain ?? Future.value()).then((_) async { + try { + final json = jsonEncode(command.toJson()); + final encrypted = await _encryptOutgoing(json); + + if (_role == RemoteSessionRole.host && _clientSocket != null) { + _clientSocket!.add(encrypted); + } else if (_role == RemoteSessionRole.remote && _channel != null) { + _channel!.sink.add(encrypted); + } + appLogger.d('CompanionRemote: Sent command: ${command.type}'); + } catch (e) { + appLogger.e('CompanionRemote: Failed to send command', error: e); + _errorController.add( + RemotePeerError( + type: RemotePeerErrorType.dataChannelError, + message: 'Failed to send command: $e', + originalError: e, + ), + ); + } + }); } Future disconnect() async { @@ -607,15 +777,23 @@ class CompanionRemotePeerService with KeepaliveMixin { _server = null; } - _sessionId = null; - _pin = null; _myPeerId = null; _hostAddress = null; _role = null; + _sessionEncKey = null; + _sendCounter = 0; + _recvCounter = 0; + _isAuthenticated = false; + _sendChain = null; + _failedAuthAttempts.clear(); + _authLockouts.clear(); _connectionStateController.add(RemoteSessionStatus.disconnected); } + /// Whether the HTTP server is currently running. + bool get isServerRunning => _server != null; + Future dispose() async { await disconnect(); await _commandReceivedController.close(); diff --git a/lib/services/companion_remote/lan_discovery_service.dart b/lib/services/companion_remote/lan_discovery_service.dart new file mode 100644 index 00000000..3f3b1606 --- /dev/null +++ b/lib/services/companion_remote/lan_discovery_service.dart @@ -0,0 +1,284 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import '../../utils/app_logger.dart'; +import 'remote_auth_service.dart'; + +/// A host discovered on the LAN via UDP broadcast. +class DiscoveredHost { + final String clientId; + final String name; + final String platform; + final int port; + final List ips; + DateTime lastSeen; + + DiscoveredHost({ + required this.clientId, + required this.name, + required this.platform, + required this.port, + required this.ips, + DateTime? lastSeen, + }) : lastSeen = lastSeen ?? DateTime.now(); + + List get addresses => ips.map((ip) => '$ip:$port').toList(); +} + +/// UDP-based LAN discovery for companion remote. +/// +/// Hosts broadcast authenticated beacons; clients listen and filter +/// by matching Plex home membership. +class LanDiscoveryService { + static const int discoveryPort = 48633; + static const int _broadcastIntervalSeconds = 3; + static const int _staleTimeoutSeconds = 10; + static const int _beaconVersion = 1; + + // Broadcaster state (host) + RawDatagramSocket? _broadcastSocket; + Timer? _broadcastTimer; + + // Listener state (client) + RawDatagramSocket? _listenSocket; + Timer? _staleCleanupTimer; + final Map _discoveredHosts = {}; + final _hostsController = StreamController>.broadcast(); + + /// Whether the broadcaster is currently active. + bool get isBroadcasting => _broadcastTimer != null; + + /// Whether the listener is currently active. + bool get isListening => _listenSocket != null; + + // ── Host: Broadcasting ── + + Future startBroadcasting({ + required List discoveryKey, + required String deviceName, + required String platform, + required String clientId, + required int wsPort, + required List ips, + }) async { + await stopBroadcasting(); + + try { + _broadcastSocket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0); + _broadcastSocket!.broadcastEnabled = true; + + appLogger.d('LanDiscovery: Broadcasting started on port $discoveryPort'); + + // Send immediately, then periodically + _sendBeacon(discoveryKey, deviceName, platform, clientId, wsPort, ips); + _broadcastTimer = Timer.periodic( + const Duration(seconds: _broadcastIntervalSeconds), + (_) => _sendBeacon(discoveryKey, deviceName, platform, clientId, wsPort, ips), + ); + } catch (e) { + appLogger.e('LanDiscovery: Failed to start broadcasting', error: e); + await stopBroadcasting(); + } + } + + void _sendBeacon( + List discoveryKey, + String deviceName, + String platform, + String clientId, + int wsPort, + List ips, + ) { + if (_broadcastSocket == null) return; + + try { + final auth = RemoteAuthService.instance; + final homeHash = auth.computeDiscoveryTag(discoveryKey); + + final beaconHmac = auth.computeBeaconHmac( + discoveryKey: discoveryKey, + version: _beaconVersion, + homeHash: homeHash, + name: deviceName, + platform: platform, + clientId: clientId, + port: wsPort, + ips: ips, + ); + + final packet = jsonEncode({ + 'app': 'plezy', + 'v': _beaconVersion, + 'homeHash': homeHash, + 'name': deviceName, + 'platform': platform, + 'clientId': clientId, + 'port': wsPort, + 'ips': ips, + 'hmac': beaconHmac, + }); + + final data = utf8.encode(packet); + _broadcastSocket!.send(data, InternetAddress('255.255.255.255'), discoveryPort); + } catch (e) { + appLogger.e('LanDiscovery: Failed to send beacon', error: e); + } + } + + Future stopBroadcasting() async { + _broadcastTimer?.cancel(); + _broadcastTimer = null; + _broadcastSocket?.close(); + _broadcastSocket = null; + appLogger.d('LanDiscovery: Broadcasting stopped'); + } + + // ── Client: Listening ── + + /// Start listening for host beacons. + /// Returns a stream of currently-visible hosts, updated on each beacon or stale cleanup. + Stream> startListening({ + required List discoveryKey, + }) { + _stopListeningInternal(); + _discoveredHosts.clear(); + + _bindListener(discoveryKey); + + // Periodically remove stale hosts + _staleCleanupTimer = Timer.periodic(const Duration(seconds: 2), (_) { + final now = DateTime.now(); + final staleIds = []; + for (final entry in _discoveredHosts.entries) { + if (now.difference(entry.value.lastSeen).inSeconds > _staleTimeoutSeconds) { + staleIds.add(entry.key); + } + } + if (staleIds.isNotEmpty) { + for (final id in staleIds) { + _discoveredHosts.remove(id); + } + _emitHosts(); + } + }); + + return _hostsController.stream; + } + + Future _bindListener(List discoveryKey) async { + try { + _listenSocket = await RawDatagramSocket.bind( + InternetAddress.anyIPv4, + discoveryPort, + reuseAddress: true, + reusePort: true, + ); + + appLogger.d('LanDiscovery: Listening on port $discoveryPort'); + + _listenSocket!.listen((RawSocketEvent event) { + if (event == RawSocketEvent.read) { + final datagram = _listenSocket?.receive(); + if (datagram != null) { + _handleDatagram(datagram, discoveryKey); + } + } + }); + } catch (e) { + appLogger.e('LanDiscovery: Failed to bind listener', error: e); + } + } + + void _handleDatagram(Datagram datagram, List discoveryKey) { + try { + final packet = utf8.decode(datagram.data); + final json = jsonDecode(packet) as Map; + + if (json['app'] != 'plezy') return; + + final version = json['v'] as int? ?? 0; + final homeHash = json['homeHash'] as String? ?? ''; + final name = json['name'] as String? ?? ''; + final platform = json['platform'] as String? ?? ''; + final clientId = json['clientId'] as String? ?? ''; + final port = json['port'] as int? ?? 0; + final ips = (json['ips'] as List?)?.cast() ?? []; + final hmac = json['hmac'] as String? ?? ''; + + // Verify beacon HMAC + final auth = RemoteAuthService.instance; + if (!auth.verifyBeaconHmac( + receivedHmac: hmac, + discoveryKey: discoveryKey, + version: version, + homeHash: homeHash, + name: name, + platform: platform, + clientId: clientId, + port: port, + ips: ips, + )) { + return; // Invalid HMAC — not from same home or tampered + } + + // Verify homeHash matches (check ±1 epoch window) + if (!auth.matchesDiscoveryTag(homeHash, discoveryKey)) { + return; // Different home + } + + // Valid beacon from same home + if (_discoveredHosts.containsKey(clientId)) { + final existing = _discoveredHosts[clientId]!; + existing.lastSeen = DateTime.now(); + // Only emit if fields actually changed + if (existing.name != name || existing.port != port) { + _discoveredHosts[clientId] = DiscoveredHost( + clientId: clientId, + name: name, + platform: platform, + port: port, + ips: ips, + ); + _emitHosts(); + } + } else { + _discoveredHosts[clientId] = DiscoveredHost( + clientId: clientId, + name: name, + platform: platform, + port: port, + ips: ips, + ); + appLogger.d('LanDiscovery: Discovered host: $name ($platform) at ${ips.join(", ")}:$port'); + _emitHosts(); + } + } catch (e) { + // Ignore malformed packets + } + } + + void _emitHosts() { + _hostsController.add(_discoveredHosts.values.toList()); + } + + void stopListening() { + _stopListeningInternal(); + _discoveredHosts.clear(); + _emitHosts(); + } + + void _stopListeningInternal() { + _staleCleanupTimer?.cancel(); + _staleCleanupTimer = null; + _listenSocket?.close(); + _listenSocket = null; + appLogger.d('LanDiscovery: Listening stopped'); + } + + void dispose() { + stopBroadcasting(); + stopListening(); + _hostsController.close(); + } +} diff --git a/lib/services/companion_remote/remote_auth_service.dart b/lib/services/companion_remote/remote_auth_service.dart new file mode 100644 index 00000000..cdc30be5 --- /dev/null +++ b/lib/services/companion_remote/remote_auth_service.dart @@ -0,0 +1,384 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart' as crypto; +import 'package:cryptography/cryptography.dart'; + +import '../../models/plex_home.dart'; +import '../../utils/app_logger.dart'; + +/// Cryptographic authentication service for companion remote. +/// +/// Proves same-home membership via a shared secret derived from Plex home data. +/// This authenticates **group membership**, not individual user identity — +/// any device in the same Plex home can connect. +class RemoteAuthService { + RemoteAuthService._(); + static final instance = RemoteAuthService._(); + + // Reusable crypto algorithm instances + static final _hkdf = Hkdf(hmac: Hmac(Sha256()), outputLength: 32); + static final _aesGcm = AesGcm.with256bits(); + + // Cached home secret — derived in memory, never persisted + List? _cachedHomeSecret; + int? _cachedHomeId; + String? _cachedAdminUUID; + + /// Build canonical IKM bytes from home data. + /// Format: [4-byte BE len][utf8 bytes] for each field, in fixed order. + static Uint8List _canonicalIkm(int homeId, String adminUUID) { + final homeIdBytes = utf8.encode(homeId.toString()); + final uuidBytes = utf8.encode(adminUUID.toLowerCase()); + + final buf = BytesWriter(); + buf.writeUint32BE(homeIdBytes.length); + buf.writeBytes(homeIdBytes); + buf.writeUint32BE(uuidBytes.length); + buf.writeBytes(uuidBytes); + return buf.toBytes(); + } + + /// Derive the long-term home secret via HKDF-SHA256. + /// This is derived in memory from cached Plex data — never persisted. + Future> deriveHomeSecret(int homeId, String adminUUID) async { + // Return cached if inputs unchanged + if (_cachedHomeSecret != null && _cachedHomeId == homeId && _cachedAdminUUID == adminUUID) { + return _cachedHomeSecret!; + } + + final hkdf = _hkdf; + final ikm = _canonicalIkm(homeId, adminUUID); + + final secretKey = await hkdf.deriveKey( + secretKey: SecretKey(ikm), + nonce: utf8.encode('plezy-remote-v1'), + info: utf8.encode('home-secret'), + ); + + _cachedHomeSecret = await secretKey.extractBytes(); + _cachedHomeId = homeId; + _cachedAdminUUID = adminUUID; + + appLogger.d('RemoteAuth: Derived home secret'); + return _cachedHomeSecret!; + } + + /// Derive the long-term home secret from a PlexHome object. + Future> deriveHomeSecretFromHome(PlexHome home) async { + final admin = home.adminUser; + if (admin == null) { + throw StateError('PlexHome has no admin user'); + } + return deriveHomeSecret(home.id, admin.uuid); + } + + /// Derive per-session encryption key from homeSecret + both nonces. + Future> deriveSessionEncKey( + List homeSecret, + List hostNonce, + List clientNonce, + ) async { + final hkdf = _hkdf; + + final salt = Uint8List(hostNonce.length + clientNonce.length); + salt.setAll(0, hostNonce); + salt.setAll(hostNonce.length, clientNonce); + + // First derive session secret + final sessionSecretKey = await hkdf.deriveKey( + secretKey: SecretKey(homeSecret), + nonce: salt, + info: utf8.encode('plezy-session-v1'), + ); + final sessionSecret = await sessionSecretKey.extractBytes(); + + // Then derive encryption key from session secret + final encKeyResult = await hkdf.deriveKey( + secretKey: SecretKey(sessionSecret), + nonce: const [], + info: utf8.encode('encryption'), + ); + + return encKeyResult.extractBytes(); + } + + /// Derive discovery key from homeSecret. + Future> deriveDiscoveryKey(List homeSecret) async { + final hkdf = _hkdf; + final key = await hkdf.deriveKey( + secretKey: SecretKey(homeSecret), + nonce: const [], + info: utf8.encode('discovery'), + ); + return key.extractBytes(); + } + + /// Compute rotating discovery tag for beacon filtering. + /// Uses 5-minute epoch windows to reduce cross-network tracking. + String computeDiscoveryTag(List discoveryKey, {DateTime? now}) { + final epochSeconds = ((now ?? DateTime.now()).millisecondsSinceEpoch ~/ 1000); + final window = epochSeconds ~/ 300; // 5-minute windows + final msg = utf8.encode('identify|$window'); + final hmac = crypto.Hmac(crypto.sha256, discoveryKey); + return hmac.convert(msg).toString(); + } + + /// Check if a received homeHash matches any of the ±1 epoch windows. + bool matchesDiscoveryTag(String receivedHash, List discoveryKey, {DateTime? now}) { + final epochSeconds = ((now ?? DateTime.now()).millisecondsSinceEpoch ~/ 1000); + final currentWindow = epochSeconds ~/ 300; + + for (final window in [currentWindow - 1, currentWindow, currentWindow + 1]) { + final msg = utf8.encode('identify|$window'); + final hmac = crypto.Hmac(crypto.sha256, discoveryKey); + final tag = hmac.convert(msg).toString(); + if (_constantTimeEquals(tag, receivedHash)) return true; + } + return false; + } + + /// Generate 32 cryptographically random bytes. + List generateNonce() { + final random = Random.secure(); + return List.generate(32, (_) => random.nextInt(256)); + } + + /// Compute auth tag (HMAC-SHA256) over the full handshake transcript. + /// Domain-separated with "plezy-auth-v1|" prefix. + String computeAuthTag({ + required List homeSecret, + required List hostNonce, + required List clientNonce, + required String hostClientId, + required String userUUID, + required String clientIdentifier, + required String deviceName, + required String platform, + }) { + final msg = _buildAuthTranscript( + hostNonce: hostNonce, + clientNonce: clientNonce, + hostClientId: hostClientId, + userUUID: userUUID, + clientIdentifier: clientIdentifier, + deviceName: deviceName, + platform: platform, + ); + final hmac = crypto.Hmac(crypto.sha256, homeSecret); + return hmac.convert(msg).toString(); + } + + /// Verify auth tag with constant-time comparison. + bool verifyAuthTag({ + required String authTag, + required List homeSecret, + required List hostNonce, + required List clientNonce, + required String hostClientId, + required String userUUID, + required String clientIdentifier, + required String deviceName, + required String platform, + }) { + final expected = computeAuthTag( + homeSecret: homeSecret, + hostNonce: hostNonce, + clientNonce: clientNonce, + hostClientId: hostClientId, + userUUID: userUUID, + clientIdentifier: clientIdentifier, + deviceName: deviceName, + platform: platform, + ); + return _constantTimeEquals(expected, authTag); + } + + /// Build the auth transcript message bytes. + List _buildAuthTranscript({ + required List hostNonce, + required List clientNonce, + required String hostClientId, + required String userUUID, + required String clientIdentifier, + required String deviceName, + required String platform, + }) { + final buf = BytesWriter(); + buf.writeBytes(utf8.encode('plezy-auth-v1|')); + // Fixed-length nonces (32 bytes each) — no prefix needed + buf.writeBytes(hostNonce); + buf.writeBytes(clientNonce); + // Variable-length strings — length-prefixed to prevent field boundary shifting + _writeLengthPrefixed(buf, utf8.encode(hostClientId)); + _writeLengthPrefixed(buf, utf8.encode(userUUID)); + _writeLengthPrefixed(buf, utf8.encode(clientIdentifier)); + _writeLengthPrefixed(buf, utf8.encode(deviceName)); + _writeLengthPrefixed(buf, utf8.encode(platform)); + return buf.toBytes(); + } + + /// Compute HMAC for discovery beacon over canonical binary layout. + /// Fields in fixed order: v, homeHash, name, platform, clientId, port, ips (sorted, comma-joined). + String computeBeaconHmac({ + required List discoveryKey, + required int version, + required String homeHash, + required String name, + required String platform, + required String clientId, + required int port, + required List ips, + }) { + final buf = BytesWriter(); + _writeLengthPrefixed(buf, utf8.encode(version.toString())); + _writeLengthPrefixed(buf, utf8.encode(homeHash)); + _writeLengthPrefixed(buf, utf8.encode(name)); + _writeLengthPrefixed(buf, utf8.encode(platform)); + _writeLengthPrefixed(buf, utf8.encode(clientId)); + _writeLengthPrefixed(buf, utf8.encode(port.toString())); + final sortedIps = List.from(ips)..sort(); + _writeLengthPrefixed(buf, utf8.encode(sortedIps.join(','))); + + final hmac = crypto.Hmac(crypto.sha256, discoveryKey); + return hmac.convert(buf.toBytes()).toString(); + } + + /// Verify a beacon's HMAC. + bool verifyBeaconHmac({ + required String receivedHmac, + required List discoveryKey, + required int version, + required String homeHash, + required String name, + required String platform, + required String clientId, + required int port, + required List ips, + }) { + final expected = computeBeaconHmac( + discoveryKey: discoveryKey, + version: version, + homeHash: homeHash, + name: name, + platform: platform, + clientId: clientId, + port: port, + ips: ips, + ); + return _constantTimeEquals(expected, receivedHmac); + } + + // ── AES-256-GCM Encryption ── + + static const int _directionHost = 0x01; + static const int _directionClient = 0x02; + + /// Build the 12-byte GCM nonce from direction + counter. + static Uint8List buildNonce(int direction, int counter) { + final nonce = Uint8List(12); + // 4-byte direction (big-endian) + nonce[0] = (direction >> 24) & 0xFF; + nonce[1] = (direction >> 16) & 0xFF; + nonce[2] = (direction >> 8) & 0xFF; + nonce[3] = direction & 0xFF; + // 8-byte counter (big-endian) + nonce[4] = (counter >> 56) & 0xFF; + nonce[5] = (counter >> 48) & 0xFF; + nonce[6] = (counter >> 40) & 0xFF; + nonce[7] = (counter >> 32) & 0xFF; + nonce[8] = (counter >> 24) & 0xFF; + nonce[9] = (counter >> 16) & 0xFF; + nonce[10] = (counter >> 8) & 0xFF; + nonce[11] = counter & 0xFF; + return nonce; + } + + /// Encrypt plaintext with AES-256-GCM. + /// Returns ciphertext + auth tag (nonce is implicit from counters). + Future> encrypt( + List sessionEncKey, + List plaintext, { + required bool isHost, + required int counter, + }) async { + final algo = _aesGcm; + final nonce = buildNonce(isHost ? _directionHost : _directionClient, counter); + + final secretBox = await algo.encrypt( + plaintext, + secretKey: SecretKey(sessionEncKey), + nonce: nonce, + ); + + // Return ciphertext + mac (nonce is implicit) + return [...secretBox.cipherText, ...secretBox.mac.bytes]; + } + + /// Decrypt ciphertext + auth tag with AES-256-GCM. + /// Receiver reconstructs the nonce from its own expected counter. + Future> decrypt( + List data, + List sessionEncKey, { + required bool fromHost, + required int expectedCounter, + }) async { + final algo = _aesGcm; + final nonce = buildNonce(fromHost ? _directionHost : _directionClient, expectedCounter); + + if (data.length < 16) { + throw ArgumentError('Encrypted data too short'); + } + + final cipherText = data.sublist(0, data.length - 16); + final mac = Mac(data.sublist(data.length - 16)); + + final secretBox = SecretBox(cipherText, nonce: nonce, mac: mac); + return algo.decrypt(secretBox, secretKey: SecretKey(sessionEncKey)); + } + + // ── Utility ── + + static void _writeLengthPrefixed(BytesWriter buf, List bytes) { + buf.writeUint32BE(bytes.length); + buf.writeBytes(bytes); + } + + /// Constant-time string comparison to prevent timing attacks. + static bool _constantTimeEquals(String a, String b) { + if (a.length != b.length) return false; + var result = 0; + for (var i = 0; i < a.length; i++) { + result |= a.codeUnitAt(i) ^ b.codeUnitAt(i); + } + return result == 0; + } + + /// Clear cached home secret (e.g. on logout). + void clearCache() { + _cachedHomeSecret = null; + _cachedHomeId = null; + _cachedAdminUUID = null; + } + + // Static direction constants for external use + static int get directionHost => _directionHost; + static int get directionClient => _directionClient; +} + +/// Helper for building byte arrays. +class BytesWriter { + final _bytes = []; + + void writeBytes(List data) => _bytes.addAll(data); + + void writeUint32BE(int value) { + _bytes.add((value >> 24) & 0xFF); + _bytes.add((value >> 16) & 0xFF); + _bytes.add((value >> 8) & 0xFF); + _bytes.add(value & 0xFF); + } + + Uint8List toBytes() => Uint8List.fromList(_bytes); +} diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index b1f4d35e..e6516539 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -92,6 +92,7 @@ class SettingsService extends BaseSharedPreferencesService { static const String _keyMpvConfigPresets = 'mpv_config_presets'; static const String _keyMaxVolume = 'max_volume'; static const String _keyEnableDiscordRPC = 'enable_discord_rpc'; + static const String _keyEnableCompanionRemoteServer = 'enable_companion_remote_server'; static const String _keyAutoPip = 'auto_pip'; static const String _keyMatchContentFrameRate = 'match_content_frame_rate'; static const String _keyTunneledPlayback = 'tunneled_playback'; @@ -1104,6 +1105,17 @@ class SettingsService extends BaseSharedPreferencesService { return prefs.getBool(_keyEnableDiscordRPC) ?? false; // Default disabled } + // Companion Remote Server + Future setEnableCompanionRemoteServer(bool enabled) async { + await prefs.setBool(_keyEnableCompanionRemoteServer, enabled); + } + + bool getEnableCompanionRemoteServer() { + // Default enabled on desktop/TV, disabled on mobile + return prefs.getBool(_keyEnableCompanionRemoteServer) ?? + (Platform.isMacOS || Platform.isWindows || Platform.isLinux); + } + // Auto Picture-in-Picture (Android & iOS) Future setAutoPip(bool enabled) async { await prefs.setBool(_keyAutoPip, enabled); diff --git a/lib/widgets/companion_remote/remote_session_dialog.dart b/lib/widgets/companion_remote/remote_session_dialog.dart index ace77e9d..fb987088 100644 --- a/lib/widgets/companion_remote/remote_session_dialog.dart +++ b/lib/widgets/companion_remote/remote_session_dialog.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import 'package:qr_flutter/qr_flutter.dart'; import '../../i18n/strings.g.dart'; import '../../providers/companion_remote_provider.dart'; +import '../../providers/user_profile_provider.dart'; import '../../focus/focusable_button.dart'; import '../../utils/app_logger.dart'; @@ -24,58 +23,55 @@ class RemoteSessionDialog extends StatefulWidget { } class _RemoteSessionDialogState extends State { - bool _isCreatingSession = false; + bool _isStarting = false; String? _errorMessage; - List? _hostAddresses; // Each format: "ip:port" @override void initState() { super.initState(); - // Defer to avoid notifyListeners() during build phase - WidgetsBinding.instance.addPostFrameCallback((_) => _createSession()); + WidgetsBinding.instance.addPostFrameCallback((_) => _ensureServerRunning()); } - Future _createSession() async { + Future _ensureServerRunning() async { + final provider = context.read(); + if (provider.isHostServerRunning) return; + setState(() { - _isCreatingSession = true; + _isStarting = true; _errorMessage = null; - _hostAddresses = null; }); try { - final provider = context.read(); - final result = await provider.createSession(); + final home = context.read().home; + await provider.ensureCryptoReady(home); + if (!mounted) return; + await provider.startHostServer(); - if (!mounted) return; - setState(() { - _isCreatingSession = false; - _hostAddresses = result.addresses; - }); + if (mounted) setState(() => _isStarting = false); } catch (e) { - appLogger.e('Failed to create companion remote session', error: e); + appLogger.e('Failed to start companion remote server', error: e); if (!mounted) return; setState(() { - _isCreatingSession = false; + _isStarting = false; _errorMessage = e.toString(); }); } } - void _copyToClipboard(String text, String label) { - Clipboard.setData(ClipboardData(text: text)); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(t.companionRemote.session.copiedToClipboard(label: label)), - duration: const Duration(seconds: 2), - ), - ); + Future _toggleServer() async { + final provider = context.read(); + if (provider.isHostServerRunning) { + await provider.stopHostServer(); + } else { + await _ensureServerRunning(); + } } @override Widget build(BuildContext context) { return Consumer( builder: (context, provider, child) { - if (_isCreatingSession) { + if (_isStarting) { return Dialog( child: Padding( padding: const EdgeInsets.all(32.0), @@ -84,7 +80,7 @@ class _RemoteSessionDialogState extends State { children: [ const CircularProgressIndicator(), const SizedBox(height: 16), - Text(t.companionRemote.session.creatingSession, style: Theme.of(context).textTheme.titleMedium), + Text(t.companionRemote.session.startingServer, style: Theme.of(context).textTheme.titleMedium), ], ), ), @@ -105,27 +101,11 @@ class _RemoteSessionDialogState extends State { ), actions: [ TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(t.common.close)), - TextButton(onPressed: _createSession, child: Text(t.common.retry)), + TextButton(onPressed: _ensureServerRunning, child: Text(t.common.retry)), ], ); } - final session = provider.session; - if (session == null || _hostAddresses == null || _hostAddresses!.isEmpty) { - return AlertDialog( - title: Text(t.common.error), - content: Text(t.companionRemote.session.noSession), - actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(t.common.close))], - ); - } - - // Extract port from first address (all share the same port) - final port = _hostAddresses!.first.split(':').last; - final ips = _hostAddresses!.map((a) => a.split(':').first).join(','); - - // URL-wrapped QR format: comma-separated IPs for multi-NIC support - final qrData = 'https://plezy.app/scan#$ips|$port|${session.sessionId}|${session.pin}'; - return Dialog( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), @@ -145,16 +125,7 @@ class _RemoteSessionDialogState extends State { children: [ Text(t.companionRemote.title, style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 4), - Text( - session.connectedDevice != null - ? t.companionRemote.connectedTo(name: session.connectedDevice!.name) - : t.companionRemote.session.waitingForConnection, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: session.connectedDevice != null - ? Colors.green - : Theme.of(context).textTheme.bodySmall?.color, - ), - ), + _buildStatusLine(context, provider), ], ), ), @@ -162,113 +133,27 @@ class _RemoteSessionDialogState extends State { ], ), const SizedBox(height: 24), - if (session.connectedDevice == null) ...[ - Text( - t.companionRemote.session.scanQrCode, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), + + // Server status card + _buildServerStatus(context, provider), + + // Connected device info + if (provider.connectedDevice != null) ...[ const SizedBox(height: 16), - Center( - child: Container( - padding: const EdgeInsets.all(16), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(12)), - ), - child: QrImageView( - data: qrData, - version: QrVersions.auto, - size: 200, - backgroundColor: Colors.white, - ), - ), - ), - const SizedBox(height: 24), - const Divider(), - const SizedBox(height: 16), - Text( - t.companionRemote.session.orEnterManually, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - ..._hostAddresses!.map( - (addr) => Padding( - padding: const EdgeInsets.only(bottom: 12), - child: _buildCodeCard( - context, - t.companionRemote.session.hostAddress, - addr, - onCopy: () => _copyToClipboard(addr, t.companionRemote.session.hostAddress), - ), - ), - ), - _buildCodeCard( - context, - t.companionRemote.session.sessionId, - session.sessionId, - onCopy: () => _copyToClipboard(session.sessionId, t.companionRemote.session.sessionId), - ), - const SizedBox(height: 12), - _buildCodeCard( - context, - t.companionRemote.session.pin, - session.pin, - onCopy: () => _copyToClipboard(session.pin, t.companionRemote.session.pin), - ), - ] else ...[ - Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - const Icon(Icons.check_circle, color: Colors.green, size: 48), - const SizedBox(height: 8), - Text(t.companionRemote.session.connected, style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 8), - Text(session.connectedDevice!.name, style: Theme.of(context).textTheme.bodyLarge), - Text(session.connectedDevice!.platform, style: Theme.of(context).textTheme.bodySmall), - ], - ), - ), - ), - const SizedBox(height: 16), - Text( - t.companionRemote.session.usePhoneToControl, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), + _buildConnectedDevice(context, provider), ], + const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - if (session.connectedDevice != null) - TextButton.icon( - onPressed: () async { - await provider.leaveSession(); - await _createSession(); - }, - icon: const Icon(Icons.refresh), - label: Text(t.companionRemote.session.newSession), - ), - const SizedBox(width: 8), - FocusableButton( - onPressed: () async { - await provider.leaveSession(); - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - child: TextButton( - onPressed: () async { - await provider.leaveSession(); - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - child: Text(t.common.disconnect), + TextButton.icon( + onPressed: _toggleServer, + icon: Icon(provider.isHostServerRunning ? Icons.stop : Icons.play_arrow), + label: Text( + provider.isHostServerRunning + ? t.companionRemote.session.stopServer + : t.companionRemote.session.startServer, ), ), const SizedBox(width: 8), @@ -290,29 +175,88 @@ class _RemoteSessionDialogState extends State { ); } - Widget _buildCodeCard(BuildContext context, String label, String code, {VoidCallback? onCopy}) { + Widget _buildStatusLine(BuildContext context, CompanionRemoteProvider provider) { + if (provider.connectedDevice != null) { + return Text( + t.companionRemote.connectedTo(name: provider.connectedDevice!.name), + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.green), + ); + } + if (provider.isHostServerRunning) { + return Text( + t.companionRemote.session.serverRunning, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).textTheme.bodySmall?.color, + ), + ); + } + return Text( + t.companionRemote.session.serverStopped, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey), + ); + } + + Widget _buildServerStatus(BuildContext context, CompanionRemoteProvider provider) { + final isRunning = provider.isHostServerRunning; + return Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: isRunning ? Colors.green : Colors.grey, + ), + ), + const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: Theme.of(context).textTheme.bodySmall), + Text( + isRunning + ? t.companionRemote.session.serverRunning + : t.companionRemote.session.serverStopped, + style: Theme.of(context).textTheme.titleMedium, + ), const SizedBox(height: 4), Text( - code, - style: Theme.of(context).textTheme.titleLarge?.copyWith(fontFamily: 'monospace', letterSpacing: 2), + isRunning + ? t.companionRemote.session.serverRunningDescription + : t.companionRemote.session.serverStoppedDescription, + style: Theme.of(context).textTheme.bodySmall, ), ], ), ), - IconButton( - icon: const Icon(Icons.copy), - onPressed: onCopy, - tooltip: t.companionRemote.session.copyToClipboard, + ], + ), + ), + ); + } + + Widget _buildConnectedDevice(BuildContext context, CompanionRemoteProvider provider) { + final device = provider.connectedDevice!; + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Icon(Icons.check_circle, color: Colors.green, size: 48), + const SizedBox(height: 8), + Text(t.companionRemote.session.connected, style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + Text(device.name, style: Theme.of(context).textTheme.bodyLarge), + Text(device.platform, style: Theme.of(context).textTheme.bodySmall), + const SizedBox(height: 12), + Text( + t.companionRemote.session.usePhoneToControl, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, ), ], ), diff --git a/pubspec.lock b/pubspec.lock index c3df64ad..717ba842 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -286,7 +286,7 @@ packages: source: hosted version: "3.0.7" cryptography: - dependency: transitive + dependency: "direct main" description: name: cryptography sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0" @@ -698,14 +698,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - mobile_scanner: - dependency: "direct main" - description: - name: mobile_scanner - sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce - url: "https://pub.dev" - source: hosted - version: "7.2.0" native_toolchain_c: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 92e134ac..5e2622a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ dependencies: drift: ^2.31.0 sqlite3_flutter_libs: ^0.5.42 crypto: ^3.0.7 + cryptography: ^2.9.0 file_picker: ^10.3.10 saf_util: ^2.0.0 saf_stream: ^2.0.0 @@ -51,7 +52,6 @@ dependencies: in_app_review: ^2.0.11 dart_discord_presence: ^1.2.0 flutter_svg: ^2.2.3 - mobile_scanner: ^7.2.0 background_downloader: ^9.5.2 sentry_flutter: git: