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

View File

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

View File

@@ -18,10 +18,12 @@ import { useLocalSearchParams } from "expo-router";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { useTranslation } from "react-i18next";
const page: React.FC = () => { const page: React.FC = () => {
const local = useLocalSearchParams(); const local = useLocalSearchParams();
const { actorId } = local as { actorId: string }; const { actorId } = local as { actorId: string };
const { t } = useTranslation();
const [api] = useAtom(apiAtom); const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom); const [user] = useAtom(userAtom);
@@ -110,7 +112,7 @@ const page: React.FC = () => {
</View> </View>
<Text className="px-4 text-2xl font-bold mb-2 text-neutral-100"> <Text className="px-4 text-2xl font-bold mb-2 text-neutral-100">
Appeared In {t("item_card.appeared_in")}
</Text> </Text>
<InfiniteHorizontalScroll <InfiniteHorizontalScroll
height={247} 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 { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { router, useLocalSearchParams, useNavigation } from "expo-router"; import { router, useLocalSearchParams, useNavigation } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { ScrollView, TouchableOpacity, View } from "react-native"; import { ScrollView, TouchableOpacity, View } from "react-native";
@@ -112,7 +113,7 @@ export default function page() {
<View className="px-4 mb-8"> <View className="px-4 mb-8">
<Text className="font-bold text-2xl mb-2">{album?.Name}</Text> <Text className="font-bold text-2xl mb-2">{album?.Name}</Text>
<Text className="text-neutral-500"> <Text className="text-neutral-500">
{songs?.TotalRecordCount} songs {t("item_card.x_songs", { count: songs?.TotalRecordCount })}
</Text> </Text>
</View> </View>
<View className="px-4"> <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 { getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { router, useLocalSearchParams, useNavigation } from "expo-router"; import { router, useLocalSearchParams, useNavigation } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FlatList, ScrollView, TouchableOpacity, View } from "react-native"; import { FlatList, ScrollView, TouchableOpacity, View } from "react-native";
@@ -107,7 +108,7 @@ export default function page() {
<View className="px-4 mb-8"> <View className="px-4 mb-8">
<Text className="font-bold text-2xl mb-2">{artist?.Name}</Text> <Text className="font-bold text-2xl mb-2">{artist?.Name}</Text>
<Text className="text-neutral-500"> <Text className="text-neutral-500">
{albums.TotalRecordCount} albums {t("item_card.x_albums", { count: albums.TotalRecordCount })}
</Text> </Text>
</View> </View>
<View className="flex flex-row flex-wrap justify-between px-4"> <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 { getArtistsApi, getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { router, useLocalSearchParams } from "expo-router"; import { router, useLocalSearchParams } from "expo-router";
import { t } from "i18next";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { FlatList, TouchableOpacity, View } from "react-native"; import { FlatList, TouchableOpacity, View } from "react-native";
@@ -81,7 +82,7 @@ export default function page() {
}} }}
ListHeaderComponent={ ListHeaderComponent={
<View className="mb-4"> <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> </View>
} }
nestedScrollEnabled nestedScrollEnabled

View File

@@ -377,7 +377,7 @@ const page: React.FC = () => {
<FlashList <FlashList
ListEmptyComponent={ ListEmptyComponent={
<View className="flex flex-col items-center justify-center h-full"> <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> </View>
} }
extraData={[ extraData={[

View File

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

View File

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

View File

@@ -423,7 +423,7 @@ export default function page() {
if (isErrorItem || isErrorStreamUrl) if (isErrorItem || isErrorStreamUrl)
return ( return (
<View className="w-screen h-screen flex flex-col items-center justify-center bg-black"> <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>
); );

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import { TouchableOpacity, View } from "react-native";
import * as DropdownMenu from "zeego/dropdown-menu"; import * as DropdownMenu from "zeego/dropdown-menu";
import { Text } from "./common/Text"; import { Text } from "./common/Text";
import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb"; import { convertBitsToMegabitsOrGigabits } from "@/utils/bToMb";
import { useTranslation } from "react-i18next";
interface Props extends React.ComponentProps<typeof View> { interface Props extends React.ComponentProps<typeof View> {
item: BaseItemDto; item: BaseItemDto;
@@ -29,6 +30,8 @@ export const MediaSourceSelector: React.FC<Props> = ({
[item, selected] [item, selected]
); );
const { t } = useTranslation();
return ( return (
<View <View
className="flex shrink" className="flex shrink"
@@ -39,7 +42,7 @@ export const MediaSourceSelector: React.FC<Props> = ({
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger> <DropdownMenu.Trigger>
<View className="flex flex-col" {...props}> <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"> <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> <Text numberOfLines={1}>{selectedName}</Text>
</TouchableOpacity> </TouchableOpacity>

View File

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

View File

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

View File

@@ -155,7 +155,7 @@ export const FilterSheet = <T,>({
> >
<View className="px-4 mt-2 mb-8"> <View className="px-4 mt-2 mb-8">
<Text className="font-bold text-2xl">{title}</Text> <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 && ( {showSearch && (
<Input <Input
placeholder={t("search.search")} placeholder={t("search.search")}

View File

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

View File

@@ -211,7 +211,7 @@ export const SeasonPicker: React.FC<Props> = ({ item, initialSeasonIndex }) => {
{(episodes?.length || 0) === 0 ? ( {(episodes?.length || 0) === 0 ? (
<View className="flex flex-col"> <View className="flex flex-col">
<Text className="text-neutral-500"> <Text className="text-neutral-500">
{t("item_card.no_episodes_for_this_seasonz")} {t("item_card.no_episodes_for_this_season")}
</Text> </Text>
</View> </View>
) : null} ) : 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"> <TouchableOpacity className="flex flex-row items-center justify-between py-3 pl-3">
<Text className="mr-1 text-[#8E8D91]"> <Text className="mr-1 text-[#8E8D91]">
{settings.downloadMethod === "remux" {settings.downloadMethod === "remux"
? "Default" ? t("home.settings.downloads.default")
: "Optimized"} : t("home.settings.downloads.optimized")}
</Text> </Text>
<Ionicons <Ionicons
name="chevron-expand-sharp" name="chevron-expand-sharp"

View File

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

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
"home": { "home": {
"home": "Home", "home": "Home",
"no_internet": "No Internet", "no_internet": "No Internet",
"no_items": "No items",
"no_internet_message": "No worries, you can still watch\ndownloaded content.", "no_internet_message": "No worries, you can still watch\ndownloaded content.",
"go_to_downloads": "Go to downloads", "go_to_downloads": "Go to downloads",
"oops": "Oops!", "oops": "Oops!",
@@ -46,7 +47,7 @@
"app_version": "App Version" "app_version": "App Version"
}, },
"quick_connect": { "quick_connect": {
"quick_connect_title": "Quick connect", "quick_connect_title": "Quick Connect",
"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",
@@ -87,7 +88,15 @@
"download_method": "Download method", "download_method": "Download method",
"remux_max_download": "Remux max download", "remux_max_download": "Remux max download",
"auto_download": "Auto 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": {
"plugins_title": "Plugins", "plugins_title": "Plugins",
@@ -149,7 +158,8 @@
"background_downloads_enabled": "Background downloads enabled", "background_downloads_enabled": "Background downloads enabled",
"background_downloads_disabled": "Background downloads disabled", "background_downloads_disabled": "Background downloads disabled",
"connected": "Connected", "connected": "Connected",
"could_not_connect": "Could not connect" "could_not_connect": "Could not connect",
"invalid_url": "Invalid URL"
} }
}, },
"downloads": { "downloads": {
@@ -172,6 +182,7 @@
"delete": "Delete", "delete": "Delete",
"something_went_wrong": "Something went wrong", "something_went_wrong": "Something went wrong",
"could_not_get_stream_url_from_jellyfin": "Could not get stream URL from Jellyfin", "could_not_get_stream_url_from_jellyfin": "Could not get stream URL from Jellyfin",
"eta": "ETA {{eta}}",
"toasts": { "toasts": {
"you_are_not_allowed_to_download_files": "You are not allowed to download files.", "you_are_not_allowed_to_download_files": "You are not allowed to download files.",
"deleted_all_movies_successfully": "Deleted all movies successfully!", "deleted_all_movies_successfully": "Deleted all movies successfully!",
@@ -199,9 +210,14 @@
} }
}, },
"search": { "search": {
"results_for_x": "Results for ",
"search_title": "Search", "search_title": "Search",
"search_here": "Search here...", "search_here": "Search here...",
"search": "Search...", "search": "Search...",
"x_items": "{{count}} items",
"library": "Library",
"discover": "Discover",
"no_results": "No results",
"no_results_found_for": "No results found for", "no_results_found_for": "No results found for",
"movies": "Movies", "movies": "Movies",
"series": "Series", "series": "Series",
@@ -248,6 +264,9 @@
"music_albums": "Music Albums", "music_albums": "Music Albums",
"audio": "Audio" "audio": "Audio"
}, },
"custom_links": {
"no_links": "No links"
},
"player": { "player": {
"error": "Error", "error": "Error",
"failed_to_get_stream_url": "Failed to get stream URL", "failed_to_get_stream_url": "Failed to get stream URL",
@@ -255,7 +274,9 @@
"client_error": "Client error", "client_error": "Client error",
"could_not_create_stream_for_chromecast": "Could not create stream for Chromecast", "could_not_create_stream_for_chromecast": "Could not create stream for Chromecast",
"message_from_server": "Message from server: {{message}}", "message_from_server": "Message from server: {{message}}",
"video_has_finished_playing": "Video has finished playing!" "video_has_finished_playing": "Video has finished playing!",
"no_video_source": "No video source...",
"next_episode": "Next Episode"
}, },
"item_card": { "item_card": {
"next_up": "Next up", "next_up": "Next up",
@@ -264,7 +285,6 @@
"series": "Series", "series": "Series",
"seasons": "Seasons", "seasons": "Seasons",
"season": "Season", "season": "Season",
"episodes": "Episodes",
"no_episodes_for_this_season": "No episodes for this season", "no_episodes_for_this_season": "No episodes for this season",
"overview": "Overview", "overview": "Overview",
"more_with": "More with {{name}}", "more_with": "More with {{name}}",
@@ -277,18 +297,37 @@
"subtitles": "Subtitle", "subtitles": "Subtitle",
"show_more": "Show more", "show_more": "Show more",
"show_less": "Show less", "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": {
"download_season": "Download Season", "download_season": "Download Season",
"download_x_item": "Download {{item_count}} items", "download_x_item": "Download {{item_count}} items",
"download_button": "Download", "download_button": "Download",
"using_optimized_server": "Using optimized server", "using_optimized_server": "Using optimized server",
"using_default_method": "Using default method", "using_default_method": "Using default method"
} }
}, },
"jellyseerr":{ "jellyseerr":{
"confirm": "Confirm", "confirm": "Confirm",
"cancel": "Cancel", "cancel": "Cancel",
"yes": "Yes", "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?", "are_you_sure_you_want_to_request_all_seasons": "Are you sure you want to request all seasons?",
"failed_to_login": "Failed to login", "failed_to_login": "Failed to login",
"toasts": { "toasts": {

View File

@@ -6,11 +6,11 @@
"login_to_title": "Se connecter à", "login_to_title": "Se connecter à",
"username_placeholder": "Nom d'utilisateur", "username_placeholder": "Nom d'utilisateur",
"password_placeholder": "Mot de passe", "password_placeholder": "Mot de passe",
"use_quick_connect": "Utiliser Quick Connect", "use_quick_connect": "Utiliser Connexion Rapide",
"login_button": "Se connecter", "login_button": "Se connecter",
"quick_connect": "Quick Connect", "quick_connect": "Connexion Rapide",
"enter_code_to_login": "Entrez le code {{code}} pour vous connecter", "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", "got_it": "D'accord",
"connection_failed": "La connection a échouée", "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." "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": {
"home": "Accueil", "home": "Accueil",
"no_internet": "Pas d'Internet", "no_internet": "Pas d'Internet",
"no_items": "Aucun item",
"no_internet_message": "Aucun problème, vous pouvez toujours regarder\nle contenu téléchargé.", "no_internet_message": "Aucun problème, vous pouvez toujours regarder\nle contenu téléchargé.",
"go_to_downloads": "Aller aux téléchargements", "go_to_downloads": "Aller aux téléchargements",
"oops": "Oups!", "oops": "Oups!",
@@ -46,11 +47,11 @@
"app_version": "Version de l'application" "app_version": "Version de l'application"
}, },
"quick_connect": { "quick_connect": {
"quick_connect_title": "Quick connect", "quick_connect_title": "Connexion Rapide",
"authorize_button": "Autoriser Quick Connect", "authorize_button": "Autoriser Connexion Rapide",
"enter_the_quick_connect_code": "Entrez le code Quick Connect", "enter_the_quick_connect_code": "Entrez le code Connexion Rapide",
"success": "Succès", "success": "Succès",
"quick_connect_autorized": "Quick Connect autorisé", "quick_connect_autorized": "Connexion Rapide autorisé",
"error": "Errur", "error": "Errur",
"invalid_code": "Code invalide" "invalid_code": "Code invalide"
}, },
@@ -63,7 +64,8 @@
"audio_title": "Audio", "audio_title": "Audio",
"set_audio_track": "Configurer la piste audio à partir de l'élément précédent", "set_audio_track": "Configurer la piste audio à partir de l'élément précédent",
"audio_language": "Langue audio", "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": { "subtitles": {
"subtitle_title": "Sous-titres", "subtitle_title": "Sous-titres",
@@ -71,7 +73,8 @@
"subtitle_mode": "Mode des sous-titres", "subtitle_mode": "Mode des sous-titres",
"set_subtitle_track": "Configurer la piste de sous-titres à partir de l'élément précédent", "set_subtitle_track": "Configurer la piste de sous-titres à partir de l'élément précédent",
"subtitle_size": "Taille des sous-titres", "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": {
"other_title": "Autres", "other_title": "Autres",
@@ -85,7 +88,15 @@
"download_method": "Méthode de téléchargement", "download_method": "Méthode de téléchargement",
"remux_max_download": "Téléchargement max remux", "remux_max_download": "Téléchargement max remux",
"auto_download": "Téléchargement automatique", "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": {
"plugins_title": "Plugiciels", "plugins_title": "Plugiciels",
@@ -147,7 +158,8 @@
"background_downloads_enabled": "Téléchargements en arrière-plan activés", "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", "background_downloads_disabled": "Téléchargements en arrière-plan désactivés",
"connected": "Connecté", "connected": "Connecté",
"could_not_connect": "Impossible de se connecter" "could_not_connect": "Impossible de se connecter",
"invalid_url": "URL invalide"
} }
}, },
"downloads": { "downloads": {
@@ -170,6 +182,7 @@
"delete": "Supprimer", "delete": "Supprimer",
"something_went_wrong": "Quelque chose s'est mal passé", "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", "could_not_get_stream_url_from_jellyfin": "Impossible d'obtenir l'URL du flux depuis Jellyfin",
"eta": "ETA {{eta}}",
"toasts": { "toasts": {
"you_are_not_allowed_to_download_files": "Vous n'êtes pas autorisé à télécharger des fichiers", "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!", "deleted_all_movies_successfully": "Tous les films ont été supprimés avec succès!",
@@ -197,9 +210,14 @@
} }
}, },
"search": { "search": {
"results_for_x": "Résultats pour ",
"search_title": "Recherche", "search_title": "Recherche",
"search_here": "Rechercher ici...", "search_here": "Rechercher ici...",
"search": "Rechercher...", "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", "no_results_found_for": "Aucun résultat trouvé pour",
"movies": "Films", "movies": "Films",
"series": "Séries", "series": "Séries",
@@ -245,6 +263,9 @@
"playlists": "Listes de lecture", "playlists": "Listes de lecture",
"music_albums": "Albums de musique", "music_albums": "Albums de musique",
"audio": "Audio" "audio": "Audio"
},
"custom_links": {
"no_links": "Aucun lien"
}, },
"player": { "player": {
"error": "Erreur", "error": "Erreur",
@@ -253,7 +274,9 @@
"client_error": "Erreur client", "client_error": "Erreur client",
"could_not_create_stream_for_chromecast": "Impossible de créer un flux pour Chromecast", "could_not_create_stream_for_chromecast": "Impossible de créer un flux pour Chromecast",
"message_from_server": "Message du serveur: {{message}}", "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": { "item_card": {
"next_up": "À suivre", "next_up": "À suivre",
@@ -262,7 +285,6 @@
"series": "Séries", "series": "Séries",
"seasons": "Saisons", "seasons": "Saisons",
"season": "Saison", "season": "Saison",
"episodes": "Épisodes",
"no_episodes_for_this_season": "Aucun épisode pour cette saison", "no_episodes_for_this_season": "Aucun épisode pour cette saison",
"overview": "Aperçu", "overview": "Aperçu",
"more_with": "Plus avec {{name}}", "more_with": "Plus avec {{name}}",
@@ -275,6 +297,17 @@
"subtitles": "Sous-titres", "subtitles": "Sous-titres",
"show_more": "Afficher plus", "show_more": "Afficher plus",
"show_less": "Afficher moins", "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": {
"download_season": "Télécharger la saison", "download_season": "Télécharger la saison",
"download_x_item": "Télécharger {{item_count}} items", "download_x_item": "Télécharger {{item_count}} items",
@@ -287,6 +320,14 @@
"confirm": "Confirmer", "confirm": "Confirmer",
"cancel": "Annuler", "cancel": "Annuler",
"yes": "Oui", "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?", "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", "failed_to_login": "Échec de la connexion",
"toasts": { "toasts": {