mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-18 10:14:17 +01:00
Compare commits
7 Commits
fix/plugin
...
feat/andro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c8268800d | ||
|
|
76d9b3177b | ||
|
|
2e79815d78 | ||
|
|
43ce50fe70 | ||
|
|
bd0eef0407 | ||
|
|
16bb1b8717 | ||
|
|
259306df52 |
@@ -71,7 +71,7 @@ export default function AppearanceHideLibrariesPage() {
|
|||||||
))}
|
))}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||||
{t("home.settings.other.select_liraries_you_want_to_hide")}
|
{t("home.settings.other.select_libraries_you_want_to_hide")}
|
||||||
</Text>
|
</Text>
|
||||||
</DisabledSetting>
|
</DisabledSetting>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default function HideLibrariesPage() {
|
|||||||
))}
|
))}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||||
{t("home.settings.other.select_liraries_you_want_to_hide")}
|
{t("home.settings.other.select_libraries_you_want_to_hide")}
|
||||||
</Text>
|
</Text>
|
||||||
</DisabledSetting>
|
</DisabledSetting>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
|
import { JellyseerrSettings } from "@/components/settings/Jellyseerr";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
export default function JellyseerrPluginPage() {
|
export default function JellyseerrPluginPage() {
|
||||||
|
const { pluginSettings } = useSettings();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -13,9 +16,12 @@ export default function JellyseerrPluginPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className='p-4'>
|
<DisabledSetting
|
||||||
|
disabled={pluginSettings?.jellyseerrServerUrl?.locked === true}
|
||||||
|
className='p-4'
|
||||||
|
>
|
||||||
<JellyseerrSettings />
|
<JellyseerrSettings />
|
||||||
</View>
|
</DisabledSetting>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import { KefinTweaksSettings } from "@/components/settings/KefinTweaks";
|
import { KefinTweaksSettings } from "@/components/settings/KefinTweaks";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
export default function KefinTweaksPage() {
|
export default function KefinTweaksPage() {
|
||||||
|
const { pluginSettings } = useSettings();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -13,9 +16,12 @@ export default function KefinTweaksPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className='px-4'>
|
<DisabledSetting
|
||||||
|
disabled={pluginSettings?.useKefinTweaks?.locked === true}
|
||||||
|
className='p-4'
|
||||||
|
>
|
||||||
<KefinTweaksSettings />
|
<KefinTweaksSettings />
|
||||||
</View>
|
</DisabledSetting>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useNavigation } from "expo-router";
|
import { useNavigation } from "expo-router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
Linking,
|
Linking,
|
||||||
@@ -14,24 +14,22 @@ import { toast } from "sonner-native";
|
|||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { ListGroup } from "@/components/list/ListGroup";
|
import { ListGroup } from "@/components/list/ListGroup";
|
||||||
import { ListItem } from "@/components/list/ListItem";
|
import { ListItem } from "@/components/list/ListItem";
|
||||||
|
import DisabledSetting from "@/components/settings/DisabledSetting";
|
||||||
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
|
import { useNetworkAwareQueryClient } from "@/hooks/useNetworkAwareQueryClient";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
export default function MarlinSearchPage() {
|
export default function MarlinSearchPage() {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
const { settings, updateSettings, pluginSettings } = useSettings();
|
||||||
const queryClient = useNetworkAwareQueryClient();
|
const queryClient = useNetworkAwareQueryClient();
|
||||||
|
|
||||||
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
|
const [value, setValue] = useState<string>(settings?.marlinServerUrl || "");
|
||||||
|
|
||||||
const searchEngineLocked = pluginSettings?.searchEngine?.locked === true;
|
|
||||||
const marlinUrlLocked = pluginSettings?.marlinServerUrl?.locked === true;
|
|
||||||
// Effective (user/admin merged) URL, same source the search screen uses —
|
|
||||||
// the raw plugin value misses a user-configured Streamystats.
|
|
||||||
const hasStreamystats = !!settings?.streamyStatsServerUrl;
|
|
||||||
|
|
||||||
const onSave = (val: string) => {
|
const onSave = (val: string) => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
|
marlinServerUrl: !val.endsWith("/") ? val : val.slice(0, -1),
|
||||||
@@ -43,8 +41,15 @@ export default function MarlinSearchPage() {
|
|||||||
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
|
Linking.openURL("https://github.com/fredrikburmester/marlin-search");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disabled = useMemo(() => {
|
||||||
|
return (
|
||||||
|
pluginSettings?.searchEngine?.locked === true &&
|
||||||
|
pluginSettings?.marlinServerUrl?.locked === true
|
||||||
|
);
|
||||||
|
}, [pluginSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!marlinUrlLocked) {
|
if (!pluginSettings?.marlinServerUrl?.locked) {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<TouchableOpacity onPress={() => onSave(value)} className='px-2'>
|
<TouchableOpacity onPress={() => onSave(value)} className='px-2'>
|
||||||
@@ -55,7 +60,7 @@ export default function MarlinSearchPage() {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [navigation, value, marlinUrlLocked, t]);
|
}, [navigation, value, pluginSettings?.marlinServerUrl?.locked, t]);
|
||||||
|
|
||||||
if (!settings) return null;
|
if (!settings) return null;
|
||||||
|
|
||||||
@@ -67,18 +72,19 @@ export default function MarlinSearchPage() {
|
|||||||
paddingRight: insets.right,
|
paddingRight: insets.right,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className='px-4'>
|
<DisabledSetting disabled={disabled} className='px-4'>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
{/* disabledByAdmin renders the "Disabled by admin" notice as the row's
|
<DisabledSetting
|
||||||
subtitle (same pattern as the Streamystats settings) — no clipping. */}
|
disabled={
|
||||||
|
pluginSettings?.searchEngine?.locked === true ||
|
||||||
|
!!pluginSettings?.streamyStatsServerUrl?.value
|
||||||
|
}
|
||||||
|
showText={!pluginSettings?.marlinServerUrl?.locked}
|
||||||
|
>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.marlin_search.enable_marlin_search",
|
"home.settings.plugins.marlin_search.enable_marlin_search",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={searchEngineLocked}
|
|
||||||
// Streamystats owns the search engine while configured — block the
|
|
||||||
// row tap too, not just the Switch, so it can't force "Jellyfin".
|
|
||||||
disabled={hasStreamystats}
|
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
updateSettings({ searchEngine: "Jellyfin" });
|
updateSettings({ searchEngine: "Jellyfin" });
|
||||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||||
@@ -86,23 +92,32 @@ export default function MarlinSearchPage() {
|
|||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.searchEngine === "Marlin"}
|
value={settings.searchEngine === "Marlin"}
|
||||||
disabled={searchEngineLocked || hasStreamystats}
|
disabled={!!pluginSettings?.streamyStatsServerUrl?.value}
|
||||||
onValueChange={(val) => {
|
onValueChange={(value) => {
|
||||||
updateSettings({ searchEngine: val ? "Marlin" : "Jellyfin" });
|
updateSettings({
|
||||||
|
searchEngine: value ? "Marlin" : "Jellyfin",
|
||||||
|
});
|
||||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
</DisabledSetting>
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
|
|
||||||
<ListGroup className='mt-2'>
|
<DisabledSetting
|
||||||
<ListItem
|
disabled={pluginSettings?.marlinServerUrl?.locked === true}
|
||||||
title={t("home.settings.plugins.marlin_search.url")}
|
showText={!pluginSettings?.searchEngine?.locked}
|
||||||
disabledByAdmin={marlinUrlLocked}
|
className='mt-2 flex flex-col rounded-xl overflow-hidden pl-4 bg-neutral-900 px-4'
|
||||||
>
|
>
|
||||||
|
<View
|
||||||
|
className={"flex flex-row items-center bg-neutral-900 h-11 pr-4"}
|
||||||
|
>
|
||||||
|
<Text className='mr-4'>
|
||||||
|
{t("home.settings.plugins.marlin_search.url")}
|
||||||
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
editable={!marlinUrlLocked && settings.searchEngine === "Marlin"}
|
editable={settings.searchEngine === "Marlin"}
|
||||||
className='text-white text-right flex-1'
|
className='text-white'
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"home.settings.plugins.marlin_search.server_url_placeholder",
|
"home.settings.plugins.marlin_search.server_url_placeholder",
|
||||||
)}
|
)}
|
||||||
@@ -113,16 +128,15 @@ export default function MarlinSearchPage() {
|
|||||||
textContentType='URL'
|
textContentType='URL'
|
||||||
onChangeText={(text) => setValue(text)}
|
onChangeText={(text) => setValue(text)}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</View>
|
||||||
</ListGroup>
|
</DisabledSetting>
|
||||||
|
|
||||||
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
<Text className='px-4 text-xs text-neutral-500 mt-1'>
|
||||||
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
|
{t("home.settings.plugins.marlin_search.marlin_search_hint")}{" "}
|
||||||
<Text className='text-blue-500' onPress={handleOpenLink}>
|
<Text className='text-blue-500' onPress={handleOpenLink}>
|
||||||
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
|
{t("home.settings.plugins.marlin_search.read_more_about_marlin")}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</DisabledSetting>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,9 @@
|
|||||||
import { useCallback } from "react";
|
import { Platform, ScrollView, View } from "react-native";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Platform, ScrollView, TouchableOpacity, View } from "react-native";
|
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { toast } from "sonner-native";
|
|
||||||
import { Text } from "@/components/common/Text";
|
|
||||||
import { PluginSettings } from "@/components/settings/PluginSettings";
|
import { PluginSettings } from "@/components/settings/PluginSettings";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
|
||||||
|
|
||||||
export default function PluginsPage() {
|
export default function PluginsPage() {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { t } = useTranslation();
|
|
||||||
const { refreshStreamyfinPluginSettings } = useSettings();
|
|
||||||
|
|
||||||
const handleRefreshFromServer = useCallback(async () => {
|
|
||||||
// Returns undefined when the API call fails (handled internally).
|
|
||||||
const refreshed = await refreshStreamyfinPluginSettings();
|
|
||||||
if (refreshed) {
|
|
||||||
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
t("home.settings.plugins.streamystats.toasts.refresh_failed"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [refreshStreamyfinPluginSettings, t]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@@ -37,17 +18,6 @@ export default function PluginsPage() {
|
|||||||
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }}
|
||||||
>
|
>
|
||||||
<PluginSettings />
|
<PluginSettings />
|
||||||
|
|
||||||
{/* Pulls the centralised Streamyfin plugin settings for every plugin,
|
|
||||||
so it lives on the plugins index rather than inside Streamystats. */}
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleRefreshFromServer}
|
|
||||||
className='py-3 rounded-xl bg-neutral-800'
|
|
||||||
>
|
|
||||||
<Text className='text-center text-blue-500'>
|
|
||||||
{t("home.settings.plugins.streamystats.refresh_from_server")}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ export default function StreamystatsPage() {
|
|||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
const {
|
||||||
|
settings,
|
||||||
|
updateSettings,
|
||||||
|
pluginSettings,
|
||||||
|
refreshStreamyfinPluginSettings,
|
||||||
|
} = useSettings();
|
||||||
const queryClient = useNetworkAwareQueryClient();
|
const queryClient = useNetworkAwareQueryClient();
|
||||||
|
|
||||||
// Local state for all editable fields
|
// Local state for all editable fields
|
||||||
@@ -44,21 +49,7 @@ export default function StreamystatsPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true;
|
const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true;
|
||||||
const searchLocked = pluginSettings?.searchEngine?.locked === true;
|
const isStreamystatsEnabled = !!url;
|
||||||
const movieRecsLocked =
|
|
||||||
pluginSettings?.streamyStatsMovieRecommendations?.locked === true;
|
|
||||||
const seriesRecsLocked =
|
|
||||||
pluginSettings?.streamyStatsSeriesRecommendations?.locked === true;
|
|
||||||
const promotedWatchlistsLocked =
|
|
||||||
pluginSettings?.streamyStatsPromotedWatchlists?.locked === true;
|
|
||||||
const hideWatchlistsTabLocked =
|
|
||||||
pluginSettings?.hideWatchlistsTab?.locked === true;
|
|
||||||
// The input renders the locked admin URL; enablement must follow the same
|
|
||||||
// effective value or every toggle stays disabled until local state syncs.
|
|
||||||
const effectiveUrl = isUrlLocked
|
|
||||||
? (settings?.streamyStatsServerUrl ?? "")
|
|
||||||
: url;
|
|
||||||
const isStreamystatsEnabled = !!effectiveUrl;
|
|
||||||
|
|
||||||
const onSave = useCallback(() => {
|
const onSave = useCallback(() => {
|
||||||
const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
||||||
@@ -122,6 +113,17 @@ export default function StreamystatsPage() {
|
|||||||
Linking.openURL("https://github.com/fredrikburmester/streamystats");
|
Linking.openURL("https://github.com/fredrikburmester/streamystats");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefreshFromServer = useCallback(async () => {
|
||||||
|
const newPluginSettings = await refreshStreamyfinPluginSettings();
|
||||||
|
// Update local state with new values
|
||||||
|
const newUrl = newPluginSettings?.streamyStatsServerUrl?.value || "";
|
||||||
|
setUrl(newUrl);
|
||||||
|
if (newUrl) {
|
||||||
|
setUseForSearch(true);
|
||||||
|
}
|
||||||
|
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
|
||||||
|
}, [refreshStreamyfinPluginSettings, t]);
|
||||||
|
|
||||||
if (!settings) return null;
|
if (!settings) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -144,7 +146,7 @@ export default function StreamystatsPage() {
|
|||||||
placeholder={t(
|
placeholder={t(
|
||||||
"home.settings.plugins.streamystats.server_url_placeholder",
|
"home.settings.plugins.streamystats.server_url_placeholder",
|
||||||
)}
|
)}
|
||||||
value={effectiveUrl}
|
value={url}
|
||||||
keyboardType='url'
|
keyboardType='url'
|
||||||
returnKeyType='done'
|
returnKeyType='done'
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
@@ -169,18 +171,11 @@ export default function StreamystatsPage() {
|
|||||||
>
|
>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t("home.settings.plugins.streamystats.enable_search")}
|
title={t("home.settings.plugins.streamystats.enable_search")}
|
||||||
disabledByAdmin={searchLocked}
|
disabledByAdmin={pluginSettings?.searchEngine?.locked === true}
|
||||||
>
|
>
|
||||||
{/* Locked controls show the live admin value and can't be toggled —
|
|
||||||
local form state would let the switch flip while the write guard
|
|
||||||
drops the change. */}
|
|
||||||
<Switch
|
<Switch
|
||||||
value={
|
value={useForSearch}
|
||||||
searchLocked
|
disabled={!isStreamystatsEnabled}
|
||||||
? settings?.searchEngine === "Streamystats"
|
|
||||||
: useForSearch
|
|
||||||
}
|
|
||||||
disabled={!isStreamystatsEnabled || searchLocked}
|
|
||||||
onValueChange={setUseForSearch}
|
onValueChange={setUseForSearch}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@@ -188,62 +183,52 @@ export default function StreamystatsPage() {
|
|||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.streamystats.enable_movie_recommendations",
|
"home.settings.plugins.streamystats.enable_movie_recommendations",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={movieRecsLocked}
|
disabledByAdmin={
|
||||||
|
pluginSettings?.streamyStatsMovieRecommendations?.locked === true
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
value={
|
value={movieRecs}
|
||||||
movieRecsLocked
|
|
||||||
? (settings?.streamyStatsMovieRecommendations ?? false)
|
|
||||||
: movieRecs
|
|
||||||
}
|
|
||||||
onValueChange={setMovieRecs}
|
onValueChange={setMovieRecs}
|
||||||
disabled={!isStreamystatsEnabled || movieRecsLocked}
|
disabled={!isStreamystatsEnabled}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.streamystats.enable_series_recommendations",
|
"home.settings.plugins.streamystats.enable_series_recommendations",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={seriesRecsLocked}
|
disabledByAdmin={
|
||||||
|
pluginSettings?.streamyStatsSeriesRecommendations?.locked === true
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
value={
|
value={seriesRecs}
|
||||||
seriesRecsLocked
|
|
||||||
? (settings?.streamyStatsSeriesRecommendations ?? false)
|
|
||||||
: seriesRecs
|
|
||||||
}
|
|
||||||
onValueChange={setSeriesRecs}
|
onValueChange={setSeriesRecs}
|
||||||
disabled={!isStreamystatsEnabled || seriesRecsLocked}
|
disabled={!isStreamystatsEnabled}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t(
|
title={t(
|
||||||
"home.settings.plugins.streamystats.enable_promoted_watchlists",
|
"home.settings.plugins.streamystats.enable_promoted_watchlists",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={promotedWatchlistsLocked}
|
disabledByAdmin={
|
||||||
|
pluginSettings?.streamyStatsPromotedWatchlists?.locked === true
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
value={
|
value={promotedWatchlists}
|
||||||
promotedWatchlistsLocked
|
|
||||||
? (settings?.streamyStatsPromotedWatchlists ?? false)
|
|
||||||
: promotedWatchlists
|
|
||||||
}
|
|
||||||
onValueChange={setPromotedWatchlists}
|
onValueChange={setPromotedWatchlists}
|
||||||
disabled={!isStreamystatsEnabled || promotedWatchlistsLocked}
|
disabled={!isStreamystatsEnabled}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem
|
<ListItem
|
||||||
title={t("home.settings.plugins.streamystats.hide_watchlists_tab")}
|
title={t("home.settings.plugins.streamystats.hide_watchlists_tab")}
|
||||||
disabledByAdmin={hideWatchlistsTabLocked}
|
disabledByAdmin={pluginSettings?.hideWatchlistsTab?.locked === true}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
value={
|
value={hideWatchlistsTab}
|
||||||
hideWatchlistsTabLocked
|
|
||||||
? (settings?.hideWatchlistsTab ?? false)
|
|
||||||
: hideWatchlistsTab
|
|
||||||
}
|
|
||||||
onValueChange={setHideWatchlistsTab}
|
onValueChange={setHideWatchlistsTab}
|
||||||
disabled={!isStreamystatsEnabled || hideWatchlistsTabLocked}
|
disabled={!isStreamystatsEnabled}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
@@ -251,6 +236,15 @@ export default function StreamystatsPage() {
|
|||||||
{t("home.settings.plugins.streamystats.home_sections_hint")}
|
{t("home.settings.plugins.streamystats.home_sections_hint")}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={handleRefreshFromServer}
|
||||||
|
className='mt-6 py-3 rounded-xl bg-neutral-800'
|
||||||
|
>
|
||||||
|
<Text className='text-center text-blue-500'>
|
||||||
|
{t("home.settings.plugins.streamystats.refresh_from_server")}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
{/* Disable button - only show if URL is not locked and Streamystats is enabled */}
|
{/* Disable button - only show if URL is not locked and Streamystats is enabled */}
|
||||||
{!isUrlLocked && isStreamystatsEnabled && (
|
{!isUrlLocked && isStreamystatsEnabled && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|||||||
@@ -1318,7 +1318,7 @@ export default function DirectPlayerPage() {
|
|||||||
console.error("Video Error:", e.nativeEvent);
|
console.error("Video Error:", e.nativeEvent);
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t("player.error"),
|
t("player.error"),
|
||||||
t("player.an_error_occured_while_playing_the_video"),
|
t("player.an_error_occurred_while_playing_the_video"),
|
||||||
);
|
);
|
||||||
writeToLog("ERROR", "Video Error", e.nativeEvent);
|
writeToLog("ERROR", "Video Error", e.nativeEvent);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ configureReanimatedLogger({
|
|||||||
if (!Platform.isTV) {
|
if (!Platform.isTV) {
|
||||||
Notifications.setNotificationHandler({
|
Notifications.setNotificationHandler({
|
||||||
handleNotification: async () => ({
|
handleNotification: async () => ({
|
||||||
shouldShowAlert: true,
|
shouldShowBanner: true,
|
||||||
|
shouldShowList: true,
|
||||||
shouldPlaySound: true,
|
shouldPlaySound: true,
|
||||||
shouldSetBadge: false,
|
shouldSetBadge: false,
|
||||||
}),
|
}),
|
||||||
@@ -350,9 +351,12 @@ function Layout() {
|
|||||||
notificationListener.current =
|
notificationListener.current =
|
||||||
Notifications?.addNotificationReceivedListener(
|
Notifications?.addNotificationReceivedListener(
|
||||||
(notification: Notification) => {
|
(notification: Notification) => {
|
||||||
|
// Log only the title — serializing the whole notification touches
|
||||||
|
// the deprecated dataString getter (deprecation warning) and dumps
|
||||||
|
// noisy payloads into the console.
|
||||||
console.log(
|
console.log(
|
||||||
"Notification received while app running",
|
"Notification received while app running:",
|
||||||
notification,
|
notification.request.content.title,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,9 +16,12 @@ export const SeriesCard: React.FC<{ items: BaseItemDto[] }> = ({ items }) => {
|
|||||||
const { showActionSheetWithOptions } = useActionSheet();
|
const { showActionSheetWithOptions } = useActionSheet();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Keyed on SeriesId so recycled FlashList cells re-read the correct poster
|
||||||
|
// instead of freezing the first-rendered series' image (empty deps bug).
|
||||||
const base64Image = useMemo(() => {
|
const base64Image = useMemo(() => {
|
||||||
return storage.getString(items[0].SeriesId!);
|
const seriesId = items[0]?.SeriesId;
|
||||||
}, []);
|
return seriesId ? storage.getString(seriesId) : undefined;
|
||||||
|
}, [items[0]?.SeriesId]);
|
||||||
|
|
||||||
const deleteSeries = useCallback(
|
const deleteSeries = useCallback(
|
||||||
async () =>
|
async () =>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import type { PropsWithChildren, ReactNode } from "react";
|
import type { PropsWithChildren, ReactNode } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { TouchableOpacity, View, type ViewProps } from "react-native";
|
import { TouchableOpacity, View, type ViewProps } from "react-native";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
|
|
||||||
@@ -33,10 +32,7 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
disabledByAdmin = false,
|
disabledByAdmin = false,
|
||||||
...viewProps
|
...viewProps
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const effectiveSubtitle = disabledByAdmin ? "Disabled by admin" : subtitle;
|
||||||
const effectiveSubtitle = disabledByAdmin
|
|
||||||
? t("home.settings.disabled_by_admin")
|
|
||||||
: subtitle;
|
|
||||||
const isDisabled = disabled || disabledByAdmin;
|
const isDisabled = disabled || disabledByAdmin;
|
||||||
if (onPress)
|
if (onPress)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export const Login: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t("login.connection_failed"),
|
t("login.connection_failed"),
|
||||||
t("login.an_unexpected_error_occured"),
|
t("login.an_unexpected_error_occurred"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ export const TVLogin: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t("login.connection_failed"),
|
t("login.connection_failed"),
|
||||||
t("login.an_unexpected_error_occured"),
|
t("login.an_unexpected_error_occurred"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -499,7 +499,7 @@ export const TVLogin: React.FC = () => {
|
|||||||
const message =
|
const message =
|
||||||
error instanceof Error
|
error instanceof Error
|
||||||
? error.message
|
? error.message
|
||||||
: t("login.an_unexpected_error_occured");
|
: t("login.an_unexpected_error_occurred");
|
||||||
Alert.alert(t("login.connection_failed"), message);
|
Alert.alert(t("login.connection_failed"), message);
|
||||||
goToQRScreen();
|
goToQRScreen();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -523,7 +523,7 @@ export const TVLogin: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t("login.connection_failed"),
|
t("login.connection_failed"),
|
||||||
t("login.an_unexpected_error_occured"),
|
t("login.an_unexpected_error_occurred"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -768,7 +768,7 @@ export const TVLogin: React.FC = () => {
|
|||||||
const message =
|
const message =
|
||||||
error instanceof Error
|
error instanceof Error
|
||||||
? error.message
|
? error.message
|
||||||
: t("login.an_unexpected_error_occured");
|
: t("login.an_unexpected_error_occurred");
|
||||||
Alert.alert(t("login.connection_failed"), message);
|
Alert.alert(t("login.connection_failed"), message);
|
||||||
goToQRScreen();
|
goToQRScreen();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ export const JellyseerrSettings = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
const { settings, updateSettings } = useSettings();
|
||||||
// Only the server URL is admin-lockable — the password stays editable so
|
|
||||||
// the user can still sign in to the admin-pinned Jellyseerr server.
|
|
||||||
const urlLocked = pluginSettings?.jellyseerrServerUrl?.locked === true;
|
|
||||||
|
|
||||||
const [jellyseerrPassword, setJellyseerrPassword] = useState<
|
const [jellyseerrPassword, setJellyseerrPassword] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
@@ -33,25 +30,22 @@ export const JellyseerrSettings = () => {
|
|||||||
string | undefined
|
string | undefined
|
||||||
>(settings?.jellyseerrServerUrl || undefined);
|
>(settings?.jellyseerrServerUrl || undefined);
|
||||||
|
|
||||||
// When the URL is admin-pinned, ignore any locally-typed value so the login
|
|
||||||
// targets the same server the (read-only) input displays.
|
|
||||||
const effectiveServerUrl = urlLocked
|
|
||||||
? settings?.jellyseerrServerUrl
|
|
||||||
: jellyseerrServerUrl || settings?.jellyseerrServerUrl;
|
|
||||||
|
|
||||||
const loginToJellyseerrMutation = useMutation({
|
const loginToJellyseerrMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (!effectiveServerUrl) throw new Error("Missing server url");
|
if (!jellyseerrServerUrl && !settings?.jellyseerrServerUrl)
|
||||||
|
throw new Error("Missing server url");
|
||||||
if (!user?.Name)
|
if (!user?.Name)
|
||||||
throw new Error("Missing required information for login");
|
throw new Error("Missing required information for login");
|
||||||
const jellyseerrTempApi = new JellyseerrApi(effectiveServerUrl);
|
const jellyseerrTempApi = new JellyseerrApi(
|
||||||
|
jellyseerrServerUrl || settings.jellyseerrServerUrl || "",
|
||||||
|
);
|
||||||
const testResult = await jellyseerrTempApi.test();
|
const testResult = await jellyseerrTempApi.test();
|
||||||
if (!testResult.isValid) throw new Error("Invalid server url");
|
if (!testResult.isValid) throw new Error("Invalid server url");
|
||||||
return jellyseerrTempApi.login(user.Name, jellyseerrPassword || "");
|
return jellyseerrTempApi.login(user.Name, jellyseerrPassword || "");
|
||||||
},
|
},
|
||||||
onSuccess: (user) => {
|
onSuccess: (user) => {
|
||||||
setJellyseerrUser(user);
|
setJellyseerrUser(user);
|
||||||
updateSettings({ jellyseerrServerUrl: effectiveServerUrl });
|
updateSettings({ jellyseerrServerUrl });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error(t("jellyseerr.failed_to_login"));
|
toast.error(t("jellyseerr.failed_to_login"));
|
||||||
@@ -121,7 +115,6 @@ export const JellyseerrSettings = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
||||||
<View style={{ opacity: urlLocked ? 0.5 : 1 }}>
|
|
||||||
<Text className='font-bold mb-1'>
|
<Text className='font-bold mb-1'>
|
||||||
{t("home.settings.plugins.jellyseerr.server_url")}
|
{t("home.settings.plugins.jellyseerr.server_url")}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -135,11 +128,7 @@ export const JellyseerrSettings = () => {
|
|||||||
placeholder={t(
|
placeholder={t(
|
||||||
"home.settings.plugins.jellyseerr.server_url_placeholder",
|
"home.settings.plugins.jellyseerr.server_url_placeholder",
|
||||||
)}
|
)}
|
||||||
value={
|
value={jellyseerrServerUrl ?? settings?.jellyseerrServerUrl}
|
||||||
urlLocked
|
|
||||||
? settings?.jellyseerrServerUrl
|
|
||||||
: (jellyseerrServerUrl ?? settings?.jellyseerrServerUrl)
|
|
||||||
}
|
|
||||||
defaultValue={
|
defaultValue={
|
||||||
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
|
settings?.jellyseerrServerUrl ?? jellyseerrServerUrl
|
||||||
}
|
}
|
||||||
@@ -148,14 +137,8 @@ export const JellyseerrSettings = () => {
|
|||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
textContentType='URL'
|
textContentType='URL'
|
||||||
onChangeText={setjellyseerrServerUrl}
|
onChangeText={setjellyseerrServerUrl}
|
||||||
editable={!urlLocked && !loginToJellyseerrMutation.isPending}
|
editable={!loginToJellyseerrMutation.isPending}
|
||||||
/>
|
/>
|
||||||
{urlLocked && (
|
|
||||||
<Text className='text-xs text-red-600 mb-2'>
|
|
||||||
{t("home.settings.disabled_by_admin")}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View>
|
<View>
|
||||||
<Text className='font-bold mb-2'>
|
<Text className='font-bold mb-2'>
|
||||||
{t("home.settings.plugins.jellyseerr.password")}
|
{t("home.settings.plugins.jellyseerr.password")}
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Switch } from "react-native";
|
import { Switch, Text, View } from "react-native";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
|
||||||
import { ListItem } from "../list/ListItem";
|
|
||||||
|
|
||||||
export const KefinTweaksSettings = () => {
|
export const KefinTweaksSettings = () => {
|
||||||
const { settings, updateSettings, pluginSettings } = useSettings();
|
const { settings, updateSettings } = useSettings();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isEnabled = settings?.useKefinTweaks ?? false;
|
const isEnabled = settings?.useKefinTweaks ?? false;
|
||||||
const locked = pluginSettings?.useKefinTweaks?.locked === true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListGroup>
|
<View className=''>
|
||||||
<ListItem
|
<View className='flex flex-col rounded-xl overflow-hidden p-4 bg-neutral-900'>
|
||||||
title={t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
|
<Text className='text-xs text-red-600 mb-2'>
|
||||||
disabledByAdmin={locked}
|
{t("home.settings.plugins.kefinTweaks.watchlist_enabler")}
|
||||||
>
|
</Text>
|
||||||
|
|
||||||
|
<View className='flex flex-row items-center justify-between mt-2'>
|
||||||
|
<Text className='text-white'>
|
||||||
|
{isEnabled ? t("Watchlist On") : t("Watchlist Off")}
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
value={isEnabled}
|
value={isEnabled}
|
||||||
disabled={locked}
|
|
||||||
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
|
onValueChange={(value) => updateSettings({ useKefinTweaks: value })}
|
||||||
|
trackColor={{ false: "#555", true: "purple" }}
|
||||||
|
thumbColor={isEnabled ? "#fff" : "#ccc"}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</View>
|
||||||
</ListGroup>
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,12 +20,7 @@ export const PluginSettings = () => {
|
|||||||
>
|
>
|
||||||
<ListItem
|
<ListItem
|
||||||
onPress={() => router.push("/settings/plugins/jellyseerr/page")}
|
onPress={() => router.push("/settings/plugins/jellyseerr/page")}
|
||||||
title='Jellyseerr'
|
title={"Jellyseerr"}
|
||||||
showArrow
|
|
||||||
/>
|
|
||||||
<ListItem
|
|
||||||
onPress={() => router.push("/settings/plugins/streamystats/page")}
|
|
||||||
title='Streamystats'
|
|
||||||
showArrow
|
showArrow
|
||||||
/>
|
/>
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -33,6 +28,11 @@ export const PluginSettings = () => {
|
|||||||
title='Marlin Search'
|
title='Marlin Search'
|
||||||
showArrow
|
showArrow
|
||||||
/>
|
/>
|
||||||
|
<ListItem
|
||||||
|
onPress={() => router.push("/settings/plugins/streamystats/page")}
|
||||||
|
title='Streamystats'
|
||||||
|
showArrow
|
||||||
|
/>
|
||||||
<ListItem
|
<ListItem
|
||||||
onPress={() => router.push("/settings/plugins/kefinTweaks/page")}
|
onPress={() => router.push("/settings/plugins/kefinTweaks/page")}
|
||||||
title='KefinTweaks'
|
title='KefinTweaks'
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
|
|||||||
successHapticFeedback();
|
successHapticFeedback();
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t("home.settings.quick_connect.success"),
|
t("home.settings.quick_connect.success"),
|
||||||
t("home.settings.quick_connect.quick_connect_autorized"),
|
t("home.settings.quick_connect.quick_connect_authorized"),
|
||||||
);
|
);
|
||||||
setQuickConnectCode(undefined);
|
setQuickConnectCode(undefined);
|
||||||
bottomSheetModalRef?.current?.close();
|
bottomSheetModalRef?.current?.close();
|
||||||
|
|||||||
212
components/settings/StorageLocationPicker.tsx
Normal file
212
components/settings/StorageLocationPicker.tsx
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import {
|
||||||
|
BottomSheetBackdrop,
|
||||||
|
type BottomSheetBackdropProps,
|
||||||
|
BottomSheetModal,
|
||||||
|
BottomSheetScrollView,
|
||||||
|
} from "@gorhom/bottom-sheet";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { forwardRef, useCallback, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Platform,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from "react-native";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { toast } from "sonner-native";
|
||||||
|
import { Text } from "@/components/common/Text";
|
||||||
|
import type { StorageLocation } from "@/modules";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
import {
|
||||||
|
clearStorageLocationsCache,
|
||||||
|
getAvailableStorageLocations,
|
||||||
|
} from "@/utils/storage";
|
||||||
|
|
||||||
|
interface StorageLocationPickerProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StorageLocationPicker = forwardRef<
|
||||||
|
BottomSheetModal,
|
||||||
|
StorageLocationPickerProps
|
||||||
|
>(({ onClose }, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { settings, updateSettings } = useSettings();
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const [selectedId, setSelectedId] = useState<string | undefined>(
|
||||||
|
settings.downloadStorageLocation || "internal",
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: locations, isLoading } = useQuery({
|
||||||
|
queryKey: ["storageLocations"],
|
||||||
|
queryFn: getAvailableStorageLocations,
|
||||||
|
enabled: Platform.OS === "android",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelect = (location: StorageLocation) => {
|
||||||
|
setSelectedId(location.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
updateSettings({ downloadStorageLocation: selectedId });
|
||||||
|
clearStorageLocationsCache(); // Clear cache so next download uses new location
|
||||||
|
toast.success(
|
||||||
|
t("settings.storage.storage_location_updated", {
|
||||||
|
defaultValue: "Storage location updated",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderBackdrop = useCallback(
|
||||||
|
(props: BottomSheetBackdropProps) => (
|
||||||
|
<BottomSheetBackdrop
|
||||||
|
{...props}
|
||||||
|
disappearsOnIndex={-1}
|
||||||
|
appearsOnIndex={0}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Platform.OS !== "android") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomSheetModal
|
||||||
|
ref={ref}
|
||||||
|
enableDynamicSizing
|
||||||
|
backdropComponent={renderBackdrop}
|
||||||
|
handleIndicatorStyle={{
|
||||||
|
backgroundColor: "white",
|
||||||
|
}}
|
||||||
|
backgroundStyle={{
|
||||||
|
backgroundColor: "#171717",
|
||||||
|
}}
|
||||||
|
enablePanDownToClose
|
||||||
|
enableDismissOnClose
|
||||||
|
>
|
||||||
|
<BottomSheetScrollView
|
||||||
|
style={{
|
||||||
|
paddingLeft: Math.max(16, insets.left),
|
||||||
|
paddingRight: Math.max(16, insets.right),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className='px-4 pt-2'>
|
||||||
|
<Text className='text-lg font-semibold mb-1'>
|
||||||
|
{t("settings.storage.select_storage_location", {
|
||||||
|
defaultValue: "Select Storage Location",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
<Text className='text-sm text-neutral-500 mb-4'>
|
||||||
|
{t("settings.storage.existing_downloads_note", {
|
||||||
|
defaultValue:
|
||||||
|
"Existing downloads will remain in their current location",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<View className='items-center justify-center py-8'>
|
||||||
|
<ActivityIndicator size='large' />
|
||||||
|
<Text className='mt-4 text-neutral-500'>
|
||||||
|
{t("settings.storage.loading_storage", {
|
||||||
|
defaultValue: "Loading storage options...",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : !locations || locations.length === 0 ? (
|
||||||
|
<View className='items-center justify-center py-8'>
|
||||||
|
<Text className='text-neutral-500'>
|
||||||
|
{t("settings.storage.no_storage_found", {
|
||||||
|
defaultValue: "No storage locations found",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{locations.map((location) => {
|
||||||
|
const isSelected = selectedId === location.id;
|
||||||
|
const freeSpaceGB = (location.freeSpace / 1024 ** 3).toFixed(2);
|
||||||
|
const totalSpaceGB = (location.totalSpace / 1024 ** 3).toFixed(
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
const usedPercent = (
|
||||||
|
((location.totalSpace - location.freeSpace) /
|
||||||
|
location.totalSpace) *
|
||||||
|
100
|
||||||
|
).toFixed(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={location.id}
|
||||||
|
onPress={() => handleSelect(location)}
|
||||||
|
className={`p-4 mb-2 rounded-lg ${
|
||||||
|
isSelected
|
||||||
|
? "bg-purple-600/20 border border-purple-600"
|
||||||
|
: "bg-neutral-800"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<View className='flex-row items-center justify-between'>
|
||||||
|
<View className='flex-1'>
|
||||||
|
<View className='flex-row items-center'>
|
||||||
|
<Text className='text-base font-semibold'>
|
||||||
|
{location.label}
|
||||||
|
</Text>
|
||||||
|
{location.type === "external" && (
|
||||||
|
<View className='ml-2 px-2 py-0.5 bg-blue-600/30 rounded'>
|
||||||
|
<Text className='text-xs text-blue-400'>
|
||||||
|
{t("settings.storage.removable", {
|
||||||
|
defaultValue: "Removable",
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Text className='text-sm text-neutral-500 mt-1'>
|
||||||
|
{t("settings.storage.space_info", {
|
||||||
|
defaultValue:
|
||||||
|
"{{free}} GB free of {{total}} GB ({{used}}% used)",
|
||||||
|
free: freeSpaceGB,
|
||||||
|
total: totalSpaceGB,
|
||||||
|
used: usedPercent,
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{isSelected && (
|
||||||
|
<View className='w-6 h-6 rounded-full bg-purple-600 items-center justify-center ml-2'>
|
||||||
|
<Text className='text-white text-xs'>✓</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<View className='flex-row gap-x-2 py-4'>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onClose}
|
||||||
|
className='flex-1 py-3 rounded-lg bg-neutral-800 items-center'
|
||||||
|
>
|
||||||
|
<Text className='text-white font-semibold'>
|
||||||
|
{t("common.cancel", { defaultValue: "Cancel" })}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={handleConfirm}
|
||||||
|
className='flex-1 py-3 rounded-lg bg-purple-600 items-center'
|
||||||
|
disabled={!selectedId}
|
||||||
|
>
|
||||||
|
<Text className='text-white font-semibold'>
|
||||||
|
{t("common.confirm", { defaultValue: "Confirm" })}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</BottomSheetScrollView>
|
||||||
|
</BottomSheetModal>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,19 +1,27 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { BottomSheetModal } from "@gorhom/bottom-sheet";
|
||||||
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View } from "react-native";
|
import { Alert, Platform, View } from "react-native";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import { useHaptic } from "@/hooks/useHaptic";
|
import { useHaptic } from "@/hooks/useHaptic";
|
||||||
import { useDownload } from "@/providers/DownloadProvider";
|
import { useDownload } from "@/providers/DownloadProvider";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
|
import { getStorageLabel } from "@/utils/storage";
|
||||||
import { ListGroup } from "../list/ListGroup";
|
import { ListGroup } from "../list/ListGroup";
|
||||||
import { ListItem } from "../list/ListItem";
|
import { ListItem } from "../list/ListItem";
|
||||||
|
import { StorageLocationPicker } from "./StorageLocationPicker";
|
||||||
|
|
||||||
export const StorageSettings = () => {
|
export const StorageSettings = () => {
|
||||||
const { deleteAllFiles, appSizeUsage } = useDownload();
|
const { deleteAllFiles, appSizeUsage } = useDownload();
|
||||||
|
const { settings } = useSettings();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const successHapticFeedback = useHaptic("success");
|
const successHapticFeedback = useHaptic("success");
|
||||||
const errorHapticFeedback = useHaptic("error");
|
const errorHapticFeedback = useHaptic("error");
|
||||||
|
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
||||||
|
|
||||||
const { data: size } = useQuery({
|
const { data: size } = useQuery({
|
||||||
queryKey: ["appSize"],
|
queryKey: ["appSize"],
|
||||||
@@ -27,16 +35,44 @@ export const StorageSettings = () => {
|
|||||||
used: (app.total - app.remaining) / app.total,
|
used: (app.total - app.remaining) / app.total,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
// Keep the bar moving while a download is writing to disk.
|
||||||
|
refetchInterval: 10 * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDeleteClicked = async () => {
|
const { data: storageLabel } = useQuery({
|
||||||
|
queryKey: ["storageLabel", settings.downloadStorageLocation],
|
||||||
|
queryFn: () => getStorageLabel(settings.downloadStorageLocation),
|
||||||
|
enabled: Platform.OS === "android",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onDeleteClicked = () => {
|
||||||
|
Alert.alert(
|
||||||
|
t("home.settings.storage.delete_all_downloaded_files_confirm"),
|
||||||
|
t("home.settings.storage.delete_all_downloaded_files_confirm_desc"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t("common.cancel"),
|
||||||
|
style: "cancel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("common.ok"),
|
||||||
|
style: "destructive",
|
||||||
|
onPress: async () => {
|
||||||
try {
|
try {
|
||||||
await deleteAllFiles();
|
await deleteAllFiles();
|
||||||
successHapticFeedback();
|
successHapticFeedback();
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
errorHapticFeedback();
|
errorHapticFeedback();
|
||||||
toast.error(t("home.settings.toasts.error_deleting_files"));
|
toast.error(t("home.settings.toasts.error_deleting_files"));
|
||||||
|
} finally {
|
||||||
|
// Reflect the freed space immediately instead of waiting for
|
||||||
|
// the next poll.
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["appSize"] });
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculatePercentage = (value: number, total: number) => {
|
const calculatePercentage = (value: number, total: number) => {
|
||||||
@@ -102,6 +138,18 @@ export const StorageSettings = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{!Platform.isTV && (
|
{!Platform.isTV && (
|
||||||
|
<>
|
||||||
|
{Platform.OS === "android" && (
|
||||||
|
<ListGroup>
|
||||||
|
<ListItem
|
||||||
|
title={t("settings.storage.download_location", {
|
||||||
|
defaultValue: "Download Location",
|
||||||
|
})}
|
||||||
|
value={storageLabel || "Internal Storage"}
|
||||||
|
onPress={() => bottomSheetModalRef.current?.present()}
|
||||||
|
/>
|
||||||
|
</ListGroup>
|
||||||
|
)}
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
<ListItem
|
<ListItem
|
||||||
textColor='red'
|
textColor='red'
|
||||||
@@ -109,7 +157,13 @@ export const StorageSettings = () => {
|
|||||||
title={t("home.settings.storage.delete_all_downloaded_files")}
|
title={t("home.settings.storage.delete_all_downloaded_files")}
|
||||||
/>
|
/>
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<StorageLocationPicker
|
||||||
|
ref={bottomSheetModalRef}
|
||||||
|
onClose={() => bottomSheetModalRef.current?.dismiss()}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import android.content.ComponentName
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.storage.StorageManager
|
||||||
|
import android.os.storage.StorageVolume
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import expo.modules.kotlin.Promise
|
import expo.modules.kotlin.Promise
|
||||||
import expo.modules.kotlin.modules.Module
|
import expo.modules.kotlin.modules.Module
|
||||||
import expo.modules.kotlin.modules.ModuleDefinition
|
import expo.modules.kotlin.modules.ModuleDefinition
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
data class DownloadTaskInfo(
|
data class DownloadTaskInfo(
|
||||||
val url: String,
|
val url: String,
|
||||||
@@ -142,6 +147,105 @@ class BackgroundDownloaderModule : Module() {
|
|||||||
promise.reject("ERROR", "Failed to get active downloads: ${e.message}", e)
|
promise.reject("ERROR", "Failed to get active downloads: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncFunction("getAvailableStorageLocations") { promise: Promise ->
|
||||||
|
try {
|
||||||
|
val storageLocations = mutableListOf<Map<String, Any>>()
|
||||||
|
|
||||||
|
// Use getExternalFilesDirs which works reliably across all Android versions
|
||||||
|
// This returns app-specific directories on both internal and external storage
|
||||||
|
val externalDirs = context.getExternalFilesDirs(null)
|
||||||
|
|
||||||
|
Log.d(TAG, "getExternalFilesDirs returned ${externalDirs.size} locations")
|
||||||
|
|
||||||
|
// Also check with StorageManager for additional info
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
|
val volumes = storageManager.storageVolumes
|
||||||
|
Log.d(TAG, "StorageManager reports ${volumes.size} volumes")
|
||||||
|
for ((i, vol) in volumes.withIndex()) {
|
||||||
|
Log.d(TAG, " Volume $i: isPrimary=${vol.isPrimary}, isRemovable=${vol.isRemovable}, state=${vol.state}, uuid=${vol.uuid}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((index, dir) in externalDirs.withIndex()) {
|
||||||
|
try {
|
||||||
|
if (dir == null) {
|
||||||
|
Log.w(TAG, "Directory at index $index is null - SD card may not be mounted")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dir.exists()) {
|
||||||
|
Log.w(TAG, "Directory at index $index does not exist: ${dir.absolutePath}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val isPrimary = index == 0
|
||||||
|
val isRemovable = !isPrimary && Environment.isExternalStorageRemovable(dir)
|
||||||
|
|
||||||
|
// Get volume UUID for better identification
|
||||||
|
val volumeId = if (isPrimary) {
|
||||||
|
"internal"
|
||||||
|
} else {
|
||||||
|
// Try to get a stable UUID for the SD card
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
|
try {
|
||||||
|
val storageVolume = storageManager.getStorageVolume(dir)
|
||||||
|
storageVolume?.uuid ?: "sdcard_$index"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"sdcard_$index"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"sdcard_$index"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get human-readable label
|
||||||
|
val label = if (isPrimary) {
|
||||||
|
"Internal Storage"
|
||||||
|
} else {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
|
try {
|
||||||
|
val storageVolume = storageManager.getStorageVolume(dir)
|
||||||
|
storageVolume?.getDescription(context) ?: "SD Card"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"SD Card"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"SD Card"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalSpace = dir.totalSpace
|
||||||
|
val freeSpace = dir.freeSpace
|
||||||
|
|
||||||
|
Log.d(TAG, "Storage location: $label (id: $volumeId, path: ${dir.absolutePath}, removable: $isRemovable)")
|
||||||
|
|
||||||
|
storageLocations.add(
|
||||||
|
mapOf(
|
||||||
|
"id" to volumeId,
|
||||||
|
"path" to dir.absolutePath,
|
||||||
|
"type" to (if (isRemovable || !isPrimary) "external" else "internal"),
|
||||||
|
"label" to label,
|
||||||
|
"totalSpace" to totalSpace,
|
||||||
|
"freeSpace" to freeSpace
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error processing storage at index $index: ${e.message}", e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Returning ${storageLocations.size} storage locations")
|
||||||
|
promise.resolve(storageLocations)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error getting storage locations: ${e.message}", e)
|
||||||
|
promise.reject("ERROR", "Failed to get storage locations: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startDownloadInternal(urlString: String, destinationPath: String?): Int {
|
private fun startDownloadInternal(urlString: String, destinationPath: String?): Int {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
DownloadErrorEvent,
|
DownloadErrorEvent,
|
||||||
DownloadProgressEvent,
|
DownloadProgressEvent,
|
||||||
DownloadStartedEvent,
|
DownloadStartedEvent,
|
||||||
|
StorageLocation,
|
||||||
} from "./src/BackgroundDownloader.types";
|
} from "./src/BackgroundDownloader.types";
|
||||||
import BackgroundDownloaderModule from "./src/BackgroundDownloaderModule";
|
import BackgroundDownloaderModule from "./src/BackgroundDownloaderModule";
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export interface BackgroundDownloader {
|
|||||||
cancelQueuedDownload(url: string): void;
|
cancelQueuedDownload(url: string): void;
|
||||||
cancelAllDownloads(): void;
|
cancelAllDownloads(): void;
|
||||||
getActiveDownloads(): Promise<ActiveDownload[]>;
|
getActiveDownloads(): Promise<ActiveDownload[]>;
|
||||||
|
getAvailableStorageLocations(): Promise<StorageLocation[]>;
|
||||||
|
|
||||||
addProgressListener(
|
addProgressListener(
|
||||||
listener: (event: DownloadProgressEvent) => void,
|
listener: (event: DownloadProgressEvent) => void,
|
||||||
@@ -64,6 +66,10 @@ const BackgroundDownloader: BackgroundDownloader = {
|
|||||||
return await BackgroundDownloaderModule.getActiveDownloads();
|
return await BackgroundDownloaderModule.getActiveDownloads();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getAvailableStorageLocations(): Promise<StorageLocation[]> {
|
||||||
|
return await BackgroundDownloaderModule.getAvailableStorageLocations();
|
||||||
|
},
|
||||||
|
|
||||||
addProgressListener(
|
addProgressListener(
|
||||||
listener: (event: DownloadProgressEvent) => void,
|
listener: (event: DownloadProgressEvent) => void,
|
||||||
): EventSubscription {
|
): EventSubscription {
|
||||||
@@ -106,4 +112,5 @@ export type {
|
|||||||
DownloadErrorEvent,
|
DownloadErrorEvent,
|
||||||
DownloadProgressEvent,
|
DownloadProgressEvent,
|
||||||
DownloadStartedEvent,
|
DownloadStartedEvent,
|
||||||
|
StorageLocation,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ export interface ActiveDownload {
|
|||||||
state: "running" | "suspended" | "canceling" | "completed" | "unknown";
|
state: "running" | "suspended" | "canceling" | "completed" | "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StorageLocation {
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
type: "internal" | "external";
|
||||||
|
label: string;
|
||||||
|
totalSpace: number;
|
||||||
|
freeSpace: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BackgroundDownloaderModuleType {
|
export interface BackgroundDownloaderModuleType {
|
||||||
startDownload(url: string, destinationPath?: string): Promise<number>;
|
startDownload(url: string, destinationPath?: string): Promise<number>;
|
||||||
enqueueDownload(url: string, destinationPath?: string): Promise<number>;
|
enqueueDownload(url: string, destinationPath?: string): Promise<number>;
|
||||||
@@ -36,6 +45,7 @@ export interface BackgroundDownloaderModuleType {
|
|||||||
cancelQueuedDownload(url: string): void;
|
cancelQueuedDownload(url: string): void;
|
||||||
cancelAllDownloads(): void;
|
cancelAllDownloads(): void;
|
||||||
getActiveDownloads(): Promise<ActiveDownload[]>;
|
getActiveDownloads(): Promise<ActiveDownload[]>;
|
||||||
|
getAvailableStorageLocations(): Promise<StorageLocation[]>;
|
||||||
addListener(
|
addListener(
|
||||||
eventName: string,
|
eventName: string,
|
||||||
listener: (event: any) => void,
|
listener: (event: any) => void,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type {
|
|||||||
DownloadErrorEvent,
|
DownloadErrorEvent,
|
||||||
DownloadProgressEvent,
|
DownloadProgressEvent,
|
||||||
DownloadStartedEvent,
|
DownloadStartedEvent,
|
||||||
|
StorageLocation,
|
||||||
} from "./background-downloader";
|
} from "./background-downloader";
|
||||||
export { default as BackgroundDownloader } from "./background-downloader";
|
export { default as BackgroundDownloader } from "./background-downloader";
|
||||||
// Glass Poster (tvOS 26+)
|
// Glass Poster (tvOS 26+)
|
||||||
|
|||||||
@@ -6,16 +6,20 @@ import type {
|
|||||||
import { Directory, File, Paths } from "expo-file-system";
|
import { Directory, File, Paths } from "expo-file-system";
|
||||||
import { getItemImage } from "@/utils/getItemImage";
|
import { getItemImage } from "@/utils/getItemImage";
|
||||||
import { fetchAndParseSegments } from "@/utils/segments";
|
import { fetchAndParseSegments } from "@/utils/segments";
|
||||||
|
import { filePathToUri } from "@/utils/storage";
|
||||||
import { generateTrickplayUrl, getTrickplayInfo } from "@/utils/trickplay";
|
import { generateTrickplayUrl, getTrickplayInfo } from "@/utils/trickplay";
|
||||||
import type { MediaTimeSegment, TrickPlayData } from "./types";
|
import type { MediaTimeSegment, TrickPlayData } from "./types";
|
||||||
import { generateFilename } from "./utils";
|
import { generateFilename } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads trickplay images for an item
|
* Downloads trickplay images for an item
|
||||||
|
* @param item - The item to download trickplay images for
|
||||||
|
* @param storagePath - Optional custom storage path (for Android SD card support)
|
||||||
* @returns TrickPlayData with path and size, or undefined if not available
|
* @returns TrickPlayData with path and size, or undefined if not available
|
||||||
*/
|
*/
|
||||||
export async function downloadTrickplayImages(
|
export async function downloadTrickplayImages(
|
||||||
item: BaseItemDto,
|
item: BaseItemDto,
|
||||||
|
storagePath?: string,
|
||||||
): Promise<TrickPlayData | undefined> {
|
): Promise<TrickPlayData | undefined> {
|
||||||
const trickplayInfo = getTrickplayInfo(item);
|
const trickplayInfo = getTrickplayInfo(item);
|
||||||
if (!trickplayInfo || !item.Id) {
|
if (!trickplayInfo || !item.Id) {
|
||||||
@@ -23,7 +27,11 @@ export async function downloadTrickplayImages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filename = generateFilename(item);
|
const filename = generateFilename(item);
|
||||||
const trickplayDir = new Directory(Paths.document, `${filename}_trickplay`);
|
|
||||||
|
// Use custom storage path if provided (Android SD card), otherwise use Paths.document
|
||||||
|
const trickplayDir = storagePath
|
||||||
|
? new Directory(filePathToUri(storagePath), `${filename}_trickplay`)
|
||||||
|
: new Directory(Paths.document, `${filename}_trickplay`);
|
||||||
|
|
||||||
// Create directory if it doesn't exist
|
// Create directory if it doesn't exist
|
||||||
if (!trickplayDir.exists) {
|
if (!trickplayDir.exists) {
|
||||||
@@ -69,12 +77,17 @@ export async function downloadTrickplayImages(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads external subtitle files and updates their delivery URLs to local paths
|
* Downloads external subtitle files and updates their delivery URLs to local paths
|
||||||
|
* @param mediaSource - The media source containing subtitle information
|
||||||
|
* @param item - The item to download subtitles for
|
||||||
|
* @param apiBasePath - The base path for the API
|
||||||
|
* @param storagePath - Optional custom storage path (for Android SD card support)
|
||||||
* @returns Updated media source with local subtitle paths
|
* @returns Updated media source with local subtitle paths
|
||||||
*/
|
*/
|
||||||
export async function downloadSubtitles(
|
export async function downloadSubtitles(
|
||||||
mediaSource: MediaSourceInfo,
|
mediaSource: MediaSourceInfo,
|
||||||
item: BaseItemDto,
|
item: BaseItemDto,
|
||||||
apiBasePath: string,
|
apiBasePath: string,
|
||||||
|
storagePath?: string,
|
||||||
): Promise<MediaSourceInfo> {
|
): Promise<MediaSourceInfo> {
|
||||||
const externalSubtitles = mediaSource.MediaStreams?.filter(
|
const externalSubtitles = mediaSource.MediaStreams?.filter(
|
||||||
(stream) =>
|
(stream) =>
|
||||||
@@ -91,7 +104,14 @@ export async function downloadSubtitles(
|
|||||||
|
|
||||||
const url = apiBasePath + subtitle.DeliveryUrl;
|
const url = apiBasePath + subtitle.DeliveryUrl;
|
||||||
const extension = subtitle.Codec || "srt";
|
const extension = subtitle.Codec || "srt";
|
||||||
const destination = new File(
|
|
||||||
|
// Use custom storage path if provided (Android SD card), otherwise use Paths.document
|
||||||
|
const destination = storagePath
|
||||||
|
? new File(
|
||||||
|
filePathToUri(storagePath),
|
||||||
|
`${filename}_subtitle_${subtitle.Index}.${extension}`,
|
||||||
|
)
|
||||||
|
: new File(
|
||||||
Paths.document,
|
Paths.document,
|
||||||
`${filename}_subtitle_${subtitle.Index}.${extension}`,
|
`${filename}_subtitle_${subtitle.Index}.${extension}`,
|
||||||
);
|
);
|
||||||
@@ -208,13 +228,21 @@ export async function downloadAdditionalAssets(params: {
|
|||||||
api: Api;
|
api: Api;
|
||||||
saveImageFn: (itemId: string, url?: string) => Promise<void>;
|
saveImageFn: (itemId: string, url?: string) => Promise<void>;
|
||||||
saveSeriesImageFn: (item: BaseItemDto) => Promise<void>;
|
saveSeriesImageFn: (item: BaseItemDto) => Promise<void>;
|
||||||
|
storagePath?: string;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
trickPlayData?: TrickPlayData;
|
trickPlayData?: TrickPlayData;
|
||||||
updatedMediaSource: MediaSourceInfo;
|
updatedMediaSource: MediaSourceInfo;
|
||||||
introSegments?: MediaTimeSegment[];
|
introSegments?: MediaTimeSegment[];
|
||||||
creditSegments?: MediaTimeSegment[];
|
creditSegments?: MediaTimeSegment[];
|
||||||
}> {
|
}> {
|
||||||
const { item, mediaSource, api, saveImageFn, saveSeriesImageFn } = params;
|
const {
|
||||||
|
item,
|
||||||
|
mediaSource,
|
||||||
|
api,
|
||||||
|
saveImageFn,
|
||||||
|
saveSeriesImageFn,
|
||||||
|
storagePath,
|
||||||
|
} = params;
|
||||||
|
|
||||||
// Run all downloads in parallel for speed
|
// Run all downloads in parallel for speed
|
||||||
const [
|
const [
|
||||||
@@ -223,11 +251,11 @@ export async function downloadAdditionalAssets(params: {
|
|||||||
segments,
|
segments,
|
||||||
// Cover images (fire and forget, errors are logged)
|
// Cover images (fire and forget, errors are logged)
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
downloadTrickplayImages(item),
|
downloadTrickplayImages(item, storagePath),
|
||||||
// Only download subtitles for non-transcoded streams
|
// Only download subtitles for non-transcoded streams
|
||||||
mediaSource.TranscodingUrl
|
mediaSource.TranscodingUrl
|
||||||
? Promise.resolve(mediaSource)
|
? Promise.resolve(mediaSource)
|
||||||
: downloadSubtitles(mediaSource, item, api.basePath || ""),
|
: downloadSubtitles(mediaSource, item, api.basePath || "", storagePath),
|
||||||
item.Id
|
item.Id
|
||||||
? fetchSegments(item.Id, api)
|
? fetchSegments(item.Id, api)
|
||||||
: Promise.resolve({
|
: Promise.resolve({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Directory, File, Paths } from "expo-file-system";
|
import { Directory, File } from "expo-file-system";
|
||||||
import { getAllDownloadedItems, getDownloadedItemById } from "./database";
|
import { getAllDownloadedItems, getDownloadedItemById } from "./database";
|
||||||
import type { DownloadedItem } from "./types";
|
import type { DownloadedItem } from "./types";
|
||||||
import { filePathToUri } from "./utils";
|
import { filePathToUri } from "./utils";
|
||||||
@@ -39,13 +39,11 @@ export function deleteAllAssociatedFiles(item: DownloadedItem): void {
|
|||||||
stream.DeliveryUrl
|
stream.DeliveryUrl
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const subtitleFilename = stream.DeliveryUrl.split("/").pop();
|
// Use the full path from DeliveryUrl (it's already a full file:// URI)
|
||||||
if (subtitleFilename) {
|
const subtitleFile = new File(stream.DeliveryUrl);
|
||||||
const subtitleFile = new File(Paths.document, subtitleFilename);
|
|
||||||
if (subtitleFile.exists) {
|
if (subtitleFile.exists) {
|
||||||
subtitleFile.delete();
|
subtitleFile.delete();
|
||||||
console.log(`[DELETE] Subtitle deleted: ${subtitleFilename}`);
|
console.log(`[DELETE] Subtitle deleted: ${stream.DeliveryUrl}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[DELETE] Failed to delete subtitle:", error);
|
console.error("[DELETE] Failed to delete subtitle:", error);
|
||||||
@@ -57,16 +55,14 @@ export function deleteAllAssociatedFiles(item: DownloadedItem): void {
|
|||||||
// Delete trickplay directory
|
// Delete trickplay directory
|
||||||
if (item.trickPlayData?.path) {
|
if (item.trickPlayData?.path) {
|
||||||
try {
|
try {
|
||||||
const trickplayDirName = item.trickPlayData.path.split("/").pop();
|
// Use the full path from trickPlayData (it's already a full file:// URI)
|
||||||
if (trickplayDirName) {
|
const trickplayDir = new Directory(item.trickPlayData.path);
|
||||||
const trickplayDir = new Directory(Paths.document, trickplayDirName);
|
|
||||||
if (trickplayDir.exists) {
|
if (trickplayDir.exists) {
|
||||||
trickplayDir.delete();
|
trickplayDir.delete();
|
||||||
console.log(
|
console.log(
|
||||||
`[DELETE] Trickplay directory deleted: ${trickplayDirName}`,
|
`[DELETE] Trickplay directory deleted: ${item.trickPlayData.path}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[DELETE] Failed to delete trickplay directory:", error);
|
console.error("[DELETE] Failed to delete trickplay directory:", error);
|
||||||
}
|
}
|
||||||
@@ -96,5 +92,24 @@ export function getDownloadedItemSize(id: string): number {
|
|||||||
*/
|
*/
|
||||||
export function calculateTotalDownloadedSize(): number {
|
export function calculateTotalDownloadedSize(): number {
|
||||||
const items = getAllDownloadedItems();
|
const items = getAllDownloadedItems();
|
||||||
return items.reduce((sum, item) => sum + (item.videoFileSize || 0), 0);
|
return items.reduce((sum, item) => {
|
||||||
|
// Trickplay bytes count too — getDownloadedItemSize models per-item size
|
||||||
|
// as video + trickplay, the total must match.
|
||||||
|
const trickplaySize = item.trickPlayData?.size ?? 0;
|
||||||
|
// Read the live file size on disk so the total reflects actual usage and
|
||||||
|
// self-heals items whose stored videoFileSize is 0 (old schema, or
|
||||||
|
// `fileInfo.size` was undefined at download time). Fall back to the stored
|
||||||
|
// value if the file can't be stat'd.
|
||||||
|
if (item.videoFilePath) {
|
||||||
|
try {
|
||||||
|
const file = new File(filePathToUri(item.videoFilePath));
|
||||||
|
if (file.exists) {
|
||||||
|
return sum + (file.size ?? item.videoFileSize ?? 0) + trickplaySize;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to stat downloaded file for size:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum + (item.videoFileSize ?? 0) + trickplaySize;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ import { File, Paths } from "expo-file-system";
|
|||||||
import type { MutableRefObject } from "react";
|
import type { MutableRefObject } from "react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform } from "react-native";
|
||||||
import DeviceInfo from "react-native-device-info";
|
import DeviceInfo from "react-native-device-info";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
import type { Bitrate } from "@/components/BitrateSelector";
|
import type { Bitrate } from "@/components/BitrateSelector";
|
||||||
import useImageStorage from "@/hooks/useImageStorage";
|
import useImageStorage from "@/hooks/useImageStorage";
|
||||||
import { BackgroundDownloader } from "@/modules";
|
import { BackgroundDownloader } from "@/modules";
|
||||||
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { getOrSetDeviceId } from "@/utils/device";
|
import { getOrSetDeviceId } from "@/utils/device";
|
||||||
import useDownloadHelper from "@/utils/download";
|
import useDownloadHelper from "@/utils/download";
|
||||||
|
import { getStoragePath } from "@/utils/storage";
|
||||||
import { downloadAdditionalAssets } from "../additionalDownloads";
|
import { downloadAdditionalAssets } from "../additionalDownloads";
|
||||||
import {
|
import {
|
||||||
clearAllDownloadedItems,
|
clearAllDownloadedItems,
|
||||||
@@ -49,6 +52,7 @@ export function useDownloadOperations({
|
|||||||
onDataChange,
|
onDataChange,
|
||||||
}: UseDownloadOperationsProps) {
|
}: UseDownloadOperationsProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { settings } = useSettings();
|
||||||
const { saveSeriesPrimaryImage } = useDownloadHelper();
|
const { saveSeriesPrimaryImage } = useDownloadHelper();
|
||||||
const { saveImage } = useImageStorage();
|
const { saveImage } = useImageStorage();
|
||||||
|
|
||||||
@@ -81,6 +85,12 @@ export function useDownloadOperations({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get storage path if custom location is set
|
||||||
|
let storagePath: string | undefined;
|
||||||
|
if (Platform.OS === "android" && settings.downloadStorageLocation) {
|
||||||
|
storagePath = await getStoragePath(settings.downloadStorageLocation);
|
||||||
|
}
|
||||||
|
|
||||||
// Download all additional assets BEFORE starting native video download
|
// Download all additional assets BEFORE starting native video download
|
||||||
const additionalAssets = await downloadAdditionalAssets({
|
const additionalAssets = await downloadAdditionalAssets({
|
||||||
item,
|
item,
|
||||||
@@ -88,6 +98,7 @@ export function useDownloadOperations({
|
|||||||
api,
|
api,
|
||||||
saveImageFn: saveImage,
|
saveImageFn: saveImage,
|
||||||
saveSeriesImageFn: saveSeriesPrimaryImage,
|
saveSeriesImageFn: saveSeriesPrimaryImage,
|
||||||
|
storagePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure URL is absolute (not relative) before storing
|
// Ensure URL is absolute (not relative) before storing
|
||||||
@@ -123,10 +134,19 @@ export function useDownloadOperations({
|
|||||||
// Add to processes
|
// Add to processes
|
||||||
setProcesses((prev) => [...prev, jobStatus]);
|
setProcesses((prev) => [...prev, jobStatus]);
|
||||||
|
|
||||||
// Generate destination path
|
// Generate destination path using custom storage location if set
|
||||||
const filename = generateFilename(item);
|
const filename = generateFilename(item);
|
||||||
|
let destinationPath: string;
|
||||||
|
|
||||||
|
if (storagePath) {
|
||||||
|
// Use custom storage location
|
||||||
|
destinationPath = `${storagePath}/${filename}.mp4`;
|
||||||
|
console.log(`[DOWNLOAD] Using custom storage: ${destinationPath}`);
|
||||||
|
} else {
|
||||||
|
// Use default Paths.document
|
||||||
const videoFile = new File(Paths.document, `${filename}.mp4`);
|
const videoFile = new File(Paths.document, `${filename}.mp4`);
|
||||||
const destinationPath = uriToFilePath(videoFile.uri);
|
destinationPath = uriToFilePath(videoFile.uri);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`[DOWNLOAD] Starting video: ${item.Name}`);
|
console.log(`[DOWNLOAD] Starting video: ${item.Name}`);
|
||||||
console.log(`[DOWNLOAD] Download URL: ${downloadUrl}`);
|
console.log(`[DOWNLOAD] Download URL: ${downloadUrl}`);
|
||||||
@@ -289,7 +309,24 @@ export function useDownloadOperations({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const appSizeUsage = useCallback(async () => {
|
const appSizeUsage = useCallback(async () => {
|
||||||
const totalSize = calculateTotalDownloadedSize();
|
let totalSize = calculateTotalDownloadedSize();
|
||||||
|
|
||||||
|
// Also count in-progress downloads (they write straight to their final
|
||||||
|
// path) so the growing file shows up as app usage instead of drifting
|
||||||
|
// into the generic device share until completion.
|
||||||
|
for (const process of processes) {
|
||||||
|
try {
|
||||||
|
const file = new File(
|
||||||
|
Paths.document,
|
||||||
|
`${generateFilename(process.item)}.mp4`,
|
||||||
|
);
|
||||||
|
if (file.exists) {
|
||||||
|
totalSize += file.size ?? 0;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// File not created yet — ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [freeDiskStorage, totalDiskCapacity] = await Promise.all([
|
const [freeDiskStorage, totalDiskCapacity] = await Promise.all([
|
||||||
@@ -310,7 +347,7 @@ export function useDownloadOperations({
|
|||||||
appSize: totalSize,
|
appSize: totalSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, []);
|
}, [processes]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startBackgroundDownload,
|
startBackgroundDownload,
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
|||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
t(
|
t(
|
||||||
"login.an_unexpected_error_occured_did_you_enter_the_correct_url",
|
"login.an_unexpected_error_occurred_did_you_enter_the_correct_url",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "حسنًا",
|
"got_it": "حسنًا",
|
||||||
"connection_failed": "فشل الاتصال",
|
"connection_failed": "فشل الاتصال",
|
||||||
"could_not_connect_to_server": "تعذر الاتصال بالخادم. يرجى التحقق من الرابط واتصال الشبكة.",
|
"could_not_connect_to_server": "تعذر الاتصال بالخادم. يرجى التحقق من الرابط واتصال الشبكة.",
|
||||||
"an_unexpected_error_occured": "حدث خطأ غير متوقع",
|
"an_unexpected_error_occurred": "حدث خطأ غير متوقع",
|
||||||
"change_server": "تغيير الخادم",
|
"change_server": "تغيير الخادم",
|
||||||
"invalid_username_or_password": "اسم المستخدم أو كلمة المرور غير صالحة",
|
"invalid_username_or_password": "اسم المستخدم أو كلمة المرور غير صالحة",
|
||||||
"user_does_not_have_permission_to_log_in": "ليس لدى المستخدم صَلاحِيَة تسجيل الدخول",
|
"user_does_not_have_permission_to_log_in": "ليس لدى المستخدم صَلاحِيَة تسجيل الدخول",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "يستغرق الخادم وقتًا طويلاً للرد، يرجى المحاولة مرة أخرى لاحقًا",
|
"server_is_taking_too_long_to_respond_try_again_later": "يستغرق الخادم وقتًا طويلاً للرد، يرجى المحاولة مرة أخرى لاحقًا",
|
||||||
"server_received_too_many_requests_try_again_later": "تلقى الخادم عددًا كبيرًا جدًا من الطلبات، يرجى المحاولة مرة أخرى لاحقًا.",
|
"server_received_too_many_requests_try_again_later": "تلقى الخادم عددًا كبيرًا جدًا من الطلبات، يرجى المحاولة مرة أخرى لاحقًا.",
|
||||||
"there_is_a_server_error": "هناك خطأ في الخادم",
|
"there_is_a_server_error": "هناك خطأ في الخادم",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "حدث خطأ غير متوقع. هل أدخلت رابط الخادم بشكل صحيح؟",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "حدث خطأ غير متوقع. هل أدخلت رابط الخادم بشكل صحيح؟",
|
||||||
"too_old_server_text": "تم اكتشاف خادم Jellyfin غير مدعوم",
|
"too_old_server_text": "تم اكتشاف خادم Jellyfin غير مدعوم",
|
||||||
"too_old_server_description": "يرجى تحديث Jellyfin إلى أحدث إصدار"
|
"too_old_server_description": "يرجى تحديث Jellyfin إلى أحدث إصدار"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "تفويض الدخول السريع",
|
"authorize_button": "تفويض الدخول السريع",
|
||||||
"enter_the_quick_connect_code": "أدخل رمز الدخول السريع...",
|
"enter_the_quick_connect_code": "أدخل رمز الدخول السريع...",
|
||||||
"success": "نجاح",
|
"success": "نجاح",
|
||||||
"quick_connect_autorized": "تم تفويض الدخول السريع",
|
"quick_connect_authorized": "تم تفويض الدخول السريع",
|
||||||
"error": "خطأ",
|
"error": "خطأ",
|
||||||
"invalid_code": "رمز غير صالح",
|
"invalid_code": "رمز غير صالح",
|
||||||
"authorize": "تفويض"
|
"authorize": "تفويض"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "إظهار روابط القائمة المخصصة",
|
"show_custom_menu_links": "إظهار روابط القائمة المخصصة",
|
||||||
"show_large_home_carousel": "إظهار شريط العرض الكبير (تجريبي)",
|
"show_large_home_carousel": "إظهار شريط العرض الكبير (تجريبي)",
|
||||||
"hide_libraries": "إخفاء المكتبات",
|
"hide_libraries": "إخفاء المكتبات",
|
||||||
"select_liraries_you_want_to_hide": "اختر المكتبات التي تريد إخفاءها من تبويب المكتبة وأقسام الصفحة الرئيسية.",
|
"select_libraries_you_want_to_hide": "اختر المكتبات التي تريد إخفاءها من تبويب المكتبة وأقسام الصفحة الرئيسية.",
|
||||||
"disable_haptic_feedback": "تعطيل ردود الفعل اللمسية",
|
"disable_haptic_feedback": "تعطيل ردود الفعل اللمسية",
|
||||||
"default_quality": "الجودة الافتراضية",
|
"default_quality": "الجودة الافتراضية",
|
||||||
"default_playback_speed": "سرعة التشغيل الافتراضية",
|
"default_playback_speed": "سرعة التشغيل الافتراضية",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "الجهاز {{availableSpace}}%",
|
"device_usage": "الجهاز {{availableSpace}}%",
|
||||||
"size_used": "تم استخدام {{used}} من {{total}}",
|
"size_used": "تم استخدام {{used}} من {{total}}",
|
||||||
"delete_all_downloaded_files": "حذف جميع الملفات التي تم تنزيلها",
|
"delete_all_downloaded_files": "حذف جميع الملفات التي تم تنزيلها",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "التخزين المؤقت للموسيقى",
|
"music_cache_title": "التخزين المؤقت للموسيقى",
|
||||||
"music_cache_description": "تخزين الأغاني تلقائياً أثناء الاستماع لضمان تشغيل أكثر سلاسة ودعم الاستماع بدون اتصال",
|
"music_cache_description": "تخزين الأغاني تلقائياً أثناء الاستماع لضمان تشغيل أكثر سلاسة ودعم الاستماع بدون اتصال",
|
||||||
"clear_music_cache": "مسح التخزين المؤقت للموسيقى",
|
"clear_music_cache": "مسح التخزين المؤقت للموسيقى",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "خطأ",
|
"error": "خطأ",
|
||||||
"failed_to_get_stream_url": "فشل في الحصول على رابط البث",
|
"failed_to_get_stream_url": "فشل في الحصول على رابط البث",
|
||||||
"an_error_occured_while_playing_the_video": "حدث خطأ أثناء تشغيل الفيديو. تحقق من السجلات في الإعدادات.",
|
"an_error_occurred_while_playing_the_video": "حدث خطأ أثناء تشغيل الفيديو. تحقق من السجلات في الإعدادات.",
|
||||||
"client_error": "خطأ في المشغّل",
|
"client_error": "خطأ في المشغّل",
|
||||||
"could_not_create_stream_for_chromecast": "تعذر إنشاء بث لـChromecast",
|
"could_not_create_stream_for_chromecast": "تعذر إنشاء بث لـChromecast",
|
||||||
"message_from_server": "رسالة من الخادم: {{message}}",
|
"message_from_server": "رسالة من الخادم: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Entesos",
|
"got_it": "Entesos",
|
||||||
"connection_failed": "Ha fallat la connexió",
|
"connection_failed": "Ha fallat la connexió",
|
||||||
"could_not_connect_to_server": "No s'ha pogut connectar amb el servidor. Comproveu l'URL i la connexió de xarxa.",
|
"could_not_connect_to_server": "No s'ha pogut connectar amb el servidor. Comproveu l'URL i la connexió de xarxa.",
|
||||||
"an_unexpected_error_occured": "S'ha produït un error inesperat",
|
"an_unexpected_error_occurred": "S'ha produït un error inesperat",
|
||||||
"change_server": "Canvia el servidor",
|
"change_server": "Canvia el servidor",
|
||||||
"invalid_username_or_password": "Nom d'usuari o contrasenya incorrectes",
|
"invalid_username_or_password": "Nom d'usuari o contrasenya incorrectes",
|
||||||
"user_does_not_have_permission_to_log_in": "L'usuari no té permís per iniciar sessió",
|
"user_does_not_have_permission_to_log_in": "L'usuari no té permís per iniciar sessió",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "El servidor triga massa a respondre, torneu-ho a provar més tard",
|
"server_is_taking_too_long_to_respond_try_again_later": "El servidor triga massa a respondre, torneu-ho a provar més tard",
|
||||||
"server_received_too_many_requests_try_again_later": "El servidor ha rebut massa sol·licituds, torneu-ho a provar més tard.",
|
"server_received_too_many_requests_try_again_later": "El servidor ha rebut massa sol·licituds, torneu-ho a provar més tard.",
|
||||||
"there_is_a_server_error": "Error del servidor",
|
"there_is_a_server_error": "Error del servidor",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "S'ha produït un error inesperat. Heu introduït correctament l'URL del servidor?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "S'ha produït un error inesperat. Heu introduït correctament l'URL del servidor?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autoritza connexió ràpida",
|
"authorize_button": "Autoritza connexió ràpida",
|
||||||
"enter_the_quick_connect_code": "Introdueix el codi de connexió ràpida...",
|
"enter_the_quick_connect_code": "Introdueix el codi de connexió ràpida...",
|
||||||
"success": "Èxit",
|
"success": "Èxit",
|
||||||
"quick_connect_autorized": "Connexió ràpida autoritzada",
|
"quick_connect_authorized": "Connexió ràpida autoritzada",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"invalid_code": "Codi invàlid",
|
"invalid_code": "Codi invàlid",
|
||||||
"authorize": "Autoritza"
|
"authorize": "Autoritza"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Mostrar enllaços del menú personalitzats",
|
"show_custom_menu_links": "Mostrar enllaços del menú personalitzats",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Oculta biblioteques",
|
"hide_libraries": "Oculta biblioteques",
|
||||||
"select_liraries_you_want_to_hide": "Seleccioneu les biblioteques que voleu ocultar de la pestanya Biblioteca i de les seccions de la pàgina d'inici.",
|
"select_libraries_you_want_to_hide": "Seleccioneu les biblioteques que voleu ocultar de la pestanya Biblioteca i de les seccions de la pàgina d'inici.",
|
||||||
"disable_haptic_feedback": "Desactiva la resposta hàptica",
|
"disable_haptic_feedback": "Desactiva la resposta hàptica",
|
||||||
"default_quality": "Qualitat per defecte",
|
"default_quality": "Qualitat per defecte",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Dispositiu {{availableSpace}}%",
|
"device_usage": "Dispositiu {{availableSpace}}%",
|
||||||
"size_used": "{{used}} de {{total}} utilitzat",
|
"size_used": "{{used}} de {{total}} utilitzat",
|
||||||
"delete_all_downloaded_files": "Suprimeix tots els fitxers descarregats",
|
"delete_all_downloaded_files": "Suprimeix tots els fitxers descarregats",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "No s'ha pogut obtenir l'URL del flux",
|
"failed_to_get_stream_url": "No s'ha pogut obtenir l'URL del flux",
|
||||||
"an_error_occured_while_playing_the_video": "S'ha produït un error en reproduir el vídeo. Consulteu els registres a la configuració.",
|
"an_error_occurred_while_playing_the_video": "S'ha produït un error en reproduir el vídeo. Consulteu els registres a la configuració.",
|
||||||
"client_error": "Error del client",
|
"client_error": "Error del client",
|
||||||
"could_not_create_stream_for_chromecast": "No s'ha pogut crear un flux per a Chromecast",
|
"could_not_create_stream_for_chromecast": "No s'ha pogut crear un flux per a Chromecast",
|
||||||
"message_from_server": "Missatge del servidor: {{message}}",
|
"message_from_server": "Missatge del servidor: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Rozumím",
|
"got_it": "Rozumím",
|
||||||
"connection_failed": "Připojení se nezdařilo",
|
"connection_failed": "Připojení se nezdařilo",
|
||||||
"could_not_connect_to_server": "Nelze se připojit k serveru. Zkontrolujte adresu URL a síťové připojení.",
|
"could_not_connect_to_server": "Nelze se připojit k serveru. Zkontrolujte adresu URL a síťové připojení.",
|
||||||
"an_unexpected_error_occured": "Došlo k neočekávané chybě",
|
"an_unexpected_error_occurred": "Došlo k neočekávané chybě",
|
||||||
"change_server": "Změnit server",
|
"change_server": "Změnit server",
|
||||||
"invalid_username_or_password": "Neplatné uživatelské jméno nebo heslo",
|
"invalid_username_or_password": "Neplatné uživatelské jméno nebo heslo",
|
||||||
"user_does_not_have_permission_to_log_in": "Uživatel nemá oprávnění k přihlášení",
|
"user_does_not_have_permission_to_log_in": "Uživatel nemá oprávnění k přihlášení",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Reakce na server trvá příliš dlouho, zkuste to znovu později",
|
"server_is_taking_too_long_to_respond_try_again_later": "Reakce na server trvá příliš dlouho, zkuste to znovu později",
|
||||||
"server_received_too_many_requests_try_again_later": "Server obdržel příliš mnoho požadavků, opakujte akci později.",
|
"server_received_too_many_requests_try_again_later": "Server obdržel příliš mnoho požadavků, opakujte akci později.",
|
||||||
"there_is_a_server_error": "Došlo k chybě serveru",
|
"there_is_a_server_error": "Došlo k chybě serveru",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Došlo k neočekávané chybě. Vložili jste adresu URL serveru?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Došlo k neočekávané chybě. Vložili jste adresu URL serveru?",
|
||||||
"too_old_server_text": "Objeven nepodporovaný Jellyfin server",
|
"too_old_server_text": "Objeven nepodporovaný Jellyfin server",
|
||||||
"too_old_server_description": "Prosím aktualizujte Jellyfin na nejnovější verzi"
|
"too_old_server_description": "Prosím aktualizujte Jellyfin na nejnovější verzi"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autorizovat rychlé připojení",
|
"authorize_button": "Autorizovat rychlé připojení",
|
||||||
"enter_the_quick_connect_code": "Zadejte kód rychlého připojení...",
|
"enter_the_quick_connect_code": "Zadejte kód rychlého připojení...",
|
||||||
"success": "Úspěšně",
|
"success": "Úspěšně",
|
||||||
"quick_connect_autorized": "Oprávněné Rychlé připojení",
|
"quick_connect_authorized": "Oprávněné Rychlé připojení",
|
||||||
"error": "Chyba",
|
"error": "Chyba",
|
||||||
"invalid_code": "Neplatný kód",
|
"invalid_code": "Neplatný kód",
|
||||||
"authorize": "Autorizovat"
|
"authorize": "Autorizovat"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Zobrazit vlastní Menu odkazy",
|
"show_custom_menu_links": "Zobrazit vlastní Menu odkazy",
|
||||||
"show_large_home_carousel": "Zobrazit velký přehled (beta)",
|
"show_large_home_carousel": "Zobrazit velký přehled (beta)",
|
||||||
"hide_libraries": "Skrýt knihovny",
|
"hide_libraries": "Skrýt knihovny",
|
||||||
"select_liraries_you_want_to_hide": "Vyberte knihovny, které chcete skrýt v záložce Knihovna a v sekcích domovské stránky.",
|
"select_libraries_you_want_to_hide": "Vyberte knihovny, které chcete skrýt v záložce Knihovna a v sekcích domovské stránky.",
|
||||||
"disable_haptic_feedback": "Zakázat Haptickou zpětnou vazbu",
|
"disable_haptic_feedback": "Zakázat Haptickou zpětnou vazbu",
|
||||||
"default_quality": "Výchozí kvalita",
|
"default_quality": "Výchozí kvalita",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Zařízení {{availableSpace}}%",
|
"device_usage": "Zařízení {{availableSpace}}%",
|
||||||
"size_used": "{{used}} z {{total}} využito",
|
"size_used": "{{used}} z {{total}} využito",
|
||||||
"delete_all_downloaded_files": "Odstranit všechny stažené soubory",
|
"delete_all_downloaded_files": "Odstranit všechny stažené soubory",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Chyba",
|
"error": "Chyba",
|
||||||
"failed_to_get_stream_url": "Nepodařilo se získat URL streamu",
|
"failed_to_get_stream_url": "Nepodařilo se získat URL streamu",
|
||||||
"an_error_occured_while_playing_the_video": "Při přehrávání videa došlo k chybě. Zkontrolujte logy v nastavení.",
|
"an_error_occurred_while_playing_the_video": "Při přehrávání videa došlo k chybě. Zkontrolujte logy v nastavení.",
|
||||||
"client_error": "Chyba klienta",
|
"client_error": "Chyba klienta",
|
||||||
"could_not_create_stream_for_chromecast": "Nelze vytvořit stream pro Chromecast",
|
"could_not_create_stream_for_chromecast": "Nelze vytvořit stream pro Chromecast",
|
||||||
"message_from_server": "Zpráva od serveru: {{message}}",
|
"message_from_server": "Zpráva od serveru: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Forstået",
|
"got_it": "Forstået",
|
||||||
"connection_failed": "Forbindelsen mislykkedes",
|
"connection_failed": "Forbindelsen mislykkedes",
|
||||||
"could_not_connect_to_server": "Kunne ikke oprette forbindelse til serveren. Tjek venligst URL'en og din netværksforbindelse.",
|
"could_not_connect_to_server": "Kunne ikke oprette forbindelse til serveren. Tjek venligst URL'en og din netværksforbindelse.",
|
||||||
"an_unexpected_error_occured": "Der opstod en uventet fejl",
|
"an_unexpected_error_occurred": "Der opstod en uventet fejl",
|
||||||
"change_server": "Skift server",
|
"change_server": "Skift server",
|
||||||
"invalid_username_or_password": "Ugyldigt brugernavn eller adgangskode",
|
"invalid_username_or_password": "Ugyldigt brugernavn eller adgangskode",
|
||||||
"user_does_not_have_permission_to_log_in": "Brugeren har ikke tilladelse til at logge ind",
|
"user_does_not_have_permission_to_log_in": "Brugeren har ikke tilladelse til at logge ind",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Serveren svarer for langsomt, prøv igen senere",
|
"server_is_taking_too_long_to_respond_try_again_later": "Serveren svarer for langsomt, prøv igen senere",
|
||||||
"server_received_too_many_requests_try_again_later": "Serveren modtog for mange forespørgsler, prøv igen senere.",
|
"server_received_too_many_requests_try_again_later": "Serveren modtog for mange forespørgsler, prøv igen senere.",
|
||||||
"there_is_a_server_error": "Der er en serverfejl",
|
"there_is_a_server_error": "Der er en serverfejl",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Der opstod en uventet fejl. Har du indtastet serverens URL korrekt?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Der opstod en uventet fejl. Har du indtastet serverens URL korrekt?",
|
||||||
"too_old_server_text": "Ikke Understøttet Jellyfin Server Opdaget",
|
"too_old_server_text": "Ikke Understøttet Jellyfin Server Opdaget",
|
||||||
"too_old_server_description": "Opdater venligst Jellyfin til den seneste version"
|
"too_old_server_description": "Opdater venligst Jellyfin til den seneste version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autoriser Hurtigforbindelse",
|
"authorize_button": "Autoriser Hurtigforbindelse",
|
||||||
"enter_the_quick_connect_code": "Indtast koden til hurtigforbindelse...",
|
"enter_the_quick_connect_code": "Indtast koden til hurtigforbindelse...",
|
||||||
"success": "Succes",
|
"success": "Succes",
|
||||||
"quick_connect_autorized": "Hurtigforbindelse autoriseret",
|
"quick_connect_authorized": "Hurtigforbindelse autoriseret",
|
||||||
"error": "Fejl",
|
"error": "Fejl",
|
||||||
"invalid_code": "Ugyldig kode",
|
"invalid_code": "Ugyldig kode",
|
||||||
"authorize": "Autoriser"
|
"authorize": "Autoriser"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Vis tilpassede menulinks",
|
"show_custom_menu_links": "Vis tilpassede menulinks",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Skjul biblioteker",
|
"hide_libraries": "Skjul biblioteker",
|
||||||
"select_liraries_you_want_to_hide": "Vælg de biblioteker, du ønsker at skjule fra fanen Bibliotek og startside sektionerne.",
|
"select_libraries_you_want_to_hide": "Vælg de biblioteker, du ønsker at skjule fra fanen Bibliotek og startside sektionerne.",
|
||||||
"disable_haptic_feedback": "Deaktiver haptisk feedback",
|
"disable_haptic_feedback": "Deaktiver haptisk feedback",
|
||||||
"default_quality": "Standard kvalitet",
|
"default_quality": "Standard kvalitet",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Enhedsforbrug: {{availableSpace}}%",
|
"device_usage": "Enhedsforbrug: {{availableSpace}}%",
|
||||||
"size_used": "{{used}} af {{total}} brugt",
|
"size_used": "{{used}} af {{total}} brugt",
|
||||||
"delete_all_downloaded_files": "Slet alle downloadede filer",
|
"delete_all_downloaded_files": "Slet alle downloadede filer",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Fejl",
|
"error": "Fejl",
|
||||||
"failed_to_get_stream_url": "Kunne ikke hente stream URL'en",
|
"failed_to_get_stream_url": "Kunne ikke hente stream URL'en",
|
||||||
"an_error_occured_while_playing_the_video": "Der opstod en fejl under afspilning af videoen. Tjek logfilerne i indstillinger.",
|
"an_error_occurred_while_playing_the_video": "Der opstod en fejl under afspilning af videoen. Tjek logfilerne i indstillinger.",
|
||||||
"client_error": "Klientfejl",
|
"client_error": "Klientfejl",
|
||||||
"could_not_create_stream_for_chromecast": "Kunne ikke oprette en stream til Chromecast",
|
"could_not_create_stream_for_chromecast": "Kunne ikke oprette en stream til Chromecast",
|
||||||
"message_from_server": "Besked fra server: {{message}}",
|
"message_from_server": "Besked fra server: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Verstanden",
|
"got_it": "Verstanden",
|
||||||
"connection_failed": "Verbindung fehlgeschlagen",
|
"connection_failed": "Verbindung fehlgeschlagen",
|
||||||
"could_not_connect_to_server": "Verbindung zum Server fehlgeschlagen. Bitte überprüf die URL und deine Netzwerkverbindung.",
|
"could_not_connect_to_server": "Verbindung zum Server fehlgeschlagen. Bitte überprüf die URL und deine Netzwerkverbindung.",
|
||||||
"an_unexpected_error_occured": "Ein unerwarteter Fehler ist aufgetreten",
|
"an_unexpected_error_occurred": "Ein unerwarteter Fehler ist aufgetreten",
|
||||||
"change_server": "Server wechseln",
|
"change_server": "Server wechseln",
|
||||||
"invalid_username_or_password": "Ungültiger Benutzername oder Passwort",
|
"invalid_username_or_password": "Ungültiger Benutzername oder Passwort",
|
||||||
"user_does_not_have_permission_to_log_in": "Benutzer hat keine Berechtigung, um sich anzumelden",
|
"user_does_not_have_permission_to_log_in": "Benutzer hat keine Berechtigung, um sich anzumelden",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Der Server benötigt zu lange, um zu antworten. Bitte versuch es später erneut",
|
"server_is_taking_too_long_to_respond_try_again_later": "Der Server benötigt zu lange, um zu antworten. Bitte versuch es später erneut",
|
||||||
"server_received_too_many_requests_try_again_later": "Der Server hat zu viele Anfragen erhalten. Bitte versuch es später erneut.",
|
"server_received_too_many_requests_try_again_later": "Der Server hat zu viele Anfragen erhalten. Bitte versuch es später erneut.",
|
||||||
"there_is_a_server_error": "Es gibt einen Serverfehler",
|
"there_is_a_server_error": "Es gibt einen Serverfehler",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Ein unerwarteter Fehler ist aufgetreten. Hast du die Server-URL korrekt eingegeben?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Ein unerwarteter Fehler ist aufgetreten. Hast du die Server-URL korrekt eingegeben?",
|
||||||
"too_old_server_text": "Nicht unterstützter Jellyfin Server entdeckt",
|
"too_old_server_text": "Nicht unterstützter Jellyfin Server entdeckt",
|
||||||
"too_old_server_description": "Bitte aktualisiere Jellyfin auf die neueste Version"
|
"too_old_server_description": "Bitte aktualisiere Jellyfin auf die neueste Version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Quick Connect autorisieren",
|
"authorize_button": "Quick Connect autorisieren",
|
||||||
"enter_the_quick_connect_code": "Quick Connect-Code eingeben...",
|
"enter_the_quick_connect_code": "Quick Connect-Code eingeben...",
|
||||||
"success": "Erfolgreich verbunden",
|
"success": "Erfolgreich verbunden",
|
||||||
"quick_connect_autorized": "Quick Connect autorisiert",
|
"quick_connect_authorized": "Quick Connect autorisiert",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"invalid_code": "Ungültiger Code",
|
"invalid_code": "Ungültiger Code",
|
||||||
"authorize": "Autorisieren"
|
"authorize": "Autorisieren"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
|
"show_custom_menu_links": "Benutzerdefinierte Menülinks anzeigen",
|
||||||
"show_large_home_carousel": "Zeige große Startseiten-Übersicht (Beta)",
|
"show_large_home_carousel": "Zeige große Startseiten-Übersicht (Beta)",
|
||||||
"hide_libraries": "Bibliotheken ausblenden",
|
"hide_libraries": "Bibliotheken ausblenden",
|
||||||
"select_liraries_you_want_to_hide": "Bibliotheken auswählen die aus dem Bibliothekstab und der Startseite ausgeblendet werden sollen.",
|
"select_libraries_you_want_to_hide": "Bibliotheken auswählen die aus dem Bibliothekstab und der Startseite ausgeblendet werden sollen.",
|
||||||
"disable_haptic_feedback": "Haptisches Feedback deaktivieren",
|
"disable_haptic_feedback": "Haptisches Feedback deaktivieren",
|
||||||
"default_quality": "Standardqualität",
|
"default_quality": "Standardqualität",
|
||||||
"default_playback_speed": "Standard-Wiedergabegeschwindigkeit",
|
"default_playback_speed": "Standard-Wiedergabegeschwindigkeit",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Gerät {{availableSpace}}%",
|
"device_usage": "Gerät {{availableSpace}}%",
|
||||||
"size_used": "{{used}} von {{total}} genutzt",
|
"size_used": "{{used}} von {{total}} genutzt",
|
||||||
"delete_all_downloaded_files": "Alle heruntergeladenen Dateien löschen",
|
"delete_all_downloaded_files": "Alle heruntergeladenen Dateien löschen",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Musik-Cache",
|
"music_cache_title": "Musik-Cache",
|
||||||
"music_cache_description": "Beim Anhören Titel automatisch in den Cache laden um bessere Wiedergabe und Offline-Wiedergabe zu ermöglichen",
|
"music_cache_description": "Beim Anhören Titel automatisch in den Cache laden um bessere Wiedergabe und Offline-Wiedergabe zu ermöglichen",
|
||||||
"clear_music_cache": "Musik-Cache leeren",
|
"clear_music_cache": "Musik-Cache leeren",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"failed_to_get_stream_url": "Fehler beim Abrufen der Stream-URL",
|
"failed_to_get_stream_url": "Fehler beim Abrufen der Stream-URL",
|
||||||
"an_error_occured_while_playing_the_video": "Ein Fehler ist beim Abspielen des Videos aufgetreten. Logs in den Einstellungen überprüfen.",
|
"an_error_occurred_while_playing_the_video": "Ein Fehler ist beim Abspielen des Videos aufgetreten. Logs in den Einstellungen überprüfen.",
|
||||||
"client_error": "Client-Fehler",
|
"client_error": "Client-Fehler",
|
||||||
"could_not_create_stream_for_chromecast": "Konnte keinen Stream für Chromecast erstellen",
|
"could_not_create_stream_for_chromecast": "Konnte keinen Stream für Chromecast erstellen",
|
||||||
"message_from_server": "Nachricht vom Server: {{message}}",
|
"message_from_server": "Nachricht vom Server: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Το Κατάλαβα",
|
"got_it": "Το Κατάλαβα",
|
||||||
"connection_failed": "Η Σύνδεση Απέτυχε",
|
"connection_failed": "Η Σύνδεση Απέτυχε",
|
||||||
"could_not_connect_to_server": "Δεν ήταν δυνατή η σύνδεση με τον διακομιστή. Παρακαλώ ελέγξτε τη διεύθυνση URL και τη σύνδεση δικτύου σας.",
|
"could_not_connect_to_server": "Δεν ήταν δυνατή η σύνδεση με τον διακομιστή. Παρακαλώ ελέγξτε τη διεύθυνση URL και τη σύνδεση δικτύου σας.",
|
||||||
"an_unexpected_error_occured": "Παρουσιάστηκε Απροσδόκητο Σφάλμα",
|
"an_unexpected_error_occurred": "Παρουσιάστηκε Απροσδόκητο Σφάλμα",
|
||||||
"change_server": "Αλλαγή Διακομιστή",
|
"change_server": "Αλλαγή Διακομιστή",
|
||||||
"invalid_username_or_password": "Μη έγκυρο όνομα χρήστη ή κωδικός πρόσβασης",
|
"invalid_username_or_password": "Μη έγκυρο όνομα χρήστη ή κωδικός πρόσβασης",
|
||||||
"user_does_not_have_permission_to_log_in": "Ο χρήστης δεν έχει άδεια να συνδεθεί",
|
"user_does_not_have_permission_to_log_in": "Ο χρήστης δεν έχει άδεια να συνδεθεί",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Ο διακομιστής καθυστερεί πάρα πολύ για να απαντήσει, δοκιμάστε ξανά αργότερα",
|
"server_is_taking_too_long_to_respond_try_again_later": "Ο διακομιστής καθυστερεί πάρα πολύ για να απαντήσει, δοκιμάστε ξανά αργότερα",
|
||||||
"server_received_too_many_requests_try_again_later": "Ο διακομιστής έλαβε πάρα πολλά αιτήματα, δοκιμάστε ξανά αργότερα.",
|
"server_received_too_many_requests_try_again_later": "Ο διακομιστής έλαβε πάρα πολλά αιτήματα, δοκιμάστε ξανά αργότερα.",
|
||||||
"there_is_a_server_error": "Υπάρχει ένα σφάλμα διακομιστή",
|
"there_is_a_server_error": "Υπάρχει ένα σφάλμα διακομιστή",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Παρουσιάστηκε μη αναμενόμενο σφάλμα. Εισαγάγετε σωστά το URL του διακομιστή?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Παρουσιάστηκε μη αναμενόμενο σφάλμα. Εισαγάγετε σωστά το URL του διακομιστή?",
|
||||||
"too_old_server_text": "Ανακαλύφθηκε Μη Υποστηριζόμενος Εξυπηρετητής Jellyfin",
|
"too_old_server_text": "Ανακαλύφθηκε Μη Υποστηριζόμενος Εξυπηρετητής Jellyfin",
|
||||||
"too_old_server_description": "Παρακαλούμε ενημερώστε το Jellyfin στην τελευταία έκδοση"
|
"too_old_server_description": "Παρακαλούμε ενημερώστε το Jellyfin στην τελευταία έκδοση"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Εξουσιοδότηση Γρήγορης Σύνδεσης",
|
"authorize_button": "Εξουσιοδότηση Γρήγορης Σύνδεσης",
|
||||||
"enter_the_quick_connect_code": "Εισάγετε τον κωδικό γρήγορης σύνδεσης...",
|
"enter_the_quick_connect_code": "Εισάγετε τον κωδικό γρήγορης σύνδεσης...",
|
||||||
"success": "Επιτυχία",
|
"success": "Επιτυχία",
|
||||||
"quick_connect_autorized": "Γρήγορη Σύνδεση Εξουσιοδοτήθηκε",
|
"quick_connect_authorized": "Γρήγορη Σύνδεση Εξουσιοδοτήθηκε",
|
||||||
"error": "Σφάλμα",
|
"error": "Σφάλμα",
|
||||||
"invalid_code": "Μη Έγκυρος Κωδικός",
|
"invalid_code": "Μη Έγκυρος Κωδικός",
|
||||||
"authorize": "Εξουσιοδότηση"
|
"authorize": "Εξουσιοδότηση"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Εμφάνιση Προσαρμοσμένων Συνδέσμων Μενού",
|
"show_custom_menu_links": "Εμφάνιση Προσαρμοσμένων Συνδέσμων Μενού",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Απόκρυψη Βιβλιοθηκών",
|
"hide_libraries": "Απόκρυψη Βιβλιοθηκών",
|
||||||
"select_liraries_you_want_to_hide": "Επιλέξτε τις βιβλιοθήκες που θέλετε να αποκρύψετε από την καρτέλα της Βιβλιοθήκης και τις ενότητες της αρχικής σελίδας.",
|
"select_libraries_you_want_to_hide": "Επιλέξτε τις βιβλιοθήκες που θέλετε να αποκρύψετε από την καρτέλα της Βιβλιοθήκης και τις ενότητες της αρχικής σελίδας.",
|
||||||
"disable_haptic_feedback": "Απενεργοποίηση Απτικής Ανατροφοδότησης",
|
"disable_haptic_feedback": "Απενεργοποίηση Απτικής Ανατροφοδότησης",
|
||||||
"default_quality": "Προεπιλεγμένη Ποιότητα",
|
"default_quality": "Προεπιλεγμένη Ποιότητα",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Device {{availableSpace}}%",
|
"device_usage": "Device {{availableSpace}}%",
|
||||||
"size_used": "{{used}} {{total}} Χρησιμοποιείται",
|
"size_used": "{{used}} {{total}} Χρησιμοποιείται",
|
||||||
"delete_all_downloaded_files": "Διαγραφή Όλων Των Ληφθέντων Αρχείων",
|
"delete_all_downloaded_files": "Διαγραφή Όλων Των Ληφθέντων Αρχείων",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Σφάλμα",
|
"error": "Σφάλμα",
|
||||||
"failed_to_get_stream_url": "Αποτυχία λήψης του URL ροής",
|
"failed_to_get_stream_url": "Αποτυχία λήψης του URL ροής",
|
||||||
"an_error_occured_while_playing_the_video": "Παρουσιάστηκε σφάλμα κατά την αναπαραγωγή του βίντεο. Ελέγξτε τα αρχεία καταγραφής στις ρυθμίσεις.",
|
"an_error_occurred_while_playing_the_video": "Παρουσιάστηκε σφάλμα κατά την αναπαραγωγή του βίντεο. Ελέγξτε τα αρχεία καταγραφής στις ρυθμίσεις.",
|
||||||
"client_error": "Σφάλμα Πελάτη",
|
"client_error": "Σφάλμα Πελάτη",
|
||||||
"could_not_create_stream_for_chromecast": "Αδυναμία δημιουργίας ροής για το Chromecast",
|
"could_not_create_stream_for_chromecast": "Αδυναμία δημιουργίας ροής για το Chromecast",
|
||||||
"message_from_server": "Μήνυμα από το διακομιστή: {{message}}",
|
"message_from_server": "Μήνυμα από το διακομιστή: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Got it",
|
"got_it": "Got it",
|
||||||
"connection_failed": "Connection failed",
|
"connection_failed": "Connection failed",
|
||||||
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
||||||
"an_unexpected_error_occured": "An unexpected error occurred",
|
"an_unexpected_error_occurred": "An unexpected error occurred",
|
||||||
"change_server": "Change server",
|
"change_server": "Change server",
|
||||||
"invalid_username_or_password": "Invalid username or password",
|
"invalid_username_or_password": "Invalid username or password",
|
||||||
"user_does_not_have_permission_to_log_in": "User does not have permission to log in",
|
"user_does_not_have_permission_to_log_in": "User does not have permission to log in",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
||||||
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
||||||
"there_is_a_server_error": "There is a server error",
|
"there_is_a_server_error": "There is a server error",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin server discovered",
|
"too_old_server_text": "Unsupported Jellyfin server discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -118,7 +118,6 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"settings_title": "Settings",
|
"settings_title": "Settings",
|
||||||
"disabled_by_admin": "Disabled by admin",
|
|
||||||
"log_out_button": "Log out",
|
"log_out_button": "Log out",
|
||||||
"switch_user": {
|
"switch_user": {
|
||||||
"title": "Switch user",
|
"title": "Switch user",
|
||||||
@@ -189,7 +188,7 @@
|
|||||||
"authorize_button": "Authorize Quick Connect",
|
"authorize_button": "Authorize Quick Connect",
|
||||||
"enter_the_quick_connect_code": "Enter the Quick Connect code...",
|
"enter_the_quick_connect_code": "Enter the Quick Connect code...",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"quick_connect_autorized": "Quick Connect authorized",
|
"quick_connect_authorized": "Quick Connect authorized",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"invalid_code": "Invalid code",
|
"invalid_code": "Invalid code",
|
||||||
"authorize": "Authorize"
|
"authorize": "Authorize"
|
||||||
@@ -299,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Show custom menu links",
|
"show_custom_menu_links": "Show custom menu links",
|
||||||
"show_large_home_carousel": "Show large home carousel (beta)",
|
"show_large_home_carousel": "Show large home carousel (beta)",
|
||||||
"hide_libraries": "Hide libraries",
|
"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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Disable haptic feedback",
|
||||||
"default_quality": "Default quality",
|
"default_quality": "Default quality",
|
||||||
"default_playback_speed": "Default playback speed",
|
"default_playback_speed": "Default playback speed",
|
||||||
@@ -371,7 +370,6 @@
|
|||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"refreshed": "Settings refreshed from server",
|
"refreshed": "Settings refreshed from server",
|
||||||
"refresh_failed": "Couldn't refresh settings from server",
|
|
||||||
"disabled": "Streamystats disabled"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Refresh settings from server"
|
"refresh_from_server": "Refresh settings from server"
|
||||||
@@ -386,6 +384,8 @@
|
|||||||
"device_usage": "Device {{availableSpace}}%",
|
"device_usage": "Device {{availableSpace}}%",
|
||||||
"size_used": "{{used}} of {{total}} used",
|
"size_used": "{{used}} of {{total}} used",
|
||||||
"delete_all_downloaded_files": "Delete all downloaded files",
|
"delete_all_downloaded_files": "Delete all downloaded files",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music cache",
|
"music_cache_title": "Music cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear music cache",
|
"clear_music_cache": "Clear music cache",
|
||||||
@@ -600,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV player",
|
"mpv_player_title": "MPV player",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||||
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
"client_error": "Client error",
|
"client_error": "Client error",
|
||||||
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
||||||
"message_from_server": "Message from server: {{message}}",
|
"message_from_server": "Message from server: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Entendido",
|
"got_it": "Entendido",
|
||||||
"connection_failed": "Conexión fallida",
|
"connection_failed": "Conexión fallida",
|
||||||
"could_not_connect_to_server": "No se pudo conectar al servidor. Por favor comprueba la URL y tu conexión de red.",
|
"could_not_connect_to_server": "No se pudo conectar al servidor. Por favor comprueba la URL y tu conexión de red.",
|
||||||
"an_unexpected_error_occured": "Ha ocurrido un error inesperado",
|
"an_unexpected_error_occurred": "Ha ocurrido un error inesperado",
|
||||||
"change_server": "Cambiar servidor",
|
"change_server": "Cambiar servidor",
|
||||||
"invalid_username_or_password": "Usuario o contraseña inválidos",
|
"invalid_username_or_password": "Usuario o contraseña inválidos",
|
||||||
"user_does_not_have_permission_to_log_in": "El usuario no tiene permiso para iniciar sesión",
|
"user_does_not_have_permission_to_log_in": "El usuario no tiene permiso para iniciar sesión",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "El servidor está tardando mucho en responder, inténtalo de nuevo más tarde.",
|
"server_is_taking_too_long_to_respond_try_again_later": "El servidor está tardando mucho en responder, inténtalo de nuevo más tarde.",
|
||||||
"server_received_too_many_requests_try_again_later": "El servidor está recibiendo muchas peticiones, inténtalo de nuevo más tarde.",
|
"server_received_too_many_requests_try_again_later": "El servidor está recibiendo muchas peticiones, inténtalo de nuevo más tarde.",
|
||||||
"there_is_a_server_error": "Hay un error en el servidor",
|
"there_is_a_server_error": "Hay un error en el servidor",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Ha ocurrido un error inesperado. ¿Has introducido la URL correcta?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Ha ocurrido un error inesperado. ¿Has introducido la URL correcta?",
|
||||||
"too_old_server_text": "Servidor Jellyfin no soportado descubierto",
|
"too_old_server_text": "Servidor Jellyfin no soportado descubierto",
|
||||||
"too_old_server_description": "Por favor, actualiza Jellyfin a la última versión"
|
"too_old_server_description": "Por favor, actualiza Jellyfin a la última versión"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autorizar conexión rápida",
|
"authorize_button": "Autorizar conexión rápida",
|
||||||
"enter_the_quick_connect_code": "Introduce el código de conexión rápida...",
|
"enter_the_quick_connect_code": "Introduce el código de conexión rápida...",
|
||||||
"success": "Hecho",
|
"success": "Hecho",
|
||||||
"quick_connect_autorized": "Conexión rápida autorizada",
|
"quick_connect_authorized": "Conexión rápida autorizada",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"invalid_code": "Código inválido",
|
"invalid_code": "Código inválido",
|
||||||
"authorize": "Autorizar"
|
"authorize": "Autorizar"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
|
"show_custom_menu_links": "Mostrar enlaces de menú personalizados",
|
||||||
"show_large_home_carousel": "Mostrar carrusel del menú principal grande (beta)",
|
"show_large_home_carousel": "Mostrar carrusel del menú principal grande (beta)",
|
||||||
"hide_libraries": "Ocultar bibliotecas",
|
"hide_libraries": "Ocultar bibliotecas",
|
||||||
"select_liraries_you_want_to_hide": "Selecciona las bibliotecas que quieres ocultar de la pestaña Bibliotecas y de Inicio.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Desactivar feedback háptico",
|
||||||
"default_quality": "Calidad por defecto",
|
"default_quality": "Calidad por defecto",
|
||||||
"default_playback_speed": "Velocidad de reproducción predeterminada",
|
"default_playback_speed": "Velocidad de reproducción predeterminada",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Dispositivo {{availableSpace}}%",
|
"device_usage": "Dispositivo {{availableSpace}}%",
|
||||||
"size_used": "{{used}} de {{total}} usado",
|
"size_used": "{{used}} de {{total}} usado",
|
||||||
"delete_all_downloaded_files": "Eliminar todos los archivos descargados",
|
"delete_all_downloaded_files": "Eliminar todos los archivos descargados",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Caché de música",
|
"music_cache_title": "Caché de música",
|
||||||
"music_cache_description": "Cachear automáticamente las canciones mientras escuchas una reproducción más suave y soporte sin conexión",
|
"music_cache_description": "Cachear automáticamente las canciones mientras escuchas una reproducción más suave y soporte sin conexión",
|
||||||
"clear_music_cache": "Borrar Caché de Música",
|
"clear_music_cache": "Borrar Caché de Música",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "Error al obtener la URL del Steam",
|
"failed_to_get_stream_url": "Error al obtener la URL del Steam",
|
||||||
"an_error_occured_while_playing_the_video": "Ha ocurrido un error al reproducir el vídeo. Comprueba los registros en la configuración.",
|
"an_error_occurred_while_playing_the_video": "Ha ocurrido un error al reproducir el vídeo. Comprueba los registros en la configuración.",
|
||||||
"client_error": "Error del cliente",
|
"client_error": "Error del cliente",
|
||||||
"could_not_create_stream_for_chromecast": "No se pudo crear el Steam para Chromecast",
|
"could_not_create_stream_for_chromecast": "No se pudo crear el Steam para Chromecast",
|
||||||
"message_from_server": "Mensaje del servidor: {{message}}",
|
"message_from_server": "Mensaje del servidor: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Okei",
|
"got_it": "Okei",
|
||||||
"connection_failed": "Yhteys epäonnistui",
|
"connection_failed": "Yhteys epäonnistui",
|
||||||
"could_not_connect_to_server": "Yhteyttä palvelimeen ei voitu muodostaa. Tarkista URL-osoite ja verkkoyhteytesi.",
|
"could_not_connect_to_server": "Yhteyttä palvelimeen ei voitu muodostaa. Tarkista URL-osoite ja verkkoyhteytesi.",
|
||||||
"an_unexpected_error_occured": "Odottamaton virhe tapahtui",
|
"an_unexpected_error_occurred": "Odottamaton virhe tapahtui",
|
||||||
"change_server": "Vaihda palvelinta",
|
"change_server": "Vaihda palvelinta",
|
||||||
"invalid_username_or_password": "Virheellinen käyttäjätunnus tai salasana",
|
"invalid_username_or_password": "Virheellinen käyttäjätunnus tai salasana",
|
||||||
"user_does_not_have_permission_to_log_in": "Käyttäjällä ei ole oikeuksia kirjautua sisään",
|
"user_does_not_have_permission_to_log_in": "Käyttäjällä ei ole oikeuksia kirjautua sisään",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Palvelin reagoi liian hitaasti, yritä myöhemmin uudelleen",
|
"server_is_taking_too_long_to_respond_try_again_later": "Palvelin reagoi liian hitaasti, yritä myöhemmin uudelleen",
|
||||||
"server_received_too_many_requests_try_again_later": "Palvelin sai liian monta pyyntöä, yritä myöhemmin uudelleen.",
|
"server_received_too_many_requests_try_again_later": "Palvelin sai liian monta pyyntöä, yritä myöhemmin uudelleen.",
|
||||||
"there_is_a_server_error": "Palvelimessa on virhe",
|
"there_is_a_server_error": "Palvelimessa on virhe",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Odottamaton virhe tapahtui. Syötitkö palvelimen URL-osoitteen oikein?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Odottamaton virhe tapahtui. Syötitkö palvelimen URL-osoitteen oikein?",
|
||||||
"too_old_server_text": "Ei-tuettu Jellyfin-palvelin löydetty",
|
"too_old_server_text": "Ei-tuettu Jellyfin-palvelin löydetty",
|
||||||
"too_old_server_description": "Ole hyvä ja päivitä Jellyfin uusimpaan versioon"
|
"too_old_server_description": "Ole hyvä ja päivitä Jellyfin uusimpaan versioon"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Valtuuta pikayhdistys",
|
"authorize_button": "Valtuuta pikayhdistys",
|
||||||
"enter_the_quick_connect_code": "Syötä nopean yhteyden koodi...",
|
"enter_the_quick_connect_code": "Syötä nopean yhteyden koodi...",
|
||||||
"success": "Onnistui",
|
"success": "Onnistui",
|
||||||
"quick_connect_autorized": "Pikayhdistys valtuutettu",
|
"quick_connect_authorized": "Pikayhdistys valtuutettu",
|
||||||
"error": "Virhe",
|
"error": "Virhe",
|
||||||
"invalid_code": "Virheellinen koodi",
|
"invalid_code": "Virheellinen koodi",
|
||||||
"authorize": "Valtuuta"
|
"authorize": "Valtuuta"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Näytä mukautetut valikkolinkit",
|
"show_custom_menu_links": "Näytä mukautetut valikkolinkit",
|
||||||
"show_large_home_carousel": "Näytä suuri kotikaruselli (beta)",
|
"show_large_home_carousel": "Näytä suuri kotikaruselli (beta)",
|
||||||
"hide_libraries": "Piilota kirjastot",
|
"hide_libraries": "Piilota kirjastot",
|
||||||
"select_liraries_you_want_to_hide": "Valitse kirjastot, jotka haluat piilottaa Kirjasto-välilehdeltä ja etusivun osioista.",
|
"select_libraries_you_want_to_hide": "Valitse kirjastot, jotka haluat piilottaa Kirjasto-välilehdeltä ja etusivun osioista.",
|
||||||
"disable_haptic_feedback": "Poista haptinen palautteet käytöstä",
|
"disable_haptic_feedback": "Poista haptinen palautteet käytöstä",
|
||||||
"default_quality": "Oletuslaatu",
|
"default_quality": "Oletuslaatu",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Laitteen käyttö {{availableSpace}}%",
|
"device_usage": "Laitteen käyttö {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} käytössä",
|
"size_used": "{{used}} / {{total}} käytössä",
|
||||||
"delete_all_downloaded_files": "Poista kaikki ladatut tiedostot",
|
"delete_all_downloaded_files": "Poista kaikki ladatut tiedostot",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Virhe",
|
"error": "Virhe",
|
||||||
"failed_to_get_stream_url": "Lähetyksen URL-osoitteen haku epäonnistui",
|
"failed_to_get_stream_url": "Lähetyksen URL-osoitteen haku epäonnistui",
|
||||||
"an_error_occured_while_playing_the_video": "Videon toiston yhteydessä tapahtui virhe. Tarkista lokit asetuksista.",
|
"an_error_occurred_while_playing_the_video": "Videon toiston yhteydessä tapahtui virhe. Tarkista lokit asetuksista.",
|
||||||
"client_error": "Asiakkaan Virhe",
|
"client_error": "Asiakkaan Virhe",
|
||||||
"could_not_create_stream_for_chromecast": "Suoratoistoa ei voitu luoda Chromecastia varten",
|
"could_not_create_stream_for_chromecast": "Suoratoistoa ei voitu luoda Chromecastia varten",
|
||||||
"message_from_server": "Viesti palvelimelta: {{message}}",
|
"message_from_server": "Viesti palvelimelta: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "D'accord",
|
"got_it": "D'accord",
|
||||||
"connection_failed": "La connexion a échoué",
|
"connection_failed": "La connexion a échoué",
|
||||||
"could_not_connect_to_server": "Impossible de se connecter au serveur. Veuillez vérifier l’URL et votre connexion réseau.",
|
"could_not_connect_to_server": "Impossible de se connecter au serveur. Veuillez vérifier l’URL et votre connexion réseau.",
|
||||||
"an_unexpected_error_occured": "Une erreur inattendue s'est produite",
|
"an_unexpected_error_occurred": "Une erreur inattendue s'est produite",
|
||||||
"change_server": "Changer de serveur",
|
"change_server": "Changer de serveur",
|
||||||
"invalid_username_or_password": "Nom d'utilisateur ou mot de passe invalide",
|
"invalid_username_or_password": "Nom d'utilisateur ou mot de passe invalide",
|
||||||
"user_does_not_have_permission_to_log_in": "L'utilisateur n'a pas la permission de se connecter",
|
"user_does_not_have_permission_to_log_in": "L'utilisateur n'a pas la permission de se connecter",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Le serveur prend trop de temps à répondre, réessayez plus tard",
|
"server_is_taking_too_long_to_respond_try_again_later": "Le serveur prend trop de temps à répondre, réessayez plus tard",
|
||||||
"server_received_too_many_requests_try_again_later": "Le serveur a reçu trop de demandes, réessayez plus tard.",
|
"server_received_too_many_requests_try_again_later": "Le serveur a reçu trop de demandes, réessayez plus tard.",
|
||||||
"there_is_a_server_error": "Il y a une erreur de serveur",
|
"there_is_a_server_error": "Il y a une erreur de serveur",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Une erreur inattendue s’est produite. Avez-vous correctement saisi l’URL du serveur ?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Une erreur inattendue s’est produite. Avez-vous correctement saisi l’URL du serveur ?",
|
||||||
"too_old_server_text": "Serveur Jellyfin non pris en charge découvert",
|
"too_old_server_text": "Serveur Jellyfin non pris en charge découvert",
|
||||||
"too_old_server_description": "Veuillez mettre à jour Jellyfin vers la dernière version"
|
"too_old_server_description": "Veuillez mettre à jour Jellyfin vers la dernière version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autoriser une connexion rapide",
|
"authorize_button": "Autoriser une connexion rapide",
|
||||||
"enter_the_quick_connect_code": "Entrez le code de connexion rapide...",
|
"enter_the_quick_connect_code": "Entrez le code de connexion rapide...",
|
||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
"quick_connect_autorized": "Connexion rapide autorisée",
|
"quick_connect_authorized": "Connexion rapide autorisée",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"invalid_code": "Code invalide",
|
"invalid_code": "Code invalide",
|
||||||
"authorize": "Autoriser"
|
"authorize": "Autoriser"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Afficher les liens personnalisés",
|
"show_custom_menu_links": "Afficher les liens personnalisés",
|
||||||
"show_large_home_carousel": "Afficher le grand carrousel d’accueil (bêta)",
|
"show_large_home_carousel": "Afficher le grand carrousel d’accueil (bêta)",
|
||||||
"hide_libraries": "Masquer les bibliothèques",
|
"hide_libraries": "Masquer les 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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Désactiver le retour haptique",
|
||||||
"default_quality": "Qualité par défaut",
|
"default_quality": "Qualité par défaut",
|
||||||
"default_playback_speed": "Vitesse de lecture par défaut",
|
"default_playback_speed": "Vitesse de lecture par défaut",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Appareil {{availableSpace}}%",
|
"device_usage": "Appareil {{availableSpace}}%",
|
||||||
"size_used": "{{used}} de {{total}} utilisés",
|
"size_used": "{{used}} de {{total}} utilisés",
|
||||||
"delete_all_downloaded_files": "Supprimer tous les fichiers téléchargés",
|
"delete_all_downloaded_files": "Supprimer tous les fichiers téléchargés",
|
||||||
|
"delete_all_downloaded_files_confirm": "Supprimer tous les fichiers téléchargés ?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Êtes-vous sûr de vouloir supprimer tous les fichiers téléchargés ? Cette action est irréversible.",
|
||||||
"music_cache_title": "Mise en cache de la musique",
|
"music_cache_title": "Mise en cache de la musique",
|
||||||
"music_cache_description": "Mettez automatiquement en cache les chansons au fur et à mesure que vous écoutez pour une lecture plus fluide et une prise en charge hors ligne",
|
"music_cache_description": "Mettez automatiquement en cache les chansons au fur et à mesure que vous écoutez pour une lecture plus fluide et une prise en charge hors ligne",
|
||||||
"clear_music_cache": "Vider le cache de la musique",
|
"clear_music_cache": "Vider le cache de la musique",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "Lecteur MPV",
|
"mpv_player_title": "Lecteur MPV",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"failed_to_get_stream_url": "Échec de l'obtention de l'URL du flux",
|
"failed_to_get_stream_url": "Échec de l'obtention de l'URL du flux",
|
||||||
"an_error_occured_while_playing_the_video": "Une erreur s’est produite lors de la lecture de la vidéo. Vérifiez les journaux dans les paramètres.",
|
"an_error_occurred_while_playing_the_video": "Une erreur s’est produite lors de la lecture de la vidéo. Vérifiez les journaux dans les paramètres.",
|
||||||
"client_error": "Erreur du client",
|
"client_error": "Erreur du client",
|
||||||
"could_not_create_stream_for_chromecast": "Impossible de créer un flux sur la Chromecast",
|
"could_not_create_stream_for_chromecast": "Impossible de créer un flux sur la Chromecast",
|
||||||
"message_from_server": "Message du serveur : {{message}}",
|
"message_from_server": "Message du serveur : {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "קיבלתי",
|
"got_it": "קיבלתי",
|
||||||
"connection_failed": "ההתחברות נכשלה",
|
"connection_failed": "ההתחברות נכשלה",
|
||||||
"could_not_connect_to_server": "לא היה ניתן להתחבר לשרת. אנא בדוק את הקישור ואת חיבור הרשת שלך.",
|
"could_not_connect_to_server": "לא היה ניתן להתחבר לשרת. אנא בדוק את הקישור ואת חיבור הרשת שלך.",
|
||||||
"an_unexpected_error_occured": "קרתה שגיאה לא צפויה",
|
"an_unexpected_error_occurred": "קרתה שגיאה לא צפויה",
|
||||||
"change_server": "החלף שרת",
|
"change_server": "החלף שרת",
|
||||||
"invalid_username_or_password": "שם משתמש או סיסמה שגויים",
|
"invalid_username_or_password": "שם משתמש או סיסמה שגויים",
|
||||||
"user_does_not_have_permission_to_log_in": "לחשבון זה אין גישה להתחבר",
|
"user_does_not_have_permission_to_log_in": "לחשבון זה אין גישה להתחבר",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "השרת לוקח יותר מדי זמן כדי להגיב, נסה שוב מאוחר יותר",
|
"server_is_taking_too_long_to_respond_try_again_later": "השרת לוקח יותר מדי זמן כדי להגיב, נסה שוב מאוחר יותר",
|
||||||
"server_received_too_many_requests_try_again_later": "השרת קיבל יותר מדי קריאות, נסה שוב מאוחר יותר",
|
"server_received_too_many_requests_try_again_later": "השרת קיבל יותר מדי קריאות, נסה שוב מאוחר יותר",
|
||||||
"there_is_a_server_error": "קרתה שגיאה בצד השרת",
|
"there_is_a_server_error": "קרתה שגיאה בצד השרת",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "קרתה שגיאה לא צפויה. האם כתובת השרת שהוקלדה נכונה?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "קרתה שגיאה לא צפויה. האם כתובת השרת שהוקלדה נכונה?",
|
||||||
"too_old_server_text": "נמצא שרת Jellyfin שלא נתמך",
|
"too_old_server_text": "נמצא שרת Jellyfin שלא נתמך",
|
||||||
"too_old_server_description": "אנא עדכן את גרסת ה-Jellyfin למעודכנת ביותר"
|
"too_old_server_description": "אנא עדכן את גרסת ה-Jellyfin למעודכנת ביותר"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "אשר התחברות מהירה",
|
"authorize_button": "אשר התחברות מהירה",
|
||||||
"enter_the_quick_connect_code": "הקלד את קוד ההתחברות המהירה...",
|
"enter_the_quick_connect_code": "הקלד את קוד ההתחברות המהירה...",
|
||||||
"success": "הצלחה",
|
"success": "הצלחה",
|
||||||
"quick_connect_autorized": "חיבור מהיר אושר",
|
"quick_connect_authorized": "חיבור מהיר אושר",
|
||||||
"error": "שגיאה",
|
"error": "שגיאה",
|
||||||
"invalid_code": "קוד שגוי",
|
"invalid_code": "קוד שגוי",
|
||||||
"authorize": "אשר"
|
"authorize": "אשר"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "הצג קישורים לתפריטים מותאמים אישית",
|
"show_custom_menu_links": "הצג קישורים לתפריטים מותאמים אישית",
|
||||||
"show_large_home_carousel": "הצג קרוסלה גדולה במסך הבית (בטא)",
|
"show_large_home_carousel": "הצג קרוסלה גדולה במסך הבית (בטא)",
|
||||||
"hide_libraries": "הסתר ספריות",
|
"hide_libraries": "הסתר ספריות",
|
||||||
"select_liraries_you_want_to_hide": "בחר את הספריות שתרצה להסתיר ממסך הספריות וגם ממסך הבית.",
|
"select_libraries_you_want_to_hide": "בחר את הספריות שתרצה להסתיר ממסך הספריות וגם ממסך הבית.",
|
||||||
"disable_haptic_feedback": "בטל משוב רטט",
|
"disable_haptic_feedback": "בטל משוב רטט",
|
||||||
"default_quality": "איכות ברירת מחדל",
|
"default_quality": "איכות ברירת מחדל",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "מכשיר {{availableSpace}}%",
|
"device_usage": "מכשיר {{availableSpace}}%",
|
||||||
"size_used": "השתמשת ב-{{used}} מתוך {{total}}",
|
"size_used": "השתמשת ב-{{used}} מתוך {{total}}",
|
||||||
"delete_all_downloaded_files": "מחק את כל הקבצים שהורדו",
|
"delete_all_downloaded_files": "מחק את כל הקבצים שהורדו",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "שגיאה",
|
"error": "שגיאה",
|
||||||
"failed_to_get_stream_url": "נכשל בהשגת קישור הזרם",
|
"failed_to_get_stream_url": "נכשל בהשגת קישור הזרם",
|
||||||
"an_error_occured_while_playing_the_video": "קרתה תקלה במהלך הניגון של הקובץ. בדוק את הלוגים בהגדרות.",
|
"an_error_occurred_while_playing_the_video": "קרתה תקלה במהלך הניגון של הקובץ. בדוק את הלוגים בהגדרות.",
|
||||||
"client_error": "שגיאת לקוח",
|
"client_error": "שגיאת לקוח",
|
||||||
"could_not_create_stream_for_chromecast": "נכשל ביצירת זרם עבור Chromecast",
|
"could_not_create_stream_for_chromecast": "נכשל ביצירת זרם עבור Chromecast",
|
||||||
"message_from_server": "הודעה מהשרת: {{message}}",
|
"message_from_server": "הודעה מהשרת: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Értettem",
|
"got_it": "Értettem",
|
||||||
"connection_failed": "Kapcsolódás Sikertelen",
|
"connection_failed": "Kapcsolódás Sikertelen",
|
||||||
"could_not_connect_to_server": "Nem sikerült csatlakozni a szerverhez. Kérjük, ellenőrizd az URL-t és a hálózati kapcsolatot.",
|
"could_not_connect_to_server": "Nem sikerült csatlakozni a szerverhez. Kérjük, ellenőrizd az URL-t és a hálózati kapcsolatot.",
|
||||||
"an_unexpected_error_occured": "Váratlan Hiba Történt",
|
"an_unexpected_error_occurred": "Váratlan Hiba Történt",
|
||||||
"change_server": "Szerverváltás",
|
"change_server": "Szerverváltás",
|
||||||
"invalid_username_or_password": "Érvénytelen Felhasználónév vagy Jelszó",
|
"invalid_username_or_password": "Érvénytelen Felhasználónév vagy Jelszó",
|
||||||
"user_does_not_have_permission_to_log_in": "A felhasználónak nincs jogosultsága a bejelentkezéshez",
|
"user_does_not_have_permission_to_log_in": "A felhasználónak nincs jogosultsága a bejelentkezéshez",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "A szerver túl sokáig válaszol, próbáld újra később",
|
"server_is_taking_too_long_to_respond_try_again_later": "A szerver túl sokáig válaszol, próbáld újra később",
|
||||||
"server_received_too_many_requests_try_again_later": "A szerver túl sok kérést kapott, próbáld újra később.",
|
"server_received_too_many_requests_try_again_later": "A szerver túl sok kérést kapott, próbáld újra később.",
|
||||||
"there_is_a_server_error": "Szerverhiba történt",
|
"there_is_a_server_error": "Szerverhiba történt",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Váratlan hiba történt. Helyesen adtad meg a szerver URL-jét?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Váratlan hiba történt. Helyesen adtad meg a szerver URL-jét?",
|
||||||
"too_old_server_text": "Nem Támogatott Jellyfin-szerver",
|
"too_old_server_text": "Nem Támogatott Jellyfin-szerver",
|
||||||
"too_old_server_description": "Frissítsd a Jellyfint a legújabb verzióra"
|
"too_old_server_description": "Frissítsd a Jellyfint a legújabb verzióra"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Gyorscsatlakozás Engedélyezése",
|
"authorize_button": "Gyorscsatlakozás Engedélyezése",
|
||||||
"enter_the_quick_connect_code": "Add meg a gyors csatlakozási kódot...",
|
"enter_the_quick_connect_code": "Add meg a gyors csatlakozási kódot...",
|
||||||
"success": "Siker",
|
"success": "Siker",
|
||||||
"quick_connect_autorized": "Gyorscsatlakozás Engedélyezve",
|
"quick_connect_authorized": "Gyorscsatlakozás Engedélyezve",
|
||||||
"error": "Hiba",
|
"error": "Hiba",
|
||||||
"invalid_code": "Érvénytelen Kód",
|
"invalid_code": "Érvénytelen Kód",
|
||||||
"authorize": "Engedélyezés"
|
"authorize": "Engedélyezés"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Egyéni Menülinkek Megjelenítése",
|
"show_custom_menu_links": "Egyéni Menülinkek Megjelenítése",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Könyvtárak Elrejtése",
|
"hide_libraries": "Könyvtárak Elrejtése",
|
||||||
"select_liraries_you_want_to_hide": "Válaszd ki azokat a könyvtárakat, amelyeket el szeretnél rejteni a Könyvtár fülön és a kezdőlapon.",
|
"select_libraries_you_want_to_hide": "Válaszd ki azokat a könyvtárakat, amelyeket el szeretnél rejteni a Könyvtár fülön és a kezdőlapon.",
|
||||||
"disable_haptic_feedback": "Haptikus Visszajelzés Letiltása",
|
"disable_haptic_feedback": "Haptikus Visszajelzés Letiltása",
|
||||||
"default_quality": "Alapértelmezett Minőség",
|
"default_quality": "Alapértelmezett Minőség",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Eszköz {{availableSpace}}%",
|
"device_usage": "Eszköz {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} Használatban",
|
"size_used": "{{used}} / {{total}} Használatban",
|
||||||
"delete_all_downloaded_files": "Minden Letöltött Fájl Törlése",
|
"delete_all_downloaded_files": "Minden Letöltött Fájl Törlése",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Hiba",
|
"error": "Hiba",
|
||||||
"failed_to_get_stream_url": "Nem sikerült lekérni a stream URL-t",
|
"failed_to_get_stream_url": "Nem sikerült lekérni a stream URL-t",
|
||||||
"an_error_occured_while_playing_the_video": "Hiba történt a videó lejátszása közben. Ellenőrizd a naplókat a beállításokban.",
|
"an_error_occurred_while_playing_the_video": "Hiba történt a videó lejátszása közben. Ellenőrizd a naplókat a beállításokban.",
|
||||||
"client_error": "Kliens Hiba",
|
"client_error": "Kliens Hiba",
|
||||||
"could_not_create_stream_for_chromecast": "A Chromecast stream létrehozása sikertelen volt",
|
"could_not_create_stream_for_chromecast": "A Chromecast stream létrehozása sikertelen volt",
|
||||||
"message_from_server": "Üzenet a szervertől: {{message}}",
|
"message_from_server": "Üzenet a szervertől: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Capito",
|
"got_it": "Capito",
|
||||||
"connection_failed": "Connessione fallita",
|
"connection_failed": "Connessione fallita",
|
||||||
"could_not_connect_to_server": "Impossibile connettersi al server. Controllare l'URL e la connessione di rete.",
|
"could_not_connect_to_server": "Impossibile connettersi al server. Controllare l'URL e la connessione di rete.",
|
||||||
"an_unexpected_error_occured": "Si è verificato un errore inaspettato",
|
"an_unexpected_error_occurred": "Si è verificato un errore inaspettato",
|
||||||
"change_server": "Cambiare il server",
|
"change_server": "Cambiare il server",
|
||||||
"invalid_username_or_password": "Nome utente o password non validi",
|
"invalid_username_or_password": "Nome utente o password non validi",
|
||||||
"user_does_not_have_permission_to_log_in": "L'utente non ha il permesso di accedere",
|
"user_does_not_have_permission_to_log_in": "L'utente non ha il permesso di accedere",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Il server sta impiegando troppo tempo per rispondere, riprovare più tardi",
|
"server_is_taking_too_long_to_respond_try_again_later": "Il server sta impiegando troppo tempo per rispondere, riprovare più tardi",
|
||||||
"server_received_too_many_requests_try_again_later": "Il server ha ricevuto troppe richieste, riprovare più tardi.",
|
"server_received_too_many_requests_try_again_later": "Il server ha ricevuto troppe richieste, riprovare più tardi.",
|
||||||
"there_is_a_server_error": "Si è verificato un errore del server",
|
"there_is_a_server_error": "Si è verificato un errore del server",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Si è verificato un errore imprevisto. L'URL del server è stato inserito correttamente?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Si è verificato un errore imprevisto. L'URL del server è stato inserito correttamente?",
|
||||||
"too_old_server_text": "Scoperto Server Jellyfin non supportato",
|
"too_old_server_text": "Scoperto Server Jellyfin non supportato",
|
||||||
"too_old_server_description": "Aggiorna Jellyfin all'ultima versione"
|
"too_old_server_description": "Aggiorna Jellyfin all'ultima versione"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autorizza Connessione Rapida",
|
"authorize_button": "Autorizza Connessione Rapida",
|
||||||
"enter_the_quick_connect_code": "Inserisci il codice per la Connessione Rapida...",
|
"enter_the_quick_connect_code": "Inserisci il codice per la Connessione Rapida...",
|
||||||
"success": "Successo",
|
"success": "Successo",
|
||||||
"quick_connect_autorized": "Connessione Rapida autorizzata",
|
"quick_connect_authorized": "Connessione Rapida autorizzata",
|
||||||
"error": "Errore",
|
"error": "Errore",
|
||||||
"invalid_code": "Codice invalido",
|
"invalid_code": "Codice invalido",
|
||||||
"authorize": "Autorizza"
|
"authorize": "Autorizza"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Mostra i link del menu personalizzato",
|
"show_custom_menu_links": "Mostra i link del menu personalizzato",
|
||||||
"show_large_home_carousel": "Mostra Carosello Grande nella Home (beta)",
|
"show_large_home_carousel": "Mostra Carosello Grande nella Home (beta)",
|
||||||
"hide_libraries": "Nascondi Librerie",
|
"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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Disabilita il feedback aptico",
|
||||||
"default_quality": "Qualità predefinita",
|
"default_quality": "Qualità predefinita",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Dispositivo {{availableSpace}}%",
|
"device_usage": "Dispositivo {{availableSpace}}%",
|
||||||
"size_used": "{{used}} di {{total}} usato",
|
"size_used": "{{used}} di {{total}} usato",
|
||||||
"delete_all_downloaded_files": "Cancella Tutti i File Scaricati",
|
"delete_all_downloaded_files": "Cancella Tutti i File Scaricati",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Precarica automaticamente i brani mentre ascolti per una riproduzione più fluida e il supporto offline",
|
"music_cache_description": "Precarica automaticamente i brani mentre ascolti per una riproduzione più fluida e il supporto offline",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Errore",
|
"error": "Errore",
|
||||||
"failed_to_get_stream_url": "Impossibile ottenere l'URL dello stream",
|
"failed_to_get_stream_url": "Impossibile ottenere l'URL dello stream",
|
||||||
"an_error_occured_while_playing_the_video": "Si è verificato un errore durante la riproduzione del video. Controllare i log nelle impostazioni.",
|
"an_error_occurred_while_playing_the_video": "Si è verificato un errore durante la riproduzione del video. Controllare i log nelle impostazioni.",
|
||||||
"client_error": "Errore del client",
|
"client_error": "Errore del client",
|
||||||
"could_not_create_stream_for_chromecast": "Impossibile creare uno stream per Chromecast",
|
"could_not_create_stream_for_chromecast": "Impossibile creare uno stream per Chromecast",
|
||||||
"message_from_server": "Messaggio dal server",
|
"message_from_server": "Messaggio dal server",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "了解",
|
"got_it": "了解",
|
||||||
"connection_failed": "接続に失敗しました",
|
"connection_failed": "接続に失敗しました",
|
||||||
"could_not_connect_to_server": "サーバーに接続できませんでした。URLとネットワーク接続を確認してください。",
|
"could_not_connect_to_server": "サーバーに接続できませんでした。URLとネットワーク接続を確認してください。",
|
||||||
"an_unexpected_error_occured": "予期しないエラーが発生しました",
|
"an_unexpected_error_occurred": "予期しないエラーが発生しました",
|
||||||
"change_server": "サーバーの変更",
|
"change_server": "サーバーの変更",
|
||||||
"invalid_username_or_password": "ユーザー名またはパスワードが無効です",
|
"invalid_username_or_password": "ユーザー名またはパスワードが無効です",
|
||||||
"user_does_not_have_permission_to_log_in": "ユーザーにログイン権限がありません",
|
"user_does_not_have_permission_to_log_in": "ユーザーにログイン権限がありません",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "サーバーの応答に時間がかかりすぎています。しばらくしてからもう一度お試しください。",
|
"server_is_taking_too_long_to_respond_try_again_later": "サーバーの応答に時間がかかりすぎています。しばらくしてからもう一度お試しください。",
|
||||||
"server_received_too_many_requests_try_again_later": "サーバーにリクエストが多すぎます。後でもう一度お試しください。",
|
"server_received_too_many_requests_try_again_later": "サーバーにリクエストが多すぎます。後でもう一度お試しください。",
|
||||||
"there_is_a_server_error": "サーバーエラーが発生しました",
|
"there_is_a_server_error": "サーバーエラーが発生しました",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "予期しないエラーが発生しました。サーバーのURLを正しく入力しましたか?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "予期しないエラーが発生しました。サーバーのURLを正しく入力しましたか?",
|
||||||
"too_old_server_text": "サポートされていないJellyfinサーバー発見",
|
"too_old_server_text": "サポートされていないJellyfinサーバー発見",
|
||||||
"too_old_server_description": "Jellyfinを最新バージョンにアップデートしてください"
|
"too_old_server_description": "Jellyfinを最新バージョンにアップデートしてください"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "クイックコネクトを承認する",
|
"authorize_button": "クイックコネクトを承認する",
|
||||||
"enter_the_quick_connect_code": "クイックコネクトコードを入力...",
|
"enter_the_quick_connect_code": "クイックコネクトコードを入力...",
|
||||||
"success": "成功しました",
|
"success": "成功しました",
|
||||||
"quick_connect_autorized": "クイックコネクトが承認されました",
|
"quick_connect_authorized": "クイックコネクトが承認されました",
|
||||||
"error": "エラー",
|
"error": "エラー",
|
||||||
"invalid_code": "無効なコードです",
|
"invalid_code": "無効なコードです",
|
||||||
"authorize": "承認"
|
"authorize": "承認"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "カスタムメニューのリンクを表示",
|
"show_custom_menu_links": "カスタムメニューのリンクを表示",
|
||||||
"show_large_home_carousel": "大きなヒーロー(Beta)",
|
"show_large_home_carousel": "大きなヒーロー(Beta)",
|
||||||
"hide_libraries": "ライブラリを非表示",
|
"hide_libraries": "ライブラリを非表示",
|
||||||
"select_liraries_you_want_to_hide": "ライブラリタブとホームページセクションから非表示にするライブラリを選択します。",
|
"select_libraries_you_want_to_hide": "ライブラリタブとホームページセクションから非表示にするライブラリを選択します。",
|
||||||
"disable_haptic_feedback": "触覚フィードバックを無効にする",
|
"disable_haptic_feedback": "触覚フィードバックを無効にする",
|
||||||
"default_quality": "デフォルトの品質",
|
"default_quality": "デフォルトの品質",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "デバイス {{availableSpace}}%",
|
"device_usage": "デバイス {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} 使用済み",
|
"size_used": "{{used}} / {{total}} 使用済み",
|
||||||
"delete_all_downloaded_files": "すべてのダウンロードファイルを削除",
|
"delete_all_downloaded_files": "すべてのダウンロードファイルを削除",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "エラー",
|
"error": "エラー",
|
||||||
"failed_to_get_stream_url": "ストリームURLを取得できませんでした",
|
"failed_to_get_stream_url": "ストリームURLを取得できませんでした",
|
||||||
"an_error_occured_while_playing_the_video": "動画の再生中にエラーが発生しました。設定でログを確認してください。",
|
"an_error_occurred_while_playing_the_video": "動画の再生中にエラーが発生しました。設定でログを確認してください。",
|
||||||
"client_error": "クライアントエラー",
|
"client_error": "クライアントエラー",
|
||||||
"could_not_create_stream_for_chromecast": "Chromecastのストリームを作成できませんでした",
|
"could_not_create_stream_for_chromecast": "Chromecastのストリームを作成できませんでした",
|
||||||
"message_from_server": "サーバーからのメッセージ",
|
"message_from_server": "サーバーからのメッセージ",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "성공",
|
"got_it": "성공",
|
||||||
"connection_failed": "연결 실패",
|
"connection_failed": "연결 실패",
|
||||||
"could_not_connect_to_server": "서버에 연결되지 않았습니다. URL과 네트워크 상태를 확인하세요.",
|
"could_not_connect_to_server": "서버에 연결되지 않았습니다. URL과 네트워크 상태를 확인하세요.",
|
||||||
"an_unexpected_error_occured": "예기치 않은 오류가 발생했습니다",
|
"an_unexpected_error_occurred": "예기치 않은 오류가 발생했습니다",
|
||||||
"change_server": "서버 변경",
|
"change_server": "서버 변경",
|
||||||
"invalid_username_or_password": "잘못된 아이디 혹은 비밀번호입니다",
|
"invalid_username_or_password": "잘못된 아이디 혹은 비밀번호입니다",
|
||||||
"user_does_not_have_permission_to_log_in": "로그인 하기 위한 권한이 없습니다",
|
"user_does_not_have_permission_to_log_in": "로그인 하기 위한 권한이 없습니다",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "서버 응답이 너무 느립니다. 나중에 다시 시도하세요",
|
"server_is_taking_too_long_to_respond_try_again_later": "서버 응답이 너무 느립니다. 나중에 다시 시도하세요",
|
||||||
"server_received_too_many_requests_try_again_later": "서버가 너무 많은 요청을 받았습니다. 나중에 다시 시도하세요.",
|
"server_received_too_many_requests_try_again_later": "서버가 너무 많은 요청을 받았습니다. 나중에 다시 시도하세요.",
|
||||||
"there_is_a_server_error": "서버 에러",
|
"there_is_a_server_error": "서버 에러",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "예기치 않은 오류가 발생했습니다. 서버 URL을 올바르게 입력하셨습니까?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "예기치 않은 오류가 발생했습니다. 서버 URL을 올바르게 입력하셨습니까?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "퀵 커넥트 승인",
|
"authorize_button": "퀵 커넥트 승인",
|
||||||
"enter_the_quick_connect_code": "퀵 커넥트 코드 입력...",
|
"enter_the_quick_connect_code": "퀵 커넥트 코드 입력...",
|
||||||
"success": "성공",
|
"success": "성공",
|
||||||
"quick_connect_autorized": "퀵 커넥트 승인됨",
|
"quick_connect_authorized": "퀵 커넥트 승인됨",
|
||||||
"error": "오류",
|
"error": "오류",
|
||||||
"invalid_code": "유효하지 않은 코드",
|
"invalid_code": "유효하지 않은 코드",
|
||||||
"authorize": "승인"
|
"authorize": "승인"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "사용자 지정 메뉴 링크 표시",
|
"show_custom_menu_links": "사용자 지정 메뉴 링크 표시",
|
||||||
"show_large_home_carousel": "대형 홈 슬라이드 배너 표시 (베타)",
|
"show_large_home_carousel": "대형 홈 슬라이드 배너 표시 (베타)",
|
||||||
"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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Disable Haptic Feedback",
|
||||||
"default_quality": "Default Quality",
|
"default_quality": "Default Quality",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "디바이스 {{availableSpace}}%",
|
"device_usage": "디바이스 {{availableSpace}}%",
|
||||||
"size_used": "{{used}} of {{total}} Used",
|
"size_used": "{{used}} of {{total}} Used",
|
||||||
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||||
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
"client_error": "Client Error",
|
"client_error": "Client Error",
|
||||||
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
||||||
"message_from_server": "Message from Server: {{message}}",
|
"message_from_server": "Message from Server: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Begrepen",
|
"got_it": "Begrepen",
|
||||||
"connection_failed": "Verbinding mislukt",
|
"connection_failed": "Verbinding mislukt",
|
||||||
"could_not_connect_to_server": "Kon niet verbinden met de server. Controleer de URL en je netwerkverbinding.",
|
"could_not_connect_to_server": "Kon niet verbinden met de server. Controleer de URL en je netwerkverbinding.",
|
||||||
"an_unexpected_error_occured": "Er is een onverwachte fout opgetreden",
|
"an_unexpected_error_occurred": "Er is een onverwachte fout opgetreden",
|
||||||
"change_server": "Server wijzigen",
|
"change_server": "Server wijzigen",
|
||||||
"invalid_username_or_password": "Onjuiste gebruikersnaam of wachtwoord",
|
"invalid_username_or_password": "Onjuiste gebruikersnaam of wachtwoord",
|
||||||
"user_does_not_have_permission_to_log_in": "Gebruiker heeft geen rechten om aan te melden",
|
"user_does_not_have_permission_to_log_in": "Gebruiker heeft geen rechten om aan te melden",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "De server doet er te lang over om te antwoorden, probeer later opnieuw",
|
"server_is_taking_too_long_to_respond_try_again_later": "De server doet er te lang over om te antwoorden, probeer later opnieuw",
|
||||||
"server_received_too_many_requests_try_again_later": "De server heeft te veel aanvragen ontvangen, probeer later opnieuw",
|
"server_received_too_many_requests_try_again_later": "De server heeft te veel aanvragen ontvangen, probeer later opnieuw",
|
||||||
"there_is_a_server_error": "Er is een serverfout",
|
"there_is_a_server_error": "Er is een serverfout",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Er is een onverwachte fout opgetreden. Heb je de server URL correct ingegeven?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Er is een onverwachte fout opgetreden. Heb je de server URL correct ingegeven?",
|
||||||
"too_old_server_text": "Niet-ondersteunde Jellyfin Server Ontdekt",
|
"too_old_server_text": "Niet-ondersteunde Jellyfin Server Ontdekt",
|
||||||
"too_old_server_description": "Werk Jellyfin bij naar de laatste versie"
|
"too_old_server_description": "Werk Jellyfin bij naar de laatste versie"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Snel Verbinden toestaan",
|
"authorize_button": "Snel Verbinden toestaan",
|
||||||
"enter_the_quick_connect_code": "Vul de Snel Verbinden code in...",
|
"enter_the_quick_connect_code": "Vul de Snel Verbinden code in...",
|
||||||
"success": "Succes",
|
"success": "Succes",
|
||||||
"quick_connect_autorized": "Snel Verbinden toegestaan",
|
"quick_connect_authorized": "Snel Verbinden toegestaan",
|
||||||
"error": "Fout",
|
"error": "Fout",
|
||||||
"invalid_code": "Ongeldige code",
|
"invalid_code": "Ongeldige code",
|
||||||
"authorize": "Toestaan"
|
"authorize": "Toestaan"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Aangepaste menulinks tonen",
|
"show_custom_menu_links": "Aangepaste menulinks tonen",
|
||||||
"show_large_home_carousel": "Toon grote carrousel op startpagina (bèta)",
|
"show_large_home_carousel": "Toon grote carrousel op startpagina (bèta)",
|
||||||
"hide_libraries": "Verberg Bibliotheken",
|
"hide_libraries": "Verberg Bibliotheken",
|
||||||
"select_liraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.",
|
"select_libraries_you_want_to_hide": "Selecteer de bibliotheken die je wil verbergen van de Bibliotheektab en hoofdpagina onderdelen.",
|
||||||
"disable_haptic_feedback": "Haptische feedback uitschakelen",
|
"disable_haptic_feedback": "Haptische feedback uitschakelen",
|
||||||
"default_quality": "Standaard kwaliteit",
|
"default_quality": "Standaard kwaliteit",
|
||||||
"default_playback_speed": "Standaard Afspeelsnelheid",
|
"default_playback_speed": "Standaard Afspeelsnelheid",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Toestel {{availableSpace}}%",
|
"device_usage": "Toestel {{availableSpace}}%",
|
||||||
"size_used": "{{used}} van {{total}} gebruikt",
|
"size_used": "{{used}} van {{total}} gebruikt",
|
||||||
"delete_all_downloaded_files": "Verwijder alle gedownloade bestanden",
|
"delete_all_downloaded_files": "Verwijder alle gedownloade bestanden",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Fout",
|
"error": "Fout",
|
||||||
"failed_to_get_stream_url": "De stream-URL kon niet worden verkregen",
|
"failed_to_get_stream_url": "De stream-URL kon niet worden verkregen",
|
||||||
"an_error_occured_while_playing_the_video": "Er is een fout opgetreden tijdens het afspelen van de video. Controleer de logs in de instellingen.",
|
"an_error_occurred_while_playing_the_video": "Er is een fout opgetreden tijdens het afspelen van de video. Controleer de logs in de instellingen.",
|
||||||
"client_error": "Fout van de client",
|
"client_error": "Fout van de client",
|
||||||
"could_not_create_stream_for_chromecast": "Kon geen stream maken voor Chromecast",
|
"could_not_create_stream_for_chromecast": "Kon geen stream maken voor Chromecast",
|
||||||
"message_from_server": "Bericht van de server",
|
"message_from_server": "Bericht van de server",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Har det",
|
"got_it": "Har det",
|
||||||
"connection_failed": "Tilkobling mislyktes",
|
"connection_failed": "Tilkobling mislyktes",
|
||||||
"could_not_connect_to_server": "Kunne ikke koble til serveren. Kontroller nettadressen og nettverkstilkoblingen.",
|
"could_not_connect_to_server": "Kunne ikke koble til serveren. Kontroller nettadressen og nettverkstilkoblingen.",
|
||||||
"an_unexpected_error_occured": "En uventet feil oppstod",
|
"an_unexpected_error_occurred": "En uventet feil oppstod",
|
||||||
"change_server": "Endre server",
|
"change_server": "Endre server",
|
||||||
"invalid_username_or_password": "Ugyldig brukernavn eller passord",
|
"invalid_username_or_password": "Ugyldig brukernavn eller passord",
|
||||||
"user_does_not_have_permission_to_log_in": "Brukeren har ikke tillatelse til å logge inn",
|
"user_does_not_have_permission_to_log_in": "Brukeren har ikke tillatelse til å logge inn",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Serveren tar for lang tid å svare, prøv igjen senere",
|
"server_is_taking_too_long_to_respond_try_again_later": "Serveren tar for lang tid å svare, prøv igjen senere",
|
||||||
"server_received_too_many_requests_try_again_later": "Serveren mottok for mange forespørsler, prøv igjen senere.",
|
"server_received_too_many_requests_try_again_later": "Serveren mottok for mange forespørsler, prøv igjen senere.",
|
||||||
"there_is_a_server_error": "Det er en serverfeil",
|
"there_is_a_server_error": "Det er en serverfeil",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Det oppstod en uventet feil. Sendte du inn URLen til serveren riktig?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Det oppstod en uventet feil. Sendte du inn URLen til serveren riktig?",
|
||||||
"too_old_server_text": "Ustøttet Jellyfin Server oppdaget",
|
"too_old_server_text": "Ustøttet Jellyfin Server oppdaget",
|
||||||
"too_old_server_description": "Vennligst oppdater Jellyfin til nyeste versjon"
|
"too_old_server_description": "Vennligst oppdater Jellyfin til nyeste versjon"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autoriser rask tilkobling",
|
"authorize_button": "Autoriser rask tilkobling",
|
||||||
"enter_the_quick_connect_code": "Skriv inn hurtig-tilkobling kode...",
|
"enter_the_quick_connect_code": "Skriv inn hurtig-tilkobling kode...",
|
||||||
"success": "Vellykket",
|
"success": "Vellykket",
|
||||||
"quick_connect_autorized": "Hurtig tilkobling autorisert",
|
"quick_connect_authorized": "Hurtig tilkobling autorisert",
|
||||||
"error": "Feil",
|
"error": "Feil",
|
||||||
"invalid_code": "Ugyldig kode",
|
"invalid_code": "Ugyldig kode",
|
||||||
"authorize": "Autoriser"
|
"authorize": "Autoriser"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Vis tilpassede menylenker",
|
"show_custom_menu_links": "Vis tilpassede menylenker",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Skjul biblioteker",
|
"hide_libraries": "Skjul biblioteker",
|
||||||
"select_liraries_you_want_to_hide": "Velg bibliotekene du vil skjule deg for Biblioteket og avsnittene for hjemmesider.",
|
"select_libraries_you_want_to_hide": "Velg bibliotekene du vil skjule deg for Biblioteket og avsnittene for hjemmesider.",
|
||||||
"disable_haptic_feedback": "Deaktiver Haptisk tilbakemelding",
|
"disable_haptic_feedback": "Deaktiver Haptisk tilbakemelding",
|
||||||
"default_quality": "Standard kvalitet",
|
"default_quality": "Standard kvalitet",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Enhet {{availableSpace}}%",
|
"device_usage": "Enhet {{availableSpace}}%",
|
||||||
"size_used": "{{used}} av {{total}} er i bruk",
|
"size_used": "{{used}} av {{total}} er i bruk",
|
||||||
"delete_all_downloaded_files": "Slett alle nedlastede filer",
|
"delete_all_downloaded_files": "Slett alle nedlastede filer",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Feil",
|
"error": "Feil",
|
||||||
"failed_to_get_stream_url": "Kan ikke hente nettadressen for stream",
|
"failed_to_get_stream_url": "Kan ikke hente nettadressen for stream",
|
||||||
"an_error_occured_while_playing_the_video": "En feil oppstod under video. Sjekk loggene i innstillingene.",
|
"an_error_occurred_while_playing_the_video": "En feil oppstod under video. Sjekk loggene i innstillingene.",
|
||||||
"client_error": "Feil med annonsør",
|
"client_error": "Feil med annonsør",
|
||||||
"could_not_create_stream_for_chromecast": "Kan ikke opprette en strøm for Chromecast",
|
"could_not_create_stream_for_chromecast": "Kan ikke opprette en strøm for Chromecast",
|
||||||
"message_from_server": "Melding fra tjener: {{message}}",
|
"message_from_server": "Melding fra tjener: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Rozumiem",
|
"got_it": "Rozumiem",
|
||||||
"connection_failed": "Połączenie nieudane",
|
"connection_failed": "Połączenie nieudane",
|
||||||
"could_not_connect_to_server": "Nie można połączyć się z serwerem. Sprawdź adres URL oraz połączenie sieciowe.",
|
"could_not_connect_to_server": "Nie można połączyć się z serwerem. Sprawdź adres URL oraz połączenie sieciowe.",
|
||||||
"an_unexpected_error_occured": "Wystąpił nieoczekiwany błąd",
|
"an_unexpected_error_occurred": "Wystąpił nieoczekiwany błąd",
|
||||||
"change_server": "Zmień serwer",
|
"change_server": "Zmień serwer",
|
||||||
"invalid_username_or_password": "Nieprawidłowa nazwa użytkownika lub hasło",
|
"invalid_username_or_password": "Nieprawidłowa nazwa użytkownika lub hasło",
|
||||||
"user_does_not_have_permission_to_log_in": "Użytkownik nie ma uprawnień do logowania",
|
"user_does_not_have_permission_to_log_in": "Użytkownik nie ma uprawnień do logowania",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Serwer zbyt długo nie odpowiada – spróbuj ponownie później",
|
"server_is_taking_too_long_to_respond_try_again_later": "Serwer zbyt długo nie odpowiada – spróbuj ponownie później",
|
||||||
"server_received_too_many_requests_try_again_later": "Serwer otrzymał zbyt wiele żądań – spróbuj ponownie później.",
|
"server_received_too_many_requests_try_again_later": "Serwer otrzymał zbyt wiele żądań – spróbuj ponownie później.",
|
||||||
"there_is_a_server_error": "Wystąpił błąd serwera",
|
"there_is_a_server_error": "Wystąpił błąd serwera",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Wystąpił nieoczekiwany błąd. Czy wpisałeś poprawny adres URL?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Wystąpił nieoczekiwany błąd. Czy wpisałeś poprawny adres URL?",
|
||||||
"too_old_server_text": "Wykryto nieobsługiwany serwer Jellyfin",
|
"too_old_server_text": "Wykryto nieobsługiwany serwer Jellyfin",
|
||||||
"too_old_server_description": "Proszę zaktualizować Jellyfin do najnowszej wersji"
|
"too_old_server_description": "Proszę zaktualizować Jellyfin do najnowszej wersji"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autoryzuj szybkie połączenie",
|
"authorize_button": "Autoryzuj szybkie połączenie",
|
||||||
"enter_the_quick_connect_code": "Wpisz kod szybkiego połączenia...",
|
"enter_the_quick_connect_code": "Wpisz kod szybkiego połączenia...",
|
||||||
"success": "Sukces",
|
"success": "Sukces",
|
||||||
"quick_connect_autorized": "Szybkie połączenie autoryzowane",
|
"quick_connect_authorized": "Szybkie połączenie autoryzowane",
|
||||||
"error": "Błąd",
|
"error": "Błąd",
|
||||||
"invalid_code": "Nieprawidłowy kod",
|
"invalid_code": "Nieprawidłowy kod",
|
||||||
"authorize": "Autoryzuj"
|
"authorize": "Autoryzuj"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Pokaż niestandardowe odnośniki w menu",
|
"show_custom_menu_links": "Pokaż niestandardowe odnośniki w menu",
|
||||||
"show_large_home_carousel": "Wyświetl Dużą Karuzelę na ekranie głównym (beta)",
|
"show_large_home_carousel": "Wyświetl Dużą Karuzelę na ekranie głównym (beta)",
|
||||||
"hide_libraries": "Ukryj biblioteki",
|
"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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Wyłącz wibracje",
|
||||||
"default_quality": "Domyślna jakość",
|
"default_quality": "Domyślna jakość",
|
||||||
"default_playback_speed": "Domyślna prędkość odtwarzania",
|
"default_playback_speed": "Domyślna prędkość odtwarzania",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Urządzenie {{availableSpace}}%",
|
"device_usage": "Urządzenie {{availableSpace}}%",
|
||||||
"size_used": "{{used}} z {{total}} wykorzystane",
|
"size_used": "{{used}} z {{total}} wykorzystane",
|
||||||
"delete_all_downloaded_files": "Usuń wszystkie pobrane pliki",
|
"delete_all_downloaded_files": "Usuń wszystkie pobrane pliki",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Bufor muzyki",
|
"music_cache_title": "Bufor muzyki",
|
||||||
"music_cache_description": "Automatycznie buforuj piosenki w trakcie słuchania dla płynniejszego odtwarzania i wsparcia offline",
|
"music_cache_description": "Automatycznie buforuj piosenki w trakcie słuchania dla płynniejszego odtwarzania i wsparcia offline",
|
||||||
"clear_music_cache": "Wyczyść bufor muzyki",
|
"clear_music_cache": "Wyczyść bufor muzyki",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Błąd",
|
"error": "Błąd",
|
||||||
"failed_to_get_stream_url": "Nie udało się pobrać adresu strumienia",
|
"failed_to_get_stream_url": "Nie udało się pobrać adresu strumienia",
|
||||||
"an_error_occured_while_playing_the_video": "Wystąpił błąd podczas odtwarzania wideo. Sprawdź logi w ustawieniach.",
|
"an_error_occurred_while_playing_the_video": "Wystąpił błąd podczas odtwarzania wideo. Sprawdź logi w ustawieniach.",
|
||||||
"client_error": "Błąd klienta",
|
"client_error": "Błąd klienta",
|
||||||
"could_not_create_stream_for_chromecast": "Nie udało się utworzyć strumienia dla Chromecasta",
|
"could_not_create_stream_for_chromecast": "Nie udało się utworzyć strumienia dla Chromecasta",
|
||||||
"message_from_server": "Wiadomość z serwera: {{message}}",
|
"message_from_server": "Wiadomość z serwera: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Tenho isso",
|
"got_it": "Tenho isso",
|
||||||
"connection_failed": "Falha na conexão",
|
"connection_failed": "Falha na conexão",
|
||||||
"could_not_connect_to_server": "Não foi possível conectar ao servidor. Verifique a URL e sua conexão de rede.",
|
"could_not_connect_to_server": "Não foi possível conectar ao servidor. Verifique a URL e sua conexão de rede.",
|
||||||
"an_unexpected_error_occured": "Ocorreu um erro inesperado",
|
"an_unexpected_error_occurred": "An unexpected error occurred",
|
||||||
"change_server": "Alterar Servidor",
|
"change_server": "Alterar Servidor",
|
||||||
"invalid_username_or_password": "Usuário ou senha inválidos",
|
"invalid_username_or_password": "Usuário ou senha inválidos",
|
||||||
"user_does_not_have_permission_to_log_in": "Usuário não tem permissão para fazer login",
|
"user_does_not_have_permission_to_log_in": "Usuário não tem permissão para fazer login",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "O servidor está demorando muito para responder, tente novamente mais tarde",
|
"server_is_taking_too_long_to_respond_try_again_later": "O servidor está demorando muito para responder, tente novamente mais tarde",
|
||||||
"server_received_too_many_requests_try_again_later": "O servidor recebeu muitos pedidos, tente novamente mais tarde.",
|
"server_received_too_many_requests_try_again_later": "O servidor recebeu muitos pedidos, tente novamente mais tarde.",
|
||||||
"there_is_a_server_error": "Existe um erro no servidor",
|
"there_is_a_server_error": "Existe um erro no servidor",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Ocorreu um erro inesperado. Você inseriu a URL do servidor corretamente?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
||||||
"too_old_server_text": "Servidor Jellyfin Descoberto Não Suportado",
|
"too_old_server_text": "Servidor Jellyfin Descoberto Não Suportado",
|
||||||
"too_old_server_description": "Por favor, atualize o Jellyfin para a versão mais recente"
|
"too_old_server_description": "Por favor, atualize o Jellyfin para a versão mais recente"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autorizar conexão rápida",
|
"authorize_button": "Autorizar conexão rápida",
|
||||||
"enter_the_quick_connect_code": "Insira o código de conexão rápida...",
|
"enter_the_quick_connect_code": "Insira o código de conexão rápida...",
|
||||||
"success": "Sucesso",
|
"success": "Sucesso",
|
||||||
"quick_connect_autorized": "Acesso Rápido Autorizado",
|
"quick_connect_authorized": "Quick Connect authorized",
|
||||||
"error": "Erro",
|
"error": "Erro",
|
||||||
"invalid_code": "Código inválido",
|
"invalid_code": "Código inválido",
|
||||||
"authorize": "Autorizar"
|
"authorize": "Autorizar"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Mostrar Links de Menu Personalizado",
|
"show_custom_menu_links": "Mostrar Links de Menu Personalizado",
|
||||||
"show_large_home_carousel": "Mostrar Carrossel Grande (beta)",
|
"show_large_home_carousel": "Mostrar Carrossel Grande (beta)",
|
||||||
"hide_libraries": "Ocultar bibliotecas",
|
"hide_libraries": "Ocultar bibliotecas",
|
||||||
"select_liraries_you_want_to_hide": "Selecione as bibliotecas que você deseja ocultar da aba Biblioteca e seções da página inicial.",
|
"select_libraries_you_want_to_hide": "Select the libraries you want to hide from the Library tab and home page sections.",
|
||||||
"disable_haptic_feedback": "Desativar o retorno tátil",
|
"disable_haptic_feedback": "Desativar o retorno tátil",
|
||||||
"default_quality": "Qualidade Padrão",
|
"default_quality": "Qualidade Padrão",
|
||||||
"default_playback_speed": "Velocidade padrão de reprodução",
|
"default_playback_speed": "Velocidade padrão de reprodução",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Dispositivo {{availableSpace}}%",
|
"device_usage": "Dispositivo {{availableSpace}}%",
|
||||||
"size_used": "{{used}} de {{total}} Utilizados",
|
"size_used": "{{used}} de {{total}} Utilizados",
|
||||||
"delete_all_downloaded_files": "Excluir todos os arquivos baixados",
|
"delete_all_downloaded_files": "Excluir todos os arquivos baixados",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Cache de Música",
|
"music_cache_title": "Cache de Música",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Limpar Cache de Música",
|
"clear_music_cache": "Limpar Cache de Música",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "ERRO",
|
"error": "ERRO",
|
||||||
"failed_to_get_stream_url": "Falha ao obter a URL de transmissão",
|
"failed_to_get_stream_url": "Falha ao obter a URL de transmissão",
|
||||||
"an_error_occured_while_playing_the_video": "Ocorreu um erro ao reproduzir o vídeo. Verifique os logs nas configurações.",
|
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
"client_error": "Erro de Cliente",
|
"client_error": "Erro de Cliente",
|
||||||
"could_not_create_stream_for_chromecast": "Não foi possível criar um fluxo para o Chromecast",
|
"could_not_create_stream_for_chromecast": "Não foi possível criar um fluxo para o Chromecast",
|
||||||
"message_from_server": "Mensagem do Servidor: {{message}}",
|
"message_from_server": "Mensagem do Servidor: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Am înţeles",
|
"got_it": "Am înţeles",
|
||||||
"connection_failed": "Conectare eșuată",
|
"connection_failed": "Conectare eșuată",
|
||||||
"could_not_connect_to_server": "Nu s-a putut conecta la server. Verificați adresa URL și conexiunea la internet.",
|
"could_not_connect_to_server": "Nu s-a putut conecta la server. Verificați adresa URL și conexiunea la internet.",
|
||||||
"an_unexpected_error_occured": "A apărut o eroare neașteptată",
|
"an_unexpected_error_occurred": "A apărut o eroare neașteptată",
|
||||||
"change_server": "Schimba",
|
"change_server": "Schimba",
|
||||||
"invalid_username_or_password": "Nume de utilizator sau parolă greșită",
|
"invalid_username_or_password": "Nume de utilizator sau parolă greșită",
|
||||||
"user_does_not_have_permission_to_log_in": "Utilizatorul nu are permisiunea de a se conecta",
|
"user_does_not_have_permission_to_log_in": "Utilizatorul nu are permisiunea de a se conecta",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Serverul răspunde prea greu, încearcă din nou mai târziu.",
|
"server_is_taking_too_long_to_respond_try_again_later": "Serverul răspunde prea greu, încearcă din nou mai târziu.",
|
||||||
"server_received_too_many_requests_try_again_later": "Serverul a primit prea multe solicitări, încercați din nou mai târziu.",
|
"server_received_too_many_requests_try_again_later": "Serverul a primit prea multe solicitări, încercați din nou mai târziu.",
|
||||||
"there_is_a_server_error": "Eroare de server",
|
"there_is_a_server_error": "Eroare de server",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "A apărut o eroare neașteptată. Ați introdus corect adresa URL a serverului?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "A apărut o eroare neașteptată. Ați introdus corect adresa URL a serverului?",
|
||||||
"too_old_server_text": "Serverul Jellyfin Neacceptat Descoperit",
|
"too_old_server_text": "Serverul Jellyfin Neacceptat Descoperit",
|
||||||
"too_old_server_description": "Vă rugăm să actualizați Jellyfin la cea mai recentă versiune"
|
"too_old_server_description": "Vă rugăm să actualizați Jellyfin la cea mai recentă versiune"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Autorizare conectare rapidă",
|
"authorize_button": "Autorizare conectare rapidă",
|
||||||
"enter_the_quick_connect_code": "Introduceți codul pt. conectare rapidă...",
|
"enter_the_quick_connect_code": "Introduceți codul pt. conectare rapidă...",
|
||||||
"success": "Succes",
|
"success": "Succes",
|
||||||
"quick_connect_autorized": "Conectare rapidă autorizată",
|
"quick_connect_authorized": "Conectare rapidă autorizată",
|
||||||
"error": "Eroare",
|
"error": "Eroare",
|
||||||
"invalid_code": "Cod invalid.",
|
"invalid_code": "Cod invalid.",
|
||||||
"authorize": "Autorizează"
|
"authorize": "Autorizează"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Afișează link-uri personalizate în meniu",
|
"show_custom_menu_links": "Afișează link-uri personalizate în meniu",
|
||||||
"show_large_home_carousel": "Arată Caruselul Media Mare (beta)",
|
"show_large_home_carousel": "Arată Caruselul Media Mare (beta)",
|
||||||
"hide_libraries": "Ascunde bibliotecile",
|
"hide_libraries": "Ascunde bibliotecile",
|
||||||
"select_liraries_you_want_to_hide": "Selectează bibliotecile pe care dorești să le ascunzi din fila Bibliotecă și din secțiunile paginii principale.",
|
"select_libraries_you_want_to_hide": "Selectează bibliotecile pe care dorești să le ascunzi din fila Bibliotecă și din secțiunile paginii principale.",
|
||||||
"disable_haptic_feedback": "Dezactivează vibrațiile tactile",
|
"disable_haptic_feedback": "Dezactivează vibrațiile tactile",
|
||||||
"default_quality": "Calitate implicită",
|
"default_quality": "Calitate implicită",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Dispozitiv {{availableSpace}}%",
|
"device_usage": "Dispozitiv {{availableSpace}}%",
|
||||||
"size_used": "{{used}} din {{total}} folosit",
|
"size_used": "{{used}} din {{total}} folosit",
|
||||||
"delete_all_downloaded_files": "Ștergeți toate fișierele descărcate",
|
"delete_all_downloaded_files": "Ștergeți toate fișierele descărcate",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Eroare",
|
"error": "Eroare",
|
||||||
"failed_to_get_stream_url": "Nu s-a putut obține adresa URL a fluxului",
|
"failed_to_get_stream_url": "Nu s-a putut obține adresa URL a fluxului",
|
||||||
"an_error_occured_while_playing_the_video": "A apărut o eroare la redarea videoclipului. Verificați jurnalele în setări.",
|
"an_error_occurred_while_playing_the_video": "A apărut o eroare la redarea videoclipului. Verificați jurnalele în setări.",
|
||||||
"client_error": "Eroare client",
|
"client_error": "Eroare client",
|
||||||
"could_not_create_stream_for_chromecast": "Nu s-a putut crea un flux pentru Chromecast",
|
"could_not_create_stream_for_chromecast": "Nu s-a putut crea un flux pentru Chromecast",
|
||||||
"message_from_server": "Mesaj de la server: {{message}}",
|
"message_from_server": "Mesaj de la server: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Принято",
|
"got_it": "Принято",
|
||||||
"connection_failed": "Соединение не удалось",
|
"connection_failed": "Соединение не удалось",
|
||||||
"could_not_connect_to_server": "Не удалось подключиться к серверу. Пожалуйста, проверьте URL и ваше интернет-соединение.",
|
"could_not_connect_to_server": "Не удалось подключиться к серверу. Пожалуйста, проверьте URL и ваше интернет-соединение.",
|
||||||
"an_unexpected_error_occured": "Возникла непредвиденная ошибка",
|
"an_unexpected_error_occurred": "Возникла непредвиденная ошибка",
|
||||||
"change_server": "Поменять сервер",
|
"change_server": "Поменять сервер",
|
||||||
"invalid_username_or_password": "Неправильное имя пользователя или пароль",
|
"invalid_username_or_password": "Неправильное имя пользователя или пароль",
|
||||||
"user_does_not_have_permission_to_log_in": "Пользователь не имеет прав на вход",
|
"user_does_not_have_permission_to_log_in": "Пользователь не имеет прав на вход",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Сервер долго не отвечает, попробуйте позже",
|
"server_is_taking_too_long_to_respond_try_again_later": "Сервер долго не отвечает, попробуйте позже",
|
||||||
"server_received_too_many_requests_try_again_later": "Сервер получил слишком много запросов, попробуйте позже.",
|
"server_received_too_many_requests_try_again_later": "Сервер получил слишком много запросов, попробуйте позже.",
|
||||||
"there_is_a_server_error": "Возникла ошибка сервера",
|
"there_is_a_server_error": "Возникла ошибка сервера",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Возникла непредвиденная ошибка. Вы правильно ввели URL?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Возникла непредвиденная ошибка. Вы правильно ввели URL?",
|
||||||
"too_old_server_text": "Обнаружен неподдерживаемый сервер Jellyfin",
|
"too_old_server_text": "Обнаружен неподдерживаемый сервер Jellyfin",
|
||||||
"too_old_server_description": "Пожалуйста, обновите Jellyfin до последней версии"
|
"too_old_server_description": "Пожалуйста, обновите Jellyfin до последней версии"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Авторизовать через быстрое подключение",
|
"authorize_button": "Авторизовать через быстрое подключение",
|
||||||
"enter_the_quick_connect_code": "Введите код для быстрого подключения...",
|
"enter_the_quick_connect_code": "Введите код для быстрого подключения...",
|
||||||
"success": "Успех",
|
"success": "Успех",
|
||||||
"quick_connect_autorized": "Быстрое подключение авторизовано",
|
"quick_connect_authorized": "Быстрое подключение авторизовано",
|
||||||
"error": "Ошибка",
|
"error": "Ошибка",
|
||||||
"invalid_code": "Неверный код",
|
"invalid_code": "Неверный код",
|
||||||
"authorize": "Авторизовать"
|
"authorize": "Авторизовать"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Показать ссылки пользовательского меню",
|
"show_custom_menu_links": "Показать ссылки пользовательского меню",
|
||||||
"show_large_home_carousel": "Показывать большую карусель (beta)",
|
"show_large_home_carousel": "Показывать большую карусель (beta)",
|
||||||
"hide_libraries": "Скрыть библиотеки",
|
"hide_libraries": "Скрыть библиотеки",
|
||||||
"select_liraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.",
|
"select_libraries_you_want_to_hide": "Выберите Библиотеки, которое хотите спрятать из вкладки Библиотеки и домашней страницы.",
|
||||||
"disable_haptic_feedback": "Отключить тактильную обратную связь",
|
"disable_haptic_feedback": "Отключить тактильную обратную связь",
|
||||||
"default_quality": "Качество по умолчанию",
|
"default_quality": "Качество по умолчанию",
|
||||||
"default_playback_speed": "Скорость воспроизведения по умолчанию",
|
"default_playback_speed": "Скорость воспроизведения по умолчанию",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Устройство {{availableSpace}}%",
|
"device_usage": "Устройство {{availableSpace}}%",
|
||||||
"size_used": "{{used}} из {{total}} использовано",
|
"size_used": "{{used}} из {{total}} использовано",
|
||||||
"delete_all_downloaded_files": "Удалить все загруженные файлы",
|
"delete_all_downloaded_files": "Удалить все загруженные файлы",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Кеш музыки",
|
"music_cache_title": "Кеш музыки",
|
||||||
"music_cache_description": "Автоматически кешировать песни по мере прослушивания для плавного воспроизведения и поддержки отсутствия интернета",
|
"music_cache_description": "Автоматически кешировать песни по мере прослушивания для плавного воспроизведения и поддержки отсутствия интернета",
|
||||||
"clear_music_cache": "Очистить кеш музыки",
|
"clear_music_cache": "Очистить кеш музыки",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Ошибка",
|
"error": "Ошибка",
|
||||||
"failed_to_get_stream_url": "Не удалось получить URL потока",
|
"failed_to_get_stream_url": "Не удалось получить URL потока",
|
||||||
"an_error_occured_while_playing_the_video": "Возникла Неожиданная ошибка во время воспроизведения. Проверьте логи в настройках.",
|
"an_error_occurred_while_playing_the_video": "Возникла Неожиданная ошибка во время воспроизведения. Проверьте логи в настройках.",
|
||||||
"client_error": "Ошибка клиента",
|
"client_error": "Ошибка клиента",
|
||||||
"could_not_create_stream_for_chromecast": "Не удалось создать поток для Chromecast",
|
"could_not_create_stream_for_chromecast": "Не удалось создать поток для Chromecast",
|
||||||
"message_from_server": "Сообщение от сервера: {{message}}",
|
"message_from_server": "Сообщение от сервера: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Koden har mottagits",
|
"got_it": "Koden har mottagits",
|
||||||
"connection_failed": "Anslutningen Misslyckades",
|
"connection_failed": "Anslutningen Misslyckades",
|
||||||
"could_not_connect_to_server": "Det gick inte att ansluta till servern. Kontrollera webbadressen och din nätverksanslutning.",
|
"could_not_connect_to_server": "Det gick inte att ansluta till servern. Kontrollera webbadressen och din nätverksanslutning.",
|
||||||
"an_unexpected_error_occured": "Ett Oväntat Fel Uppstod",
|
"an_unexpected_error_occurred": "Ett Oväntat Fel Uppstod",
|
||||||
"change_server": "Byt Server",
|
"change_server": "Byt Server",
|
||||||
"invalid_username_or_password": "Ogiltigt användarnamn eller lösenord",
|
"invalid_username_or_password": "Ogiltigt användarnamn eller lösenord",
|
||||||
"user_does_not_have_permission_to_log_in": "Användaren har inte behörighet att logga in",
|
"user_does_not_have_permission_to_log_in": "Användaren har inte behörighet att logga in",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Servern svarar för långsamt, försök igen senare",
|
"server_is_taking_too_long_to_respond_try_again_later": "Servern svarar för långsamt, försök igen senare",
|
||||||
"server_received_too_many_requests_try_again_later": "Servern har fått för många förfrågningar, försök igen senare.",
|
"server_received_too_many_requests_try_again_later": "Servern har fått för många förfrågningar, försök igen senare.",
|
||||||
"there_is_a_server_error": "Ett serverfel uppstod",
|
"there_is_a_server_error": "Ett serverfel uppstod",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Ett oväntat fel uppstod. Har du angett serverns URL korrekt?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Ett oväntat fel uppstod. Har du angett serverns URL korrekt?",
|
||||||
"too_old_server_text": "Jellyfin Servern Stöds Inte",
|
"too_old_server_text": "Jellyfin Servern Stöds Inte",
|
||||||
"too_old_server_description": "Var God Uppdatera Jellyfin Till Den Senaste Versionen"
|
"too_old_server_description": "Var God Uppdatera Jellyfin Till Den Senaste Versionen"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Godkänn snabbanslutning",
|
"authorize_button": "Godkänn snabbanslutning",
|
||||||
"enter_the_quick_connect_code": "Ange snabbanslutningskod...",
|
"enter_the_quick_connect_code": "Ange snabbanslutningskod...",
|
||||||
"success": "Snabbanslutning lyckades",
|
"success": "Snabbanslutning lyckades",
|
||||||
"quick_connect_autorized": "Snabbanslutning Godkänd",
|
"quick_connect_authorized": "Snabbanslutning Godkänd",
|
||||||
"error": "Fel",
|
"error": "Fel",
|
||||||
"invalid_code": "Ogiltig kod",
|
"invalid_code": "Ogiltig kod",
|
||||||
"authorize": "Autentisera"
|
"authorize": "Autentisera"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Visa anpassade menylänkar",
|
"show_custom_menu_links": "Visa anpassade menylänkar",
|
||||||
"show_large_home_carousel": "Visa toppbanner (beta)",
|
"show_large_home_carousel": "Visa toppbanner (beta)",
|
||||||
"hide_libraries": "Dölj bibliotek",
|
"hide_libraries": "Dölj bibliotek",
|
||||||
"select_liraries_you_want_to_hide": "Välj de bibliotek du vill dölja på fliken Bibliotek och på startsidan.",
|
"select_libraries_you_want_to_hide": "Välj de bibliotek du vill dölja på fliken Bibliotek och på startsidan.",
|
||||||
"disable_haptic_feedback": "Stäng av vibrationer",
|
"disable_haptic_feedback": "Stäng av vibrationer",
|
||||||
"default_quality": "Förvald Kvalitet",
|
"default_quality": "Förvald Kvalitet",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Telefon {{availableSpace}}%",
|
"device_usage": "Telefon {{availableSpace}}%",
|
||||||
"size_used": "{{used}} av {{total}} används",
|
"size_used": "{{used}} av {{total}} används",
|
||||||
"delete_all_downloaded_files": "Ta bort alla nerladdade filer",
|
"delete_all_downloaded_files": "Ta bort alla nerladdade filer",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Musikcache",
|
"music_cache_title": "Musikcache",
|
||||||
"music_cache_description": "Cacha automatiskt låtar när du lyssnar för smidigare uppspelning och offline-stöd",
|
"music_cache_description": "Cacha automatiskt låtar när du lyssnar för smidigare uppspelning och offline-stöd",
|
||||||
"clear_music_cache": "Rensa musikcache",
|
"clear_music_cache": "Rensa musikcache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Fel",
|
"error": "Fel",
|
||||||
"failed_to_get_stream_url": "Kunde inte hämta stream-URL",
|
"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.",
|
"an_error_occurred_while_playing_the_video": "Ett fel uppstod vid uppspelning av videon. Kontrollera loggarna i inställningarna.",
|
||||||
"client_error": "Klientfel",
|
"client_error": "Klientfel",
|
||||||
"could_not_create_stream_for_chromecast": "Kunde inte skapa stream för Chromecast",
|
"could_not_create_stream_for_chromecast": "Kunde inte skapa stream för Chromecast",
|
||||||
"message_from_server": "Meddelande från servern: {{message}}",
|
"message_from_server": "Meddelande från servern: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Got It",
|
"got_it": "Got It",
|
||||||
"connection_failed": "Connection Failed",
|
"connection_failed": "Connection Failed",
|
||||||
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
||||||
"an_unexpected_error_occured": "An Unexpected Error Occurred",
|
"an_unexpected_error_occurred": "An unexpected error occurred",
|
||||||
"change_server": "Change Server",
|
"change_server": "Change Server",
|
||||||
"invalid_username_or_password": "Invalid Username or Password",
|
"invalid_username_or_password": "Invalid Username or Password",
|
||||||
"user_does_not_have_permission_to_log_in": "ผู้ใช้ไม่มีสิทธิ์ในการเข้าสู่ระบบ",
|
"user_does_not_have_permission_to_log_in": "ผู้ใช้ไม่มีสิทธิ์ในการเข้าสู่ระบบ",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
||||||
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
||||||
"there_is_a_server_error": "There is a server error",
|
"there_is_a_server_error": "There is a server error",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Authorize Quick Connect",
|
"authorize_button": "Authorize Quick Connect",
|
||||||
"enter_the_quick_connect_code": "Enter the quick connect code...",
|
"enter_the_quick_connect_code": "Enter the quick connect code...",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"quick_connect_autorized": "Quick Connect Authorized",
|
"quick_connect_authorized": "Quick Connect authorized",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"invalid_code": "Invalid Code",
|
"invalid_code": "Invalid Code",
|
||||||
"authorize": "Authorize"
|
"authorize": "Authorize"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Show Custom Menu Links",
|
"show_custom_menu_links": "Show Custom Menu Links",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Hide Libraries",
|
"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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Disable Haptic Feedback",
|
||||||
"default_quality": "Default Quality",
|
"default_quality": "Default Quality",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Device {{availableSpace}}%",
|
"device_usage": "Device {{availableSpace}}%",
|
||||||
"size_used": "{{used}} of {{total}} Used",
|
"size_used": "{{used}} of {{total}} Used",
|
||||||
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||||
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
"client_error": "Client Error",
|
"client_error": "Client Error",
|
||||||
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
||||||
"message_from_server": "Message from Server: {{message}}",
|
"message_from_server": "Message from Server: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "jIyaj",
|
"got_it": "jIyaj",
|
||||||
"connection_failed": "ngoQlaHbe'",
|
"connection_failed": "ngoQlaHbe'",
|
||||||
"could_not_connect_to_server": "SeHlaw veS Ho'Do'laHbe'. URL 'ej ret ghun mej.",
|
"could_not_connect_to_server": "SeHlaw veS Ho'Do'laHbe'. URL 'ej ret ghun mej.",
|
||||||
"an_unexpected_error_occured": "num ghIq Doch",
|
"an_unexpected_error_occurred": "num ghIq Doch",
|
||||||
"change_server": "Ho'Do' veS yIghoS",
|
"change_server": "Ho'Do' veS yIghoS",
|
||||||
"invalid_username_or_password": "tlhIngan pagh ngoq De' law'be'",
|
"invalid_username_or_password": "tlhIngan pagh ngoq De' law'be'",
|
||||||
"user_does_not_have_permission_to_log_in": "tlhIngan lut 'el je'laHbe'",
|
"user_does_not_have_permission_to_log_in": "tlhIngan lut 'el je'laHbe'",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Ho'Do' veS jachrup. pItlh yIHaD",
|
"server_is_taking_too_long_to_respond_try_again_later": "Ho'Do' veS jachrup. pItlh yIHaD",
|
||||||
"server_received_too_many_requests_try_again_later": "Ho'Do' veS lutlh ngeb petlh law'. pItlh yIHaD.",
|
"server_received_too_many_requests_try_again_later": "Ho'Do' veS lutlh ngeb petlh law'. pItlh yIHaD.",
|
||||||
"there_is_a_server_error": "Ho'Do' veS ghIq maS",
|
"there_is_a_server_error": "Ho'Do' veS ghIq maS",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "num ghIq Doch. URL mej Danej'a'?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "num ghIq Doch. URL mej Danej'a'?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "**Jellyfin chu'qu' Dotlh yIchoHmoH**"
|
"too_old_server_description": "**Jellyfin chu'qu' Dotlh yIchoHmoH**"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "parmaq ngoQ yIje'",
|
"authorize_button": "parmaq ngoQ yIje'",
|
||||||
"enter_the_quick_connect_code": "parmaq ngoQ De' yI'el...",
|
"enter_the_quick_connect_code": "parmaq ngoQ De' yI'el...",
|
||||||
"success": "Qapla'",
|
"success": "Qapla'",
|
||||||
"quick_connect_autorized": "parmaq ngoQ je'laH",
|
"quick_connect_authorized": "parmaq ngoQ je'laH",
|
||||||
"error": "ghIq",
|
"error": "ghIq",
|
||||||
"invalid_code": "De' law'be'",
|
"invalid_code": "De' law'be'",
|
||||||
"authorize": "yIje'"
|
"authorize": "yIje'"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "menuDaq ret teqlu' yInej",
|
"show_custom_menu_links": "menuDaq ret teqlu' yInej",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "De'wI' bom yIQIj",
|
"hide_libraries": "De'wI' bom yIQIj",
|
||||||
"select_liraries_you_want_to_hide": "De'wI' bom Danej QIj yIwIv.",
|
"select_libraries_you_want_to_hide": "De'wI' bom Danej QIj yIwIv.",
|
||||||
"disable_haptic_feedback": "Qub quvHa' yIQIj",
|
"disable_haptic_feedback": "Qub quvHa' yIQIj",
|
||||||
"default_quality": "wa' luj",
|
"default_quality": "wa' luj",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "naDev {{availableSpace}}%",
|
"device_usage": "naDev {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} ram",
|
"size_used": "{{used}} / {{total}} ram",
|
||||||
"delete_all_downloaded_files": "Hoch Qaw' Doch yIQaw'",
|
"delete_all_downloaded_files": "Hoch Qaw' Doch yIQaw'",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "ghIq",
|
"error": "ghIq",
|
||||||
"failed_to_get_stream_url": "tlhol ret URL tu'laHbe'",
|
"failed_to_get_stream_url": "tlhol ret URL tu'laHbe'",
|
||||||
"an_error_occured_while_playing_the_video": "mu'tlhegh tlholDI' ghIq. menDaq De' qon mej.",
|
"an_error_occurred_while_playing_the_video": "mu'tlhegh tlholDI' ghIq. menDaq De' qon mej.",
|
||||||
"client_error": "lut 'el ghIq",
|
"client_error": "lut 'el ghIq",
|
||||||
"could_not_create_stream_for_chromecast": "Chromecast tlhol ret qonlaHbe'",
|
"could_not_create_stream_for_chromecast": "Chromecast tlhol ret qonlaHbe'",
|
||||||
"message_from_server": "Ho'Do' veS jach: {{message}}",
|
"message_from_server": "Ho'Do' veS jach: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Anlaşıldı",
|
"got_it": "Anlaşıldı",
|
||||||
"connection_failed": "Bağlantı başarısız",
|
"connection_failed": "Bağlantı başarısız",
|
||||||
"could_not_connect_to_server": "Sunucuya bağlanılamadı. Lütfen URL'yi ve ağ bağlantınızı kontrol edin.",
|
"could_not_connect_to_server": "Sunucuya bağlanılamadı. Lütfen URL'yi ve ağ bağlantınızı kontrol edin.",
|
||||||
"an_unexpected_error_occured": "Beklenmedik bir hata oluştu",
|
"an_unexpected_error_occurred": "Beklenmedik bir hata oluştu",
|
||||||
"change_server": "Sunucu değiştir",
|
"change_server": "Sunucu değiştir",
|
||||||
"invalid_username_or_password": "Geçersiz kullanıcı adı veya şifre",
|
"invalid_username_or_password": "Geçersiz kullanıcı adı veya şifre",
|
||||||
"user_does_not_have_permission_to_log_in": "Kullanıcının giriş yapma izni yok",
|
"user_does_not_have_permission_to_log_in": "Kullanıcının giriş yapma izni yok",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Sunucunun yanıt vermesi çok uzun sürüyor, lütfen daha sonra tekrar deneyin",
|
"server_is_taking_too_long_to_respond_try_again_later": "Sunucunun yanıt vermesi çok uzun sürüyor, lütfen daha sonra tekrar deneyin",
|
||||||
"server_received_too_many_requests_try_again_later": "Sunucu çok fazla istek aldı, lütfen daha sonra tekrar deneyin.",
|
"server_received_too_many_requests_try_again_later": "Sunucu çok fazla istek aldı, lütfen daha sonra tekrar deneyin.",
|
||||||
"there_is_a_server_error": "Sunucu hatası var",
|
"there_is_a_server_error": "Sunucu hatası var",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Beklenmedik bir hata oluştu. Sunucu URL'sini doğru girdiğinizden emin misiniz?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Beklenmedik bir hata oluştu. Sunucu URL'sini doğru girdiğinizden emin misiniz?",
|
||||||
"too_old_server_text": "Desteklenmeyen Jellyfin Sunucu sürümü bulundu.",
|
"too_old_server_text": "Desteklenmeyen Jellyfin Sunucu sürümü bulundu.",
|
||||||
"too_old_server_description": "Lütfen Jellyfin'i en son sürüme güncelleyin."
|
"too_old_server_description": "Lütfen Jellyfin'i en son sürüme güncelleyin."
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Hızlı Bağlantıyı Yetkilendir",
|
"authorize_button": "Hızlı Bağlantıyı Yetkilendir",
|
||||||
"enter_the_quick_connect_code": "Hızlı bağlantı kodunu girin...",
|
"enter_the_quick_connect_code": "Hızlı bağlantı kodunu girin...",
|
||||||
"success": "Başarılı",
|
"success": "Başarılı",
|
||||||
"quick_connect_autorized": "Hızlı Bağlantı Yetkilendirildi",
|
"quick_connect_authorized": "Hızlı Bağlantı Yetkilendirildi",
|
||||||
"error": "Hata",
|
"error": "Hata",
|
||||||
"invalid_code": "Geçersiz kod",
|
"invalid_code": "Geçersiz kod",
|
||||||
"authorize": "Yetkilendir"
|
"authorize": "Yetkilendir"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
|
"show_custom_menu_links": "Özel Menü Bağlantılarını Göster",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Kütüphaneleri Gizle",
|
"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.",
|
"select_libraries_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",
|
"default_quality": "Varsayılan kalite",
|
||||||
"default_playback_speed": "Varsayılan Oynatma Hızı",
|
"default_playback_speed": "Varsayılan Oynatma Hızı",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Cihaz {{availableSpace}}%",
|
"device_usage": "Cihaz {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} kullanıldı",
|
"size_used": "{{used}} / {{total}} kullanıldı",
|
||||||
"delete_all_downloaded_files": "Tüm indirilen dosyaları sil",
|
"delete_all_downloaded_files": "Tüm indirilen dosyaları sil",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Müzik Ön Belleği",
|
"music_cache_title": "Müzik Ön Belleği",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Müzik Ön Belleğini Temizle",
|
"clear_music_cache": "Müzik Ön Belleğini Temizle",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Hata",
|
"error": "Hata",
|
||||||
"failed_to_get_stream_url": "Yayın URL'si alınamadı",
|
"failed_to_get_stream_url": "Yayın URL'si alınamadı",
|
||||||
"an_error_occured_while_playing_the_video": "Video oynatılırken bir hata oluştu. Ayarlardaki günlüklere bakın.",
|
"an_error_occurred_while_playing_the_video": "Video oynatılırken bir hata oluştu. Ayarlardaki günlüklere bakın.",
|
||||||
"client_error": "İstemci hatası",
|
"client_error": "İstemci hatası",
|
||||||
"could_not_create_stream_for_chromecast": "Chromecast için yayın oluşturulamadı",
|
"could_not_create_stream_for_chromecast": "Chromecast için yayın oluşturulamadı",
|
||||||
"message_from_server": "Sunucudan mesaj: {{message}}",
|
"message_from_server": "Sunucudan mesaj: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Готово",
|
"got_it": "Готово",
|
||||||
"connection_failed": "Помилка зʼєднання",
|
"connection_failed": "Помилка зʼєднання",
|
||||||
"could_not_connect_to_server": "Неможливо підʼєднатися до серверу. Будь ласка перевірте URL і ваше зʼєднання з мережею",
|
"could_not_connect_to_server": "Неможливо підʼєднатися до серверу. Будь ласка перевірте URL і ваше зʼєднання з мережею",
|
||||||
"an_unexpected_error_occured": "Сталася несподівана помилка",
|
"an_unexpected_error_occurred": "Сталася несподівана помилка",
|
||||||
"change_server": "Змінити сервер",
|
"change_server": "Змінити сервер",
|
||||||
"invalid_username_or_password": "Неправильні імʼя користувача або пароль",
|
"invalid_username_or_password": "Неправильні імʼя користувача або пароль",
|
||||||
"user_does_not_have_permission_to_log_in": "Користувач не маю дозволу на вхід",
|
"user_does_not_have_permission_to_log_in": "Користувач не маю дозволу на вхід",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Сервер відповідає занадто довго, будь-ласка спробуйте пізніше",
|
"server_is_taking_too_long_to_respond_try_again_later": "Сервер відповідає занадто довго, будь-ласка спробуйте пізніше",
|
||||||
"server_received_too_many_requests_try_again_later": "Сервер отримав забагато запитів, будь ласка спробуйте пізніше.",
|
"server_received_too_many_requests_try_again_later": "Сервер отримав забагато запитів, будь ласка спробуйте пізніше.",
|
||||||
"there_is_a_server_error": "Відбулася помилка на стороні сервера",
|
"there_is_a_server_error": "Відбулася помилка на стороні сервера",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Відбулася несподівана помилка. Чи введений URL сервера правильний?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Відбулася несподівана помилка. Чи введений URL сервера правильний?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Авторизуйте Швидке Зʼєднання",
|
"authorize_button": "Авторизуйте Швидке Зʼєднання",
|
||||||
"enter_the_quick_connect_code": "Введіть код для швидкого зʼєднання...",
|
"enter_the_quick_connect_code": "Введіть код для швидкого зʼєднання...",
|
||||||
"success": "Успіх",
|
"success": "Успіх",
|
||||||
"quick_connect_autorized": "Швидке Зʼєднання авторизовано",
|
"quick_connect_authorized": "Швидке Зʼєднання авторизовано",
|
||||||
"error": "Помилка",
|
"error": "Помилка",
|
||||||
"invalid_code": "Не правильний код",
|
"invalid_code": "Не правильний код",
|
||||||
"authorize": "Авторизувати"
|
"authorize": "Авторизувати"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Показати користувацькі посилання меню",
|
"show_custom_menu_links": "Показати користувацькі посилання меню",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Сховати медіатеки",
|
"hide_libraries": "Сховати медіатеки",
|
||||||
"select_liraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.",
|
"select_libraries_you_want_to_hide": "Виберіть медіатеки, що бажаєте приховати з вкладки Медіатека і з секції на головній сторінці.",
|
||||||
"disable_haptic_feedback": "Вимкнути тактильний зворотний зв'язок",
|
"disable_haptic_feedback": "Вимкнути тактильний зворотний зв'язок",
|
||||||
"default_quality": "Якість за замовченням",
|
"default_quality": "Якість за замовченням",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Гаджет {{availableSpace}}%",
|
"device_usage": "Гаджет {{availableSpace}}%",
|
||||||
"size_used": "{{used}} з {{total}} використано",
|
"size_used": "{{used}} з {{total}} використано",
|
||||||
"delete_all_downloaded_files": "Видалити усі завантаженні файли",
|
"delete_all_downloaded_files": "Видалити усі завантаженні файли",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Помилка",
|
"error": "Помилка",
|
||||||
"failed_to_get_stream_url": "Не вдалося отримати URL-адресу потоку",
|
"failed_to_get_stream_url": "Не вдалося отримати URL-адресу потоку",
|
||||||
"an_error_occured_while_playing_the_video": "Під час відтворення відео сталася помилка. Перевірте журнал в налаштуваннях.",
|
"an_error_occurred_while_playing_the_video": "Під час відтворення відео сталася помилка. Перевірте журнал в налаштуваннях.",
|
||||||
"client_error": "Помилка клієнту",
|
"client_error": "Помилка клієнту",
|
||||||
"could_not_create_stream_for_chromecast": "Не вдалося створити потік для Chromecast",
|
"could_not_create_stream_for_chromecast": "Не вдалося створити потік для Chromecast",
|
||||||
"message_from_server": "Повідомлення від серверу: {{message}}",
|
"message_from_server": "Повідомлення від серверу: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Đã hiểu",
|
"got_it": "Đã hiểu",
|
||||||
"connection_failed": "Kết nối thất bại",
|
"connection_failed": "Kết nối thất bại",
|
||||||
"could_not_connect_to_server": "Không thể kết nối tới máy chủ. Vui lòng kiểm tra URL và kết nối mạng.",
|
"could_not_connect_to_server": "Không thể kết nối tới máy chủ. Vui lòng kiểm tra URL và kết nối mạng.",
|
||||||
"an_unexpected_error_occured": "Đã xảy ra lỗi không mong muốn",
|
"an_unexpected_error_occurred": "Đã xảy ra lỗi không mong muốn",
|
||||||
"change_server": "Đổi máy chủ",
|
"change_server": "Đổi máy chủ",
|
||||||
"invalid_username_or_password": "Tên đăng nhập hoặc mật khẩu không đúng",
|
"invalid_username_or_password": "Tên đăng nhập hoặc mật khẩu không đúng",
|
||||||
"user_does_not_have_permission_to_log_in": "Người dùng không có quyền đăng nhập",
|
"user_does_not_have_permission_to_log_in": "Người dùng không có quyền đăng nhập",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Máy chủ phản hồi quá lâu, vui lòng thử lại sau",
|
"server_is_taking_too_long_to_respond_try_again_later": "Máy chủ phản hồi quá lâu, vui lòng thử lại sau",
|
||||||
"server_received_too_many_requests_try_again_later": "Máy chủ nhận quá nhiều yêu cầu, vui lòng thử lại sau.",
|
"server_received_too_many_requests_try_again_later": "Máy chủ nhận quá nhiều yêu cầu, vui lòng thử lại sau.",
|
||||||
"there_is_a_server_error": "Đã xảy ra lỗi ở máy chủ",
|
"there_is_a_server_error": "Đã xảy ra lỗi ở máy chủ",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "Đã xảy ra lỗi không mong muốn. Bạn có chắc đã nhập đúng URL máy chủ?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "Đã xảy ra lỗi không mong muốn. Bạn có chắc đã nhập đúng URL máy chủ?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Cho phép Kết nối nhanh",
|
"authorize_button": "Cho phép Kết nối nhanh",
|
||||||
"enter_the_quick_connect_code": "Nhập mã kết nối nhanh...",
|
"enter_the_quick_connect_code": "Nhập mã kết nối nhanh...",
|
||||||
"success": "Thành công",
|
"success": "Thành công",
|
||||||
"quick_connect_autorized": "Kết nối nhanh đã được cho phép",
|
"quick_connect_authorized": "Kết nối nhanh đã được cho phép",
|
||||||
"error": "Lỗi",
|
"error": "Lỗi",
|
||||||
"invalid_code": "Mã không hợp lệ",
|
"invalid_code": "Mã không hợp lệ",
|
||||||
"authorize": "Cho phép"
|
"authorize": "Cho phép"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Hiện liên kết tùy chỉnh",
|
"show_custom_menu_links": "Hiện liên kết tùy chỉnh",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Ẩn thư viện",
|
"hide_libraries": "Ẩn thư viện",
|
||||||
"select_liraries_you_want_to_hide": "Chọn các thư viện muốn ẩn khỏi mục Thư viện và Trang chủ.",
|
"select_libraries_you_want_to_hide": "Chọn các thư viện muốn ẩn khỏi mục Thư viện và Trang chủ.",
|
||||||
"disable_haptic_feedback": "Tắt phản hồi rung",
|
"disable_haptic_feedback": "Tắt phản hồi rung",
|
||||||
"default_quality": "Chất lượng mặc định",
|
"default_quality": "Chất lượng mặc định",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Thiết bị sử dụng {{availableSpace}}%",
|
"device_usage": "Thiết bị sử dụng {{availableSpace}}%",
|
||||||
"size_used": "{{used}} / {{total}} đã dùng",
|
"size_used": "{{used}} / {{total}} đã dùng",
|
||||||
"delete_all_downloaded_files": "Xóa toàn bộ tập tin đã tải",
|
"delete_all_downloaded_files": "Xóa toàn bộ tập tin đã tải",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Lỗi",
|
"error": "Lỗi",
|
||||||
"failed_to_get_stream_url": "Không thể lấy URL phát trực tiếp",
|
"failed_to_get_stream_url": "Không thể lấy URL phát trực tiếp",
|
||||||
"an_error_occured_while_playing_the_video": "Có lỗi khi phát video. Xem nhật ký trong cài đặt.",
|
"an_error_occurred_while_playing_the_video": "Có lỗi khi phát video. Xem nhật ký trong cài đặt.",
|
||||||
"client_error": "Lỗi phía máy khách",
|
"client_error": "Lỗi phía máy khách",
|
||||||
"could_not_create_stream_for_chromecast": "Không thể tạo luồng cho Chromecast",
|
"could_not_create_stream_for_chromecast": "Không thể tạo luồng cho Chromecast",
|
||||||
"message_from_server": "Thông báo từ máy chủ: {{message}}",
|
"message_from_server": "Thông báo từ máy chủ: {{message}}",
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
"got_it": "Got It",
|
"got_it": "Got It",
|
||||||
"connection_failed": "Connection Failed",
|
"connection_failed": "Connection Failed",
|
||||||
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
"could_not_connect_to_server": "Could not connect to the server. Please check the URL and your network connection.",
|
||||||
"an_unexpected_error_occured": "An Unexpected Error Occurred",
|
"an_unexpected_error_occurred": "An unexpected error occurred",
|
||||||
"change_server": "Change Server",
|
"change_server": "Change Server",
|
||||||
"invalid_username_or_password": "Invalid Username or Password",
|
"invalid_username_or_password": "Invalid Username or Password",
|
||||||
"user_does_not_have_permission_to_log_in": "User does not have permission to log in",
|
"user_does_not_have_permission_to_log_in": "User does not have permission to log in",
|
||||||
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
"server_is_taking_too_long_to_respond_try_again_later": "Server is taking too long to respond, try again later",
|
||||||
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
"server_received_too_many_requests_try_again_later": "Server received too many requests, try again later.",
|
||||||
"there_is_a_server_error": "There is a server error",
|
"there_is_a_server_error": "There is a server error",
|
||||||
"an_unexpected_error_occured_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
"an_unexpected_error_occurred_did_you_enter_the_correct_url": "An unexpected error occurred. Did you enter the server URL correctly?",
|
||||||
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
"too_old_server_text": "Unsupported Jellyfin Server Discovered",
|
||||||
"too_old_server_description": "Please update Jellyfin to the latest version"
|
"too_old_server_description": "Please update Jellyfin to the latest version"
|
||||||
},
|
},
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
"authorize_button": "Authorize Quick Connect",
|
"authorize_button": "Authorize Quick Connect",
|
||||||
"enter_the_quick_connect_code": "Enter the quick connect code...",
|
"enter_the_quick_connect_code": "Enter the quick connect code...",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"quick_connect_autorized": "Quick Connect Authorized",
|
"quick_connect_authorized": "Quick Connect authorized",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"invalid_code": "Invalid Code",
|
"invalid_code": "Invalid Code",
|
||||||
"authorize": "Authorize"
|
"authorize": "Authorize"
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
"show_custom_menu_links": "Show Custom Menu Links",
|
"show_custom_menu_links": "Show Custom Menu Links",
|
||||||
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
"show_large_home_carousel": "Show Large Home Carousel (beta)",
|
||||||
"hide_libraries": "Hide Libraries",
|
"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.",
|
"select_libraries_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",
|
"disable_haptic_feedback": "Disable Haptic Feedback",
|
||||||
"default_quality": "Default Quality",
|
"default_quality": "Default Quality",
|
||||||
"default_playback_speed": "Default Playback Speed",
|
"default_playback_speed": "Default Playback Speed",
|
||||||
@@ -384,6 +384,8 @@
|
|||||||
"device_usage": "Device {{availableSpace}}%",
|
"device_usage": "Device {{availableSpace}}%",
|
||||||
"size_used": "{{used}} of {{total}} Used",
|
"size_used": "{{used}} of {{total}} Used",
|
||||||
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
"delete_all_downloaded_files": "Delete All Downloaded Files",
|
||||||
|
"delete_all_downloaded_files_confirm": "Delete All Downloaded Files?",
|
||||||
|
"delete_all_downloaded_files_confirm_desc": "Are you sure you want to delete all downloaded files? This action cannot be undone.",
|
||||||
"music_cache_title": "Music Cache",
|
"music_cache_title": "Music Cache",
|
||||||
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
"music_cache_description": "Automatically cache songs as you listen for smoother playback and offline support",
|
||||||
"clear_music_cache": "Clear Music Cache",
|
"clear_music_cache": "Clear Music Cache",
|
||||||
@@ -598,7 +600,7 @@
|
|||||||
"mpv_player_title": "MPV Player",
|
"mpv_player_title": "MPV Player",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"failed_to_get_stream_url": "Failed to get the stream URL",
|
"failed_to_get_stream_url": "Failed to get the stream URL",
|
||||||
"an_error_occured_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
"an_error_occurred_while_playing_the_video": "An error occurred while playing the video. Check logs in settings.",
|
||||||
"client_error": "Client Error",
|
"client_error": "Client Error",
|
||||||
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
"could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast",
|
||||||
"message_from_server": "Message from Server: {{message}}",
|
"message_from_server": "Message from Server: {{message}}",
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ export type Settings = {
|
|||||||
streamyStatsSeriesRecommendations?: boolean;
|
streamyStatsSeriesRecommendations?: boolean;
|
||||||
streamyStatsPromotedWatchlists?: boolean;
|
streamyStatsPromotedWatchlists?: boolean;
|
||||||
downloadQuality?: DownloadOption;
|
downloadQuality?: DownloadOption;
|
||||||
|
downloadStorageLocation?: string;
|
||||||
defaultBitrate?: Bitrate;
|
defaultBitrate?: Bitrate;
|
||||||
libraryOptions: LibraryOptions;
|
libraryOptions: LibraryOptions;
|
||||||
defaultAudioLanguage: CultureDto | null;
|
defaultAudioLanguage: CultureDto | null;
|
||||||
@@ -321,6 +322,7 @@ export const defaultValues: Settings = {
|
|||||||
streamyStatsSeriesRecommendations: false,
|
streamyStatsSeriesRecommendations: false,
|
||||||
streamyStatsPromotedWatchlists: false,
|
streamyStatsPromotedWatchlists: false,
|
||||||
downloadQuality: DownloadOptions[0],
|
downloadQuality: DownloadOptions[0],
|
||||||
|
downloadStorageLocation: undefined,
|
||||||
defaultBitrate: BITRATES[0],
|
defaultBitrate: BITRATES[0],
|
||||||
libraryOptions: {
|
libraryOptions: {
|
||||||
display: "list",
|
display: "list",
|
||||||
@@ -449,6 +451,11 @@ export const pluginSettingsAtom = atom<PluginLockableSettings | undefined>(
|
|||||||
const hasMeaningfulSettingValue = (value: unknown) =>
|
const hasMeaningfulSettingValue = (value: unknown) =>
|
||||||
value !== undefined && value !== null && value !== "";
|
value !== undefined && value !== null && value !== "";
|
||||||
|
|
||||||
|
const getEffectiveSettingValue = <K extends keyof Settings>(
|
||||||
|
settings: Partial<Settings> | null | undefined,
|
||||||
|
settingsKey: K,
|
||||||
|
) => settings?.[settingsKey] ?? defaultValues[settingsKey];
|
||||||
|
|
||||||
export const useSettings = () => {
|
export const useSettings = () => {
|
||||||
const api = useAtomValue(apiAtom);
|
const api = useAtomValue(apiAtom);
|
||||||
const [_settings, setSettings] = useAtom(settingsAtom);
|
const [_settings, setSettings] = useAtom(settingsAtom);
|
||||||
@@ -505,17 +512,7 @@ export const useSettings = () => {
|
|||||||
if (!_settings) {
|
if (!_settings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Admin-locked settings are enforced at write time too: a control that
|
const hasChanges = Object.entries(update).some(
|
||||||
// isn't disabled in the UI must not persist a value the admin pinned.
|
|
||||||
// The read memo already overrides locked keys, but without this guard the
|
|
||||||
// write would silently land in user storage and resurface once unlocked.
|
|
||||||
const sanitizedUpdate = Object.fromEntries(
|
|
||||||
Object.entries(update).filter(
|
|
||||||
([key]) => pluginSettings?.[key as keyof Settings]?.locked !== true,
|
|
||||||
),
|
|
||||||
) as Partial<Settings>;
|
|
||||||
|
|
||||||
const hasChanges = Object.entries(sanitizedUpdate).some(
|
|
||||||
([key, value]) => _settings[key as keyof Settings] !== value,
|
([key, value]) => _settings[key as keyof Settings] !== value,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -524,7 +521,7 @@ export const useSettings = () => {
|
|||||||
const newSettings = {
|
const newSettings = {
|
||||||
...defaultValues,
|
...defaultValues,
|
||||||
..._settings,
|
..._settings,
|
||||||
...sanitizedUpdate,
|
...update,
|
||||||
} as Settings;
|
} as Settings;
|
||||||
setSettings(newSettings);
|
setSettings(newSettings);
|
||||||
saveSettings(newSettings);
|
saveSettings(newSettings);
|
||||||
@@ -547,24 +544,13 @@ export const useSettings = () => {
|
|||||||
// Normalize object-typed settings from plugin (plain primitive → { key, value })
|
// Normalize object-typed settings from plugin (plain primitive → { key, value })
|
||||||
value = normalizePluginValue(settingsKey, value);
|
value = normalizePluginValue(settingsKey, value);
|
||||||
|
|
||||||
// When unlocked, keep the user's value only if they explicitly diverged
|
const effectiveValue = getEffectiveSettingValue(_settings, settingsKey);
|
||||||
// from the app default. Otherwise the plugin value is the admin's
|
|
||||||
// default and must win over the hardcoded app default — e.g. a toggle
|
|
||||||
// that was always locked then unlocked should reflect the plugin
|
|
||||||
// default, not the app's `false`. Object-typed settings compare by
|
|
||||||
// reference, so their behaviour is unchanged.
|
|
||||||
const userValue = _settings?.[settingsKey];
|
|
||||||
const userDiverged =
|
|
||||||
hasMeaningfulSettingValue(userValue) &&
|
|
||||||
userValue !== defaultValues[settingsKey];
|
|
||||||
|
|
||||||
(acc as any)[settingsKey] = locked
|
(acc as any)[settingsKey] = locked
|
||||||
? value
|
? value
|
||||||
: userDiverged
|
: hasMeaningfulSettingValue(effectiveValue)
|
||||||
? userValue
|
? effectiveValue
|
||||||
: hasMeaningfulSettingValue(value)
|
: value;
|
||||||
? value
|
|
||||||
: defaultValues[settingsKey];
|
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
143
utils/storage.ts
Normal file
143
utils/storage.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { Directory, Paths } from "expo-file-system";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { BackgroundDownloader, type StorageLocation } from "@/modules";
|
||||||
|
|
||||||
|
let cachedStorageLocations: StorageLocation[] | null = null;
|
||||||
|
|
||||||
|
// Debug mode: Set to true to simulate an SD card for testing in emulator
|
||||||
|
// This creates a real writable directory that mimics SD card behavior
|
||||||
|
const DEBUG_SIMULATE_SD_CARD = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available storage locations (Android only)
|
||||||
|
* Returns cached result on subsequent calls
|
||||||
|
*/
|
||||||
|
export async function getAvailableStorageLocations(): Promise<
|
||||||
|
StorageLocation[]
|
||||||
|
> {
|
||||||
|
if (Platform.OS !== "android") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedStorageLocations !== null) {
|
||||||
|
return cachedStorageLocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const locations = await BackgroundDownloader.getAvailableStorageLocations();
|
||||||
|
|
||||||
|
// Debug mode: Add a functional simulated SD card for testing
|
||||||
|
if (DEBUG_SIMULATE_SD_CARD && locations.length === 1) {
|
||||||
|
// Use a real writable path within the app's document directory
|
||||||
|
const sdcardSimDir = new Directory(Paths.document, "sdcard_sim");
|
||||||
|
|
||||||
|
// Create the directory if it doesn't exist
|
||||||
|
if (!sdcardSimDir.exists) {
|
||||||
|
sdcardSimDir.create({ intermediates: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockSdCard: StorageLocation = {
|
||||||
|
id: "sdcard_sim",
|
||||||
|
path: sdcardSimDir.uri.replace("file://", ""),
|
||||||
|
type: "external",
|
||||||
|
label: "SD Card (Simulated)",
|
||||||
|
totalSpace: 64 * 1024 * 1024 * 1024, // 64 GB
|
||||||
|
freeSpace: 32 * 1024 * 1024 * 1024, // 32 GB free
|
||||||
|
};
|
||||||
|
locations.push(mockSdCard);
|
||||||
|
console.log("[DEBUG] Added simulated SD card:", mockSdCard.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedStorageLocations = locations;
|
||||||
|
return locations;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get storage locations:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cached storage locations
|
||||||
|
* Useful when storage configuration might have changed
|
||||||
|
*/
|
||||||
|
export function clearStorageLocationsCache(): void {
|
||||||
|
cachedStorageLocations = null;
|
||||||
|
console.log("[Storage] Cache cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a simplified label for a storage location ID
|
||||||
|
* @param storageId - The storage location ID (e.g., "internal", "sdcard_0")
|
||||||
|
* @returns Human-readable label (e.g., "Internal Storage", "SD Card")
|
||||||
|
*/
|
||||||
|
export async function getStorageLabel(storageId?: string): Promise<string> {
|
||||||
|
if (!storageId || Platform.OS !== "android") {
|
||||||
|
return "Internal Storage";
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = await getAvailableStorageLocations();
|
||||||
|
const location = locations.find((loc) => loc.id === storageId);
|
||||||
|
|
||||||
|
return location?.label || "Internal Storage";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the filesystem path for a storage location ID
|
||||||
|
* @param storageId - The storage location ID (e.g., "internal", "sdcard_0")
|
||||||
|
* @returns The filesystem path, or default path if not found
|
||||||
|
*/
|
||||||
|
export async function getStoragePath(storageId?: string): Promise<string> {
|
||||||
|
if (!storageId || Platform.OS !== "android") {
|
||||||
|
return getDefaultStoragePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = await getAvailableStorageLocations();
|
||||||
|
const location = locations.find((loc) => loc.id === storageId);
|
||||||
|
|
||||||
|
if (!location) {
|
||||||
|
console.warn(`Storage location not found: ${storageId}, using default`);
|
||||||
|
return getDefaultStoragePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return location.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default storage path (current behavior using Paths.document)
|
||||||
|
* @returns The default storage path
|
||||||
|
*/
|
||||||
|
export function getDefaultStoragePath(): string {
|
||||||
|
// Paths.document returns a Directory with a URI like "file:///data/user/0/..."
|
||||||
|
// We need to extract the actual path
|
||||||
|
const uri = Paths.document.uri;
|
||||||
|
return uri.replace("file://", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a storage location by ID
|
||||||
|
* @param storageId - The storage location ID
|
||||||
|
* @returns The storage location or undefined if not found
|
||||||
|
*/
|
||||||
|
export async function getStorageLocationById(
|
||||||
|
storageId?: string,
|
||||||
|
): Promise<StorageLocation | undefined> {
|
||||||
|
if (!storageId || Platform.OS !== "android") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = await getAvailableStorageLocations();
|
||||||
|
return locations.find((loc) => loc.id === storageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert plain file path to file:// URI
|
||||||
|
* Required for expo-file-system File constructor
|
||||||
|
* @param path - The file path
|
||||||
|
* @returns The file:// URI
|
||||||
|
*/
|
||||||
|
export function filePathToUri(path: string): string {
|
||||||
|
if (path.startsWith("file://")) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
return `file://${path}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user