Compare commits

..

3 Commits

Author SHA1 Message Date
Gauvain
03ae18e3c0 fix(tv): only defer option-modal selection when it navigates
The close-first + runAfterInteractions change (needed so the in-player audio
switch's replacePlayer isn't swallowed by the modal route) also runs for every
other tv-option-modal caller — detail-page audio, library filters, settings —
whose onSelect only updates state. Deferring those until after dismissal
re-renders the page after focus returns and yanks TV focus, leaving navigation
stuck. Gate the deferral behind deferApplyUntilDismissed (set only by the
in-player audio caller); everyone else applies before closing, as before.
2026-07-07 00:36:31 +02:00
Gauvain
271ed84811 fix(tv): destroy mpv instance before re-negotiating stream on audio switch 2026-07-06 22:21:28 +02:00
Gauvain
ab90a1a52e fix(tv): re-negotiate the stream when changing audio track while transcoding 2026-07-06 21:05:24 +02:00
7 changed files with 353 additions and 296 deletions

View File

@@ -893,6 +893,27 @@ export default function DirectPlayerPage() {
// Check if we're transcoding // Check if we're transcoding
const isTranscoding = Boolean(stream?.mediaSource?.TranscodingUrl); const isTranscoding = Boolean(stream?.mediaSource?.TranscodingUrl);
// A transcoded stream only carries the audio track the server encoded
// into it — switching requires re-negotiating the stream with the new
// index (like the mobile menu's replacePlayer), not an mpv aid change.
if (isTranscoding) {
const queryParams = new URLSearchParams({
itemId: item?.Id ?? "",
audioIndex: String(index),
subtitleIndex: String(currentSubtitleIndex),
mediaSourceId: stream?.mediaSource?.Id ?? "",
bitrateValue: bitrateValue?.toString() ?? "",
playbackPosition: msToTicks(progress.get()).toString(),
}).toString();
// Destroy the current mpv instance BEFORE navigating, same rationale as
// goToNextItem/goToPreviousItem: Expo Router briefly holds two players
// during the transition, and two simultaneous decoders OOM-kill low-RAM
// devices. Resume is preserved via the playbackPosition param.
videoRef.current?.destroy().catch(() => {});
router.replace(`player/direct-player?${queryParams}` as any);
return;
}
// Convert Jellyfin index to MPV track ID // Convert Jellyfin index to MPV track ID
const mpvTrackId = getMpvAudioId( const mpvTrackId = getMpvAudioId(
stream?.mediaSource, stream?.mediaSource,
@@ -904,7 +925,14 @@ export default function DirectPlayerPage() {
await videoRef.current?.setAudioTrack?.(mpvTrackId); await videoRef.current?.setAudioTrack?.(mpvTrackId);
} }
}, },
[stream?.mediaSource], [
stream?.mediaSource,
item?.Id,
currentSubtitleIndex,
bitrateValue,
router,
progress,
],
); );
// TV subtitle track change handler // TV subtitle track change handler

View File

@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { import {
Animated, Animated,
Easing, Easing,
InteractionManager,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
TVFocusGuideView, TVFocusGuideView,
@@ -75,6 +76,20 @@ export default function TVOptionModal() {
}, [isReady]); }, [isReady]);
const handleSelect = (value: any) => { const handleSelect = (value: any) => {
if (modalState?.deferApplyUntilDismissed) {
// onSelect navigates (the transcode audio switch replacing the player);
// a router.replace fired while this modal is the active route would be
// swallowed. Close FIRST, apply after dismissal.
const onSelect = modalState.onSelect;
store.set(tvOptionModalAtom, null);
router.back();
InteractionManager.runAfterInteractions(() => onSelect?.(value));
return;
}
// State-only callers (detail page, library filters, settings): run before
// closing so the re-render happens while the modal is up. Deferring it until
// after dismissal re-renders the page after focus returns and yanks TV
// focus, leaving navigation stuck.
modalState?.onSelect(value); modalState?.onSelect(value);
store.set(tvOptionModalAtom, null); store.set(tvOptionModalAtom, null);
router.back(); router.back();

View File

@@ -564,6 +564,9 @@ export const Controls: FC<Props> = ({
title: t("item_card.audio"), title: t("item_card.audio"),
options: audioOptions, options: audioOptions,
onSelect: handleAudioChange, onSelect: handleAudioChange,
// In-player audio selection navigates (replacePlayer while transcoding);
// apply it after the modal is dismissed so it isn't swallowed.
deferApplyUntilDismissed: true,
}); });
controlsInteractionRef.current(); controlsInteractionRef.current();
}, [showOptions, t, audioOptions, handleAudioChange]); }, [showOptions, t, audioOptions, handleAudioChange]);

View File

@@ -12,6 +12,7 @@ interface ShowOptionsParams<T> {
onSelect: (value: T) => void; onSelect: (value: T) => void;
cardWidth?: number; cardWidth?: number;
cardHeight?: number; cardHeight?: number;
deferApplyUntilDismissed?: boolean;
} }
export const useTVOptionModal = () => { export const useTVOptionModal = () => {
@@ -26,6 +27,7 @@ export const useTVOptionModal = () => {
onSelect: params.onSelect, onSelect: params.onSelect,
cardWidth: params.cardWidth, cardWidth: params.cardWidth,
cardHeight: params.cardHeight, cardHeight: params.cardHeight,
deferApplyUntilDismissed: params.deferApplyUntilDismissed,
}); });
router.push("/(auth)/tv-option-modal"); router.push("/(auth)/tv-option-modal");
}, },

View File

@@ -4,8 +4,8 @@
"error_title": "Erreur", "error_title": "Erreur",
"login_title": "Se connecter", "login_title": "Se connecter",
"login_to_title": "Se connecter à", "login_to_title": "Se connecter à",
"select_user": "Sélectionnez un utilisateur pour vous connecter", "select_user": "Select a user to log in",
"add_user_to_login": "Ajouter un utilisateur pour se connecter", "add_user_to_login": "Add a user to log in",
"add_user": "Add User", "add_user": "Add User",
"username_placeholder": "Nom d'utilisateur", "username_placeholder": "Nom d'utilisateur",
"password_placeholder": "Mot de passe", "password_placeholder": "Mot de passe",
@@ -47,9 +47,9 @@
"add_account": "Ajouter un compte", "add_account": "Ajouter un compte",
"remove_account_description": "Cela supprimera les identifiants enregistrés pour {{username}}.", "remove_account_description": "Cela supprimera les identifiants enregistrés pour {{username}}.",
"remove_server": "Remove Server", "remove_server": "Remove Server",
"remove_server_description": "Ceci supprimera {{server}} et tous les comptes enregistrés de votre liste.", "remove_server_description": "This will remove {{server}} and all saved accounts from your list.",
"select_your_server": "Select Your Server", "select_your_server": "Select Your Server",
"add_server_to_get_started": "Ajouter un serveur pour commencer", "add_server_to_get_started": "Add a server to get started",
"add_server": "Add Server", "add_server": "Add Server",
"change_server": "Change Server" "change_server": "Change Server"
}, },
@@ -95,7 +95,7 @@
"oops": "Oups!", "oops": "Oups!",
"error_message": "Quelque chose s'est mal passé.\nVeuillez vous reconnecter à nouveau.", "error_message": "Quelque chose s'est mal passé.\nVeuillez vous reconnecter à nouveau.",
"continue_watching": "Continuer à regarder", "continue_watching": "Continuer à regarder",
"continue": "Continuer", "continue": "Continue",
"next_up": "À suivre", "next_up": "À suivre",
"continue_and_next_up": "Continuer de regarder et à suivre", "continue_and_next_up": "Continuer de regarder et à suivre",
"recently_added_in": "Ajoutés récemment dans {{libraryName}}", "recently_added_in": "Ajoutés récemment dans {{libraryName}}",
@@ -121,9 +121,9 @@
"log_out_button": "Déconnexion", "log_out_button": "Déconnexion",
"switch_user": { "switch_user": {
"title": "Switch User", "title": "Switch User",
"account": "Compte", "account": "Account",
"switch_user": "Switch User on This Server", "switch_user": "Switch User on This Server",
"current": "actuel" "current": "current"
}, },
"categories": { "categories": {
"title": "Catégories" "title": "Catégories"
@@ -143,8 +143,8 @@
"show_series_poster_on_episode": "Show Series Poster on Episodes", "show_series_poster_on_episode": "Show Series Poster on Episodes",
"theme_music": "Theme Music", "theme_music": "Theme Music",
"display_size": "Display Size", "display_size": "Display Size",
"display_size_small": "Petite", "display_size_small": "Small",
"display_size_default": "Par défaut", "display_size_default": "Default",
"display_size_large": "Large", "display_size_large": "Large",
"display_size_extra_large": "Extra Large" "display_size_extra_large": "Extra Large"
}, },
@@ -203,8 +203,8 @@
"title": "Buffer Settings", "title": "Buffer Settings",
"cache_mode": "Cache Mode", "cache_mode": "Cache Mode",
"cache_auto": "Auto", "cache_auto": "Auto",
"cache_yes": "Activé", "cache_yes": "Enabled",
"cache_no": "Désactivé", "cache_no": "Disabled",
"buffer_duration": "Buffer Duration", "buffer_duration": "Buffer Duration",
"max_cache_size": "Max Cache Size", "max_cache_size": "Max Cache Size",
"max_backward_cache": "Max Backward Cache" "max_backward_cache": "Max Backward Cache"
@@ -212,7 +212,7 @@
"vo_driver": { "vo_driver": {
"title": "Video Output", "title": "Video Output",
"vo_mode": "VO Driver", "vo_mode": "VO Driver",
"gpu_next": "gpu-next (Recommandé)", "gpu_next": "gpu-next (Recommended)",
"gpu": "gpu" "gpu": "gpu"
}, },
"gesture_controls": { "gesture_controls": {
@@ -262,20 +262,20 @@
"OnlyForced": "Forcés seulement" "OnlyForced": "Forcés seulement"
}, },
"opensubtitles_title": "OpenSubtitles", "opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Entrez votre clé API OpenSubtitles pour activer la recherche de sous-titres côté client comme solution de secours lorsque votre serveur Jellyfin n'a pas de fournisseur de sous-titres configuré.", "opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key", "opensubtitles_api_key": "API Key",
"opensubtitles_api_key_placeholder": "Entrez la clé API...", "opensubtitles_api_key_placeholder": "Enter API key...",
"opensubtitles_get_key": "Obtenez votre clé API gratuite sur opensubtitles.com/fr/consumers", "opensubtitles_get_key": "Get your free API key at opensubtitles.com/en/consumers",
"mpv_subtitle_scale": "Subtitle Scale", "mpv_subtitle_scale": "Subtitle Scale",
"mpv_subtitle_margin_y": "Vertical Margin", "mpv_subtitle_margin_y": "Vertical Margin",
"mpv_subtitle_align_x": "Horizontal Align", "mpv_subtitle_align_x": "Horizontal Align",
"mpv_subtitle_align_y": "Vertical Align", "mpv_subtitle_align_y": "Vertical Align",
"align": { "align": {
"left": "Gauche", "left": "Left",
"center": "Centre", "center": "Center",
"right": "Droite", "right": "Right",
"top": "Haut", "top": "Top",
"bottom": "Bas" "bottom": "Bottom"
} }
}, },
"other": { "other": {
@@ -394,8 +394,8 @@
"downloaded_songs_deleted": "Chansons téléchargées supprimées", "downloaded_songs_deleted": "Chansons téléchargées supprimées",
"clear_all_cache": "Clear All Cache", "clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?", "clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Êtes-vous sûr de vouloir effacer toutes les données en cache ? Cela supprimera toutes les images, fichiers musicaux, sous-titres et caches de requêtes mis en cache. Vos paramètres et votre session de connexion seront conservés.", "clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_error_desc": "Une erreur est survenue lors de l'effacement du cache." "clear_all_cache_error_desc": "An error occurred while clearing the cache."
}, },
"intro": { "intro": {
"title": "Introduction", "title": "Introduction",
@@ -419,17 +419,17 @@
"error_deleting_files": "Erreur lors de la suppression des fichiers" "error_deleting_files": "Erreur lors de la suppression des fichiers"
}, },
"security": { "security": {
"title": "Sécurité", "title": "Security",
"inactivity_timeout": { "inactivity_timeout": {
"title": "Inactivity Timeout", "title": "Inactivity Timeout",
"disabled": "Désactivé", "disabled": "Disabled",
"1_minute": "1 minute", "1_minute": "1 minute",
"5_minutes": "5 minutes", "5_minutes": "5 minutes",
"15_minutes": "15 minutes", "15_minutes": "15 minutes",
"30_minutes": "30 minutes", "30_minutes": "30 minutes",
"1_hour": "1 heure", "1_hour": "1 hour",
"4_hours": "4 heures", "4_hours": "4 hours",
"24_hours": "24 heures" "24_hours": "24 hours"
} }
} }
}, },
@@ -501,11 +501,11 @@
"back": "Précédent", "back": "Précédent",
"continue": "Continuer", "continue": "Continuer",
"verifying": "Vérification...", "verifying": "Vérification...",
"login": "Connexion", "login": "Login",
"episodes": "Épisodes", "episodes": "Episodes",
"movies": "Films", "movies": "Movies",
"loading": "Chargement…", "loading": "Loading…",
"seeAll": "Tout afficher" "seeAll": "See all"
}, },
"search": { "search": {
"search": "Rechercher...", "search": "Rechercher...",
@@ -575,10 +575,10 @@
"filter_by": "Filtrer par", "filter_by": "Filtrer par",
"sort_order": "Ordre de tri", "sort_order": "Ordre de tri",
"tags": "Tags", "tags": "Tags",
"all": "Tout", "all": "All",
"reset": "Réinitialiser", "reset": "Reset",
"asc": "Croissant", "asc": "Ascending",
"desc": "Décroissant" "desc": "Descending"
} }
}, },
"favorites": { "favorites": {
@@ -595,7 +595,7 @@
"no_links": "Aucuns liens" "no_links": "Aucuns liens"
}, },
"player": { "player": {
"live": "EN DIRECT", "live": "LIVE",
"mpv_player_title": "MPV Player", "mpv_player_title": "MPV Player",
"error": "Erreur", "error": "Erreur",
"failed_to_get_stream_url": "Échec de l'obtention de l'URL du flux", "failed_to_get_stream_url": "Échec de l'obtention de l'URL du flux",
@@ -611,35 +611,35 @@
"downloaded_file_yes": "Oui", "downloaded_file_yes": "Oui",
"downloaded_file_no": "Non", "downloaded_file_no": "Non",
"downloaded_file_cancel": "Annuler", "downloaded_file_cancel": "Annuler",
"swipe_down_settings": "Balayez vers le bas pour les paramètres", "swipe_down_settings": "Swipe down for settings",
"ends_at": "Se termine à {{time}}", "ends_at": "Ends at {{time}}",
"search_subtitles": "Search Subtitles", "search_subtitles": "Search Subtitles",
"subtitle_tracks": "Pistes", "subtitle_tracks": "Tracks",
"subtitle_search": "Search & Download", "subtitle_search": "Search & Download",
"download": "Télécharger", "download": "Download",
"subtitle_download_hint": "Les sous-titres téléchargés seront enregistrés dans votre bibliothèque", "subtitle_download_hint": "Downloaded subtitles will be saved to your library",
"using_jellyfin_server": "Using Jellyfin Server", "using_jellyfin_server": "Using Jellyfin Server",
"language": "Langue", "language": "Language",
"results": "Résultats", "results": "Results",
"searching": "Recherche...", "searching": "Searching...",
"search_failed": "Recherche échouée", "search_failed": "Search failed",
"no_subtitle_provider": "Aucun fournisseur de sous-titres configuré sur le serveur", "no_subtitle_provider": "No subtitle provider configured on server",
"no_subtitles_found": "Aucun sous-titre trouvé", "no_subtitles_found": "No subtitles found",
"add_opensubtitles_key_hint": "Ajoutez une clé API OpenSubtitles dans les paramètres pour une solution de secours côté client", "add_opensubtitles_key_hint": "Add OpenSubtitles API key in settings for client-side fallback",
"settings": "Réglages", "settings": "Settings",
"skip_intro": "Skip Intro", "skip_intro": "Skip Intro",
"skip_credits": "Skip Credits", "skip_credits": "Skip Credits",
"stopPlayback": "Stop Playback", "stopPlayback": "Stop Playback",
"stopPlayingTitle": "Arrêter de lire \"{{title}}\"?", "stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Êtes-vous sûr de vouloir arrêter la lecture ?", "stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Téléchargé", "downloaded": "Downloaded",
"missing_parameters": "Paramètres de lecture manquants" "missing_parameters": "Missing playback parameters"
}, },
"chapters": { "chapters": {
"title": "Chapitres", "title": "Chapters",
"chapter_number": "Chapitre {{number}}", "chapter_number": "Chapter {{number}}",
"open": "Ouvrir les chapitres", "open": "Open chapters",
"close": "Fermer les chapitres" "close": "Close chapters"
}, },
"item_card": { "item_card": {
"next_up": "À suivre", "next_up": "À suivre",
@@ -664,19 +664,19 @@
"quality": "Qualité", "quality": "Qualité",
"audio": "Audio", "audio": "Audio",
"subtitles": { "subtitles": {
"label": "Sous-titres", "label": "Subtitle",
"none": "Aucun", "none": "None",
"tracks": "Pistes" "tracks": "Tracks"
}, },
"show_more": "Afficher plus", "show_more": "Afficher plus",
"show_less": "Afficher moins", "show_less": "Afficher moins",
"left": "gauche", "left": "left",
"director": "Réalisateur", "director": "Director",
"cast": "Acteurs", "cast": "Cast",
"technical_details": "Technical Details", "technical_details": "Technical Details",
"appeared_in": "Apparu dans", "appeared_in": "Apparu dans",
"movies": "Films", "movies": "Movies",
"shows": "Émissions", "shows": "Shows",
"could_not_load_item": "Impossible de charger le média", "could_not_load_item": "Impossible de charger le média",
"none": "Aucun", "none": "Aucun",
"download": { "download": {
@@ -691,10 +691,10 @@
"mark_played": "Mark as Watched", "mark_played": "Mark as Watched",
"mark_unplayed": "Mark as Unwatched", "mark_unplayed": "Mark as Unwatched",
"resume_playback": "Resume Playback", "resume_playback": "Resume Playback",
"resume_playback_description": "Voulez-vous continuer où vous vous êtes arrêté ou commencer à partir du début ?", "resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start", "play_from_start": "Play from Start",
"continue_from": "Continuer depuis {{time}}", "continue_from": "Continue from {{time}}",
"no_data_available": "Aucune donnée disponible" "no_data_available": "No data available"
}, },
"live_tv": { "live_tv": {
"next": "Suivant", "next": "Suivant",
@@ -706,16 +706,16 @@
"sports": "Sports", "sports": "Sports",
"for_kids": "Pour enfants", "for_kids": "Pour enfants",
"news": "Actualités", "news": "Actualités",
"page_of": "Page {{current}} sur {{total}}", "page_of": "Page {{current}} of {{total}}",
"no_programs": "Aucun programme disponible", "no_programs": "No programs available",
"no_channels": "Aucune chaîne disponible", "no_channels": "No channels available",
"tabs": { "tabs": {
"programs": "Programmes", "programs": "Programs",
"guide": "Guide", "guide": "Guide",
"channels": "Chaines", "channels": "Channels",
"recordings": "Enregistrements", "recordings": "Recordings",
"schedule": "Programme", "schedule": "Schedule",
"series": "Séries" "series": "Series"
} }
}, },
"jellyseerr": { "jellyseerr": {
@@ -761,12 +761,12 @@
"decline": "Refuser", "decline": "Refuser",
"requested_by": "Demandé par {{user}}", "requested_by": "Demandé par {{user}}",
"unknown_user": "Utilisateur inconnu", "unknown_user": "Utilisateur inconnu",
"select": "Sélectionner", "select": "Select",
"request_all": "Request All", "request_all": "Request All",
"request_seasons": "Request Seasons", "request_seasons": "Request Seasons",
"select_seasons": "Select Seasons", "select_seasons": "Select Seasons",
"request_selected": "Request Selected", "request_selected": "Request Selected",
"n_selected": "{{count}} sélectionnés", "n_selected": "{{count}} selected",
"toasts": { "toasts": {
"jellyseer_does_not_meet_requirements": "Seerr ne répond pas aux exigences! Veuillez mettre à jour au moins vers la version 2.0.0.", "jellyseer_does_not_meet_requirements": "Seerr ne répond pas aux exigences! Veuillez mettre à jour au moins vers la version 2.0.0.",
"jellyseerr_test_failed": "Le test Seerr a échoué. Veuillez réessayer.", "jellyseerr_test_failed": "Le test Seerr a échoué. Veuillez réessayer.",
@@ -787,7 +787,7 @@
"library": "Bibliothèque", "library": "Bibliothèque",
"custom_links": "Liens personnalisés", "custom_links": "Liens personnalisés",
"favorites": "Favoris", "favorites": "Favoris",
"settings": "Réglages" "settings": "Settings"
}, },
"music": { "music": {
"title": "Musique", "title": "Musique",
@@ -910,33 +910,33 @@
} }
}, },
"companion_login": { "companion_login": {
"title": "Associer à la TV", "title": "Pair with TV",
"align_qr": "Alignez le code-barres dans le cadre", "align_qr": "Align the QR code within the frame",
"enter_code_manually": "Saisir le code manuellement", "enter_code_manually": "Enter code manually",
"pairing_enter_credentials": "Entrez les identifiants pour le téléviseur", "pairing_enter_credentials": "Enter credentials for TV",
"pairing_code_label": "Code d'appairage", "pairing_code_label": "Pairing code",
"server": "Serveur", "server": "Server",
"authorize_button": "Autoriser", "authorize_button": "Authorize",
"authorizing": "Autorisation en cours...", "authorizing": "Authorizing...",
"scan_again": "Scan Again", "scan_again": "Scan Again",
"done": "Terminé", "done": "Done",
"success_title": "Authorization Sent", "success_title": "Authorization Sent",
"pairing_tv_connecting": "La TV se connecte à votre compte", "pairing_tv_connecting": "The TV is connecting to your account",
"error_title": "Authorization Failed", "error_title": "Authorization Failed",
"error_invalid_qr": "Code QR invalide. Veuillez scanner le code d'appairage du téléviseur.", "error_invalid_qr": "Invalid QR code. Please scan the TV pairing code.",
"error_generic": "Une erreur s'est produite. Veuillez réessayer.", "error_generic": "Something went wrong. Please try again.",
"error_permission_denied": "L'accès à la caméra est nécessaire pour scanner les QR codes.", "error_permission_denied": "Camera permission is required to scan QR codes.",
"login_as": "Se connecter en tant que {{username}}?", "login_as": "Log in as {{username}}?",
"on_server": "sur {{server}}", "on_server": "on {{server}}",
"use_different_user": "Utiliser un autre utilisateur", "use_different_user": "Use a different user",
"open_settings": "Ouvrir les paramètres" "open_settings": "Open Settings"
}, },
"pairing": { "pairing": {
"pair_with_phone": "Pair with Phone", "pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV", "pair_with_phone_title": "Login TV",
"waiting_for_phone": "En attente du téléphone...", "waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scanner avec l'application Streamyfin sur votre téléphone", "scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Connexion en cours...", "logging_in": "Logging in...",
"logging_in_description": "Connexion à votre serveur" "logging_in_description": "Connecting to your server"
} }
} }

View File

@@ -226,7 +226,7 @@
"hide_volume_slider": "Hide Volume Slider", "hide_volume_slider": "Hide Volume Slider",
"hide_volume_slider_description": "Nascondi il cursore del volume nel lettore video", "hide_volume_slider_description": "Nascondi il cursore del volume nel lettore video",
"hide_brightness_slider": "Hide Brightness Slider", "hide_brightness_slider": "Hide Brightness Slider",
"hide_brightness_slider_description": "Nascondi il cursore della luminosità nel lettore video" "hide_brightness_slider_description": "Hide the brightness slider in the video player"
}, },
"audio": { "audio": {
"audio_title": "Audio", "audio_title": "Audio",
@@ -237,10 +237,10 @@
"language": "Lingua", "language": "Lingua",
"transcode_mode": { "transcode_mode": {
"title": "Audio Transcoding", "title": "Audio Transcoding",
"description": "Controlla come viene gestito l'audio surround (7.1, TrueHD, DTS-HD)", "description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled",
"auto": "Automatico", "auto": "Auto",
"stereo": "Force Stereo", "stereo": "Force Stereo",
"5_1": "Consenti 5.1", "5_1": "Allow 5.1",
"passthrough": "Passthrough" "passthrough": "Passthrough"
} }
}, },
@@ -262,20 +262,20 @@
"OnlyForced": "Solo forzati" "OnlyForced": "Solo forzati"
}, },
"opensubtitles_title": "OpenSubtitles", "opensubtitles_title": "OpenSubtitles",
"opensubtitles_hint": "Inserisci la tua chiave API OpenSubtitles per abilitare la ricerca dei sottotitoli quando il tuo server Jellyfin non ha un provider di sottotitoli configurato.", "opensubtitles_hint": "Enter your OpenSubtitles API key to enable client-side subtitle search as a fallback when your Jellyfin server doesn't have a subtitle provider configured.",
"opensubtitles_api_key": "API Key", "opensubtitles_api_key": "API Key",
"opensubtitles_api_key_placeholder": "Inserisci la chiave API...", "opensubtitles_api_key_placeholder": "Enter API key...",
"opensubtitles_get_key": "Ottieni la tua chiave API gratuita su opensubtitles.com/en/consumers", "opensubtitles_get_key": "Get your free API key at opensubtitles.com/en/consumers",
"mpv_subtitle_scale": "Subtitle Scale", "mpv_subtitle_scale": "Subtitle Scale",
"mpv_subtitle_margin_y": "Vertical Margin", "mpv_subtitle_margin_y": "Vertical Margin",
"mpv_subtitle_align_x": "Horizontal Align", "mpv_subtitle_align_x": "Horizontal Align",
"mpv_subtitle_align_y": "Vertical Align", "mpv_subtitle_align_y": "Vertical Align",
"align": { "align": {
"left": "Sinistra", "left": "Left",
"center": "Centro", "center": "Center",
"right": "Destra", "right": "Right",
"top": "Alto", "top": "Top",
"bottom": "Basso" "bottom": "Bottom"
} }
}, },
"other": { "other": {
@@ -307,9 +307,9 @@
"disabled": "Disabilitato" "disabled": "Disabilitato"
}, },
"music": { "music": {
"title": "Musica", "title": "Music",
"playback_title": "Riproduzione", "playback_title": "Playback",
"playback_description": "Configura come viene riprodotta la musica.", "playback_description": "Configure how music is played.",
"prefer_downloaded": "Prefer Downloaded Songs", "prefer_downloaded": "Prefer Downloaded Songs",
"caching_title": "Caching", "caching_title": "Caching",
"caching_description": "Automatically cache upcoming tracks for smoother playback.", "caching_description": "Automatically cache upcoming tracks for smoother playback.",
@@ -333,7 +333,7 @@
"tv_quota_days": "Giorni di quota per le serie TV", "tv_quota_days": "Giorni di quota per le serie TV",
"reset_jellyseerr_config_button": "Ripristina la configurazione di Jellyseerr", "reset_jellyseerr_config_button": "Ripristina la configurazione di Jellyseerr",
"unlimited": "Illimitato", "unlimited": "Illimitato",
"plus_n_more": "+{{n}} altro", "plus_n_more": "+{{n}} more",
"order_by": { "order_by": {
"DEFAULT": "Predefinito", "DEFAULT": "Predefinito",
"VOTE_COUNT_AND_AVERAGE": "Conteggio delle votazioni e media", "VOTE_COUNT_AND_AVERAGE": "Conteggio delle votazioni e media",
@@ -352,25 +352,25 @@
} }
}, },
"streamystats": { "streamystats": {
"disable_streamystats": "Disabilita Streamystats", "disable_streamystats": "Disable Streamystats",
"enable_search": "Use for Search", "enable_search": "Use for Search",
"url": "URL", "url": "URL",
"server_url_placeholder": "http(s)://streamystats.example.com", "server_url_placeholder": "http(s)://streamystats.example.com",
"streamystats_search_hint": "Inserisci l'URL per il tuo server Streamystats. L'URL dovrebbe includere http o https ed eventualmente la porta.", "streamystats_search_hint": "Enter the URL for your Streamystats server. The URL should include http or https and optionally the port.",
"read_more_about_streamystats": "Read More About Streamystats.", "read_more_about_streamystats": "Read More About Streamystats.",
"save": "Salva", "save": "Save",
"features_title": "Funzionalità", "features_title": "Features",
"enable_movie_recommendations": "Movie Recommendations", "enable_movie_recommendations": "Movie Recommendations",
"enable_series_recommendations": "Series Recommendations", "enable_series_recommendations": "Series Recommendations",
"enable_promoted_watchlists": "Promoted Watchlists", "enable_promoted_watchlists": "Promoted Watchlists",
"hide_watchlists_tab": "Hide Watchlists Tab", "hide_watchlists_tab": "Hide Watchlists Tab",
"home_sections_hint": "Mostra consigli personalizzati e watchlist promosse da Streamystats nella home page.", "home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.",
"recommended_movies": "Recommended Movies", "recommended_movies": "Recommended Movies",
"recommended_series": "Recommended Series", "recommended_series": "Recommended Series",
"toasts": { "toasts": {
"saved": "Salvato", "saved": "Saved",
"refreshed": "Impostazioni aggiornate dal server", "refreshed": "Settings refreshed from server",
"disabled": "Streamystats disabilitato" "disabled": "Streamystats disabled"
}, },
"refresh_from_server": "Refresh Settings from Server" "refresh_from_server": "Refresh Settings from Server"
}, },
@@ -385,17 +385,17 @@
"size_used": "{{used}} di {{total}} usato", "size_used": "{{used}} di {{total}} usato",
"delete_all_downloaded_files": "Cancella Tutti i File Scaricati", "delete_all_downloaded_files": "Cancella Tutti i File Scaricati",
"music_cache_title": "Music Cache", "music_cache_title": "Music Cache",
"music_cache_description": "Precarica automaticamente i brani mentre ascolti per una riproduzione più fluida e il supporto offline", "music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
"clear_music_cache": "Clear Music Cache", "clear_music_cache": "Clear Music Cache",
"music_cache_size": "{{size}} nella cache", "music_cache_size": "{{size}} cached",
"music_cache_cleared": "Cache musicale cancellata", "music_cache_cleared": "Music cache cleared",
"delete_all_downloaded_songs": "Delete All Downloaded Songs", "delete_all_downloaded_songs": "Delete All Downloaded Songs",
"downloaded_songs_size": "{{size}} scaricato", "downloaded_songs_size": "{{size}} downloaded",
"downloaded_songs_deleted": "Brani scaricati eliminati", "downloaded_songs_deleted": "Downloaded songs deleted",
"clear_all_cache": "Clear All Cache", "clear_all_cache": "Clear All Cache",
"clear_all_cache_confirm": "Clear All Cache?", "clear_all_cache_confirm": "Clear All Cache?",
"clear_all_cache_confirm_desc": "Sei sicuro di voler cancellare tutti i dati nella cache? Questo cancellerà tutte le immagini nella cache, i file musicali, i sottotitoli e le cache delle interrogazioni. Le impostazioni e la sessione di login verranno mantenute.", "clear_all_cache_confirm_desc": "Are you sure you want to clear all cached data? This will clear all cached images, music files, subtitles, and query caches. Your settings and login session will be kept.",
"clear_all_cache_error_desc": "Si è verificato un errore durante la cancellazione della cache." "clear_all_cache_error_desc": "An error occurred while clearing the cache."
}, },
"intro": { "intro": {
"title": "Intro", "title": "Intro",
@@ -404,8 +404,8 @@
}, },
"logs": { "logs": {
"logs_title": "Log", "logs_title": "Log",
"export_logs": "Esporta i logs", "export_logs": "Export logs",
"click_for_more_info": "Clicca per maggiori informazioni", "click_for_more_info": "Click for more info",
"level": "Livello", "level": "Livello",
"no_logs_available": "Nessun log disponibile", "no_logs_available": "Nessun log disponibile",
"delete_all_logs": "Cancella tutti i log" "delete_all_logs": "Cancella tutti i log"
@@ -419,17 +419,17 @@
"error_deleting_files": "Errore nella cancellazione dei file" "error_deleting_files": "Errore nella cancellazione dei file"
}, },
"security": { "security": {
"title": "Sicurezza", "title": "Security",
"inactivity_timeout": { "inactivity_timeout": {
"title": "Inactivity Timeout", "title": "Inactivity Timeout",
"disabled": "Disabilitato", "disabled": "Disabled",
"1_minute": "1 minuto", "1_minute": "1 minute",
"5_minutes": "5 minuti", "5_minutes": "5 minutes",
"15_minutes": "15 minuti", "15_minutes": "15 minutes",
"30_minutes": "30 minuti", "30_minutes": "30 minutes",
"1_hour": "1 ora", "1_hour": "1 hour",
"4_hours": "4 ore", "4_hours": "4 hours",
"24_hours": "24 ore" "24_hours": "24 hours"
} }
} }
}, },
@@ -494,18 +494,18 @@
"mark_as_not_played": "Mark as not Played", "mark_as_not_played": "Mark as not Played",
"none": "Nulla", "none": "Nulla",
"track": "Traccia", "track": "Traccia",
"cancel": "Annulla", "cancel": "Cancel",
"delete": "Cancella", "delete": "Delete",
"ok": "OK", "ok": "OK",
"remove": "Rimuovi", "remove": "Remove",
"back": "Indietro", "back": "Back",
"continue": "Continua", "continue": "Continue",
"verifying": "Verifica in corso...", "verifying": "Verifying...",
"login": "Accedi", "login": "Login",
"episodes": "Episodi", "episodes": "Episodes",
"movies": "Film", "movies": "Movies",
"loading": "Caricamento…", "loading": "Loading…",
"seeAll": "Visualizza tutti" "seeAll": "See all"
}, },
"search": { "search": {
"search": "Cerca...", "search": "Cerca...",
@@ -519,10 +519,10 @@
"episodes": "Episodi", "episodes": "Episodi",
"collections": "Collezioni", "collections": "Collezioni",
"actors": "Attori", "actors": "Attori",
"artists": "Artisti", "artists": "Artists",
"albums": "Album", "albums": "Albums",
"songs": "Tracce", "songs": "Songs",
"playlists": "Playlist", "playlists": "Playlists",
"request_movies": "Film Richiesti", "request_movies": "Film Richiesti",
"request_series": "Serie Richieste", "request_series": "Serie Richieste",
"recently_added": "Aggiunti di Recente", "recently_added": "Aggiunti di Recente",
@@ -554,7 +554,7 @@
"movies": "film", "movies": "film",
"series": "serie TV", "series": "serie TV",
"boxsets": "cofanetti", "boxsets": "cofanetti",
"playlists": "Playlist", "playlists": "Playlists",
"items": "elementi" "items": "elementi"
}, },
"options": { "options": {
@@ -566,7 +566,7 @@
"cover": "Copertina", "cover": "Copertina",
"show_titles": "Mostra titoli", "show_titles": "Mostra titoli",
"show_stats": "Mostra statistiche", "show_stats": "Mostra statistiche",
"options_title": "Impostazioni" "options_title": "Options"
}, },
"filters": { "filters": {
"genres": "Generi", "genres": "Generi",
@@ -575,10 +575,10 @@
"filter_by": "Filter By", "filter_by": "Filter By",
"sort_order": "Criterio di ordinamento", "sort_order": "Criterio di ordinamento",
"tags": "Tag", "tags": "Tag",
"all": "Tutto", "all": "All",
"reset": "Ripristina", "reset": "Reset",
"asc": "Crescente", "asc": "Ascending",
"desc": "Decrescente" "desc": "Descending"
} }
}, },
"favorites": { "favorites": {
@@ -595,7 +595,7 @@
"no_links": "Nessun link" "no_links": "Nessun link"
}, },
"player": { "player": {
"live": "IN DIRETTA", "live": "LIVE",
"mpv_player_title": "MPV Player", "mpv_player_title": "MPV Player",
"error": "Errore", "error": "Errore",
"failed_to_get_stream_url": "Impossibile ottenere l'URL dello stream", "failed_to_get_stream_url": "Impossibile ottenere l'URL dello stream",
@@ -606,40 +606,40 @@
"next_episode": "Prossimo Episodio", "next_episode": "Prossimo Episodio",
"continue_watching": "Continua a guardare", "continue_watching": "Continua a guardare",
"go_back": "Indietro", "go_back": "Indietro",
"downloaded_file_title": "Questo file è stato scaricato", "downloaded_file_title": "You have this file downloaded",
"downloaded_file_message": "Vuoi riprodurre il file scaricato?", "downloaded_file_message": "Do you want to play the downloaded file?",
"downloaded_file_yes": "Si", "downloaded_file_yes": "Yes",
"downloaded_file_no": "No", "downloaded_file_no": "No",
"downloaded_file_cancel": "Annulla", "downloaded_file_cancel": "Cancel",
"swipe_down_settings": "Scorri in basso per le impostazioni", "swipe_down_settings": "Swipe down for settings",
"ends_at": "Termina alle {{time}}", "ends_at": "Ends at {{time}}",
"search_subtitles": "Search Subtitles", "search_subtitles": "Search Subtitles",
"subtitle_tracks": "Tracce", "subtitle_tracks": "Tracks",
"subtitle_search": "Search & Download", "subtitle_search": "Search & Download",
"download": "Scarica", "download": "Download",
"subtitle_download_hint": "I sottotitoli scaricati verranno salvati nella tua libreria", "subtitle_download_hint": "Downloaded subtitles will be saved to your library",
"using_jellyfin_server": "Using Jellyfin Server", "using_jellyfin_server": "Using Jellyfin Server",
"language": "Lingua", "language": "Language",
"results": "Risultati", "results": "Results",
"searching": "Ricerca in corso...", "searching": "Searching...",
"search_failed": "Ricerca fallita", "search_failed": "Search failed",
"no_subtitle_provider": "Nessun provider di sottotitoli configurato sul server", "no_subtitle_provider": "No subtitle provider configured on server",
"no_subtitles_found": "Nessun sottotitolo trovato", "no_subtitles_found": "No subtitles found",
"add_opensubtitles_key_hint": "Aggiungi la chiave API OpenSubtitles nelle impostazioni", "add_opensubtitles_key_hint": "Add OpenSubtitles API key in settings for client-side fallback",
"settings": "Impostazioni", "settings": "Settings",
"skip_intro": "Skip Intro", "skip_intro": "Skip Intro",
"skip_credits": "Skip Credits", "skip_credits": "Skip Credits",
"stopPlayback": "Stop Playback", "stopPlayback": "Stop Playback",
"stopPlayingTitle": "Interrompere la riproduzione \"{{title}}\"?", "stopPlayingTitle": "Stop playing \"{{title}}\"?",
"stopPlayingConfirm": "Sei sicuro di voler interrompere la riproduzione?", "stopPlayingConfirm": "Are you sure you want to stop playback?",
"downloaded": "Scaricato", "downloaded": "Downloaded",
"missing_parameters": "Parametri di riproduzione mancanti" "missing_parameters": "Missing playback parameters"
}, },
"chapters": { "chapters": {
"title": "Capitoli", "title": "Chapters",
"chapter_number": "Capitolo {{number}}", "chapter_number": "Chapter {{number}}",
"open": "Apri capitoli", "open": "Open chapters",
"close": "Chiudi i capitoli" "close": "Close chapters"
}, },
"item_card": { "item_card": {
"next_up": "Il prossimo", "next_up": "Il prossimo",
@@ -664,19 +664,19 @@
"quality": "Qualità", "quality": "Qualità",
"audio": "Audio", "audio": "Audio",
"subtitles": { "subtitles": {
"label": "Sottotitoli", "label": "Subtitle",
"none": "Vuoto", "none": "None",
"tracks": "Tracce" "tracks": "Tracks"
}, },
"show_more": "Mostra di più", "show_more": "Mostra di più",
"show_less": "Mostra di meno", "show_less": "Mostra di meno",
"left": "sinistra", "left": "left",
"director": "Regista", "director": "Director",
"cast": "Cast", "cast": "Cast",
"technical_details": "Technical Details", "technical_details": "Technical Details",
"appeared_in": "Apparso in", "appeared_in": "Apparso in",
"movies": "Film", "movies": "Movies",
"shows": "Serie", "shows": "Shows",
"could_not_load_item": "Impossibile caricare l'elemento", "could_not_load_item": "Impossibile caricare l'elemento",
"none": "Nessuno", "none": "Nessuno",
"download": { "download": {
@@ -691,10 +691,10 @@
"mark_played": "Mark as Watched", "mark_played": "Mark as Watched",
"mark_unplayed": "Mark as Unwatched", "mark_unplayed": "Mark as Unwatched",
"resume_playback": "Resume Playback", "resume_playback": "Resume Playback",
"resume_playback_description": "Vuoi continuare da dove hai lasciato o riniziare da capo?", "resume_playback_description": "Do you want to continue where you left off or start from the beginning?",
"play_from_start": "Play from Start", "play_from_start": "Play from Start",
"continue_from": "Continua da {{time}}", "continue_from": "Continue from {{time}}",
"no_data_available": "Nessun dato disponibile" "no_data_available": "No data available"
}, },
"live_tv": { "live_tv": {
"next": "Prossimo", "next": "Prossimo",
@@ -706,16 +706,16 @@
"sports": "Sport", "sports": "Sport",
"for_kids": "Per Bambini", "for_kids": "Per Bambini",
"news": "Notiziari", "news": "Notiziari",
"page_of": "Pagina {{current}} di {{total}}", "page_of": "Page {{current}} of {{total}}",
"no_programs": "Nessun programma disponibile", "no_programs": "No programs available",
"no_channels": "Nessun canale disponibile", "no_channels": "No channels available",
"tabs": { "tabs": {
"programs": "Programmi", "programs": "Programs",
"guide": "Guida", "guide": "Guide",
"channels": "Canali", "channels": "Channels",
"recordings": "Registrazioni", "recordings": "Recordings",
"schedule": "Pianifica", "schedule": "Schedule",
"series": "Serie Tv" "series": "Series"
} }
}, },
"jellyseerr": { "jellyseerr": {
@@ -761,12 +761,12 @@
"decline": "Rifiuta", "decline": "Rifiuta",
"requested_by": "Richiesto da {{user}}", "requested_by": "Richiesto da {{user}}",
"unknown_user": "Utente Sconosciuto", "unknown_user": "Utente Sconosciuto",
"select": "Seleziona", "select": "Select",
"request_all": "Request All", "request_all": "Request All",
"request_seasons": "Request Seasons", "request_seasons": "Request Seasons",
"select_seasons": "Select Seasons", "select_seasons": "Select Seasons",
"request_selected": "Request Selected", "request_selected": "Request Selected",
"n_selected": "{{count}} selezionati", "n_selected": "{{count}} selected",
"toasts": { "toasts": {
"jellyseer_does_not_meet_requirements": "Il server Jellyseerr non soddisfa i requisiti minimi di versione! Aggiornare almeno alla versione 2.0.0.", "jellyseer_does_not_meet_requirements": "Il server Jellyseerr non soddisfa i requisiti minimi di versione! Aggiornare almeno alla versione 2.0.0.",
"jellyseerr_test_failed": "Il test di Jellyseerr non è riuscito. Riprovare.", "jellyseerr_test_failed": "Il test di Jellyseerr non è riuscito. Riprovare.",
@@ -787,39 +787,39 @@
"library": "Libreria", "library": "Libreria",
"custom_links": "Collegamenti personalizzati", "custom_links": "Collegamenti personalizzati",
"favorites": "Preferiti", "favorites": "Preferiti",
"settings": "Impostazioni" "settings": "Settings"
}, },
"music": { "music": {
"title": "Musica", "title": "Music",
"tabs": { "tabs": {
"suggestions": "Suggerimenti", "suggestions": "Suggestions",
"albums": "Album", "albums": "Albums",
"artists": "Artisti", "artists": "Artists",
"playlists": "Playlist", "playlists": "Playlists",
"tracks": "tracks" "tracks": "tracks"
}, },
"recently_added": "Recently Added", "recently_added": "Recently Added",
"recently_played": "Recently Played", "recently_played": "Recently Played",
"frequently_played": "Frequently Played", "frequently_played": "Frequently Played",
"top_tracks": "Top Tracks", "top_tracks": "Top Tracks",
"play": "Riproduci", "play": "Play",
"shuffle": "Riproduzione casuale", "shuffle": "Shuffle",
"play_top_tracks": "Play Top Tracks", "play_top_tracks": "Play Top Tracks",
"no_suggestions": "Nessun suggerimento disponibile", "no_suggestions": "No suggestions available",
"no_albums": "Nessun album trovato", "no_albums": "No albums found",
"no_artists": "Artista non trovato", "no_artists": "No artists found",
"no_playlists": "Nessuna playlist trovata", "no_playlists": "No playlists found",
"album_not_found": "Album non trovato", "album_not_found": "Album not found",
"artist_not_found": "Artista non trovato", "artist_not_found": "Artist not found",
"playlist_not_found": "Playlist non trovata", "playlist_not_found": "Playlist not found",
"track_options": { "track_options": {
"play_next": "Play Next", "play_next": "Play Next",
"add_to_queue": "Add to Queue", "add_to_queue": "Add to Queue",
"add_to_playlist": "Add to Playlist", "add_to_playlist": "Add to Playlist",
"download": "Scarica", "download": "Download",
"downloaded": "Scaricato", "downloaded": "Downloaded",
"downloading": "Scaricamento...", "downloading": "Downloading...",
"cached": "Memorizzato nella cache", "cached": "Cached",
"delete_download": "Delete Download", "delete_download": "Delete Download",
"delete_cache": "Remove from Cache", "delete_cache": "Remove from Cache",
"go_to_artist": "Go to Artist", "go_to_artist": "Go to Artist",
@@ -831,112 +831,112 @@
"playlists": { "playlists": {
"create_playlist": "Create Playlist", "create_playlist": "Create Playlist",
"playlist_name": "Playlist Name", "playlist_name": "Playlist Name",
"enter_name": "Inserisci il nome della playlist", "enter_name": "Enter playlist name",
"create": "Crea", "create": "Create",
"search_playlists": "Cerca playlist...", "search_playlists": "Search playlists...",
"added_to": "Aggiunto a {{name}}", "added_to": "Added to {{name}}",
"added": "Aggiunto alla playlist", "added": "Added to playlist",
"removed_from": "Rimosso da {{name}}", "removed_from": "Removed from {{name}}",
"removed": "Rimosso dalla playlist", "removed": "Removed from playlist",
"created": "Playlist creata", "created": "Playlist created",
"create_new": "Create New Playlist", "create_new": "Create New Playlist",
"failed_to_add": "Impossibile aggiungere alla playlist", "failed_to_add": "Failed to add to playlist",
"failed_to_remove": "Impossibile rimuovere dalla playlist", "failed_to_remove": "Failed to remove from playlist",
"failed_to_create": "Impossibile creare la playlist", "failed_to_create": "Failed to create playlist",
"delete_playlist": "Delete Playlist", "delete_playlist": "Delete Playlist",
"delete_confirm": "Sei sicuro di voler eliminare\"{{name}}\"? Questa azione non può essere annullata.", "delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
"deleted": "Playlist eliminata", "deleted": "Playlist deleted",
"failed_to_delete": "Impossibile eliminare la playlist" "failed_to_delete": "Failed to delete playlist"
}, },
"sort": { "sort": {
"title": "Sort By", "title": "Sort By",
"alphabetical": "Alfabetico", "alphabetical": "Alphabetical",
"date_created": "Date Created" "date_created": "Date Created"
} }
}, },
"watchlists": { "watchlists": {
"title": "Da vedere", "title": "Watchlists",
"my_watchlists": "My Watchlists", "my_watchlists": "My Watchlists",
"public_watchlists": "Public Watchlists", "public_watchlists": "Public Watchlists",
"create_title": "Create Watchlist", "create_title": "Create Watchlist",
"edit_title": "Edit Watchlist", "edit_title": "Edit Watchlist",
"create_button": "Create Watchlist", "create_button": "Create Watchlist",
"save_button": "Save Changes", "save_button": "Save Changes",
"delete_button": "Cancella", "delete_button": "Delete",
"remove_button": "Rimuovi", "remove_button": "Remove",
"cancel_button": "Annulla", "cancel_button": "Cancel",
"name_label": "Nome", "name_label": "Name",
"name_placeholder": "Inserisci il nome della lista \"Da vedere\"", "name_placeholder": "Enter watchlist name",
"description_label": "Descrizione", "description_label": "Description",
"description_placeholder": "Inserisci descrizione (opzionale)", "description_placeholder": "Enter description (optional)",
"is_public_label": "Public Watchlist", "is_public_label": "Public Watchlist",
"is_public_description": "Permetti ad altri di vedere questa lista", "is_public_description": "Allow others to view this watchlist",
"allowed_type_label": "Content Type", "allowed_type_label": "Content Type",
"sort_order_label": "Default Sort Order", "sort_order_label": "Default Sort Order",
"empty_title": "No Watchlists", "empty_title": "No Watchlists",
"empty_description": "Crea la tua prima lista \"Da vedere\" per iniziare a organizzare i tuoi media", "empty_description": "Create your first watchlist to start organizing your media",
"empty_watchlist": "Questa lista è vuota", "empty_watchlist": "This watchlist is empty",
"empty_watchlist_hint": "Aggiungi elementi dalla tua libreria a questa lista", "empty_watchlist_hint": "Add items from your library to this watchlist",
"not_configured_title": "Streamystats Not Configured", "not_configured_title": "Streamystats Not Configured",
"not_configured_description": "Configura Streamystats nelle impostazioni per utilizzare le watchlist", "not_configured_description": "Configure Streamystats in settings to use watchlists",
"go_to_settings": "Vai alle impostazioni", "go_to_settings": "Go to Settings",
"add_to_watchlist": "Add to Watchlist", "add_to_watchlist": "Add to Watchlist",
"remove_from_watchlist": "Remove from Watchlist", "remove_from_watchlist": "Remove from Watchlist",
"select_watchlist": "Select Watchlist", "select_watchlist": "Select Watchlist",
"create_new": "Create New Watchlist", "create_new": "Create New Watchlist",
"item": "elemento", "item": "item",
"items": "elementi", "items": "items",
"public": "Pubblico", "public": "Public",
"private": "Privato", "private": "Private",
"you": "Tu", "you": "You",
"by_owner": "Da un altro utente", "by_owner": "By another user",
"not_found": "\"Da vedere\" non trovata", "not_found": "Watchlist not found",
"delete_confirm_title": "Delete Watchlist", "delete_confirm_title": "Delete Watchlist",
"delete_confirm_message": "Sei sicuro di voler eliminare\"{{name}}\"? Questa azione non può essere annullata.", "delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
"remove_item_title": "Remove from Watchlist", "remove_item_title": "Remove from Watchlist",
"remove_item_message": "Rimuovere \"{{name}}\" da questa lista?", "remove_item_message": "Remove \"{{name}}\" from this watchlist?",
"loading": "Caricamento liste...", "loading": "Loading watchlists...",
"no_compatible_watchlists": "Nessuna lista compatibile", "no_compatible_watchlists": "No compatible watchlists",
"create_one_first": "Crea una lista che accetti questo tipo di contenuto" "create_one_first": "Create a watchlist that accepts this content type"
}, },
"playback_speed": { "playback_speed": {
"title": "Playback Speed", "title": "Playback Speed",
"apply_to": "Apply To", "apply_to": "Apply To",
"speed": "Velocità", "speed": "Speed",
"scope": { "scope": {
"media": "Solo questo media", "media": "This media only",
"show": "Questo show", "show": "This show",
"all": "Tutti i media (predefinito)" "all": "All media (default)"
} }
}, },
"companion_login": { "companion_login": {
"title": "Associa con la TV", "title": "Pair with TV",
"align_qr": "Allinea il QR code all'interno del riquadro", "align_qr": "Align the QR code within the frame",
"enter_code_manually": "Inserisci il codice manualmente", "enter_code_manually": "Enter code manually",
"pairing_enter_credentials": "Inserire le credenziali per la TV", "pairing_enter_credentials": "Enter credentials for TV",
"pairing_code_label": "Codice di associazione", "pairing_code_label": "Pairing code",
"server": "Server", "server": "Server",
"authorize_button": "Autorizza", "authorize_button": "Authorize",
"authorizing": "Autorizzando...", "authorizing": "Authorizing...",
"scan_again": "Scan Again", "scan_again": "Scan Again",
"done": "Fatto", "done": "Done",
"success_title": "Authorization Sent", "success_title": "Authorization Sent",
"pairing_tv_connecting": "La TV si sta collegando al tuo account", "pairing_tv_connecting": "The TV is connecting to your account",
"error_title": "Authorization Failed", "error_title": "Authorization Failed",
"error_invalid_qr": "QR code non valido. Scansiona il codice di associazione della TV.", "error_invalid_qr": "Invalid QR code. Please scan the TV pairing code.",
"error_generic": "Si è verificato un errore. Riprova.", "error_generic": "Something went wrong. Please try again.",
"error_permission_denied": "Per scansionare i codici QR è necessaria l'autorizzazione della fotocamera.", "error_permission_denied": "Camera permission is required to scan QR codes.",
"login_as": "Accedi come {{username}}?", "login_as": "Log in as {{username}}?",
"on_server": "su {{server}}", "on_server": "on {{server}}",
"use_different_user": "Usa un altro utente", "use_different_user": "Use a different user",
"open_settings": "Apri le impostazioni" "open_settings": "Open Settings"
}, },
"pairing": { "pairing": {
"pair_with_phone": "Pair with Phone", "pair_with_phone": "Pair with Phone",
"pair_with_phone_title": "Login TV", "pair_with_phone_title": "Login TV",
"waiting_for_phone": "In attesa del telefono...", "waiting_for_phone": "Waiting for phone...",
"scan_with_phone": "Scansiona con l'applicazione Streamyfin sul tuo telefono", "scan_with_phone": "Scan with the Streamyfin app on your phone",
"logging_in": "Accesso in corso...", "logging_in": "Logging in...",
"logging_in_description": "Sto connettendo al server" "logging_in_description": "Connecting to your server"
} }
} }

View File

@@ -13,6 +13,15 @@ export type TVOptionModalState = {
onSelect: (value: any) => void; onSelect: (value: any) => void;
cardWidth?: number; cardWidth?: number;
cardHeight?: number; cardHeight?: number;
/**
* Run onSelect AFTER the modal route is dismissed. Needed only when onSelect
* navigates (the in-player audio switch replacing the player while
* transcoding), which the still-active modal route would otherwise swallow.
* Default (false) runs onSelect before closing, so state-only callers (detail
* page, library filters, settings) don't re-render after focus returns and
* lose TV focus.
*/
deferApplyUntilDismissed?: boolean;
} | null; } | null;
export const tvOptionModalAtom = atom<TVOptionModalState>(null); export const tvOptionModalAtom = atom<TVOptionModalState>(null);