diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 83c3a81c..fb706fff 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -66,11 +66,14 @@ export const PlayButton: React.FC = ({ const startColor = useSharedValue(colorAtom); const widthProgress = useSharedValue(0); const colorChangeProgress = useSharedValue(0); - const [settings] = useSettings(); + const [settings, updateSettings] = useSettings(); const lightHapticFeedback = useHaptic("light"); const goToPlayer = useCallback( (q: string) => { + if (settings.maxAutoPlayEpisodeCount.value !== -1) { + updateSettings({ autoPlayEpisodeCount: 0 }); + } router.push(`/player/direct-player?${q}`); }, [router], diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index 47e2efa1..800ca897 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -10,6 +10,7 @@ import { } from "@/utils/background-tasks"; import { Ionicons } from "@expo/vector-icons"; import { useRouter } from "expo-router"; +import i18n, { TFunction } from "i18next"; import type React from "react"; import { useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -251,7 +252,46 @@ export const OtherSettings: React.FC = () => { } /> + + item.key} + titleExtractor={(item) => item.key} + title={ + + + {t(settings?.maxAutoPlayEpisodeCount.key)} + + + + } + label={t("home.settings.other.max_auto_play_episode_count")} + onSelected={(maxAutoPlayEpisodeCount) => + updateSettings({ maxAutoPlayEpisodeCount }) + } + /> + ); }; + +const AUTOPLAY_EPISODES_COUNT = ( + t: TFunction<"translation", undefined>, +): { + key: string; + value: number; +}[] => [ + { key: t("home.settings.other.disabled"), value: -1 }, + { key: "1", value: 1 }, + { key: "2", value: 2 }, + { key: "3", value: 3 }, + { key: "4", value: 4 }, + { key: "5", value: 5 }, + { key: "6", value: 6 }, + { key: "7", value: 7 }, +]; diff --git a/components/video-player/controls/ContinueWatchingOverlay.tsx b/components/video-player/controls/ContinueWatchingOverlay.tsx new file mode 100644 index 00000000..11ed0daa --- /dev/null +++ b/components/video-player/controls/ContinueWatchingOverlay.tsx @@ -0,0 +1,49 @@ +import { Button } from "@/components/Button"; +import { Text } from "@/components/common/Text"; +import { useSettings } from "@/utils/atoms/settings"; +import { useRouter } from "expo-router"; +import { t } from "i18next"; +import React from "react"; +import { View } from "react-native"; + +export interface ContinueWatchingOverlayProps { + goToNextItem: (options: { + isAutoPlay: boolean; + resetWatchCount: boolean; + }) => void; +} + +const ContinueWatchingOverlay: React.FC = ({ + goToNextItem, +}) => { + const [settings] = useSettings(); + const router = useRouter(); + + return settings.autoPlayEpisodeCount >= + settings.maxAutoPlayEpisodeCount.value ? ( + + + Are you still watching ? + + + + + + ) : null; +}; + +export default ContinueWatchingOverlay; diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx index d0084720..e02c3975 100644 --- a/components/video-player/controls/Controls.tsx +++ b/components/video-player/controls/Controls.tsx @@ -1,5 +1,6 @@ import { Loader } from "@/components/Loader"; import { Text } from "@/components/common/Text"; +import ContinueWatchingOverlay from "@/components/video-player/controls/ContinueWatchingOverlay"; import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes"; import { useCreditSkipper } from "@/hooks/useCreditSkipper"; import { useHaptic } from "@/hooks/useHaptic"; @@ -28,7 +29,7 @@ import { Image } from "expo-image"; import { useLocalSearchParams, useRouter } from "expo-router"; import { useAtom } from "jotai"; import { debounce } from "lodash"; -import { +import React, { type Dispatch, type FC, type MutableRefObject, @@ -121,7 +122,7 @@ export const Controls: FC = ({ enableTrickplay = true, isVlc = false, }) => { - const [settings] = useSettings(); + const [settings, updateSettings] = useSettings(); const router = useRouter(); const insets = useSafeAreaInsets(); const [api] = useAtom(apiAtom); @@ -236,15 +237,76 @@ export const Controls: FC = ({ goToItemCommon(previousItem); }, [previousItem, goToItemCommon]); - const goToNextItem = useCallback(() => { - if (!nextItem) return; - goToItemCommon(nextItem); - }, [nextItem, goToItemCommon]); + const goToNextItem = useCallback( + ({ + isAutoPlay, + resetWatchCount, + }: { isAutoPlay?: boolean; resetWatchCount?: boolean }) => { + if (!nextItem) { + return; + } + + if (!isAutoPlay) { + // if we are not autoplaying, we won't update anything, we just go to the next item + goToItemCommon(nextItem); + if (resetWatchCount) { + updateSettings({ + autoPlayEpisodeCount: 0, + }); + } + return; + } + + // Skip autoplay logic if maxAutoPlayEpisodeCount is -1 + if (settings.maxAutoPlayEpisodeCount.value === -1) { + goToItemCommon(nextItem); + return; + } + + if ( + settings.autoPlayEpisodeCount + 1 < + settings.maxAutoPlayEpisodeCount.value + ) { + goToItemCommon(nextItem); + } + + // Check if the autoPlayEpisodeCount is less than maxAutoPlayEpisodeCount for the autoPlay + if ( + settings.autoPlayEpisodeCount < settings.maxAutoPlayEpisodeCount.value + ) { + // update the autoPlayEpisodeCount in settings + updateSettings({ + autoPlayEpisodeCount: settings.autoPlayEpisodeCount + 1, + }); + } + }, + [nextItem, goToItemCommon], + ); + + // Add a memoized handler for autoplay next episode + const handleNextEpisodeAutoPlay = useCallback(() => { + goToNextItem({ isAutoPlay: true }); + }, [goToNextItem]); + + // Add a memoized handler for manual next episode + const handleNextEpisodeManual = useCallback(() => { + goToNextItem({ isAutoPlay: false }); + }, [goToNextItem]); + + // Add a memoized handler for ContinueWatchingOverlay + const handleContinueWatching = useCallback( + (options: { isAutoPlay?: boolean; resetWatchCount?: boolean }) => { + goToNextItem(options); + }, + [goToNextItem], + ); const goToItem = useCallback( async (itemId: string) => { const gotoItem = await getItemById(api, itemId); - if (!gotoItem) return; + if (!gotoItem) { + return; + } goToItemCommon(gotoItem); }, [goToItemCommon, api], @@ -300,7 +362,9 @@ export const Controls: FC = ({ }; const handleSliderStart = useCallback(() => { - if (!showControls) return; + if (!showControls) { + return; + } setIsSliding(true); wasPlayingRef.current = isPlaying; @@ -339,7 +403,9 @@ export const Controls: FC = ({ ); const handleSkipBackward = useCallback(async () => { - if (!settings?.rewindSkipTime) return; + if (!settings?.rewindSkipTime) { + return; + } wasPlayingRef.current = isPlaying; lightHapticFeedback(); try { @@ -371,7 +437,9 @@ export const Controls: FC = ({ ? curr + secondsToMs(settings.forwardSkipTime) : ticksToSeconds(curr) + settings.forwardSkipTime; seek(Math.max(0, newTime)); - if (wasPlayingRef.current) play(); + if (wasPlayingRef.current) { + play(); + } } } catch (error) { writeToLog("ERROR", "Error seeking video forwards", error); @@ -546,7 +614,7 @@ export const Controls: FC = ({ {nextItem && !offline && ( goToNextItem({ isAutoPlay: false })} className='aspect-square flex flex-col rounded-xl items-center justify-center p-2' > @@ -741,17 +809,21 @@ export const Controls: FC = ({ onPress={skipCredit} buttonText='Skip Credits' /> - + {(settings.maxAutoPlayEpisodeCount.value === -1 || + settings.autoPlayEpisodeCount < + settings.maxAutoPlayEpisodeCount.value) && ( + + )} = ({ )} + {settings.maxAutoPlayEpisodeCount.value !== -1 && ( + + )} ); }; diff --git a/components/video-player/controls/NextEpisodeCountDownButton.tsx b/components/video-player/controls/NextEpisodeCountDownButton.tsx index 69e5b38b..093ec4de 100644 --- a/components/video-player/controls/NextEpisodeCountDownButton.tsx +++ b/components/video-player/controls/NextEpisodeCountDownButton.tsx @@ -79,7 +79,7 @@ const NextEpisodeCountDownButton: React.FC = ({ > - + {t("player.next_episode")} diff --git a/i18n.ts b/i18n.ts index ed91a75e..6a29d559 100644 --- a/i18n.ts +++ b/i18n.ts @@ -11,8 +11,8 @@ import ja from "./translations/ja.json"; import nl from "./translations/nl.json"; import pl from "./translations/pl.json"; import ptBR from "./translations/pt-BR.json"; -import sv from "./translations/sv.json"; import ru from "./translations/ru.json"; +import sv from "./translations/sv.json"; import tr from "./translations/tr.json"; import uk from "./translations/uk.json"; import zhCN from "./translations/zh-CN.json"; diff --git a/translations/de.json b/translations/de.json index fd85456b..a575b31a 100644 --- a/translations/de.json +++ b/translations/de.json @@ -138,7 +138,8 @@ "hide_libraries": "Bibliotheken ausblenden", "select_liraries_you_want_to_hide": "Wähl die Bibliotheken aus, die du im Bibliothekstab und auf der Startseite ausblenden möchtest.", "disable_haptic_feedback": "Haptisches Feedback deaktivieren", - "default_quality": "Standardqualität" + "default_quality": "Standardqualität", + "disabled": "Deaktiviert" }, "downloads": { "downloads_title": "Downloads", @@ -370,7 +371,9 @@ "audio_tracks": "Audiospuren:", "playback_state": "Wiedergabestatus:", "no_data_available": "Keine Daten verfügbar", - "index": "Index:" + "index": "Index:", + "continue_watching": "Weiterschauen", + "go_back": "Zurück" }, "item_card": { "next_up": "Als Nächstes", diff --git a/translations/en.json b/translations/en.json index fbeee14d..7b8faad5 100644 --- a/translations/en.json +++ b/translations/en.json @@ -138,7 +138,9 @@ "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_quality": "Default quality", + "max_auto_play_episode_count": "Max auto play episode count", + "disabled": "Disabled" }, "downloads": { "downloads_title": "Downloads", @@ -374,7 +376,9 @@ "audio_tracks": "Audio Tracks:", "playback_state": "Playback State:", "no_data_available": "No data available", - "index": "Index:" + "index": "Index:", + "continue_watching": "Continue Watching", + "go_back": "Go back" }, "item_card": { "next_up": "Next up", diff --git a/translations/es.json b/translations/es.json index 1c3d8876..f7a78f61 100644 --- a/translations/es.json +++ b/translations/es.json @@ -138,7 +138,8 @@ "hide_libraries": "Ocultar bibliotecas", "select_liraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.", "disable_haptic_feedback": "Desactivar feedback háptico", - "default_quality": "Calidad por defecto" + "default_quality": "Calidad por defecto", + "disabled": "Deshabilitado" }, "downloads": { "downloads_title": "Descargas", @@ -370,7 +371,9 @@ "audio_tracks": "Pistas de audio:", "playback_state": "Estado de la reproducción:", "no_data_available": "No hay datos disponibles", - "index": "Índice:" + "index": "Índice:", + "continue_watching": "Continuar viendo", + "go_back": "Volver" }, "item_card": { "next_up": "A continuación", diff --git a/translations/fr.json b/translations/fr.json index 487967d7..e50cc400 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -138,7 +138,8 @@ "hide_libraries": "Cacher des bibliothèques", "select_liraries_you_want_to_hide": "Sélectionnez les bibliothèques que vous souhaitez masquer dans l'onglet Bibliothèque et les sections de la page d'accueil.", "disable_haptic_feedback": "Désactiver le retour haptique", - "default_quality": "Qualité par défaut" + "default_quality": "Qualité par défaut", + "disabled": "Désactivé" }, "downloads": { "downloads_title": "Téléchargements", @@ -370,7 +371,9 @@ "audio_tracks": "Pistes audio:", "playback_state": "État de lecture:", "no_data_available": "Aucune donnée disponible", - "index": "Index:" + "index": "Index :", + "continue_watching": "Continuer à regarder", + "go_back": "Retour" }, "item_card": { "next_up": "À suivre", diff --git a/translations/it.json b/translations/it.json index 6d07c1c2..e73fd39d 100644 --- a/translations/it.json +++ b/translations/it.json @@ -138,7 +138,8 @@ "hide_libraries": "Nascondi Librerie", "select_liraries_you_want_to_hide": "Selezionate le librerie che volete nascondere dalla scheda Libreria e dalle sezioni della pagina iniziale.", "disable_haptic_feedback": "Disabilita il feedback aptico", - "default_quality": "Qualità predefinita" + "default_quality": "Qualità predefinita", + "disabled": "Disabilitato" }, "downloads": { "downloads_title": "Scaricamento", @@ -370,7 +371,9 @@ "audio_tracks": "Tracce audio:", "playback_state": "Stato della riproduzione:", "no_data_available": "Nessun dato disponibile", - "index": "Indice:" + "index": "Indice:", + "continue_watching": "Continua a guardare", + "go_back": "Indietro" }, "item_card": { "next_up": "Il prossimo", diff --git a/translations/ja.json b/translations/ja.json index a9c2c1b9..3151591c 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -152,7 +152,9 @@ "optimized_version_hint": "OptimizeサーバーのURLを入力します。URLにはhttpまたはhttpsを含め、オプションでポートを指定します。", "read_more_about_optimized_server": "Optimizeサーバーの詳細をご覧ください。", "url": "URL", - "server_url_placeholder": "http(s)://domain.org:ポート" + "server_url_placeholder": "http(s)://domain.org:ポート", + "default_quality": "デフォルトの品質", + "disabled": "無効" }, "plugins": { "plugins_title": "プラグイン", @@ -369,7 +371,9 @@ "audio_tracks": "音声トラック:", "playback_state": "再生状態:", "no_data_available": "データなし", - "index": "インデックス:" + "index": "インデックス:", + "continue_watching": "視聴を続ける", + "go_back": "戻る" }, "item_card": { "next_up": "次", diff --git a/translations/nl.json b/translations/nl.json index 4b2bc603..01cca337 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -138,7 +138,8 @@ "hide_libraries": "Verberg Bibliotheken", "select_liraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.", "disable_haptic_feedback": "Haptische feedback uitschakelen", - "default_quality": "Standaard kwaliteit" + "default_quality": "Standaard kwaliteit", + "disabled": "Uitgeschakeld" }, "downloads": { "downloads_title": "Downloads", @@ -370,7 +371,9 @@ "audio_tracks": "Audio Tracks:", "playback_state": "Afspeelstatus:", "no_data_available": "Geen data beschikbaar", - "index": "Index:" + "index": "Index:", + "continue_watching": "Verder kijken", + "go_back": "Terug" }, "item_card": { "next_up": "Volgende", diff --git a/translations/pl.json b/translations/pl.json index ab7e4182..839eb209 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -138,7 +138,8 @@ "hide_libraries": "Ukryj biblioteki", "select_liraries_you_want_to_hide": "Wybierz biblioteki, które chcesz ukryć na karcie Biblioteka i w sekcjach strony głównej.", "disable_haptic_feedback": "Wyłącz wibracje", - "default_quality": "Domyślna jakość" + "default_quality": "Domyślna jakość", + "disabled": "Wyłączone" }, "downloads": { "downloads_title": "Pobieranie", @@ -374,7 +375,9 @@ "audio_tracks": "Ścieżki audio:", "playback_state": "Stan odtwarzania:", "no_data_available": "Brak dostępnych danych", - "index": "Indeks:" + "index": "Indeks:", + "continue_watching": "Kontynuuj oglądanie", + "go_back": "Wstecz" }, "item_card": { "next_up": "Następne", diff --git a/translations/pt-BR.json b/translations/pt-BR.json index 28224375..4b6efe05 100644 --- a/translations/pt-BR.json +++ b/translations/pt-BR.json @@ -138,7 +138,8 @@ "hide_libraries": "Ocultar bibliotecas", "select_liraries_you_want_to_hide": "Selecione as bibliotecas que você deseja ocultar das abas Biblioteca e Início.", "disable_haptic_feedback": "Desativar o feedback háptico", - "default_quality": "Qualidade padrão" + "default_quality": "Qualidade padrão", + "disabled": "Desativado" }, "downloads": { "downloads_title": "Downloads", @@ -371,7 +372,9 @@ "audio_tracks": "Faixas do áudio:", "playback_state": "Playback State:", "no_data_available": "Nenhum dado disponível", - "index": "Índice:" + "index": "Índice:", + "continue_watching": "Continuar assistindo", + "go_back": "Voltar" }, "item_card": { "next_up": "Próximo em", diff --git a/translations/ru.json b/translations/ru.json index c3a33437..3b78c2a9 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -1,478 +1,480 @@ { - "login": { - "username_required": "Имя пользователя обязательно", - "error_title": "Ошибка", - "login_title": "Вход", - "login_to_title": "Вход в", - "username_placeholder": "Имя пользователя", - "password_placeholder": "Пароль", - "login_button": "Войти", - "quick_connect": "Быстрое подключение", - "enter_code_to_login": "Введите код {{code}} чтобы войти", - "failed_to_initiate_quick_connect": "Не удалось инициировать быстрое подключение", - "got_it": "Принято", - "connection_failed": "Соединение не удалось", - "could_not_connect_to_server": "Не удалось подключиться к серверу. Пожалуйста проверьте URL и ваше интернет соединение.", - "an_unexpected_error_occured": "Возникла непредвиденная ошибка", - "change_server": "Поменять сервер", - "invalid_username_or_password": "Неправильное имя пользователя или пароль", - "user_does_not_have_permission_to_log_in": "Пользователь не имеет прав на вход", - "server_is_taking_too_long_to_respond_try_again_later": "Сервер долго не отвечает, попробуйте позже.", - "server_received_too_many_requests_try_again_later": "Сервер получил слишком много запросов, попробуйте позже.", - "there_is_a_server_error": "Возникла ошибка сервера", - "an_unexpected_error_occured_did_you_enter_the_correct_url": "Возникла непредвиденная ошибка. Вы правильно ввели URL?" + "login": { + "username_required": "Имя пользователя обязательно", + "error_title": "Ошибка", + "login_title": "Вход", + "login_to_title": "Вход в", + "username_placeholder": "Имя пользователя", + "password_placeholder": "Пароль", + "login_button": "Войти", + "quick_connect": "Быстрое подключение", + "enter_code_to_login": "Введите код {{code}} чтобы войти", + "failed_to_initiate_quick_connect": "Не удалось инициировать быстрое подключение", + "got_it": "Принято", + "connection_failed": "Соединение не удалось", + "could_not_connect_to_server": "Не удалось подключиться к серверу. Пожалуйста проверьте URL и ваше интернет соединение.", + "an_unexpected_error_occured": "Возникла непредвиденная ошибка", + "change_server": "Поменять сервер", + "invalid_username_or_password": "Неправильное имя пользователя или пароль", + "user_does_not_have_permission_to_log_in": "Пользователь не имеет прав на вход", + "server_is_taking_too_long_to_respond_try_again_later": "Сервер долго не отвечает, попробуйте позже.", + "server_received_too_many_requests_try_again_later": "Сервер получил слишком много запросов, попробуйте позже.", + "there_is_a_server_error": "Возникла ошибка сервера", + "an_unexpected_error_occured_did_you_enter_the_correct_url": "Возникла непредвиденная ошибка. Вы правильно ввели URL?" + }, + "server": { + "enter_url_to_jellyfin_server": "Укажите URL на ваш Jellyfin сервер", + "server_url_placeholder": "http(s)://your-server.com", + "connect_button": "Подключиться", + "previous_servers": "предыдущие серверы", + "clear_button": "Очистить", + "search_for_local_servers": "Поиск локальных серверов", + "searching": "Поиск...", + "servers": "Сервера" + }, + "home": { + "no_internet": "Нет интернета", + "no_items": "Нет элементов", + "no_internet_message": "Не переживайте, Вы всё ещё можете смотреть\nскачанный контент.", + "go_to_downloads": "В загрузки", + "oops": "Упс!", + "error_message": "Что-то пошло не так.\nПожалуйста выйдите и зайдите снова.", + "continue_watching": "Продолжить просмотр", + "next_up": "Следующее", + "recently_added_in": "Недавно добавлено в {{libraryName}}", + "suggested_movies": "Предложенные фильмы", + "suggested_episodes": "Предложенные серии", + "intro": { + "welcome_to_streamyfin": "Добро пожаловать в Streamyfin", + "a_free_and_open_source_client_for_jellyfin": "Бесплатный клиент для Jellyfin с открытым кодом", + "features_title": "Функции", + "features_description": "Streamyfin имеет множество функций и интегрируется с широким спектром программ, которое вы можете найти в меню настроек:", + "jellyseerr_feature_description": "Подключитесь к Jellyseerr и запрашивайте фильмы прямо в приложении.", + "downloads_feature_title": "Загрузки", + "downloads_feature_description": "Скачивайте фильмы и сериалы для просмотра без интернета. Используйте стандартный способ или установите сервер оптимизации для загрузки файлов в фоновом режиме.", + "chromecast_feature_description": "Транслируйте фильмы и сериалы на ваши устройста с поддержкой Chromecast.", + "centralised_settings_plugin_title": "Плагин для централизованной настройки", + "centralised_settings_plugin_description": "Настраивайте параметры из централизованного места на сервере Jellyfin. Все настройки клиента для всех пользователей будут синхронизированы автоматически.", + "done_button": "Готово", + "go_to_settings_button": "Перейти в настройки", + "read_more": "Узнать больше" }, - "server": { - "enter_url_to_jellyfin_server": "Укажите URL на ваш Jellyfin сервер", - "server_url_placeholder": "http(s)://your-server.com", - "connect_button": "Подключиться", - "previous_servers": "предыдущие серверы", - "clear_button": "Очистить", - "search_for_local_servers": "Поиск локальных серверов", - "searching": "Поиск...", - "servers": "Сервера" - }, - "home": { - "no_internet": "Нет интернета", - "no_items": "Нет элементов", - "no_internet_message": "Не переживайте, Вы всё ещё можете смотреть\nскачанный контент.", - "go_to_downloads": "В загрузки", - "oops": "Упс!", - "error_message": "Что-то пошло не так.\nПожалуйста выйдите и зайдите снова.", - "continue_watching": "Продолжить просмотр", - "next_up": "Следующее", - "recently_added_in": "Недавно добавлено в {{libraryName}}", - "suggested_movies": "Предложенные фильмы", - "suggested_episodes": "Предложенные серии", - "intro": { - "welcome_to_streamyfin": "Добро пожаловать в Streamyfin", - "a_free_and_open_source_client_for_jellyfin": "Бесплатный клиент для Jellyfin с открытым кодом", - "features_title": "Функции", - "features_description": "Streamyfin имеет множество функций и интегрируется с широким спектром программ, которое вы можете найти в меню настроек:", - "jellyseerr_feature_description": "Подключитесь к Jellyseerr и запрашивайте фильмы прямо в приложении.", - "downloads_feature_title": "Загрузки", - "downloads_feature_description": "Скачивайте фильмы и сериалы для просмотра без интернета. Используйте стандартный способ или установите сервер оптимизации для загрузки файлов в фоновом режиме.", - "chromecast_feature_description": "Транслируйте фильмы и сериалы на ваши устройста с поддержкой Chromecast.", - "centralised_settings_plugin_title": "Плагин для централизованной настройки", - "centralised_settings_plugin_description": "Настраивайте параметры из централизованного места на сервере Jellyfin. Все настройки клиента для всех пользователей будут синхронизированы автоматически.", - "done_button": "Готово", - "go_to_settings_button": "Перейти в настройки", - "read_more": "Узнать больше" + "settings": { + "settings_title": "Настройки", + "log_out_button": "Выйти", + "user_info": { + "user_info_title": "Информация о пользователе", + "user": "Пользователь", + "server": "Сервер", + "token": "Токен", + "app_version": "Версия приложения" }, - "settings": { - "settings_title": "Настройки", - "log_out_button": "Выйти", - "user_info": { - "user_info_title": "Информация о пользователе", - "user": "Пользователь", - "server": "Сервер", - "token": "Токен", - "app_version": "Версия приложения" - }, - "quick_connect": { - "quick_connect_title": "Быстрое подключение", - "authorize_button": "Авторизировать через быстрое подключение", - "enter_the_quick_connect_code": "Введите код для быстрого подключения...", - "success": "Успех", - "quick_connect_autorized": "Быстрое подключение авторизовано", - "error": "Ошибка", - "invalid_code": "Неверный код", - "authorize": "Авторизировать" - }, - "media_controls": { - "media_controls_title": "Медиа-контроль", - "forward_skip_length": "Длина пропуска вперед", - "rewind_length": "Длина перемотки", - "seconds_unit": "c" - }, - "audio": { - "audio_title": "Аудио", - "set_audio_track": "Устанавливать аудио дорожку из предыдущего элемента", - "audio_language": "Язык аудио", - "audio_hint": "Выберите стандартный язык аудио.", - "none": "Отсутствует", - "language": "Язык" - }, - "subtitles": { - "subtitle_title": "Субтитры", - "subtitle_language": "Язык субтитров", - "subtitle_mode": "Режим субтитров", - "set_subtitle_track": "Устанавливать субтитры из предыдущего элемента", - "subtitle_size": "Размер субтитров", - "subtitle_hint": "Настроить субтитры.", - "none": "Отсутствует", - "language": "Язык", - "loading": "Загрузка", - "modes": { - "Default": "Стандартный", - "Smart": "Умный", - "Always": "Всегда", - "None": "Отсутствует", - "OnlyForced": "Только принудительные" - } - }, - "other": { - "other_title": "Другое", - "follow_device_orientation": "Авто-поворот", - "video_orientation": "Ориентация видео", - "orientation": "Ориентация", - "orientations": { - "DEFAULT": "Стандартный", - "ALL": "Все", - "PORTRAIT": "Портретный", - "PORTRAIT_UP": "Портрет вверх", - "PORTRAIT_DOWN": "Портрет вниз", - "LANDSCAPE": "Ландшафтный", - "LANDSCAPE_LEFT": "Ландшафтный слева", - "LANDSCAPE_RIGHT": "Ландшафтный справа", - "OTHER": "Другое", - "UNKNOWN": "Неизвестное" - }, - "safe_area_in_controls": "Безопасная зона в элементах управления", - "video_player": "Видео прейер", - "video_players": { - "VLC_3": "VLC 3", - "VLC_4": "VLC 4 (Экспериментальный + PiP)" - }, - "show_custom_menu_links": "Показать ссылки кастомного меню", - "hide_libraries": "Скрыть библиотеки", - "select_liraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.", - "disable_haptic_feedback": "Отключить тактильную обратную связь", - "default_quality": "Качество по умолчанию" - }, - "downloads": { - "downloads_title": "Загрузки", - "download_method": "способ загрузки", - "remux_max_download": "Remux max скачать", - "auto_download": "Авто-загрузка", - "optimized_versions_server": "Оптимизированные версии сервера", - "save_button": "Сохранить", - "optimized_server": "Оптимизированный сервер", - "optimized": "Оптимизированный", - "default": "По умолчанию", - "optimized_version_hint": "Укажите URL на оптимизированный сервер. URL должен включать http or https и опционально порт.", - "read_more_about_optimized_server": "Узнать больше про оптимизацию сервера.", - "url": "URL", - "server_url_placeholder": "http(s)://domain.org:port" - }, - "plugins": { - "plugins_title": "Плагины", - "jellyseerr": { - "jellyseerr_warning": "Эта интеграция находится на ранней стадии. Ожидайте изменений.", - "server_url": "URL сервера", - "server_url_hint": "Пример: http(s)://your-host.url\n(Добавьте порт если необходимо)", - "server_url_placeholder": "Jellyseerr URL...", - "password": "Пароль", - "password_placeholder": "Введите пароль для пользователя Jellyfin {{username}}", - "save_button": "Сохранить", - "clear_button": "Очистить", - "login_button": "Войти", - "total_media_requests": "Всего запросов на медиа", - "movie_quota_limit": "Ограничение квоты на фильмы", - "movie_quota_days": "Дни квоты на фильмы", - "tv_quota_limit": "Ограничение квоты на сериалы", - "tv_quota_days": "Дни квоты на сериалы", - "reset_jellyseerr_config_button": "Сбросить конфигурацию Jellyseerr", - "unlimited": "Неограниченно", - "plus_n_more": "+{{n}} больше", - "order_by": { - "DEFAULT": "По умолчанию", - "VOTE_COUNT_AND_AVERAGE": "Количеству голосов и среднему", - "POPULARITY": "Популярности" - } - }, - "marlin_search": { - "enable_marlin_search": "Включить Marlin Search ", - "url": "URL", - "server_url_placeholder": "http(s)://domain.org:port", - "marlin_search_hint": "Введите URL для Marlin сервера. URL должен включать http or https и опционально порт.", - "read_more_about_marlin": "Узнать больше о Marlin.", - "save_button": "Сохранить", - "toasts": { - "saved": "Сохранено" - } - } - }, - "storage": { - "storage_title": "Хранилище", - "app_usage": "Приложение {{usedSpace}}%", - "device_usage": "Устройство {{availableSpace}}%", - "size_used": "{{used}} из {{total}} использовано", - "delete_all_downloaded_files": "Удалить все загруженные файлы", - }, - "intro": { - "show_intro": "Показать вступление", - "reset_intro": "Сбросить вступление" - }, - "logs": { - "logs_title": "Логи", - "no_logs_available": "Логи не доступны", - "delete_all_logs": "Удалить все логи", - }, - "languages": { - "title": "Языки", - "app_language": "Язык приложения", - "app_language_description": "Выберите язык для приложения.", - "system": "Системный" - }, - "toasts": { - "error_deleting_files": "Ошибка при удалении файлов", - "background_downloads_enabled": "Фоновая загрузка включена", - "background_downloads_disabled": "Фоновая загрузка отключена", - "connected": "Подключено", - "could_not_connect": "Не удалось подключиться", - "invalid_url": "Неверный URL" + "quick_connect": { + "quick_connect_title": "Быстрое подключение", + "authorize_button": "Авторизировать через быстрое подключение", + "enter_the_quick_connect_code": "Введите код для быстрого подключения...", + "success": "Успех", + "quick_connect_autorized": "Быстрое подключение авторизовано", + "error": "Ошибка", + "invalid_code": "Неверный код", + "authorize": "Авторизировать" + }, + "media_controls": { + "media_controls_title": "Медиа-контроль", + "forward_skip_length": "Длина пропуска вперед", + "rewind_length": "Длина перемотки", + "seconds_unit": "c" + }, + "audio": { + "audio_title": "Аудио", + "set_audio_track": "Устанавливать аудио дорожку из предыдущего элемента", + "audio_language": "Язык аудио", + "audio_hint": "Выберите стандартный язык аудио.", + "none": "Отсутствует", + "language": "Язык" + }, + "subtitles": { + "subtitle_title": "Субтитры", + "subtitle_language": "Язык субтитров", + "subtitle_mode": "Режим субтитров", + "set_subtitle_track": "Устанавливать субтитры из предыдущего элемента", + "subtitle_size": "Размер субтитров", + "subtitle_hint": "Настроить субтитры.", + "none": "Отсутствует", + "language": "Язык", + "loading": "Загрузка", + "modes": { + "Default": "Стандартный", + "Smart": "Умный", + "Always": "Всегда", + "None": "Отсутствует", + "OnlyForced": "Только принудительные" } }, - "sessions": { - "title": "Сессии", - "no_active_sessions": "Нет активных сессий", + "other": { + "other_title": "Другое", + "follow_device_orientation": "Авто-поворот", + "video_orientation": "Ориентация видео", + "orientation": "Ориентация", + "orientations": { + "DEFAULT": "Стандартный", + "ALL": "Все", + "PORTRAIT": "Портретный", + "PORTRAIT_UP": "Портрет вверх", + "PORTRAIT_DOWN": "Портрет вниз", + "LANDSCAPE": "Ландшафтный", + "LANDSCAPE_LEFT": "Ландшафтный слева", + "LANDSCAPE_RIGHT": "Ландшафтный справа", + "OTHER": "Другое", + "UNKNOWN": "Неизвестное" + }, + "safe_area_in_controls": "Безопасная зона в элементах управления", + "video_player": "Видео прейер", + "video_players": { + "VLC_3": "VLC 3", + "VLC_4": "VLC 4 (Экспериментальный + PiP)" + }, + "show_custom_menu_links": "Показать ссылки кастомного меню", + "hide_libraries": "Скрыть библиотеки", + "select_liraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.", + "disable_haptic_feedback": "Отключить тактильную обратную связь", + "default_quality": "Качество по умолчанию", + "disabled": "Отключено" }, "downloads": { "downloads_title": "Загрузки", - "tvseries": "Сериалы", - "movies": "Фильмы", - "queue": "Очередь", - "queue_hint": "Очередь и загрузки будут удалены при перезагрузке приложения", - "no_items_in_queue": "Нет элементов в очереди", - "no_downloaded_items": "Нет загруженых предметов", - "delete_all_movies_button": "Удалить все фильмы", - "delete_all_tvseries_button": "Удалить все сериалы", - "delete_all_button": "Удалить все", - "active_download": "Активно загружается", - "no_active_downloads": "Нет активных загрузок", - "active_downloads": "Активные загрузки", - "new_app_version_requires_re_download": "Новая версия приложения требует повторной загрузки", - "new_app_version_requires_re_download_description": "Новая версия приложения требует повторной загрузки. Пожалуйста удалите всё и попробуйте заново.", - "back": "Назад", - "delete": "Удалить", - "something_went_wrong": "Что-то пошло не так", - "could_not_get_stream_url_from_jellyfin": "Не удалось получить ссылку трансляции из Jellyfin", - "eta": "ETA {{eta}}", - "methods": "Методы", - "toasts": { - "you_are_not_allowed_to_download_files": "Нет разрешения на скачивание файлов.", - "deleted_all_movies_successfully": "Все фильмы были успешно удалены!", - "failed_to_delete_all_movies": "Возникла ошибка при удалении всех фильмов", - "deleted_all_tvseries_successfully": "Все сериалы были успешно удалены!", - "failed_to_delete_all_tvseries": "Возникла ошибка при удалении всех сериалов", - "download_cancelled": "Загрузка отменена", - "could_not_cancel_download": "Не удалось отменить загрузку", - "download_completed": "Загрузка завершена", - "download_started_for": "Загрузка {{item}} началась", - "item_is_ready_to_be_downloaded": "{{item}} готов к загрузке", - "download_stated_for_item": "Загрузка {{item} началась", - "download_failed_for_item": "Загрузка {{item}} провалилась с ошибкой: {{error}}", - "download_completed_for_item": "{{item}} успешно загружен", - "queued_item_for_optimization": "{{item}} поставлен в очередь для оптимизации", - "failed_to_start_download_for_item": "Не удалось начать загрузку {{item}}: {{message}}", - "server_responded_with_status_code": "Сервер ответил со статусом {{statusCode}}", - "no_response_received_from_server": "Нет ответа от сервера", - "error_setting_up_the_request": "Ошибка при создании запроса", - "failed_to_start_download_for_item_unexpected_error": "Не удалось начать загрузку {{item}}: Неожиданная ошибка", - "all_files_folders_and_jobs_deleted_successfully": "Все файлы, папки, и задачи были успешно удалены", - "an_error_occured_while_deleting_files_and_jobs": "Возникла ошибка при удалении файлов и работ", - "go_to_downloads": "В загрузки" + "download_method": "способ загрузки", + "remux_max_download": "Remux max скачать", + "auto_download": "Авто-загрузка", + "optimized_versions_server": "Оптимизированные версии сервера", + "save_button": "Сохранить", + "optimized_server": "Оптимизированный сервер", + "optimized": "Оптимизированный", + "default": "По умолчанию", + "optimized_version_hint": "Укажите URL на оптимизированный сервер. URL должен включать http or https и опционально порт.", + "read_more_about_optimized_server": "Узнать больше про оптимизацию сервера.", + "url": "URL", + "server_url_placeholder": "http(s)://domain.org:port" + }, + "plugins": { + "plugins_title": "Плагины", + "jellyseerr": { + "jellyseerr_warning": "Эта интеграция находится на ранней стадии. Ожидайте изменений.", + "server_url": "URL сервера", + "server_url_hint": "Пример: http(s)://your-host.url\n(Добавьте порт если необходимо)", + "server_url_placeholder": "Jellyseerr URL...", + "password": "Пароль", + "password_placeholder": "Введите пароль для пользователя Jellyfin {{username}}", + "save_button": "Сохранить", + "clear_button": "Очистить", + "login_button": "Войти", + "total_media_requests": "Всего запросов на медиа", + "movie_quota_limit": "Ограничение квоты на фильмы", + "movie_quota_days": "Дни квоты на фильмы", + "tv_quota_limit": "Ограничение квоты на сериалы", + "tv_quota_days": "Дни квоты на сериалы", + "reset_jellyseerr_config_button": "Сбросить конфигурацию Jellyseerr", + "unlimited": "Неограниченно", + "plus_n_more": "+{{n}} больше", + "order_by": { + "DEFAULT": "По умолчанию", + "VOTE_COUNT_AND_AVERAGE": "Количеству голосов и среднему", + "POPULARITY": "Популярности" + } + }, + "marlin_search": { + "enable_marlin_search": "Включить Marlin Search ", + "url": "URL", + "server_url_placeholder": "http(s)://domain.org:port", + "marlin_search_hint": "Введите URL для Marlin сервера. URL должен включать http or https и опционально порт.", + "read_more_about_marlin": "Узнать больше о Marlin.", + "save_button": "Сохранить", + "toasts": { + "saved": "Сохранено" + } } - } - }, - "search": { - "search_here": "Искать здесь...", - "search": "Поиск...", - "x_items": "{{count}} предметов", - "library": "Библиотека", - "discover": "Найти новое", - "no_results": "Нет результатов", - "no_results_found_for": "Не было результатов при поиске", - "movies": "Фильмы", - "series": "Сериалы", - "episodes": "Серии", - "collections": "Коллекции", - "actors": "Актеры", - "request_movies": "Запросить фильмы", - "request_series": "Запросить сериалы", - "recently_added": "Недавно добавлено", - "recent_requests": "Недавно запрошено", - "plex_watchlist": "Список просмотра с Plex", - "trending": "В тренде", - "popular_movies": "Популярные фильмы", - "movie_genres": "Популярные жанры", - "upcoming_movies": "Предстоящие фильмы", - "studios": "Студии", - "popular_tv": "Популярные сериалы", - "tv_genres": "жанры сериалов", - "upcoming_tv": "Предстоящие сериалы", - "networks": "Сети", - "tmdb_movie_keyword": "TMDB Ключевые слова фильмов", - "tmdb_movie_genre": "TMDB Жанры фильмов", - "tmdb_tv_keyword": "TMDB Ключевые слова сериалов", - "tmdb_tv_genre": "TMDB Жанры сериалов", - "tmdb_search": "TMDB Поиск", - "tmdb_studio": "TMDB Студии", - "tmdb_network": "TMDB Сеть", - "tmdb_movie_streaming_services": "TMDB Потоковые сервисы фильмов", - "tmdb_tv_streaming_services": "TMDB Потоковые сервисы сериалов", - }, - "library": { - "no_items_found": "элементы не найдены", - "no_results": "Нет результатов", - "no_libraries_found": "Библиотеки не найдены", - "item_types": { - "movies": "фильмы", - "series": "Сериалы", - "boxsets": "Коллекции", - "items": "элементы" }, - "options": { - "display": "Отображать", - "row": "Ряд", - "list": "Список", - "image_style": "Стиль изображения", - "poster": "Постер", - "cover": "Обложка", - "show_titles": "Показывать загаловки", - "show_stats": "Показывать статистику", + "storage": { + "storage_title": "Хранилище", + "app_usage": "Приложение {{usedSpace}}%", + "device_usage": "Устройство {{availableSpace}}%", + "size_used": "{{used}} из {{total}} использовано", + "delete_all_downloaded_files": "Удалить все загруженные файлы" + }, + "intro": { + "show_intro": "Показать вступление", + "reset_intro": "Сбросить вступление" + }, + "logs": { + "logs_title": "Логи", + "no_logs_available": "Логи не доступны", + "delete_all_logs": "Удалить все логи" + }, + "languages": { + "title": "Языки", + "app_language": "Язык приложения", + "app_language_description": "Выберите язык для приложения.", + "system": "Системный" }, - "filters": { - "genres": "Жанры", - "years": "Года", - "sort_by": "Сортировать по", - "sort_order": "Порядок сортировки", - "asc": "По Возрастанию", - "desc": "По убыванию", - "tags": "Тэги" - } - }, - "favorites": { - "series": "Сериалы", - "movies": "Фильмы", - "episodes": "Серии", - "videos": "Видео", - "boxsets": "Коллекции", - "playlists": "Плейлисты", - "noDataTitle": "Пока нет избранных", - "noData": "Отметьте элементы как избранные, чтобы они отображались здесь для быстрого доступа." - }, - "custom_links": { - "no_links": "Нет ссылок" - }, - "player": { - "error": "Ошибка", - "failed_to_get_stream_url": "Не удалось получить URL потока", - "an_error_occured_while_playing_the_video": "Возникла Неожиданная ошибка во время воспроизведения. Проверьте логи в настройках.", - "client_error": "Ошибка клиента", - "could_not_create_stream_for_chromecast": "Не удалось создать поток для Chromecast", - "message_from_server": "Сообщение от сервера: {{message}}", - "video_has_finished_playing": "Видео закончило воспроизводиться!", - "no_video_source": "Нет источника видео...", - "next_episode": "Следующая серия", - "refresh_tracks": "Обновить дорожки", - "subtitle_tracks": "Субтитры:", - "audio_tracks": "Аудио дорожки:", - "playback_state": "Состояние воспроизведения:", - "no_data_available": "Данные не доступны", - "index": "Индекс:" - }, - "item_card": { - "next_up": "Следующее", - "no_items_to_display": "Нет элементов для отображения", - "cast_and_crew": "Актеры и съемочная группа", - "series": "Серии", - "seasons": "Сезоны", - "season": "Сезон", - "no_episodes_for_this_season": "В этом сезоне нет серий", - "overview": "Обзор", - "more_with": "Больше с {{name}}", - "similar_items": "Похожие элементы", - "no_similar_items_found": "Похожие элементы не найдены", - "video": "Видео", - "more_details": "Больше деталей", - "quality": "Качество", - "audio": "Звук", - "subtitles": "Субтитры", - "show_more": "Показать больше", - "show_less": "Показать меньше", - "appeared_in": "Появлялся в", - "could_not_load_item": "Не удалось загрузить элемент", - "none": "Отсутствует", - "download": { - "download_season": "Загрузить сезон", - "download_series": "Загрузить сериал", - "download_episode": "Загрузить серию", - "download_movie": "Скачать фильм", - "download_x_item": "Загрузить {{item_count}} элементов", - "download_button": "Загрузить", - "using_optimized_server": "Использовать оптимизированный сервер", - "using_default_method": "Использовать стандартный метод", - } - }, - "live_tv": { - "next": "Следующая", - "previous": "Предыдущая", - "live_tv": "Прямой эфир ТВ", - "coming_soon": "Скоро", - "on_now": "Сейчас в эфире", - "shows": "Сериалы", - "movies": "Фильмы", - "sports": "Спорт", - "for_kids": "Для детей", - "news": "Новости" - }, - "jellyseerr": { - "confirm": "Подтвердить", - "cancel": "Отменить", - "yes": "Да", - "whats_wrong": "В чем дело?", - "issue_type": "Вид проблемы", - "select_an_issue": "Выберите проблему", - "types": "Типы", - "describe_the_issue": "(опционально) Опишите проблему...", - "submit_button": "Подать", - "report_issue_button": "Сообщить о проблеме", - "request_button": "Запросить", - "are_you_sure_you_want_to_request_all_seasons": "Вы уверены, что хотите запросить все сезоны?", - "failed_to_login": "Не удалось войти", - "cast": "Транслировать", - "details": "Детали", - "status": "Статус", - "original_title": "Оригинальное название", - "series_type": "Тип сериала", - "release_dates": "Дата релиза", - "first_air_date": "Первая дата выхода в эфир", - "next_air_date": "Следующая дата выхода в эфир", - "revenue": "Прибыль", - "budget": "Бюджет", - "original_language": "Оригинальный язык", - "production_country": "Страна производства", - "studios": "Студия", - "network": "Сеть", - "currently_streaming_on": "Сейчас доступно на", - "advanced": "Продвинутое", - "request_as": "Запросить как", - "tags": "Тэги", - "quality_profile": "Профиль качества", - "root_folder": "Корневая папка", - "season_all": "Сезон (все)", - "season_number": "Сезон {{season_number}}", - "number_episodes": "{{episode_number}} серий", - "born": "Рожден", - "appearances": "Появления", "toasts": { - "jellyseer_does_not_meet_requirements": "Сервер Jellyseerr не соответствует минимальным требованиям версии! Пожалуйста, обновите до версии не ниже 2.0.0", - "jellyseerr_test_failed": "Тест Jellyseerr не пройден. Попробуйте еще раз.", - "failed_to_test_jellyseerr_server_url": "Не удалось проверить URL-адрес сервера jellyseerr", - "issue_submitted": "Проблема отправлена!", - "requested_item": "Запрошено {{item}}!", - "you_dont_have_permission_to_request": "У вас нет разрешения на запрос!", - "something_went_wrong_requesting_media": "Что-то пошло не так при запросе медиафайлов!" + "error_deleting_files": "Ошибка при удалении файлов", + "background_downloads_enabled": "Фоновая загрузка включена", + "background_downloads_disabled": "Фоновая загрузка отключена", + "connected": "Подключено", + "could_not_connect": "Не удалось подключиться", + "invalid_url": "Неверный URL" } }, - "tabs": { - "home": "Дом", - "search": "Поиск", - "library": "Библиотека", - "custom_links": "Кастомные ссылки", - "favorites": "Избранное" + "sessions": { + "title": "Сессии", + "no_active_sessions": "Нет активных сессий" + }, + "downloads": { + "downloads_title": "Загрузки", + "tvseries": "Сериалы", + "movies": "Фильмы", + "queue": "Очередь", + "queue_hint": "Очередь и загрузки будут удалены при перезагрузке приложения", + "no_items_in_queue": "Нет элементов в очереди", + "no_downloaded_items": "Нет загруженых предметов", + "delete_all_movies_button": "Удалить все фильмы", + "delete_all_tvseries_button": "Удалить все сериалы", + "delete_all_button": "Удалить все", + "active_download": "Активно загружается", + "no_active_downloads": "Нет активных загрузок", + "active_downloads": "Активные загрузки", + "new_app_version_requires_re_download": "Новая версия приложения требует повторной загрузки", + "new_app_version_requires_re_download_description": "Новая версия приложения требует повторной загрузки. Пожалуйста удалите всё и попробуйте заново.", + "back": "Назад", + "delete": "Удалить", + "something_went_wrong": "Что-то пошло не так", + "could_not_get_stream_url_from_jellyfin": "Не удалось получить ссылку трансляции из Jellyfin", + "eta": "ETA {{eta}}", + "methods": "Методы", + "toasts": { + "you_are_not_allowed_to_download_files": "Нет разрешения на скачивание файлов.", + "deleted_all_movies_successfully": "Все фильмы были успешно удалены!", + "failed_to_delete_all_movies": "Возникла ошибка при удалении всех фильмов", + "deleted_all_tvseries_successfully": "Все сериалы были успешно удалены!", + "failed_to_delete_all_tvseries": "Возникла ошибка при удалении всех сериалов", + "download_cancelled": "Загрузка отменена", + "could_not_cancel_download": "Не удалось отменить загрузку", + "download_completed": "Загрузка завершена", + "download_started_for": "Загрузка {{item}} началась", + "item_is_ready_to_be_downloaded": "{{item}} готов к загрузке", + "download_stated_for_item": "Загрузка {{item} началась", + "download_failed_for_item": "Загрузка {{item}} провалилась с ошибкой: {{error}}", + "download_completed_for_item": "{{item}} успешно загружен", + "queued_item_for_optimization": "{{item}} поставлен в очередь для оптимизации", + "failed_to_start_download_for_item": "Не удалось начать загрузку {{item}}: {{message}}", + "server_responded_with_status_code": "Сервер ответил со статусом {{statusCode}}", + "no_response_received_from_server": "Нет ответа от сервера", + "error_setting_up_the_request": "Ошибка при создании запроса", + "failed_to_start_download_for_item_unexpected_error": "Не удалось начать загрузку {{item}}: Неожиданная ошибка", + "all_files_folders_and_jobs_deleted_successfully": "Все файлы, папки, и задачи были успешно удалены", + "an_error_occured_while_deleting_files_and_jobs": "Возникла ошибка при удалении файлов и работ", + "go_to_downloads": "В загрузки" + } } + }, + "search": { + "search_here": "Искать здесь...", + "search": "Поиск...", + "x_items": "{{count}} предметов", + "library": "Библиотека", + "discover": "Найти новое", + "no_results": "Нет результатов", + "no_results_found_for": "Не было результатов при поиске", + "movies": "Фильмы", + "series": "Сериалы", + "episodes": "Серии", + "collections": "Коллекции", + "actors": "Актеры", + "request_movies": "Запросить фильмы", + "request_series": "Запросить сериалы", + "recently_added": "Недавно добавлено", + "recent_requests": "Недавно запрошено", + "plex_watchlist": "Список просмотра с Plex", + "trending": "В тренде", + "popular_movies": "Популярные фильмы", + "movie_genres": "Популярные жанры", + "upcoming_movies": "Предстоящие фильмы", + "studios": "Студии", + "popular_tv": "Популярные сериалы", + "tv_genres": "жанры сериалов", + "upcoming_tv": "Предстоящие сериалы", + "networks": "Сети", + "tmdb_movie_keyword": "TMDB Ключевые слова фильмов", + "tmdb_movie_genre": "TMDB Жанры фильмов", + "tmdb_tv_keyword": "TMDB Ключевые слова сериалов", + "tmdb_tv_genre": "TMDB Жанры сериалов", + "tmdb_search": "TMDB Поиск", + "tmdb_studio": "TMDB Студии", + "tmdb_network": "TMDB Сеть", + "tmdb_movie_streaming_services": "TMDB Потоковые сервисы фильмов", + "tmdb_tv_streaming_services": "TMDB Потоковые сервисы сериалов" + }, + "library": { + "no_items_found": "элементы не найдены", + "no_results": "Нет результатов", + "no_libraries_found": "Библиотеки не найдены", + "item_types": { + "movies": "фильмы", + "series": "Сериалы", + "boxsets": "Коллекции", + "items": "элементы" + }, + "options": { + "display": "Отображать", + "row": "Ряд", + "list": "Список", + "image_style": "Стиль изображения", + "poster": "Постер", + "cover": "Обложка", + "show_titles": "Показывать загаловки", + "show_stats": "Показывать статистику" + }, + "filters": { + "genres": "Жанры", + "years": "Года", + "sort_by": "Сортировать по", + "sort_order": "Порядок сортировки", + "asc": "По Возрастанию", + "desc": "По убыванию", + "tags": "Тэги" + } + }, + "favorites": { + "series": "Сериалы", + "movies": "Фильмы", + "episodes": "Серии", + "videos": "Видео", + "boxsets": "Коллекции", + "playlists": "Плейлисты", + "noDataTitle": "Пока нет избранных", + "noData": "Отметьте элементы как избранные, чтобы они отображались здесь для быстрого доступа." + }, + "custom_links": { + "no_links": "Нет ссылок" + }, + "player": { + "error": "Ошибка", + "failed_to_get_stream_url": "Не удалось получить URL потока", + "an_error_occured_while_playing_the_video": "Возникла Неожиданная ошибка во время воспроизведения. Проверьте логи в настройках.", + "client_error": "Ошибка клиента", + "could_not_create_stream_for_chromecast": "Не удалось создать поток для Chromecast", + "message_from_server": "Сообщение от сервера: {{message}}", + "video_has_finished_playing": "Видео закончило воспроизводиться!", + "no_video_source": "Нет источника видео...", + "next_episode": "Следующая серия", + "refresh_tracks": "Обновить дорожки", + "subtitle_tracks": "Субтитры:", + "audio_tracks": "Аудио дорожки:", + "playback_state": "Состояние воспроизведения:", + "no_data_available": "Данные не доступны", + "index": "Индекс:", + "continue_watching": "Продолжить просмотр", + "go_back": "Назад" + }, + "item_card": { + "next_up": "Следующее", + "no_items_to_display": "Нет элементов для отображения", + "cast_and_crew": "Актеры и съемочная группа", + "series": "Серии", + "seasons": "Сезоны", + "season": "Сезон", + "no_episodes_for_this_season": "В этом сезоне нет серий", + "overview": "Обзор", + "more_with": "Больше с {{name}}", + "similar_items": "Похожие элементы", + "no_similar_items_found": "Похожие элементы не найдены", + "video": "Видео", + "more_details": "Больше деталей", + "quality": "Качество", + "audio": "Звук", + "subtitles": "Субтитры", + "show_more": "Показать больше", + "show_less": "Показать меньше", + "appeared_in": "Появлялся в", + "could_not_load_item": "Не удалось загрузить элемент", + "none": "Отсутствует", + "download": { + "download_season": "Загрузить сезон", + "download_series": "Загрузить сериал", + "download_episode": "Загрузить серию", + "download_movie": "Скачать фильм", + "download_x_item": "Загрузить {{item_count}} элементов", + "download_button": "Загрузить", + "using_optimized_server": "Использовать оптимизированный сервер", + "using_default_method": "Использовать стандартный метод" + } + }, + "live_tv": { + "next": "Следующая", + "previous": "Предыдущая", + "live_tv": "Прямой эфир ТВ", + "coming_soon": "Скоро", + "on_now": "Сейчас в эфире", + "shows": "Сериалы", + "movies": "Фильмы", + "sports": "Спорт", + "for_kids": "Для детей", + "news": "Новости" + }, + "jellyseerr": { + "confirm": "Подтвердить", + "cancel": "Отменить", + "yes": "Да", + "whats_wrong": "В чем дело?", + "issue_type": "Вид проблемы", + "select_an_issue": "Выберите проблему", + "types": "Типы", + "describe_the_issue": "(опционально) Опишите проблему...", + "submit_button": "Подать", + "report_issue_button": "Сообщить о проблеме", + "request_button": "Запросить", + "are_you_sure_you_want_to_request_all_seasons": "Вы уверены, что хотите запросить все сезоны?", + "failed_to_login": "Не удалось войти", + "cast": "Транслировать", + "details": "Детали", + "status": "Статус", + "original_title": "Оригинальное название", + "series_type": "Тип сериала", + "release_dates": "Дата релиза", + "first_air_date": "Первая дата выхода в эфир", + "next_air_date": "Следующая дата выхода в эфир", + "revenue": "Прибыль", + "budget": "Бюджет", + "original_language": "Оригинальный язык", + "production_country": "Страна производства", + "studios": "Студия", + "network": "Сеть", + "currently_streaming_on": "Сейчас доступно на", + "advanced": "Продвинутое", + "request_as": "Запросить как", + "tags": "Тэги", + "quality_profile": "Профиль качества", + "root_folder": "Корневая папка", + "season_all": "Сезон (все)", + "season_number": "Сезон {{season_number}}", + "number_episodes": "{{episode_number}} серий", + "born": "Рожден", + "appearances": "Появления", + "toasts": { + "jellyseer_does_not_meet_requirements": "Сервер Jellyseerr не соответствует минимальным требованиям версии! Пожалуйста, обновите до версии не ниже 2.0.0", + "jellyseerr_test_failed": "Тест Jellyseerr не пройден. Попробуйте еще раз.", + "failed_to_test_jellyseerr_server_url": "Не удалось проверить URL-адрес сервера jellyseerr", + "issue_submitted": "Проблема отправлена!", + "requested_item": "Запрошено {{item}}!", + "you_dont_have_permission_to_request": "У вас нет разрешения на запрос!", + "something_went_wrong_requesting_media": "Что-то пошло не так при запросе медиафайлов!" + } + }, + "tabs": { + "home": "Дом", + "search": "Поиск", + "library": "Библиотека", + "custom_links": "Кастомные ссылки", + "favorites": "Избранное" } - \ No newline at end of file +} diff --git a/translations/sv.json b/translations/sv.json index 65cc3aed..d6f185ff 100644 --- a/translations/sv.json +++ b/translations/sv.json @@ -30,5 +30,24 @@ "home": "Hem", "search": "Sök", "library": "Bibliotek" + }, + "player": { + "error": "Fel", + "failed_to_get_stream_url": "Kunde inte hämta stream-URL", + "an_error_occured_while_playing_the_video": "Ett fel uppstod vid uppspelning av videon. Kontrollera loggarna i inställningarna.", + "client_error": "Klientfel", + "could_not_create_stream_for_chromecast": "Kunde inte skapa stream för Chromecast", + "message_from_server": "Meddelande från servern: {{message}}", + "video_has_finished_playing": "Videon har spelat klart!", + "no_video_source": "Ingen videokälla...", + "next_episode": "Nästa avsnitt", + "refresh_tracks": "Uppdatera spår", + "subtitle_tracks": "Textspår:", + "audio_tracks": "Ljudspår:", + "playback_state": "Uppspelningsstatus:", + "no_data_available": "Inga data tillgängliga", + "index": "Index:", + "continue_watching": "Fortsätt titta", + "go_back": "Tillbaka" } } diff --git a/translations/tr.json b/translations/tr.json index 46254c01..ea08de58 100644 --- a/translations/tr.json +++ b/translations/tr.json @@ -137,7 +137,9 @@ "show_custom_menu_links": "Özel Menü Bağlantılarını Göster", "hide_libraries": "Kütüphaneleri Gizle", "select_liraries_you_want_to_hide": "Kütüphane sekmesinden ve ana sayfa bölümlerinden gizlemek istediğiniz kütüphaneleri seçin.", - "disable_haptic_feedback": "Dokunsal Geri Bildirimi Devre Dışı Bırak" + "disable_haptic_feedback": "Dokunsal Geri Bildirimi Devre Dışı Bırak", + "default_quality": "Varsayılan kalite", + "disabled": "Devre dışı" }, "downloads": { "downloads_title": "İndirmeler", @@ -369,7 +371,9 @@ "audio_tracks": "Ses Parçaları:", "playback_state": "Oynatma Durumu:", "no_data_available": "Veri bulunamadı", - "index": "İndeks:" + "index": "İndeks:", + "continue_watching": "İzlemeye devam et", + "go_back": "Geri" }, "item_card": { "next_up": "Sıradaki", diff --git a/translations/uk.json b/translations/uk.json index 488b0188..96cdcb67 100644 --- a/translations/uk.json +++ b/translations/uk.json @@ -137,8 +137,9 @@ "show_custom_menu_links": "Показати користувацькі посилання меню", "hide_libraries": "Сховати медіатеки", "select_liraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.", - "disable_haptic_feedback": "Вимкнути тактильний відгук", - "default_quality": "Якість за замовченням" + "disable_haptic_feedback": "Вимкнути тактильний зворотний зв'язок", + "default_quality": "Якість за замовченням", + "disabled": "Вимкнено" }, "downloads": { "downloads_title": "Завантаження", @@ -374,7 +375,9 @@ "audio_tracks": "Аудіо-доріжки:", "playback_state": "Стан відтворення:", "no_data_available": "Дані відсутні", - "index": "Індекс:" + "index": "Індекс:", + "continue_watching": "Продовжити перегляд", + "go_back": "Назад" }, "item_card": { "next_up": "Далі", @@ -477,4 +480,4 @@ "custom_links": "Ваші Посилання", "favorites": "Улюблене" } -} \ No newline at end of file +} diff --git a/translations/zh-CN.json b/translations/zh-CN.json index 668d8522..6a0a2c11 100644 --- a/translations/zh-CN.json +++ b/translations/zh-CN.json @@ -369,7 +369,9 @@ "audio_tracks": "音频轨道:", "playback_state": "播放状态:", "no_data_available": "无可用数据", - "index": "索引:" + "index": "索引:", + "continue_watching": "继续观看", + "go_back": "返回" }, "item_card": { "next_up": "下一个", diff --git a/translations/zh-TW.json b/translations/zh-TW.json index a794b4ee..b52cb680 100644 --- a/translations/zh-TW.json +++ b/translations/zh-TW.json @@ -137,7 +137,9 @@ "show_custom_menu_links": "顯示自定義菜單鏈接", "hide_libraries": "隱藏媒體庫", "select_liraries_you_want_to_hide": "選擇您想從媒體庫頁面和主頁隱藏的媒體庫。", - "disable_haptic_feedback": "禁用觸覺回饋" + "disable_haptic_feedback": "禁用觸覺回饋", + "default_quality": "預設品質", + "disabled": "已停用" }, "downloads": { "downloads_title": "下載", @@ -369,7 +371,9 @@ "audio_tracks": "音頻軌道:", "playback_state": "播放狀態:", "no_data_available": "無可用數據", - "index": "索引:" + "index": "索引:", + "continue_watching": "繼續觀看", + "go_back": "返回" }, "item_card": { "next_up": "下一個", diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index 6b69c608..d21d3839 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -114,6 +114,11 @@ export type HomeSectionNextUpResolver = { enableRewatching?: boolean; }; +export interface MaxAutoPlayEpisodeCount { + key: string; + value: number; +} + export type HomeSectionLatestResolver = { parentId?: string; limit?: number; @@ -163,6 +168,8 @@ export type Settings = { hiddenLibraries?: string[]; enableH265ForChromecast: boolean; defaultPlayer: VideoPlayer; + maxAutoPlayEpisodeCount: MaxAutoPlayEpisodeCount; + autoPlayEpisodeCount: number; }; export interface Lockable { @@ -217,7 +224,9 @@ const defaultValues: Settings = { jellyseerrServerUrl: undefined, hiddenLibraries: [], enableH265ForChromecast: false, - defaultPlayer: VideoPlayer.VLC_3, // ios only setting. does not matter what this is for android + defaultPlayer: VideoPlayer.VLC_3, // ios-only setting. does not matter what this is for android + maxAutoPlayEpisodeCount: { key: "3", value: 3 }, + autoPlayEpisodeCount: 0, }; const loadSettings = (): Partial => { @@ -236,11 +245,11 @@ const loadSettings = (): Partial => { const EXCLUDE_FROM_SAVE = ["home"]; const saveSettings = (settings: Settings) => { - Object.keys(settings).forEach((key) => { + for (const key of Object.keys(settings)) { if (EXCLUDE_FROM_SAVE.includes(key)) { delete settings[key as keyof Settings]; } - }); + } const jsonValue = JSON.stringify(settings); storage.set("settings", jsonValue); }; @@ -271,7 +280,9 @@ export const useSettings = () => { ); const refreshStreamyfinPluginSettings = useCallback(async () => { - if (!api) return; + if (!api) { + return; + } const settings = await api.getStreamyfinPluginConfig().then( ({ data }) => { writeInfoLog("Got plugin settings", data?.settings); @@ -284,7 +295,9 @@ export const useSettings = () => { }, [api]); const updateSettings = (update: Partial) => { - if (!_settings) return; + if (!_settings) { + return; + } const hasChanges = Object.entries(update).some( ([key, value]) => _settings[key as keyof Settings] !== value, ); @@ -305,34 +318,31 @@ export const useSettings = () => { // If admin sets locked to false but provides a value, // use user settings first and fallback on admin setting if required. const settings: Settings = useMemo(() => { - let unlockedPluginDefaults = {} as Settings; - const overrideSettings = Object.entries(pluginSettings || {}).reduce( - (acc, [key, setting]) => { - if (setting) { - const { value, locked } = setting; + const unlockedPluginDefaults = {} as Settings; + const overrideSettings = Object.entries(pluginSettings ?? {}).reduce< + Partial + >((acc, [key, setting]) => { + if (setting) { + const { value, locked } = setting; + const settingsKey = key as keyof Settings; - // Make sure we override default settings with plugin settings when they are not locked. - // Admin decided what users defaults should be and grants them the ability to change them too. - if ( - locked === false && - value && - _settings?.[key as keyof Settings] !== value - ) { - unlockedPluginDefaults = Object.assign(unlockedPluginDefaults, { - [key as keyof Settings]: value, - }); - } - - acc = Object.assign(acc, { - [key]: locked - ? value - : (_settings?.[key as keyof Settings] ?? value), + // Make sure we override default settings with plugin settings when they are not locked. + if ( + !locked && + value !== undefined && + _settings?.[settingsKey] !== value + ) { + Object.assign(unlockedPluginDefaults, { + [settingsKey]: value, }); } - return acc; - }, - {} as Settings, - ); + + Object.assign(acc, { + [settingsKey]: locked ? value : (_settings?.[settingsKey] ?? value), + }); + } + return acc; + }, {}); return { ...defaultValues,