More Translations

This commit is contained in:
Simon Caron
2025-01-04 16:41:54 -05:00
parent 459ca3245b
commit 53ea1cc899
27 changed files with 189 additions and 73 deletions

View File

@@ -7,6 +7,7 @@ import { ListItem } from "@/components/list/ListItem";
import * as WebBrowser from "expo-web-browser";
import Ionicons from "@expo/vector-icons/Ionicons";
import { Text } from "@/components/common/Text";
import { useTranslation } from "react-i18next";
export interface MenuLink {
name: string;
@@ -18,6 +19,7 @@ export default function menuLinks() {
const [api] = useAtom(apiAtom);
const insets = useSafeAreaInsets();
const [menuLinks, setMenuLinks] = useState<MenuLink[]>([]);
const { t } = useTranslation();
const getMenuLinks = useCallback(async () => {
try {
@@ -67,7 +69,7 @@ export default function menuLinks() {
)}
ListEmptyComponent={
<View className="flex flex-col items-center justify-center h-full">
<Text className="font-bold text-xl text-neutral-500">No links</Text>
<Text className="font-bold text-xl text-neutral-500">{t("custom_links.no_links")}</Text>
</View>
}
/>

View File

@@ -11,10 +11,13 @@ import { useAtom } from "jotai";
import { useEffect, useState } from "react";
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import { toast } from "sonner-native";
import { useTranslation } from "react-i18next";
export default function page() {
const navigation = useNavigation();
const { t } = useTranslation();
const [api] = useAtom(apiAtom);
const [settings, updateSettings] = useSettings();
@@ -24,7 +27,7 @@ export default function page() {
const saveMutation = useMutation({
mutationFn: async (newVal: string) => {
if (newVal.length === 0 || !newVal.startsWith("http")) {
toast.error("Invalid URL");
toast.error(t("home.settings.toasts.invalid_url"));
return;
}
@@ -42,13 +45,13 @@ export default function page() {
},
onSuccess: (data) => {
if (data) {
toast.success("Connected");
toast.success(t("home.settings.toasts.connected"));
} else {
toast.error("Could not connect");
toast.error(t("home.settings.toasts.could_not_connect"));
}
},
onError: () => {
toast.error("Could not connect");
toast.error(t("home.settings.toasts.could_not_connect"));
},
});

View File

@@ -10,10 +10,13 @@ import { useAtom } from "jotai";
import { useEffect, useState } from "react";
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import { toast } from "sonner-native";
import { useTranslation } from "react-i18next";
export default function page() {
const navigation = useNavigation();
const { t } = useTranslation();
const [api] = useAtom(apiAtom);
const [settings, updateSettings] = useSettings();
@@ -23,7 +26,7 @@ export default function page() {
const saveMutation = useMutation({
mutationFn: async (newVal: string) => {
if (newVal.length === 0 || !newVal.startsWith("http")) {
toast.error("Invalid URL");
toast.error(t("home.settings.toasts.invalid_url"));
return;
}
@@ -41,13 +44,13 @@ export default function page() {
},
onSuccess: (data) => {
if (data) {
toast.success("Connected");
toast.success(t("home.settings.toasts.connected"));
} else {
toast.error("Could not connect");
toast.error(t("home.settings.toasts.could_not_connect"));
}
},
onError: () => {
toast.error("Could not connect");
toast.error(t("home.settings.toasts.could_not_connect"));
},
});
@@ -57,13 +60,13 @@ export default function page() {
useEffect(() => {
navigation.setOptions({
title: "Optimized Server",
title: t("home.settings.downloads.optimized_server"),
headerRight: () =>
saveMutation.isPending ? (
<ActivityIndicator size={"small"} color={"white"} />
) : (
<TouchableOpacity onPress={() => onSave(optimizedVersionsServerUrl)}>
<Text className="text-blue-500">Save</Text>
<Text className="text-blue-500">{t("home.settings.downloads.save_button")}</Text>
</TouchableOpacity>
),
});

View File

@@ -18,10 +18,12 @@ import { useLocalSearchParams } from "expo-router";
import { useAtom } from "jotai";
import { useCallback, useMemo } from "react";
import { View } from "react-native";
import { useTranslation } from "react-i18next";
const page: React.FC = () => {
const local = useLocalSearchParams();
const { actorId } = local as { actorId: string };
const { t } = useTranslation();
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
@@ -110,7 +112,7 @@ const page: React.FC = () => {
</View>
<Text className="px-4 text-2xl font-bold mb-2 text-neutral-100">
Appeared In
{t("item_card.appeared_in")}
</Text>
<InfiniteHorizontalScroll
height={247}

View File

@@ -10,6 +10,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { router, useLocalSearchParams, useNavigation } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai";
import { useEffect, useState } from "react";
import { ScrollView, TouchableOpacity, View } from "react-native";
@@ -112,7 +113,7 @@ export default function page() {
<View className="px-4 mb-8">
<Text className="font-bold text-2xl mb-2">{album?.Name}</Text>
<Text className="text-neutral-500">
{songs?.TotalRecordCount} songs
{t("item_card.x_songs", { count: songs?.TotalRecordCount })}
</Text>
</View>
<View className="px-4">

View File

@@ -5,6 +5,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { router, useLocalSearchParams, useNavigation } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai";
import { useEffect, useState } from "react";
import { FlatList, ScrollView, TouchableOpacity, View } from "react-native";
@@ -107,7 +108,7 @@ export default function page() {
<View className="px-4 mb-8">
<Text className="font-bold text-2xl mb-2">{artist?.Name}</Text>
<Text className="text-neutral-500">
{albums.TotalRecordCount} albums
{t("item_card.x_albums", { count: albums.TotalRecordCount })}
</Text>
</View>
<View className="flex flex-row flex-wrap justify-between px-4">

View File

@@ -7,6 +7,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getArtistsApi, getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { router, useLocalSearchParams } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai";
import { useMemo, useState } from "react";
import { FlatList, TouchableOpacity, View } from "react-native";
@@ -81,7 +82,7 @@ export default function page() {
}}
ListHeaderComponent={
<View className="mb-4">
<Text className="font-bold text-3xl mb-2">Artists</Text>
<Text className="font-bold text-3xl mb-2">{t("item_card.artists")}</Text>
</View>
}
nestedScrollEnabled

View File

@@ -377,7 +377,7 @@ const page: React.FC = () => {
<FlashList
ListEmptyComponent={
<View className="flex flex-col items-center justify-center h-full">
<Text className="font-bold text-xl text-neutral-500">No results</Text>
<Text className="font-bold text-xl text-neutral-500">{t("search.no_results")}</Text>
</View>
}
extraData={[

View File

@@ -13,11 +13,13 @@ import Animated, {
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { useTranslation } from "react-i18next";
const Page: React.FC = () => {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const { id } = useLocalSearchParams() as { id: string };
const { t } = useTranslation();
const { data: item, isError } = useQuery({
queryKey: ["item", id],
@@ -74,7 +76,7 @@ const Page: React.FC = () => {
if (isError)
return (
<View className="flex flex-col items-center justify-center h-screen w-screen">
<Text>Could not load item</Text>
<Text>{t("item_card.could_not_load_item")}</Text>
</View>
);

View File

@@ -27,10 +27,12 @@ import * as DropdownMenu from "zeego/dropdown-menu";
import { TvDetails } from "@/utils/jellyseerr/server/models/Tv";
import JellyseerrSeasons from "@/components/series/JellyseerrSeasons";
import { JellyserrRatings } from "@/components/Ratings";
import { useTranslation } from "react-i18next";
const Page: React.FC = () => {
const insets = useSafeAreaInsets();
const params = useLocalSearchParams();
const { t } = useTranslation();
const {
mediaTitle,
releaseYear,
@@ -184,7 +186,7 @@ const Page: React.FC = () => {
</View>
{canRequest ? (
<Button color="purple" onPress={request}>
Request
{t("jellyseerr.request_button")}
</Button>
) : (
<Button
@@ -199,7 +201,7 @@ const Page: React.FC = () => {
borderStyle: "solid",
}}
>
Report issue
{t("jellyseerr.report_issue_button")}
</Button>
)}
<OverviewText text={result.overview} className="mt-4" />
@@ -231,7 +233,7 @@ const Page: React.FC = () => {
<View className="flex flex-col space-y-4 px-4 pb-8 pt-2">
<View>
<Text className="font-bold text-2xl text-neutral-100">
Whats wrong?
{t("jellyseerr.whats_wrong")}
</Text>
</View>
<View className="flex flex-col space-y-2 items-start">
@@ -240,13 +242,13 @@ const Page: React.FC = () => {
<DropdownMenu.Trigger>
<View className="flex flex-col">
<Text className="opacity-50 mb-1 text-xs">
Issue Type
{t("jellyseerr.issue_type")}
</Text>
<TouchableOpacity className="bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center justify-between">
<Text style={{}} className="" numberOfLines={1}>
{issueType
? IssueTypeName[issueType]
: "Select an issue"}
: t("jellyseerr.select_an_issue")}
</Text>
</TouchableOpacity>
</View>
@@ -260,7 +262,7 @@ const Page: React.FC = () => {
collisionPadding={0}
sideOffset={0}
>
<DropdownMenu.Label>Types</DropdownMenu.Label>
<DropdownMenu.Label>{t("jellyseerr.types")}</DropdownMenu.Label>
{Object.entries(IssueTypeName)
.reverse()
.map(([key, value], idx) => (
@@ -287,7 +289,7 @@ const Page: React.FC = () => {
maxLength={254}
style={{color: "white"}}
clearButtonMode="always"
placeholder="(optional) Describe the issue..."
placeholder={t("jellyseerr.describe_the_issue")}
placeholderTextColor="#9CA3AF"
// Issue with multiline + Textinput inside a portal
// https://github.com/callstack/react-native-paper/issues/1668
@@ -297,7 +299,7 @@ const Page: React.FC = () => {
</View>
</View>
<Button className="mt-auto" onPress={submitIssue} color="purple">
Submit
{t("jellyseerr.submit_button")}
</Button>
</View>
</BottomSheetView>

View File

@@ -320,7 +320,7 @@ export default function search() {
<View className="flex flex-row flex-wrap space-x-2 px-4 mb-2">
<TouchableOpacity onPress={() => setSearchType("Library")}>
<Tag
text="Library"
text={t("search.library")}
textClass="p-1"
className={
searchType === "Library" ? "bg-purple-600" : undefined
@@ -329,7 +329,7 @@ export default function search() {
</TouchableOpacity>
<TouchableOpacity onPress={() => setSearchType("Discover")}>
<Tag
text="Discover"
text={t("search.discover")}
textClass="p-1"
className={
searchType === "Discover" ? "bg-purple-600" : undefined
@@ -341,7 +341,7 @@ export default function search() {
{!!q && (
<View className="px-4 flex flex-col space-y-2">
<Text className="text-neutral-500 ">
Results for <Text className="text-purple-600">{q}</Text>
{t("search.results_for_x")} <Text className="text-purple-600">{q}</Text>
</Text>
</View>
)}

View File

@@ -423,7 +423,7 @@ export default function page() {
if (isErrorItem || isErrorStreamUrl)
return (
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
<Text className="text-white">Error</Text>
<Text className="text-white">{t("player.error")}</Text>
</View>
);

View File

@@ -25,6 +25,7 @@ import React, { useCallback, useMemo, useRef, useState } from "react";
import { Pressable, useWindowDimensions, View } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import Video, { OnProgressData, VideoRef } from "react-native-video";
import { useTranslation } from "react-i18next";
export default function page() {
const api = useAtomValue(apiAtom);
@@ -32,6 +33,7 @@ export default function page() {
const [settings] = useSettings();
const videoRef = useRef<VideoRef | null>(null);
const windowDimensions = useWindowDimensions();
const { t } = useTranslation();
const firstTime = useRef(true);
@@ -278,14 +280,14 @@ export default function page() {
if (isErrorItem || isErrorStreamUrl)
return (
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
<Text className="text-white">Error</Text>
<Text className="text-white">{t("player.error")}</Text>
</View>
);
if (!item || !stream)
return (
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
<Text className="text-white">Error</Text>
<Text className="text-white">{t("player.error")}</Text>
</View>
);

View File

@@ -39,12 +39,14 @@ import Video, {
VideoRef,
} from "react-native-video";
import { SubtitleHelper } from "@/utils/SubtitleHelper";
import { useTranslation } from "react-i18next";
const Player = () => {
const api = useAtomValue(apiAtom);
const user = useAtomValue(userAtom);
const [settings] = useSettings();
const videoRef = useRef<VideoRef | null>(null);
const { t } = useTranslation();
const firstTime = useRef(true);
const revalidateProgressCache = useInvalidatePlaybackProgressCache();
@@ -373,7 +375,7 @@ const Player = () => {
if (isErrorItem || isErrorStreamUrl)
return (
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black">
<Text className="text-white">Error</Text>
<Text className="text-white">{t("player.error")}</Text>
</View>
);
@@ -440,7 +442,7 @@ const Player = () => {
/>
</>
) : (
<Text>No video source...</Text>
<Text>{t("player.no_video_source")}</Text>
)}
</View>

View File

@@ -8,6 +8,7 @@ import { TouchableOpacity, View } from "react-native";
import * as DropdownMenu from "zeego/dropdown-menu";
import { Text } from "./common/Text";
import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb";
import { useTranslation } from "react-i18next";
interface Props extends React.ComponentProps<typeof View> {
item: BaseItemDto;
@@ -29,6 +30,8 @@ export const MediaSourceSelector: React.FC<Props> = ({
[item, selected]
);
const { t } = useTranslation();
return (
<View
className="flex shrink"
@@ -39,7 +42,7 @@ export const MediaSourceSelector: React.FC<Props> = ({
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<View className="flex flex-col" {...props}>
<Text className="opacity-50 mb-1 text-xs">Video</Text>
<Text className="opacity-50 mb-1 text-xs">{t("item_card.video")}</Text>
<TouchableOpacity className="bg-neutral-900 h-10 rounded-xl border-neutral-800 border px-3 py-2 flex flex-row items-center">
<Text numberOfLines={1}>{selectedName}</Text>
</TouchableOpacity>

View File

@@ -15,6 +15,7 @@ import Animated, {
} from "react-native-reanimated";
import { Loader } from "../Loader";
import { Text } from "./Text";
import { t } from "i18next";
interface HorizontalScrollProps
extends Omit<FlashListProps<BaseItemDto>, "renderItem" | "data" | "style"> {
@@ -136,7 +137,7 @@ export function InfiniteHorizontalScroll({
showsHorizontalScrollIndicator={false}
ListEmptyComponent={
<View className="flex-1 justify-center items-center">
<Text className="text-center text-gray-500">No data available</Text>
<Text className="text-center text-gray-500">{t("item_card.no_data_available")}</Text>
</View>
}
{...props}

View File

@@ -154,7 +154,7 @@ const DownloadCard = ({ process, ...props }: DownloadCardProps) => {
<Text className="text-xs">{process.speed?.toFixed(2)}x</Text>
)}
{eta(process) && (
<Text className="text-xs">ETA {eta(process)}</Text>
<Text className="text-xs">{t("home.downloads.eta", {eta: eta(process)})}</Text>
)}
</View>

View File

@@ -155,7 +155,7 @@ export const FilterSheet = <T,>({
>
<View className="px-4 mt-2 mb-8">
<Text className="font-bold text-2xl">{title}</Text>
<Text className="mb-2 text-neutral-500">{_data?.length} items</Text>
<Text className="mb-2 text-neutral-500">{t("search.items", {count: _data?.length})}</Text>
{showSearch && (
<Input
placeholder={t("search.search")}

View File

@@ -11,6 +11,7 @@ import ContinueWatchingPoster from "../ContinueWatchingPoster";
import { ItemCardText } from "../ItemCardText";
import { TouchableItemRouter } from "../common/TouchableItemRouter";
import SeriesPoster from "../posters/SeriesPoster";
import { useTranslation } from "react-i18next";
interface Props extends ViewProps {
title?: string | null;
@@ -43,6 +44,8 @@ export const ScrollingCollectionList: React.FC<Props> = ({
if (hideIfEmpty === true && data?.length === 0) return null;
const { t } = useTranslation();
return (
<View {...props}>
<Text className="px-4 text-lg font-bold mb-2 text-neutral-100">
@@ -50,7 +53,7 @@ export const ScrollingCollectionList: React.FC<Props> = ({
</Text>
{isLoading === false && data?.length === 0 && (
<View className="px-4">
<Text className="text-neutral-500">No items</Text>
<Text className="text-neutral-500">{t("home.no_items")}</Text>
</View>
)}
{isLoading ? (

View File

@@ -211,7 +211,7 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
{(episodes?.length || 0) === 0 ? (
<View className="flex flex-col">
<Text className="text-neutral-500">
{t("item_card.no_episodes_for_this_seasonz")}
{t("item_card.no_episodes_for_this_season")}
</Text>
</View>
) : null}

View File

@@ -30,8 +30,8 @@ export const DownloadSettings: React.FC = ({ ...props }) => {
<TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3">
<Text className="mr-1 text-[#8E8D91]">
{settings.downloadMethod === "remux"
? "Default"
: "Optimized"}
? t("home.settings.downloads.default")
: t("home.settings.downloads.optimized")}
</Text>
<Ionicons
name="chevron-expand-sharp"

View File

@@ -1,5 +1,6 @@
import { TextInput, View, Linking } from "react-native";
import { Text } from "../common/Text";
import { useTranslation } from "react-i18next";
interface Props {
value: string;
@@ -14,14 +15,16 @@ export const OptimizedServerForm: React.FC<Props> = ({
Linking.openURL("https://github.com/streamyfin/optimized-versions-server");
};
const { t } = useTranslation();
return (
<View>
<View className="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">URL</Text>
<Text className="mr-4">{t("home.settings.downloads.url")}</Text>
<TextInput
className="text-white"
placeholder="http(s)://domain.org:port"
placeholder={t("home.settings.downloads.server_url_placeholder")}
value={value}
keyboardType="url"
returnKeyType="done"
@@ -32,10 +35,9 @@ export const OptimizedServerForm: React.FC<Props> = ({
</View>
</View>
<Text className="px-4 text-xs text-neutral-500 mt-1">
Enter the URL for the optimize server. The URL should include http or
https and optionally the port.{" "}
{t("home.settings.downloads.optimized_version_hint")}{" "}
<Text className="text-blue-500" onPress={handleOpenLink}>
Read more about the optimize server.
{t("home.settings.downloads.read_more_about_optimized_server")}
</Text>
</Text>
</View>

View File

@@ -86,7 +86,7 @@ export const QuickConnect: React.FC<Props> = ({ ...props }) => {
<View className="flex flex-col space-y-4 px-4 pb-8 pt-2">
<View>
<Text className="font-bold text-2xl text-neutral-100">
Quick Connect
{t("home.settings.quick_connect.quick_connect_title")}
</Text>
</View>
<View className="flex flex-col space-y-2">

View File

@@ -9,6 +9,7 @@ import Animated, {
runOnJS,
} from "react-native-reanimated";
import { Colors } from "@/constants/Colors";
import { useTranslation } from "react-i18next";
interface NextEpisodeCountDownButtonProps extends TouchableOpacityProps {
onFinish?: () => void;
@@ -63,6 +64,8 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
return null;
}
const { t } = useTranslation();
return (
<TouchableOpacity
className="w-32 overflow-hidden rounded-md bg-black/60 border border-neutral-900"
@@ -71,7 +74,7 @@ const NextEpisodeCountDownButton: React.FC<NextEpisodeCountDownButtonProps> = ({
>
<Animated.View style={animatedStyle} />
<View className="px-3 py-3">
<Text className="text-center font-bold">Next Episode</Text>
<Text className="text-center font-bold">{t("player.next_episode")}</Text>
</View>
</TouchableOpacity>
);

View File

@@ -6,6 +6,7 @@ import React, { useEffect, useState } from "react";
import { TouchableOpacity, View, ViewProps } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Text } from "../common/Text";
import { useTranslation } from "react-i18next";
interface Props extends ViewProps {
playerRef: React.RefObject<VlcPlayerViewRef>;
@@ -32,6 +33,8 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
const insets = useSafeAreaInsets();
const { t } = useTranslation();
return (
<View
style={{
@@ -42,19 +45,19 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
}}
{...props}
>
<Text className="font-bold">Playback State:</Text>
<Text className="font-bold mt-2.5">Audio Tracks:</Text>
<Text className="font-bold">{t("item_card.playback_state")}</Text>
<Text className="font-bold mt-2.5">{t("item_card.audio_tracks")}</Text>
{audioTracks &&
audioTracks.map((track, index) => (
<Text key={index}>
{track.name} (Index: {track.index})
{track.name} ({t("item_card.index")} {track.index})
</Text>
))}
<Text className="font-bold mt-2.5">Subtitle Tracks:</Text>
<Text className="font-bold mt-2.5">{t("item_card.subtitles_tracks")}</Text>
{subtitleTracks &&
subtitleTracks.map((track, index) => (
<Text key={index}>
{track.name} (Index: {track.index})
{track.name} ({t("item_card.index")} {track.index})
</Text>
))}
<TouchableOpacity
@@ -66,7 +69,7 @@ export const VideoDebugInfo: React.FC<Props> = ({ playerRef, ...props }) => {
}
}}
>
<Text className="text-white text-center">Refresh Tracks</Text>
<Text className="text-white text-center">{t("item_card.refresh_tracks")}</Text>
</TouchableOpacity>
</View>
);

View File

@@ -26,6 +26,7 @@
"home": {
"home": "Home",
"no_internet": "No Internet",
"no_items": "No items",
"no_internet_message": "No worries, you can still watch\ndownloaded content.",
"go_to_downloads": "Go to downloads",
"oops": "Oops!",
@@ -46,7 +47,7 @@
"app_version": "App Version"
},
"quick_connect": {
"quick_connect_title": "Quick connect",
"quick_connect_title": "Quick Connect",
"authorize_button": "Authorize Quick Connect",
"enter_the_quick_connect_code": "Enter the Quick Connect code",
"success": "Success",
@@ -87,7 +88,15 @@
"download_method": "Download method",
"remux_max_download": "Remux max download",
"auto_download": "Auto download",
"optimized_versions_server": "Optimized versions server"
"optimized_versions_server": "Optimized versions server",
"save_button": "Save",
"optimized_server": "Optimized Server",
"optimized": "Optimized",
"default": "Default",
"optimized_version_hint": "Enter the URL for the optimize server. The URL should include http or https and optionally the port.",
"read_more_about_optimized_server": "Read more about the optimize server.",
"url":"URL",
"server_url_placeholder": "http(s)://domain.org:port"
},
"plugins": {
"plugins_title": "Plugins",
@@ -149,7 +158,8 @@
"background_downloads_enabled": "Background downloads enabled",
"background_downloads_disabled": "Background downloads disabled",
"connected": "Connected",
"could_not_connect": "Could not connect"
"could_not_connect": "Could not connect",
"invalid_url": "Invalid URL"
}
},
"downloads": {
@@ -172,6 +182,7 @@
"delete": "Delete",
"something_went_wrong": "Something went wrong",
"could_not_get_stream_url_from_jellyfin": "Could not get stream URL from Jellyfin",
"eta": "ETA {{eta}}",
"toasts": {
"you_are_not_allowed_to_download_files": "You are not allowed to download files.",
"deleted_all_movies_successfully": "Deleted all movies successfully!",
@@ -199,9 +210,14 @@
}
},
"search": {
"results_for_x": "Results for ",
"search_title": "Search",
"search_here": "Search here...",
"search": "Search...",
"x_items": "{{count}} items",
"library": "Library",
"discover": "Discover",
"no_results": "No results",
"no_results_found_for": "No results found for",
"movies": "Movies",
"series": "Series",
@@ -248,6 +264,9 @@
"music_albums": "Music Albums",
"audio": "Audio"
},
"custom_links": {
"no_links": "No links"
},
"player": {
"error": "Error",
"failed_to_get_stream_url": "Failed to get stream URL",
@@ -255,7 +274,9 @@
"client_error": "Client error",
"could_not_create_stream_for_chromecast": "Could not create stream for Chromecast",
"message_from_server": "Message from server: {{message}}",
"video_has_finished_playing": "Video has finished playing!"
"video_has_finished_playing": "Video has finished playing!",
"no_video_source": "No video source...",
"next_episode": "Next Episode"
},
"item_card": {
"next_up": "Next up",
@@ -264,7 +285,6 @@
"series": "Series",
"seasons": "Seasons",
"season": "Season",
"episodes": "Episodes",
"no_episodes_for_this_season": "No episodes for this season",
"overview": "Overview",
"more_with": "More with {{name}}",
@@ -277,18 +297,37 @@
"subtitles": "Subtitle",
"show_more": "Show more",
"show_less": "Show less",
"appeared_in": "Appeared in",
"x_songs": "{{count}} songs",
"x_albums": "{{count}} albums",
"artists": "Artists",
"could_not_load_item": "Could not load item",
"refresh_tracks": "Refresh Tracks",
"subtitle_tracks": "Subtitle Tracks:",
"audio_tracks": "Audio Tracks:",
"playback_state": "Playback State:",
"no_data_available": "No data available",
"index": "Index:",
"download": {
"download_season": "Download Season",
"download_x_item": "Download {{item_count}} items",
"download_button": "Download",
"using_optimized_server": "Using optimized server",
"using_default_method": "Using default method",
"using_default_method": "Using default method"
}
},
"jellyseerr":{
"confirm": "Confirm",
"cancel": "Cancel",
"yes": "Yes",
"whats_wrong": "What's wrong?",
"issue_type": "Issue type",
"select_an_issue": "Select an issue",
"types": "Types",
"describe_the_issue": "(optional) Describe the issue...",
"submit_button": "Submit",
"report_issue_button": "Report issue",
"request_button": "Request",
"are_you_sure_you_want_to_request_all_seasons": "Are you sure you want to request all seasons?",
"failed_to_login": "Failed to login",
"toasts": {

View File

@@ -6,11 +6,11 @@
"login_to_title": "Se connecter à",
"username_placeholder": "Nom d'utilisateur",
"password_placeholder": "Mot de passe",
"use_quick_connect": "Utiliser Quick Connect",
"use_quick_connect": "Utiliser Connexion Rapide",
"login_button": "Se connecter",
"quick_connect": "Quick Connect",
"quick_connect": "Connexion Rapide",
"enter_code_to_login": "Entrez le code {{code}} pour vous connecter",
"failed_to_initiate_quick_connect": "Échec de l'initialisation de Quick Connect",
"failed_to_initiate_quick_connect": "Échec de l'initialisation de Connexion Rapide",
"got_it": "D'accord",
"connection_failed": "La connection a échouée",
"could_not_connect_to_server": "Impossible de se connecter au serveur. Veuillez vérifier l'URL et votre connection réseau."
@@ -26,6 +26,7 @@
"home": {
"home": "Accueil",
"no_internet": "Pas d'Internet",
"no_items": "Aucun item",
"no_internet_message": "Aucun problème, vous pouvez toujours regarder\nle contenu téléchargé.",
"go_to_downloads": "Aller aux téléchargements",
"oops": "Oups!",
@@ -46,11 +47,11 @@
"app_version": "Version de l'application"
},
"quick_connect": {
"quick_connect_title": "Quick connect",
"authorize_button": "Autoriser Quick Connect",
"enter_the_quick_connect_code": "Entrez le code Quick Connect",
"quick_connect_title": "Connexion Rapide",
"authorize_button": "Autoriser Connexion Rapide",
"enter_the_quick_connect_code": "Entrez le code Connexion Rapide",
"success": "Succès",
"quick_connect_autorized": "Quick Connect autorisé",
"quick_connect_autorized": "Connexion Rapide autorisé",
"error": "Errur",
"invalid_code": "Code invalide"
},
@@ -63,7 +64,8 @@
"audio_title": "Audio",
"set_audio_track": "Configurer la piste audio à partir de l'élément précédent",
"audio_language": "Langue audio",
"audio_hint": "Chosissez une langue audio par défaut."
"audio_hint": "Chosissez une langue audio par défaut.",
"none": "Aucun"
},
"subtitles": {
"subtitle_title": "Sous-titres",
@@ -71,7 +73,8 @@
"subtitle_mode": "Mode des sous-titres",
"set_subtitle_track": "Configurer la piste de sous-titres à partir de l'élément précédent",
"subtitle_size": "Taille des sous-titres",
"subtitle_hint": "Configurez les préférences des sous-titres."
"subtitle_hint": "Configurez les préférences des sous-titres.",
"none": "Aucun"
},
"other": {
"other_title": "Autres",
@@ -85,7 +88,15 @@
"download_method": "Méthode de téléchargement",
"remux_max_download": "Téléchargement max remux",
"auto_download": "Téléchargement automatique",
"optimized_versions_server": "Serveur de versions optimisées"
"optimized_versions_server": "Serveur de versions optimisées",
"save_button": "Enregistrer",
"optimized_server": "Serveur optimisé",
"optimized": "Optimisé",
"default": "Défaut",
"optimized_version_hint": "Entrez l'URL du serveur de versions optimisées. L'URL devrait inclure http ou https et optionnellement le port.",
"read_more_about_optimized_server": "Lisez-en plus sur le serveur de versions optimisées.",
"url": "URL",
"server_url_placeholder": "http(s)://domaine.org:port"
},
"plugins": {
"plugins_title": "Plugiciels",
@@ -147,7 +158,8 @@
"background_downloads_enabled": "Téléchargements en arrière-plan activés",
"background_downloads_disabled": "Téléchargements en arrière-plan désactivés",
"connected": "Connecté",
"could_not_connect": "Impossible de se connecter"
"could_not_connect": "Impossible de se connecter",
"invalid_url": "URL invalide"
}
},
"downloads": {
@@ -170,6 +182,7 @@
"delete": "Supprimer",
"something_went_wrong": "Quelque chose s'est mal passé",
"could_not_get_stream_url_from_jellyfin": "Impossible d'obtenir l'URL du flux depuis Jellyfin",
"eta": "ETA {{eta}}",
"toasts": {
"you_are_not_allowed_to_download_files": "Vous n'êtes pas autorisé à télécharger des fichiers",
"deleted_all_movies_successfully": "Tous les films ont été supprimés avec succès!",
@@ -197,9 +210,14 @@
}
},
"search": {
"results_for_x": "Résultats pour ",
"search_title": "Recherche",
"search_here": "Rechercher ici...",
"search": "Rechercher...",
"x_items": "{{count}} items",
"library": "Bibliothèque",
"discover": "Découvrir",
"no_results": "Aucun résultat",
"no_results_found_for": "Aucun résultat trouvé pour",
"movies": "Films",
"series": "Séries",
@@ -245,6 +263,9 @@
"playlists": "Listes de lecture",
"music_albums": "Albums de musique",
"audio": "Audio"
},
"custom_links": {
"no_links": "Aucun lien"
},
"player": {
"error": "Erreur",
@@ -253,7 +274,9 @@
"client_error": "Erreur client",
"could_not_create_stream_for_chromecast": "Impossible de créer un flux pour Chromecast",
"message_from_server": "Message du serveur: {{message}}",
"video_has_finished_playing": "La vidéo a fini de jouer!"
"video_has_finished_playing": "La vidéo a fini de jouer!",
"no_video_source": "Aucune source vidéo...",
"next_episode": "Épisode suivant"
},
"item_card": {
"next_up": "À suivre",
@@ -262,7 +285,6 @@
"series": "Séries",
"seasons": "Saisons",
"season": "Saison",
"episodes": "Épisodes",
"no_episodes_for_this_season": "Aucun épisode pour cette saison",
"overview": "Aperçu",
"more_with": "Plus avec {{name}}",
@@ -275,6 +297,17 @@
"subtitles": "Sous-titres",
"show_more": "Afficher plus",
"show_less": "Afficher moins",
"appeared_in": "Apparu dans",
"x_songs": "{{count}} chansons",
"x_albums": "{{count}} albums",
"artists": "Artistes",
"could_not_load_item": "Impossible de charger l'item",
"refresh_tracks": "Rafraîchir les pistes",
"subtitle_tracks": "Pistes de sous-titres:",
"audio_tracks": "Pistes audio:",
"playback_state": "État de lecture:",
"no_data_available": "Aucune donnée disponible",
"index": "Index:",
"download": {
"download_season": "Télécharger la saison",
"download_x_item": "Télécharger {{item_count}} items",
@@ -287,6 +320,14 @@
"confirm": "Confirmer",
"cancel": "Annuler",
"yes": "Oui",
"whats_wrong": "Qu'est-ce qui ne va pas?",
"issue_type": "Type de problème",
"select_an_issue": "Sélectionnez un problème",
"types": "Types",
"describe_the_issue": "(optionnel) Décrivez le problème...",
"submit_button": "Soumettre",
"report_issue_button": "Signaler un problème",
"request_button": "Demander",
"are_you_sure_you_want_to_request_all_seasons": "Êtes-vous sûr de vouloir demander toutes les saisons?",
"failed_to_login": "Échec de la connexion",
"toasts": {