feat: add "Are you still watching" modal overlay with configurable options (#663)

Co-authored-by: Fredrik Burmester <fredrik.burmester@gmail.com>
This commit is contained in:
Ahmed Sbai
2025-05-18 09:21:50 +02:00
committed by GitHub
parent 963a54a36c
commit 99938ddf5a
22 changed files with 784 additions and 544 deletions

View File

@@ -66,11 +66,14 @@ export const PlayButton: React.FC<Props> = ({
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],

View File

@@ -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 = () => {
}
/>
</ListItem>
<ListItem title={t("home.settings.other.max_auto_play_episode_count")}>
<Dropdown
data={AUTOPLAY_EPISODES_COUNT(t)}
keyExtractor={(item) => item.key}
titleExtractor={(item) => item.key}
title={
<TouchableOpacity className='flex flex-row items-center justify-between py-3 pl-3'>
<Text className='mr-1 text-[#8E8D91]'>
{t(settings?.maxAutoPlayEpisodeCount.key)}
</Text>
<Ionicons
name='chevron-expand-sharp'
size={18}
color='#5A5960'
/>
</TouchableOpacity>
}
label={t("home.settings.other.max_auto_play_episode_count")}
onSelected={(maxAutoPlayEpisodeCount) =>
updateSettings({ maxAutoPlayEpisodeCount })
}
/>
</ListItem>
</ListGroup>
</DisabledSetting>
);
};
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 },
];

View File

@@ -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<ContinueWatchingOverlayProps> = ({
goToNextItem,
}) => {
const [settings] = useSettings();
const router = useRouter();
return settings.autoPlayEpisodeCount >=
settings.maxAutoPlayEpisodeCount.value ? (
<View
className={
"absolute top-0 bottom-0 left-0 right-0 flex flex-col px-4 items-center justify-center bg-[#000000B3]"
}
>
<Text className='text-2xl font-bold text-white py-4 '>
Are you still watching ?
</Text>
<Button
onPress={() => {
goToNextItem({ isAutoPlay: false, resetWatchCount: true });
}}
color={"purple"}
className='my-4 w-2/3'
>
{t("player.continue_watching")}
</Button>
<Button onPress={router.back} color={"transparent"} className='w-2/3'>
{t("player.go_back")}
</Button>
</View>
) : null;
};
export default ContinueWatchingOverlay;

View File

@@ -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<Props> = ({
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<Props> = ({
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<Props> = ({
};
const handleSliderStart = useCallback(() => {
if (!showControls) return;
if (!showControls) {
return;
}
setIsSliding(true);
wasPlayingRef.current = isPlaying;
@@ -339,7 +403,9 @@ export const Controls: FC<Props> = ({
);
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<Props> = ({
? 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<Props> = ({
{nextItem && !offline && (
<TouchableOpacity
onPress={goToNextItem}
onPress={() => goToNextItem({ isAutoPlay: false })}
className='aspect-square flex flex-col rounded-xl items-center justify-center p-2'
>
<Ionicons name='play-skip-forward' size={24} color='white' />
@@ -741,17 +809,21 @@ export const Controls: FC<Props> = ({
onPress={skipCredit}
buttonText='Skip Credits'
/>
<NextEpisodeCountDownButton
show={
!nextItem
? false
: isVlc
? remainingTime < 10000
: remainingTime < 10
}
onFinish={goToNextItem}
onPress={goToNextItem}
/>
{(settings.maxAutoPlayEpisodeCount.value === -1 ||
settings.autoPlayEpisodeCount <
settings.maxAutoPlayEpisodeCount.value) && (
<NextEpisodeCountDownButton
show={
!nextItem
? false
: isVlc
? remainingTime < 10000
: remainingTime < 10
}
onFinish={handleNextEpisodeAutoPlay}
onPress={handleNextEpisodeManual}
/>
)}
</View>
</View>
<View
@@ -799,6 +871,9 @@ export const Controls: FC<Props> = ({
</View>
</>
)}
{settings.maxAutoPlayEpisodeCount.value !== -1 && (
<ContinueWatchingOverlay goToNextItem={handleContinueWatching} />
)}
</ControlProvider>
);
};

View File

@@ -79,7 +79,7 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
>
<Animated.View style={animatedStyle} />
<View className='px-3 py-3'>
<Text className='text-center font-bold'>
<Text numberOfLines={1} className='text-center font-bold'>
{t("player.next_episode")}
</Text>
</View>

View File

@@ -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";

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "次",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "Избранное"
}
}

View File

@@ -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"
}
}

View File

@@ -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",

View File

@@ -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": "Улюблене"
}
}
}

View File

@@ -369,7 +369,9 @@
"audio_tracks": "音频轨道:",
"playback_state": "播放状态:",
"no_data_available": "无可用数据",
"index": "索引:"
"index": "索引:",
"continue_watching": "继续观看",
"go_back": "返回"
},
"item_card": {
"next_up": "下一个",

View File

@@ -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": "下一個",

View File

@@ -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<T> {
@@ -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<Settings> => {
@@ -236,11 +245,11 @@ const loadSettings = (): Partial<Settings> => {
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<Settings>) => {
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<Settings>
>((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,