diff --git a/app/(auth)/player/google-cast-player.tsx b/app/(auth)/player/google-cast-player.tsx index d1f2a11d..423557ac 100644 --- a/app/(auth)/player/google-cast-player.tsx +++ b/app/(auth)/player/google-cast-player.tsx @@ -1,48 +1,27 @@ -import React, { useMemo, useRef, useState } from "react"; -import { TouchableOpacity, View } from "react-native"; +import React, { useMemo } from "react"; +import { View } from "react-native"; import { Text } from "@/components/common/Text"; import { Loader } from "@/components/Loader"; import { Button } from "@/components/Button"; -import { Feather, Ionicons } from "@expo/vector-icons"; +import { Feather } from "@expo/vector-icons"; import { RoundButton } from "@/components/RoundButton"; import GoogleCast, { CastButton, CastContext, CastState, - MediaInfo, - MediaStatus, - RemoteMediaClient, useCastDevice, useCastState, useDevices, useMediaStatus, useRemoteMediaClient, - useStreamPosition, } from "react-native-google-cast"; import { useCallback, useEffect } from "react"; import { Platform } from "react-native"; -import { Image } from "expo-image"; import { useRouter } from "expo-router"; -import { Slider } from "react-native-awesome-slider"; -import { - runOnJS, - useAnimatedReaction, - useSharedValue, -} from "react-native-reanimated"; -import { debounce } from "lodash"; -import { useSettings } from "@/utils/atoms/settings"; import { useHaptic } from "@/hooks/useHaptic"; -import { writeToLog } from "@/utils/log"; -import { formatTimeString } from "@/utils/time"; -import { BlurView } from "expo-blur"; -import SkipButton from "@/components/video-player/controls/SkipButton"; -import NextEpisodeCountDownButton from "@/components/video-player/controls/NextEpisodeCountDownButton"; -import { useIntroSkipper } from "@/hooks/useIntroSkipper"; -import { useCreditSkipper } from "@/hooks/useCreditSkipper"; -import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes"; -import { secondsToTicks } from "@/utils/secondsToTicks"; -import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; +import ChromecastControls from "@/components/ChromecastControls"; +import { useTranslation } from "react-i18next"; export default function Player() { const castState = useCastState(); @@ -58,6 +37,8 @@ export default function Player() { const lightHapticFeedback = useHaptic("light"); + const { t } = useTranslation(); + useEffect(() => { (async () => { if (!discoveryManager) { @@ -80,16 +61,24 @@ export default function Player() { [Platform.OS] ); - const GoHomeButton = () => ( - + const GoHomeButton = useCallback( + () => ( + + ), + [router] ); + const ChromecastControlsMemoized = useMemo(() => { + if (!mediaStatus || !client) return undefined; + return ; + }, [mediaStatus, client]); + if ( castState === CastState.NO_DEVICES_AVAILABLE || castState === CastState.NOT_CONNECTED @@ -101,9 +90,11 @@ export default function Player() { - No Google Cast devices available. + {t("chromecast.no_devices_available")} + + + {t("chromecast.are_you_on_same_network")} - Are you on the same network? @@ -127,8 +118,12 @@ export default function Player() { - No device selected - Click icon to connect. + + {t("chromecast.no_device_selected")} + + + {t("chromecast.click_icon_to_connect")} + @@ -141,7 +136,7 @@ export default function Player() { return ( - Establishing connection... + {t("chromecast.establishing_connection")} @@ -153,8 +148,10 @@ export default function Player() { return ( - No media selected. - Start playing any media + + {t("chromecast.no_media_selected")} + + {t("chromecast.start_playing")} @@ -163,415 +160,5 @@ export default function Player() { ); } - return ; -} - -function ChromecastControls({ - mediaStatus, - client, -}: { - mediaStatus: MediaStatus; - client: RemoteMediaClient | null; -}) { - const lightHapticFeedback = useHaptic("light"); - - const streamPosition = useStreamPosition(); - - const [settings] = useSettings(); - - const [isSliding, setIsSliding] = useState(false); - - const [currentTime, setCurrentTime] = useState(0); - const [remainingTime, setRemainingTime] = useState(Infinity); - - const min = useSharedValue(0); - const max = useSharedValue(mediaStatus.mediaInfo?.streamDuration || 0); - const progress = useSharedValue(streamPosition || 0); - const isSeeking = useSharedValue(false); - - const wasPlayingRef = useRef(false); - const lastProgressRef = useRef(0); - - const isPlaying = mediaStatus.playerState === "playing"; - const isBufferingOrLoading = - mediaStatus.playerState === "buffering" || - mediaStatus.playerState === "loading"; - - // request update of media status every player state change - useEffect(() => { - client?.requestStatus(); - }, [mediaStatus.playerState]); - - // update progess on stream position change - useEffect(() => { - if (streamPosition) progress.value = streamPosition; - }, [streamPosition]); - - // update max progress - useEffect(() => { - if (mediaStatus.mediaInfo?.streamDuration) - max.value = mediaStatus.mediaInfo?.streamDuration; - }, [mediaStatus.mediaInfo?.streamDuration]); - - const updateTimes = useCallback( - (currentProgress: number, maxValue: number) => { - setCurrentTime(progress.value); - setRemainingTime(max.value - progress.value); - }, - [] - ); - - useAnimatedReaction( - () => ({ - progress: progress.value, - max: max.value, - isSeeking: isSeeking.value, - }), - (result) => { - if (result.isSeeking === false) { - runOnJS(updateTimes)(result.progress, result.max); - } - }, - [updateTimes] - ); - - function pause() { - client?.pause(); - } - - function play() { - client?.play(); - } - - function seek(time: number) { - client?.seek({ - position: time, - }); - } - - function togglePlay() { - if (isPlaying) pause(); - else play(); - } - - const handleSliderStart = useCallback(() => { - setIsSliding(true); - wasPlayingRef.current = isPlaying; - lastProgressRef.current = progress.value; - - pause(); - isSeeking.value = true; - }, [isPlaying]); - - const handleSliderComplete = useCallback(async (value: number) => { - isSeeking.value = false; - progress.value = value; - setIsSliding(false); - - seek(Math.max(0, Math.floor(value))); - if (wasPlayingRef.current === true) play(); - }, []); - - const [time, setTime] = useState({ hours: 0, minutes: 0, seconds: 0 }); - const handleSliderChange = useCallback( - debounce((value: number) => { - // TODO check if something must be done here - - const progressInSeconds = Math.floor(value); - const hours = Math.floor(progressInSeconds / 3600); - const minutes = Math.floor((progressInSeconds % 3600) / 60); - const seconds = progressInSeconds % 60; - setTime({ hours, minutes, seconds }); - }, 3), - [] - ); - - const handleSkipBackward = useCallback(async () => { - if (!settings?.rewindSkipTime) return; - wasPlayingRef.current = isPlaying; - lightHapticFeedback(); - try { - const curr = progress.value; - if (curr !== undefined) { - const newTime = Math.max(0, curr - settings.rewindSkipTime); - seek(newTime); - if (wasPlayingRef.current === true) play(); - } - } catch (error) { - writeToLog("ERROR", "Error seeking video backwards", error); - } - }, [settings, isPlaying]); - - const handleSkipForward = useCallback(async () => { - if (!settings?.forwardSkipTime) return; - wasPlayingRef.current = isPlaying; - lightHapticFeedback(); - try { - const curr = progress.value; - if (curr !== undefined) { - const newTime = curr + settings.forwardSkipTime; - seek(Math.max(0, newTime)); - if (wasPlayingRef.current === true) play(); - } - } catch (error) { - writeToLog("ERROR", "Error seeking video forwards", error); - } - }, [settings, isPlaying]); - - const mediaMetadata = mediaStatus.mediaInfo?.metadata; - const itemId = mediaStatus.mediaInfo?.contentId; - - const type = mediaMetadata?.type || "generic"; - const images = mediaMetadata?.images || []; - - const item: BaseItemDto | undefined = mediaStatus.mediaInfo?.customData; - - const { previousItem, nextItem } = useAdjacentItems({ - item: { - Id: itemId, - SeriesId: item?.SeriesId, - Type: item?.Type, - }, - }); - - const goToNextItem = () => { - console.warn("go to next item not implemented yet"); - }; - const goToPreviousItem = () => { - console.warn("go to previous item not implemented yet"); - }; - - const { showSkipButton, skipIntro } = useIntroSkipper( - itemId, - currentTime, - seek, - play, - false - ); - - const { showSkipCreditButton, skipCredit } = useCreditSkipper( - itemId, - currentTime, - seek, - play, - false - ); - - const blurhash = - "|rF?hV%2WCj[ayj[a|j[az_NaeWBj@ayfRayfQfQM{M|azj[azf6fQfQfQIpWXofj[ayj[j[fQayWCoeoeaya}j[ayfQa{oLj?j[WVj[ayayj[fQoff7azayj[ayj[j[ayofayayayj[fQj[ayayj[ayfjj[j[ayjuayj["; - - const ItemInfo = useMemo(() => { - switch (type) { - case "generic": - return ; - case "movie": - return ; - case "tvShow": - return ; - default: - return {type} not implemented yet!; - } - }, [type]); - - // Android requires the cast button to be present for startDiscovery to work - const AndroidCastButton = useCallback( - () => - Platform.OS === "android" ? ( - - ) : ( - <> - ), - [Platform.OS] - ); - - return ( - - - - - {ItemInfo} - { - CastContext.showCastDialog(); - }} - > - - - - - - - - - - - - - - - null} - onSlidingStart={handleSliderStart} - onSlidingComplete={handleSliderComplete} - onValueChange={handleSliderChange} - containerStyle={{ - borderRadius: 100, - }} - renderBubble={() => isSliding} - sliderHeight={10} - thumbWidth={0} - progress={progress} - minimumValue={min} - maximumValue={max} - /> - - - {formatTimeString(currentTime, "s")} - - - -{formatTimeString(remainingTime, "s")} - - - - - - - - - - togglePlay()} - className="flex w-14 h-14 items-center justify-center" - > - {!isBufferingOrLoading ? ( - - ) : ( - - )} - - - - - - - - - - - - - - ); -} - -type MetadataInfoProps = { mediaMetadata: MediaInfo["metadata"] }; - -function GenericInfo({ mediaMetadata }: MetadataInfoProps) { - const title = mediaMetadata?.title || "Title not found!"; - - return ( - <> - {title} - { - // @ts-expect-error The metadata type doesn't have subtitle, but the object has - mediaMetadata?.subtitle && {mediaMetadata?.subtitle} - } - - ); -} - -function MovieInfo({ mediaMetadata }: MetadataInfoProps) { - const title = mediaMetadata?.title || "Title not found!"; - - return ( - <> - {title} - { - // @ts-expect-error The metadata type doesn't have subtitle, but the object has - mediaMetadata?.subtitle && {mediaMetadata?.subtitle} - } - - ); -} - -function TvShowInfo({ mediaMetadata }: MetadataInfoProps) { - const itemTitle: string = mediaMetadata?.title || "Title not found!"; - // @ts-expect-error - const seriesTitle: string = mediaMetadata?.seriesTitle || "Title not found!"; - - // @ts-expect-error - const episodeNumber: number = mediaMetadata?.episodeNumber || 0; - // @ts-expect-error - const seasonNumber: number = mediaMetadata?.seasonNumber || 0; - - return ( - <> - - {seriesTitle} - {itemTitle} - - - Season {seasonNumber.toLocaleString()} Episode{" "} - {episodeNumber.toLocaleString()} - - - ); + return ChromecastControlsMemoized; } diff --git a/translations/en.json b/translations/en.json index 3fe9efb6..3155b309 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,156 +1,67 @@ { "login": { - "username_required": "Username Is Required", + "username_required": "Username is required", "error_title": "Error", - "login_title": "Log In", + "login_title": "Log in", "login_to_title": "Log in to", "username_placeholder": "Username", "password_placeholder": "Password", - "login_button": "Log In", + "login_button": "Log in", "quick_connect": "Quick Connect", "enter_code_to_login": "Enter code {{code}} to login", "failed_to_initiate_quick_connect": "Failed to initiate Quick Connect", - "got_it": "Got It", - "connection_failed": "Connection Failed", + "got_it": "Got it", + "connection_failed": "Connection failed", "could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.", - "an_unexpected_error_occured": "An Unexpected Error Occurred", - "change_server": "Change Server", - "invalid_username_or_password": "Invalid Username or Password", + "an_unexpected_error_occured": "An unexpected error occurred", + "change_server": "Change server", + "invalid_username_or_password": "Invalid username or password", "user_does_not_have_permission_to_log_in": "User does not have permission to log in", "server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later", "server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.", "there_is_a_server_error": "There is a server error", - "an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?", - "too_old_server_text": "Unsupported Jellyfin Server Discovered", - "too_old_server_description": "Please update Jellyfin to the latest version" + "an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?" }, "server": { "enter_url_to_jellyfin_server": "Enter the URL to your Jellyfin server", "server_url_placeholder": "http(s)://your-server.com", "connect_button": "Connect", - "previous_servers": "Previous Servers", - "clear_button": "Clear all", - "swipe_to_remove": "Swipe to remove", - "search_for_local_servers": "Search for Local Servers", + "previous_servers": "previous servers", + "clear_button": "Clear", + "search_for_local_servers": "Search for local servers", "searching": "Searching...", - "servers": "Servers", - "saved": "Saved", - "session_expired": "Session Expired", - "please_login_again": "Your saved session has expired. Please log in again.", - "remove_saved_login": "Remove Saved Login", - "remove_saved_login_description": "This will remove your saved credentials for this server. You'll need to enter your username and password again next time.", - "accounts_count": "{{count}} accounts", - "select_account": "Select Account", - "add_account": "Add Account", - "remove_account_description": "This will remove the saved credentials for {{username}}." - }, - "save_account": { - "title": "Save Account", - "save_for_later": "Save this account", - "security_option": "Security Option", - "no_protection": "No protection", - "no_protection_desc": "Quick login without authentication", - "pin_code": "PIN code", - "pin_code_desc": "4-digit PIN required when switching", - "password": "Re-enter password", - "password_desc": "Password required when switching", - "save_button": "Save", - "cancel_button": "Cancel" - }, - "pin": { - "enter_pin": "Enter PIN", - "enter_pin_for": "Enter PIN for {{username}}", - "enter_4_digits": "Enter 4 digits", - "invalid_pin": "Invalid PIN", - "setup_pin": "Set Up PIN", - "confirm_pin": "Confirm PIN", - "pins_dont_match": "PINs don't match", - "forgot_pin": "Forgot PIN?", - "forgot_pin_desc": "Your saved credentials will be removed" - }, - "password": { - "enter_password": "Enter Password", - "enter_password_for": "Enter password for {{username}}", - "invalid_password": "Invalid password" + "servers": "Servers" }, "home": { - "checking_server_connection": "Checking server connection...", "no_internet": "No Internet", - "no_items": "No Items", + "no_items": "No items", "no_internet_message": "No worries, you can still watch\ndownloaded content.", - "checking_server_connection_message": "Checking connection to server", - "go_to_downloads": "Go to Downloads", - "retry": "Retry", - "server_unreachable": "Server Unreachable", - "server_unreachable_message": "Could not reach the server.\nPlease check your network connection.", + "go_to_downloads": "Go to downloads", "oops": "Oops!", "error_message": "Something went wrong.\nPlease log out and in again.", "continue_watching": "Continue Watching", "next_up": "Next Up", - "continue_and_next_up": "Continue & Next Up", "recently_added_in": "Recently Added in {{libraryName}}", "suggested_movies": "Suggested Movies", "suggested_episodes": "Suggested Episodes", "intro": { "welcome_to_streamyfin": "Welcome to Streamyfin", - "a_free_and_open_source_client_for_jellyfin": "A Free and Open-Source Client for Jellyfin.", + "a_free_and_open_source_client_for_jellyfin": "A free and open-source client for Jellyfin.", "features_title": "Features", "features_description": "Streamyfin has a bunch of features and integrates with a wide array of software which you can find in the settings menu, these include:", - "jellyseerr_feature_description": "Connect to your Seerr instance and request movies directly in the app.", + "jellyseerr_feature_description": "Connect to your Jellyseerr instance and request movies directly in the app.", "downloads_feature_title": "Downloads", "downloads_feature_description": "Download movies and tv-shows to view offline. Use either the default method or install the optimize server to download files in the background.", "chromecast_feature_description": "Cast movies and tv-shows to your Chromecast devices.", "centralised_settings_plugin_title": "Centralised Settings Plugin", "centralised_settings_plugin_description": "Configure settings from a centralised location on your Jellyfin server. All client settings for all users will be synced automatically.", "done_button": "Done", - "go_to_settings_button": "Go to Settings", - "read_more": "Read More" + "go_to_settings_button": "Go to settings", + "read_more": "Read more" }, "settings": { "settings_title": "Settings", - "log_out_button": "Log Out", - "categories": { - "title": "Categories" - }, - "playback_controls": { - "title": "Playback & Controls" - }, - "audio_subtitles": { - "title": "Audio & Subtitles" - }, - "appearance": { - "title": "Appearance", - "merge_next_up_continue_watching": "Merge Continue Watching & Next Up", - "hide_remote_session_button": "Hide Remote Session Button" - }, - "network": { - "title": "Network", - "local_network": "Local Network", - "auto_switch_enabled": "Auto-switch when at home", - "auto_switch_description": "Automatically switch to local URL when connected to home WiFi", - "local_url": "Local URL", - "local_url_hint": "Enter your local server address (e.g., http://192.168.1.100:8096)", - "local_url_placeholder": "http://192.168.1.100:8096", - "home_wifi_networks": "Home WiFi Networks", - "add_current_network": "Add \"{{ssid}}\"", - "not_connected_to_wifi": "Not connected to WiFi", - "no_networks_configured": "No networks configured", - "add_network_hint": "Add your home WiFi network to enable auto-switching", - "current_wifi": "Current WiFi", - "using_url": "Using", - "local": "Local URL", - "remote": "Remote URL", - "not_connected": "Not connected", - "current_server": "Current Server", - "remote_url": "Remote URL", - "active_url": "Active URL", - "not_configured": "Not configured", - "network_added": "Network added", - "network_already_added": "Network already added", - "no_wifi_connected": "Not connected to WiFi", - "permission_denied": "Location permission denied", - "permission_denied_explanation": "Location permission is required to detect WiFi network for auto-switching. Please enable it in Settings." - }, + "log_out_button": "Log out", "user_info": { "user_info_title": "User Info", "user": "User", @@ -163,53 +74,32 @@ "authorize_button": "Authorize Quick Connect", "enter_the_quick_connect_code": "Enter the quick connect code...", "success": "Success", - "quick_connect_autorized": "Quick Connect Authorized", + "quick_connect_autorized": "Quick Connect authorized", "error": "Error", - "invalid_code": "Invalid Code", + "invalid_code": "Invalid code", "authorize": "Authorize" }, "media_controls": { "media_controls_title": "Media Controls", - "forward_skip_length": "Forward Skip Length", - "rewind_length": "Rewind Length", + "forward_skip_length": "Forward skip length", + "rewind_length": "Rewind length", "seconds_unit": "s" }, - "gesture_controls": { - "gesture_controls_title": "Gesture Controls", - "horizontal_swipe_skip": "Horizontal Swipe to Skip", - "horizontal_swipe_skip_description": "Swipe left/right when controls are hidden to skip", - "left_side_brightness": "Left Side Brightness Control", - "left_side_brightness_description": "Swipe up/down on left side to adjust brightness", - "right_side_volume": "Right Side Volume Control", - "right_side_volume_description": "Swipe up/down on right side to adjust volume", - "hide_volume_slider": "Hide Volume Slider", - "hide_volume_slider_description": "Hide the volume slider in the video player", - "hide_brightness_slider": "Hide Brightness Slider", - "hide_brightness_slider_description": "Hide the brightness slider in the video player" - }, "audio": { "audio_title": "Audio", "set_audio_track": "Set Audio Track From Previous Item", - "audio_language": "Audio Language", + "audio_language": "Audio language", "audio_hint": "Choose a default audio language.", "none": "None", - "language": "Language", - "transcode_mode": { - "title": "Audio Transcoding", - "description": "Controls how surround audio (7.1, TrueHD, DTS-HD) is handled", - "auto": "Auto", - "stereo": "Force Stereo", - "5_1": "Allow 5.1", - "passthrough": "Passthrough" - } + "language": "Language" }, "subtitles": { "subtitle_title": "Subtitles", - "subtitle_hint": "Configure how subtitles look and behave.", "subtitle_language": "Subtitle language", "subtitle_mode": "Subtitle Mode", "set_subtitle_track": "Set Subtitle Track From Previous Item", "subtitle_size": "Subtitle Size", + "subtitle_hint": "Configure subtitle preference.", "none": "None", "language": "Language", "loading": "Loading", @@ -219,110 +109,45 @@ "Always": "Always", "None": "None", "OnlyForced": "OnlyForced" - }, - "text_color": "Text Color", - "background_color": "Background Color", - "outline_color": "Outline Color", - "outline_thickness": "Outline Thickness", - "background_opacity": "Background Opacity", - "outline_opacity": "Outline Opacity", - "bold_text": "Bold Text", - "colors": { - "Black": "Black", - "Gray": "Gray", - "Silver": "Silver", - "White": "White", - "Maroon": "Maroon", - "Red": "Red", - "Fuchsia": "Fuchsia", - "Yellow": "Yellow", - "Olive": "Olive", - "Green": "Green", - "Teal": "Teal", - "Lime": "Lime", - "Purple": "Purple", - "Navy": "Navy", - "Blue": "Blue", - "Aqua": "Aqua" - }, - "thickness": { - "None": "None", - "Thin": "Thin", - "Normal": "Normal", - "Thick": "Thick" - }, - "subtitle_color": "Subtitle Color", - "subtitle_background_color": "Background Color", - "subtitle_font": "Subtitle Font", - "ksplayer_title": "KSPlayer Settings", - "hardware_decode": "Hardware Decoding", - "hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues." - }, - "vlc_subtitles": { - "title": "VLC Subtitle Settings", - "hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.", - "text_color": "Text Color", - "background_color": "Background Color", - "background_opacity": "Background Opacity", - "outline_color": "Outline Color", - "outline_opacity": "Outline Opacity", - "outline_thickness": "Outline Thickness", - "bold": "Bold Text", - "margin": "Bottom Margin" - }, - "video_player": { - "title": "Video Player", - "video_player": "Video Player", - "video_player_description": "Choose which video player to use on iOS.", - "ksplayer": "KSPlayer", - "vlc": "VLC" + } }, "other": { "other_title": "Other", - "video_orientation": "Video Orientation", + "auto_rotate": "Auto rotate", + "video_orientation": "Video orientation", "orientation": "Orientation", "orientations": { - "DEFAULT": "Follow Device Orientation", + "DEFAULT": "Default", "ALL": "All", - "PORTRAIT": "Portrait Auto", + "PORTRAIT": "Portrait", "PORTRAIT_UP": "Portrait Up", "PORTRAIT_DOWN": "Portrait Down", - "LANDSCAPE": "Landscape Auto", + "LANDSCAPE": "Landscape", "LANDSCAPE_LEFT": "Landscape Left", "LANDSCAPE_RIGHT": "Landscape Right", "OTHER": "Other", "UNKNOWN": "Unknown" }, - "safe_area_in_controls": "Safe Area in Controls", - "video_player": "Video Player", - "video_players": { - "VLC_3": "VLC 3", - "VLC_4": "VLC 4 (Experimental + PiP)" - }, + "safe_area_in_controls": "Safe area in controls", "show_custom_menu_links": "Show Custom Menu Links", - "show_large_home_carousel": "Show Large Home Carousel (beta)", "hide_libraries": "Hide Libraries", "select_liraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.", - "disable_haptic_feedback": "Disable Haptic Feedback", - "default_quality": "Default Quality", - "default_playback_speed": "Default Playback Speed", - "auto_play_next_episode": "Auto-play Next Episode", - "max_auto_play_episode_count": "Max Auto Play Episode Count", - "disabled": "Disabled" + "disable_haptic_feedback": "Disable Haptic Feedback" }, "downloads": { - "downloads_title": "Downloads" - }, - "music": { - "title": "Music", - "playback_title": "Playback", - "playback_description": "Configure how music is played.", - "prefer_downloaded": "Prefer Downloaded Songs", - "caching_title": "Caching", - "caching_description": "Automatically cache upcoming tracks for smoother playback.", - "lookahead_enabled": "Enable Look-Ahead Caching", - "lookahead_count": "Tracks to Pre-cache", - "max_cache_size": "Max Cache Size" + "downloads_title": "Downloads", + "download_method": "Download method", + "remux_max_download": "Remux max download", + "auto_download": "Auto download", + "optimized_versions_server": "Optimized versions server", + "save_button": "Save", + "optimized_server": "Optimized Server", + "optimized": "Optimized", + "default": "Default", + "optimized_version_hint": "Enter the URL for the optimize server. The URL should include http or https and optionally the port.", + "read_more_about_optimized_server": "Read more about the optimize server.", + "url": "URL", + "server_url_placeholder": "http(s)://domain.org:port" }, "plugins": { "plugins_title": "Plugins", @@ -330,201 +155,124 @@ "jellyseerr_warning": "This integration is in its early stages. Expect things to change.", "server_url": "Server URL", "server_url_hint": "Example: http(s)://your-host.url\n(add port if required)", - "server_url_placeholder": "Seerr URL", + "server_url_placeholder": "Jellyseerr URL...", "password": "Password", "password_placeholder": "Enter password for Jellyfin user {{username}}", + "save_button": "Save", + "clear_button": "Clear", "login_button": "Login", - "total_media_requests": "Total Media Requests", - "movie_quota_limit": "Movie Quota Limit", - "movie_quota_days": "Movie Quota Days", - "tv_quota_limit": "TV Quota Limit", - "tv_quota_days": "TV Quota Days", - "reset_jellyseerr_config_button": "Reset Seerr Config", - "unlimited": "Unlimited", - "plus_n_more": "+{{n}} More", - "order_by": { - "DEFAULT": "Default", - "VOTE_COUNT_AND_AVERAGE": "Vote count and average", - "POPULARITY": "Popularity" - } + "total_media_requests": "Total media requests", + "movie_quota_limit": "Movie quota limit", + "movie_quota_days": "Movie quota days", + "tv_quota_limit": "TV quota limit", + "tv_quota_days": "TV quota days", + "reset_jellyseerr_config_button": "Reset Jellyseerr config", + "unlimited": "Unlimited" }, "marlin_search": { - "enable_marlin_search": "Enable Marlin Search", + "enable_marlin_search": "Enable Marlin Search ", "url": "URL", "server_url_placeholder": "http(s)://domain.org:port", "marlin_search_hint": "Enter the URL for the Marlin server. The URL should include http or https and optionally the port.", - "read_more_about_marlin": "Read More About Marlin.", + "read_more_about_marlin": "Read more about Marlin.", "save_button": "Save", "toasts": { - "saved": "Saved", - "refreshed": "Settings refreshed from server" - }, - "refresh_from_server": "Refresh Settings from Server" - }, - "streamystats": { - "enable_streamystats": "Enable Streamystats", - "disable_streamystats": "Disable Streamystats", - "enable_search": "Use for Search", - "url": "URL", - "server_url_placeholder": "http(s)://streamystats.example.com", - "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.", - "save_button": "Save", - "save": "Save", - "features_title": "Features", - "home_sections_title": "Home Sections", - "enable_movie_recommendations": "Movie Recommendations", - "enable_series_recommendations": "Series Recommendations", - "enable_promoted_watchlists": "Promoted Watchlists", - "hide_watchlists_tab": "Hide Watchlists Tab", - "home_sections_hint": "Show personalized recommendations and promoted watchlists from Streamystats on the home page.", - "recommended_movies": "Recommended Movies", - "recommended_series": "Recommended Series", - "toasts": { - "saved": "Saved", - "refreshed": "Settings refreshed from server", - "disabled": "Streamystats disabled" - }, - "refresh_from_server": "Refresh Settings from Server" - }, - "kefinTweaks": { - "watchlist_enabler": "Enable our Watchlist integration", - "watchlist_button": "Toggle Watchlist integration" + "saved": "Saved" + } } }, "storage": { "storage_title": "Storage", "app_usage": "App {{usedSpace}}%", - "device_usage": "Device {{availableSpace}}%", - "size_used": "{{used}} of {{total}} Used", - "delete_all_downloaded_files": "Delete All Downloaded Files", - "music_cache_title": "Music Cache", - "music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support", - "enable_music_cache": "Enable Music Cache", - "clear_music_cache": "Clear Music Cache", - "music_cache_size": "{{size}} cached", - "music_cache_cleared": "Music cache cleared", - "delete_all_downloaded_songs": "Delete All Downloaded Songs", - "downloaded_songs_size": "{{size}} downloaded", - "downloaded_songs_deleted": "Downloaded songs deleted" + "phone_usage": "Phone {{availableSpace}}%", + "size_used": "{{used}} of {{total}} used", + "delete_all_downloaded_files": "Delete All Downloaded Files" }, "intro": { - "title": "Intro", - "show_intro": "Show Intro", - "reset_intro": "Reset Intro" + "show_intro": "Show intro", + "reset_intro": "Reset intro" }, "logs": { "logs_title": "Logs", - "export_logs": "Export Logs", - "click_for_more_info": "Click for More Info", - "level": "Level", - "no_logs_available": "No Logs Available", - "delete_all_logs": "Delete All Logs" + "no_logs_available": "No logs available", + "delete_all_logs": "Delete all logs" }, "languages": { "title": "Languages", - "app_language": "App Language", + "app_language": "App language", + "app_language_description": "Select the language for the app.", "system": "System" }, "toasts": { - "error_deleting_files": "Error Deleting Files", + "error_deleting_files": "Error deleting files", "background_downloads_enabled": "Background downloads enabled", - "background_downloads_disabled": "Background downloads disabled" + "background_downloads_disabled": "Background downloads disabled", + "connected": "Connected", + "could_not_connect": "Could not connect", + "invalid_url": "Invalid URL" } }, - "sessions": { - "title": "Sessions", - "no_active_sessions": "No Active Sessions" - }, "downloads": { "downloads_title": "Downloads", "tvseries": "TV-Series", "movies": "Movies", "queue": "Queue", - "other_media": "Other media", "queue_hint": "Queue and downloads will be lost on app restart", - "no_items_in_queue": "No Items in Queue", - "no_downloaded_items": "No Downloaded Items", - "delete_all_movies_button": "Delete All Movies", - "delete_all_tvseries_button": "Delete All TV-Series", - "delete_all_button": "Delete All", - "delete_all_other_media_button": "Delete other media", - "active_download": "Active Download", - "no_active_downloads": "No Active Downloads", - "active_downloads": "Active Downloads", + "no_items_in_queue": "No items in queue", + "no_downloaded_items": "No downloaded items", + "delete_all_movies_button": "Delete all Movies", + "delete_all_tvseries_button": "Delete all TV-Series", + "delete_all_button": "Delete all", + "active_download": "Active download", + "no_active_downloads": "No active downloads", + "active_downloads": "Active downloads", "new_app_version_requires_re_download": "New app version requires re-download", "new_app_version_requires_re_download_description": "The new update requires content to be downloaded again. Please remove all downloaded content and try again.", "back": "Back", "delete": "Delete", - "something_went_wrong": "Something Went Wrong", + "something_went_wrong": "Something went wrong", "could_not_get_stream_url_from_jellyfin": "Could not get the stream URL from Jellyfin", "eta": "ETA {{eta}}", + "methods": "Methods", "toasts": { "you_are_not_allowed_to_download_files": "You are not allowed to download files.", - "deleted_all_movies_successfully": "Deleted All Movies Successfully!", - "failed_to_delete_all_movies": "Failed to Delete All Movies", - "deleted_all_tvseries_successfully": "Deleted All TV-Series Successfully!", - "failed_to_delete_all_tvseries": "Failed to Delete All TV-Series", - "deleted_media_successfully": "Deleted other media Successfully!", - "failed_to_delete_media": "Failed to Delete other media", - "download_deleted": "Download Deleted", - "download_cancelled": "Download Cancelled", - "could_not_delete_download": "Could Not Delete Download", - "download_paused": "Download Paused", - "could_not_pause_download": "Could Not Pause Download", - "download_resumed": "Download Resumed", - "could_not_resume_download": "Could Not Resume Download", - "download_completed": "Download Completed", - "download_failed": "Download Failed", + "deleted_all_movies_successfully": "Deleted all movies successfully!", + "failed_to_delete_all_movies": "Failed to delete all movies", + "deleted_all_tvseries_successfully": "Deleted all TV-Series successfully!", + "failed_to_delete_all_tvseries": "Failed to delete all TV-Series", + "download_cancelled": "Download cancelled", + "could_not_cancel_download": "Could not cancel download", + "download_completed": "Download completed", + "download_started_for": "Download started for {{item}}", + "item_is_ready_to_be_downloaded": "{{item}} is ready to be downloaded", + "download_stated_for_item": "Download started for {{item}}", "download_failed_for_item": "Download failed for {{item}} - {{error}}", - "download_completed_for_item": "Download Completed for {{item}}", - "download_started_for_item": "Download Started for {{item}}", - "failed_to_start_download": "Failed to start download", - "item_already_downloading": "{{item}} is already downloading", - "all_files_deleted": "All Downloads Deleted Successfully", - "files_deleted_by_type": "{{count}} {{type}} deleted", + "download_completed_for_item": "Download completed for {{item}}", + "queued_item_for_optimization": "Queued {{item}} for optimization", + "failed_to_start_download_for_item": "Failed to start downloading for {{item}}: {{message}}", + "server_responded_with_status_code": "Server responded with status {{statusCode}}", + "no_response_received_from_server": "No response received from the server", + "error_setting_up_the_request": "Error setting up the request", + "failed_to_start_download_for_item_unexpected_error": "Failed to start downloading for {{item}}: Unexpected error", "all_files_folders_and_jobs_deleted_successfully": "All files, folders, and jobs deleted successfully", - "failed_to_clean_cache_directory": "Failed to clean cache directory", - "could_not_get_download_url_for_item": "Could not get download URL for {{itemName}}", - "go_to_downloads": "Go to Downloads", - "file_deleted": "{{item}} deleted" + "an_error_occured_while_deleting_files_and_jobs": "An error occurred while deleting files and jobs", + "go_to_downloads": "Go to downloads" } } }, - "common": { - "select": "Select", - "no_trailer_available": "No trailer available", - "video": "Video", - "audio": "Audio", - "subtitle": "Subtitle", - "play": "Play", - "none": "None", - "track": "Track", - "cancel": "Cancel", - "delete": "Delete", - "ok": "OK", - "remove": "Remove", - "next": "Next", - "back": "Back", - "continue": "Continue", - "verifying": "Verifying..." - }, "search": { + "search_here": "Search here...", "search": "Search...", - "x_items": "{{count}} Items", + "x_items": "{{count}} items", "library": "Library", "discover": "Discover", - "no_results": "No Results", - "no_results_found_for": "No Results Found For", + "no_results": "No results", + "no_results_found_for": "No results found for", "movies": "Movies", "series": "Series", "episodes": "Episodes", "collections": "Collections", "actors": "Actors", - "artists": "Artists", - "albums": "Albums", - "songs": "Songs", - "playlists": "Playlists", "request_movies": "Request Movies", "request_series": "Request Series", "recently_added": "Recently Added", @@ -550,29 +298,29 @@ "tmdb_tv_streaming_services": "TMDB TV Streaming Services" }, "library": { - "no_results": "No Results", - "no_libraries_found": "No Libraries Found", + "no_items_found": "No items found", + "no_results": "No results", + "no_libraries_found": "No libraries found", "item_types": { - "movies": "Movies", - "series": "Series", - "boxsets": "Box Sets", - "items": "Items" + "movies": "movies", + "series": "series", + "boxsets": "box sets", + "items": "items" }, "options": { "display": "Display", "row": "Row", "list": "List", - "image_style": "Image Style", + "image_style": "Image style", "poster": "Poster", "cover": "Cover", - "show_titles": "Show Titles", - "show_stats": "Show Stats" + "show_titles": "Show titles", + "show_stats": "Show stats" }, "filters": { "genres": "Genres", "years": "Years", "sort_by": "Sort By", - "filter_by": "Filter By", "sort_order": "Sort Order", "tags": "Tags" } @@ -582,37 +330,32 @@ "movies": "Movies", "episodes": "Episodes", "videos": "Videos", - "boxsets": "Box Sets", - "playlists": "Playlists", - "noDataTitle": "No Favorites Yet", - "noData": "Mark items as favorites to see them appear here for quick access." + "boxsets": "Boxsets", + "playlists": "Playlists" }, "custom_links": { - "no_links": "No Links" + "no_links": "No links" }, "player": { "error": "Error", "failed_to_get_stream_url": "Failed to get the stream URL", "an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.", - "client_error": "Client Error", + "client_error": "Client error", "could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast", - "message_from_server": "Message from Server: {{message}}", + "message_from_server": "Message from server: {{message}}", + "video_has_finished_playing": "Video has finished playing!", + "no_video_source": "No video source...", "next_episode": "Next Episode", "refresh_tracks": "Refresh Tracks", + "subtitle_tracks": "Subtitle Tracks:", "audio_tracks": "Audio Tracks:", "playback_state": "Playback State:", - "index": "Index:", - "continue_watching": "Continue Watching", - "go_back": "Go Back", - "downloaded_file_title": "You have this file downloaded", - "downloaded_file_message": "Do you want to play the downloaded file?", - "downloaded_file_yes": "Yes", - "downloaded_file_no": "No", - "downloaded_file_cancel": "Cancel" + "no_data_available": "No data available", + "index": "Index:" }, "item_card": { - "next_up": "Next Up", - "no_items_to_display": "No Items to Display", + "next_up": "Next up", + "no_items_to_display": "No items to display", "cast_and_crew": "Cast & Crew", "series": "Series", "seasons": "Seasons", @@ -620,34 +363,35 @@ "no_episodes_for_this_season": "No episodes for this season", "overview": "Overview", "more_with": "More with {{name}}", - "similar_items": "Similar Items", - "no_similar_items_found": "No Similar Items Found", + "similar_items": "Similar items", + "no_similar_items_found": "No similar items found", "video": "Video", - "more_details": "More Details", - "media_options": "Media Options", + "more_details": "More details", "quality": "Quality", "audio": "Audio", "subtitles": "Subtitle", - "show_more": "Show More", - "show_less": "Show Less", - "appeared_in": "Appeared In", - "could_not_load_item": "Could Not Load Item", + "show_more": "Show more", + "show_less": "Show less", + "appeared_in": "Appeared in", + "could_not_load_item": "Could not load item", "none": "None", "download": { "download_season": "Download Season", "download_series": "Download Series", "download_episode": "Download Episode", "download_movie": "Download Movie", - "download_x_item": "Download {{item_count}} Items", - "download_unwatched_only": "Unwatched Only", - "download_button": "Download" + "download_x_item": "Download {{item_count}} items", + "download_button": "Download", + "using_optimized_server": "Using optimized server", + "using_default_method": "Using default method" } }, "live_tv": { "next": "Next", "previous": "Previous", - "coming_soon": "Coming Soon", - "on_now": "On Now", + "live_tv": "Live TV", + "coming_soon": "Coming soon", + "on_now": "On now", "shows": "Shows", "movies": "Movies", "sports": "Sports", @@ -658,16 +402,16 @@ "confirm": "Confirm", "cancel": "Cancel", "yes": "Yes", - "whats_wrong": "What's Wrong?", - "issue_type": "Issue Type", - "select_an_issue": "Select an Issue", + "whats_wrong": "What's wrong?", + "issue_type": "Issue type", + "select_an_issue": "Select an issue", "types": "Types", - "describe_the_issue": "(Optional) Describe the Issue...", + "describe_the_issue": "(optional) Describe the issue...", "submit_button": "Submit", - "report_issue_button": "Report Issue", + "report_issue_button": "Report issue", "request_button": "Request", "are_you_sure_you_want_to_request_all_seasons": "Are you sure you want to request all seasons?", - "failed_to_login": "Failed to Login", + "failed_to_login": "Failed to login", "cast": "Cast", "details": "Details", "status": "Status", @@ -682,33 +426,25 @@ "production_country": "Production Country", "studios": "Studios", "network": "Network", - "currently_streaming_on": "Currently Streaming On", + "currently_streaming_on": "Currently Streaming on", "advanced": "Advanced", "request_as": "Request As", "tags": "Tags", "quality_profile": "Quality Profile", "root_folder": "Root Folder", - "season_all": "Season (All)", + "season_x": "Season {{seasons}}", "season_number": "Season {{season_number}}", "number_episodes": "{{episode_number}} Episodes", "born": "Born", "appearances": "Appearances", - "approve": "Approve", - "decline": "Decline", - "requested_by": "Requested by {{user}}", - "unknown_user": "Unknown User", "toasts": { - "jellyseer_does_not_meet_requirements": "Seerr server does not meet minimum version requirements! Please update to at least 2.0.0", - "jellyseerr_test_failed": "Seerr test failed. Please try again.", - "failed_to_test_jellyseerr_server_url": "Failed to test Seerr server url", - "issue_submitted": "Issue Submitted!", + "jellyseer_does_not_meet_requirements": "Jellyseerr server does not meet minimum version requirements! Please update to at least 2.0.0", + "jellyseerr_test_failed": "Jellyseerr test failed. Please try again.", + "failed_to_test_jellyseerr_server_url": "Failed to test jellyseerr server url", + "issue_submitted": "Issue submitted!", "requested_item": "Requested {{item}}!", "you_dont_have_permission_to_request": "You don't have permission to request!", - "something_went_wrong_requesting_media": "Something went wrong requesting media!", - "request_approved": "Request Approved!", - "request_declined": "Request Declined!", - "failed_to_approve_request": "Failed to Approve Request", - "failed_to_decline_request": "Failed to Decline Request" + "something_went_wrong_requesting_media": "Something went wrong requesting media!" } }, "tabs": { @@ -718,128 +454,14 @@ "custom_links": "Custom Links", "favorites": "Favorites" }, - "music": { - "title": "Music", - "tabs": { - "suggestions": "Suggestions", - "albums": "Albums", - "artists": "Artists", - "playlists": "Playlists", - "tracks": "tracks" - }, - "filters": { - "all": "All" - }, - "recently_added": "Recently Added", - "recently_played": "Recently Played", - "frequently_played": "Frequently Played", - "explore": "Explore", - "top_tracks": "Top Tracks", - "play": "Play", - "shuffle": "Shuffle", - "play_top_tracks": "Play Top Tracks", - "no_suggestions": "No suggestions available", - "no_albums": "No albums found", - "no_artists": "No artists found", - "no_playlists": "No playlists found", - "album_not_found": "Album not found", - "artist_not_found": "Artist not found", - "playlist_not_found": "Playlist not found", - "track_options": { - "play_next": "Play Next", - "add_to_queue": "Add to Queue", - "add_to_playlist": "Add to Playlist", - "download": "Download", - "downloaded": "Downloaded", - "downloading": "Downloading...", - "cached": "Cached", - "delete_download": "Delete Download", - "delete_cache": "Remove from Cache", - "go_to_artist": "Go to Artist", - "go_to_album": "Go to Album", - "add_to_favorites": "Add to Favorites", - "remove_from_favorites": "Remove from Favorites", - "remove_from_playlist": "Remove from Playlist" - }, - "playlists": { - "create_playlist": "Create Playlist", - "playlist_name": "Playlist Name", - "enter_name": "Enter playlist name", - "create": "Create", - "search_playlists": "Search playlists...", - "added_to": "Added to {{name}}", - "added": "Added to playlist", - "removed_from": "Removed from {{name}}", - "removed": "Removed from playlist", - "created": "Playlist created", - "create_new": "Create New Playlist", - "failed_to_add": "Failed to add to playlist", - "failed_to_remove": "Failed to remove from playlist", - "failed_to_create": "Failed to create playlist", - "delete_playlist": "Delete Playlist", - "delete_confirm": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.", - "deleted": "Playlist deleted", - "failed_to_delete": "Failed to delete playlist" - }, - "sort": { - "title": "Sort By", - "alphabetical": "Alphabetical", - "date_created": "Date Created" - } - }, - "watchlists": { - "title": "Watchlists", - "my_watchlists": "My Watchlists", - "public_watchlists": "Public Watchlists", - "create_title": "Create Watchlist", - "edit_title": "Edit Watchlist", - "create_button": "Create Watchlist", - "save_button": "Save Changes", - "delete_button": "Delete", - "remove_button": "Remove", - "cancel_button": "Cancel", - "name_label": "Name", - "name_placeholder": "Enter watchlist name", - "description_label": "Description", - "description_placeholder": "Enter description (optional)", - "is_public_label": "Public Watchlist", - "is_public_description": "Allow others to view this watchlist", - "allowed_type_label": "Content Type", - "sort_order_label": "Default Sort Order", - "empty_title": "No Watchlists", - "empty_description": "Create your first watchlist to start organizing your media", - "empty_watchlist": "This watchlist is empty", - "empty_watchlist_hint": "Add items from your library to this watchlist", - "not_configured_title": "Streamystats Not Configured", - "not_configured_description": "Configure Streamystats in settings to use watchlists", - "go_to_settings": "Go to Settings", - "add_to_watchlist": "Add to Watchlist", - "remove_from_watchlist": "Remove from Watchlist", - "select_watchlist": "Select Watchlist", - "create_new": "Create New Watchlist", - "item": "item", - "items": "items", - "public": "Public", - "private": "Private", - "you": "You", - "by_owner": "By another user", - "not_found": "Watchlist not found", - "delete_confirm_title": "Delete Watchlist", - "delete_confirm_message": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.", - "remove_item_title": "Remove from Watchlist", - "remove_item_message": "Remove \"{{name}}\" from this watchlist?", - "loading": "Loading watchlists...", - "no_compatible_watchlists": "No compatible watchlists", - "create_one_first": "Create a watchlist that accepts this content type" - }, - "playback_speed": { - "title": "Playback Speed", - "apply_to": "Apply To", - "speed": "Speed", - "scope": { - "media": "This media only", - "show": "This show", - "all": "All media (default)" - } + "chromecast": { + "no_devices_available": "No Google Cast devices available.", + "are_you_on_same_network": "Are you on the same network?", + "no_device_selected": "No device selected", + "click_icon_to_connect": "Click icon to connect.", + "establishing_connection": "Establishing connection...", + "no_media_selected": "No media selected.", + "start_playing": "Start playing any media", + "go_home": "Go Home" } }