feat(i18n): components pack translated
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
Globe,
|
Globe,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { CacheType } from "@/lib/services/base-api.service";
|
import { CacheType } from "@/lib/services/base-api.service";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface RequestTiming {
|
interface RequestTiming {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -49,6 +50,7 @@ export function DebugInfo() {
|
|||||||
const [isMinimized, setIsMinimized] = useState(false);
|
const [isMinimized, setIsMinimized] = useState(false);
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const fetchLogs = async () => {
|
const fetchLogs = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -100,10 +102,10 @@ export function DebugInfo() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-4 sticky top-0 bg-zinc-900 pb-2">
|
<div className="flex items-center justify-between mb-4 sticky top-0 bg-zinc-900 pb-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h2 className="font-bold text-lg">DEBUG</h2>
|
<h2 className="font-bold text-lg">{t("debug.title")}</h2>
|
||||||
{!isMinimized && (
|
{!isMinimized && (
|
||||||
<span className="text-xs text-zinc-400">
|
<span className="text-xs text-zinc-400">
|
||||||
{sortedLogs.length} entrée{sortedLogs.length > 1 ? "s" : ""}
|
{sortedLogs.length} {t("debug.entries", { count: sortedLogs.length })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +113,7 @@ export function DebugInfo() {
|
|||||||
<button
|
<button
|
||||||
onClick={fetchLogs}
|
onClick={fetchLogs}
|
||||||
className="hover:bg-zinc-700 rounded-full p-1.5"
|
className="hover:bg-zinc-700 rounded-full p-1.5"
|
||||||
aria-label="Rafraîchir les logs"
|
aria-label={t("debug.actions.refresh")}
|
||||||
disabled={isRefreshing}
|
disabled={isRefreshing}
|
||||||
>
|
>
|
||||||
<RefreshCw className={`h-5 w-5 ${isRefreshing ? "animate-spin" : ""}`} />
|
<RefreshCw className={`h-5 w-5 ${isRefreshing ? "animate-spin" : ""}`} />
|
||||||
@@ -119,14 +121,14 @@ export function DebugInfo() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setIsMinimized(!isMinimized)}
|
onClick={() => setIsMinimized(!isMinimized)}
|
||||||
className="hover:bg-zinc-700 rounded-full p-1.5"
|
className="hover:bg-zinc-700 rounded-full p-1.5"
|
||||||
aria-label={isMinimized ? "Agrandir" : "Minimiser"}
|
aria-label={t(isMinimized ? "debug.actions.maximize" : "debug.actions.minimize")}
|
||||||
>
|
>
|
||||||
{isMinimized ? <Maximize2 className="h-5 w-5" /> : <Minimize2 className="h-5 w-5" />}
|
{isMinimized ? <Maximize2 className="h-5 w-5" /> : <Minimize2 className="h-5 w-5" />}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={clearLogs}
|
onClick={clearLogs}
|
||||||
className="hover:bg-zinc-700 rounded-full p-1.5"
|
className="hover:bg-zinc-700 rounded-full p-1.5"
|
||||||
aria-label="Effacer les logs"
|
aria-label={t("debug.actions.clear")}
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -136,32 +138,40 @@ export function DebugInfo() {
|
|||||||
{!isMinimized && (
|
{!isMinimized && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{sortedLogs.length === 0 ? (
|
{sortedLogs.length === 0 ? (
|
||||||
<p className="text-sm opacity-75">Aucune requête enregistrée</p>
|
<p className="text-sm opacity-75">{t("debug.noRequests")}</p>
|
||||||
) : (
|
) : (
|
||||||
sortedLogs.map((log, index) => (
|
sortedLogs.map((log, index) => (
|
||||||
<div key={index} className="text-sm space-y-1.5 bg-zinc-800 p-2 rounded">
|
<div key={index} className="text-sm space-y-1.5 bg-zinc-800 p-2 rounded">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
{log.fromCache && (
|
{log.fromCache && (
|
||||||
<div title={`Cache: ${log.cacheType || "DEFAULT"}`} className="flex-shrink-0">
|
<div
|
||||||
|
title={t("debug.tooltips.cache", { type: log.cacheType || "DEFAULT" })}
|
||||||
|
className="flex-shrink-0"
|
||||||
|
>
|
||||||
<Database className="h-4 w-4" />
|
<Database className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{log.mongoAccess && (
|
{log.mongoAccess && (
|
||||||
<div
|
<div
|
||||||
title={`MongoDB: ${log.mongoAccess.operation}`}
|
title={t("debug.tooltips.mongodb", {
|
||||||
|
operation: log.mongoAccess.operation,
|
||||||
|
})}
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
>
|
>
|
||||||
<CircleDot className="h-4 w-4 text-blue-400" />
|
<CircleDot className="h-4 w-4 text-blue-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{log.pageRender && (
|
{log.pageRender && (
|
||||||
<div title={`Page Render: ${log.pageRender.page}`} className="flex-shrink-0">
|
<div
|
||||||
|
title={t("debug.tooltips.pageRender", { page: log.pageRender.page })}
|
||||||
|
className="flex-shrink-0"
|
||||||
|
>
|
||||||
<Layout className="h-4 w-4 text-purple-400" />
|
<Layout className="h-4 w-4 text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!log.fromCache && !log.mongoAccess && !log.pageRender && (
|
{!log.fromCache && !log.mongoAccess && !log.pageRender && (
|
||||||
<div title="API Call" className="flex-shrink-0">
|
<div title={t("debug.tooltips.apiCall")} className="flex-shrink-0">
|
||||||
<Globe className="h-4 w-4 text-rose-400" />
|
<Globe className="h-4 w-4 text-rose-400" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -170,7 +180,10 @@ export function DebugInfo() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 flex-shrink-0">
|
<div className="flex items-center gap-3 flex-shrink-0">
|
||||||
<div className="flex items-center gap-1 text-zinc-400" title="Heure du log">
|
<div
|
||||||
|
className="flex items-center gap-1 text-zinc-400"
|
||||||
|
title={t("debug.tooltips.logTime")}
|
||||||
|
>
|
||||||
<Clock className="h-3 w-3" />
|
<Clock className="h-3 w-3" />
|
||||||
<span>{formatTime(log.timestamp)}</span>
|
<span>{formatTime(log.timestamp)}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +201,7 @@ export function DebugInfo() {
|
|||||||
{formatDuration(log.duration)}ms
|
{formatDuration(log.duration)}ms
|
||||||
</span>
|
</span>
|
||||||
{log.mongoAccess && (
|
{log.mongoAccess && (
|
||||||
<span className="text-blue-400" title="Temps d'accès MongoDB">
|
<span className="text-blue-400" title={t("debug.tooltips.mongoAccess")}>
|
||||||
+{formatDuration(log.mongoAccess.duration)}ms
|
+{formatDuration(log.mongoAccess.duration)}ms
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Menu, Moon, Sun } from "lucide-react";
|
import { Menu, Moon, Sun } from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import LanguageSelector from "@/components/LanguageSelector";
|
import LanguageSelector from "@/components/LanguageSelector";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
onToggleSidebar: () => void;
|
onToggleSidebar: () => void;
|
||||||
@@ -8,6 +9,7 @@ interface HeaderProps {
|
|||||||
|
|
||||||
export function Header({ onToggleSidebar }: HeaderProps) {
|
export function Header({ onToggleSidebar }: HeaderProps) {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
setTheme(theme === "dark" ? "light" : "dark");
|
setTheme(theme === "dark" ? "light" : "dark");
|
||||||
@@ -19,7 +21,7 @@ export function Header({ onToggleSidebar }: HeaderProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={onToggleSidebar}
|
onClick={onToggleSidebar}
|
||||||
className="mr-2 px-2 py-1.5 hover:bg-accent hover:text-accent-foreground rounded-md"
|
className="mr-2 px-2 py-1.5 hover:bg-accent hover:text-accent-foreground rounded-md"
|
||||||
aria-label="Toggle sidebar"
|
aria-label={t("header.toggleSidebar")}
|
||||||
id="sidebar-toggle"
|
id="sidebar-toggle"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center w-5 h-5">
|
<div className="flex items-center justify-center w-5 h-5">
|
||||||
@@ -39,13 +41,13 @@ export function Header({ onToggleSidebar }: HeaderProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
className="px-2 py-1.5 hover:bg-accent hover:text-accent-foreground rounded-md"
|
className="px-2 py-1.5 hover:bg-accent hover:text-accent-foreground rounded-md"
|
||||||
aria-label="Toggle theme"
|
aria-label={t("header.toggleTheme")}
|
||||||
>
|
>
|
||||||
<div className="relative flex items-center w-5 h-5">
|
<div className="relative flex items-center w-5 h-5">
|
||||||
<Sun className="absolute inset-0 h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
<Sun className="absolute inset-0 h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||||
<Moon className="absolute inset-0 h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
<Moon className="absolute inset-0 h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||||
</div>
|
</div>
|
||||||
<span className="sr-only">Toggle theme</span>
|
<span className="sr-only">{t("header.toggleTheme")}</span>
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { RefreshCw } from "lucide-react";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface RefreshButtonProps {
|
interface RefreshButtonProps {
|
||||||
libraryId: string;
|
libraryId: string;
|
||||||
@@ -14,6 +15,7 @@ interface RefreshButtonProps {
|
|||||||
export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps) {
|
export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps) {
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
@@ -22,8 +24,8 @@ export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps)
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toast({
|
toast({
|
||||||
title: "Bibliothèque rafraîchie",
|
title: t("library.refresh.success.title"),
|
||||||
description: "La bibliothèque a été rafraîchie avec succès",
|
description: t("library.refresh.success.description"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
@@ -31,8 +33,9 @@ export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps)
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Erreur",
|
title: t("library.refresh.error.title"),
|
||||||
description: error instanceof Error ? error.message : "Une erreur est survenue",
|
description:
|
||||||
|
error instanceof Error ? error.message : t("library.refresh.error.description"),
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsRefreshing(false);
|
setIsRefreshing(false);
|
||||||
@@ -46,7 +49,7 @@ export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps)
|
|||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={isRefreshing}
|
disabled={isRefreshing}
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
aria-label="Rafraîchir la bibliothèque"
|
aria-label={t("library.refresh.button")}
|
||||||
>
|
>
|
||||||
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
|
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export const ControlButtons = ({
|
|||||||
"absolute top-4 right-4 p-2 rounded-full bg-background/50 hover:bg-background/80 transition-all duration-300 z-30",
|
"absolute top-4 right-4 p-2 rounded-full bg-background/50 hover:bg-background/80 transition-all duration-300 z-30",
|
||||||
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||||
)}
|
)}
|
||||||
aria-label="Fermer"
|
aria-label={t("reader.controls.close")}
|
||||||
>
|
>
|
||||||
<X className="h-6 w-6" />
|
<X className="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
@@ -136,7 +136,7 @@ export const ControlButtons = ({
|
|||||||
direction === "rtl" ? "right-4" : "left-4",
|
direction === "rtl" ? "right-4" : "left-4",
|
||||||
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||||
)}
|
)}
|
||||||
aria-label="Page précédente"
|
aria-label={t("reader.controls.previousPage")}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-8 w-8" />
|
<ChevronLeft className="h-8 w-8" />
|
||||||
</button>
|
</button>
|
||||||
@@ -154,7 +154,7 @@ export const ControlButtons = ({
|
|||||||
direction === "rtl" ? "left-4" : "right-4",
|
direction === "rtl" ? "left-4" : "right-4",
|
||||||
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||||
)}
|
)}
|
||||||
aria-label="Page suivante"
|
aria-label={t("reader.controls.nextPage")}
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-8 w-8" />
|
<ChevronRight className="h-8 w-8" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Button } from "./button";
|
|||||||
import { useToast } from "./use-toast";
|
import { useToast } from "./use-toast";
|
||||||
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
|
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface MarkAsReadButtonProps {
|
interface MarkAsReadButtonProps {
|
||||||
bookId: string;
|
bookId: string;
|
||||||
@@ -23,6 +24,7 @@ export function MarkAsReadButton({
|
|||||||
}: MarkAsReadButtonProps) {
|
}: MarkAsReadButtonProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleMarkAsRead = async (e: React.MouseEvent) => {
|
const handleMarkAsRead = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation(); // Empêcher la propagation au parent
|
e.stopPropagation(); // Empêcher la propagation au parent
|
||||||
@@ -38,19 +40,19 @@ export function MarkAsReadButton({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Erreur lors de la mise à jour");
|
throw new Error(t("books.actions.markAsRead.error.update"));
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Succès",
|
title: t("books.actions.markAsRead.success.title"),
|
||||||
description: "Le tome a été marqué comme lu",
|
description: t("books.actions.markAsRead.success.description"),
|
||||||
});
|
});
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur lors de la mise à jour du progresseur de lecture:", error);
|
console.error("Erreur lors de la mise à jour du progresseur de lecture:", error);
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur",
|
title: t("books.actions.markAsRead.error.title"),
|
||||||
description: "Impossible de marquer le tome comme lu",
|
description: t("books.actions.markAsRead.error.description"),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -65,7 +67,7 @@ export function MarkAsReadButton({
|
|||||||
onClick={handleMarkAsRead}
|
onClick={handleMarkAsRead}
|
||||||
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
|
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
|
||||||
disabled={isRead || isLoading}
|
disabled={isRead || isLoading}
|
||||||
aria-label="Marquer comme lu"
|
aria-label={t("books.actions.markAsRead.button")}
|
||||||
>
|
>
|
||||||
{isLoading ? <Loader2 className="h-5 w-5 animate-spin" /> : <BookCheck className="h-5 w-5" />}
|
{isLoading ? <Loader2 className="h-5 w-5 animate-spin" /> : <BookCheck className="h-5 w-5" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Button } from "./button";
|
|||||||
import { useToast } from "./use-toast";
|
import { useToast } from "./use-toast";
|
||||||
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
|
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface MarkAsUnreadButtonProps {
|
interface MarkAsUnreadButtonProps {
|
||||||
bookId: string;
|
bookId: string;
|
||||||
@@ -15,6 +16,7 @@ interface MarkAsUnreadButtonProps {
|
|||||||
export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnreadButtonProps) {
|
export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnreadButtonProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleMarkAsUnread = async (e: React.MouseEvent) => {
|
const handleMarkAsUnread = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation(); // Empêcher la propagation au parent
|
e.stopPropagation(); // Empêcher la propagation au parent
|
||||||
@@ -26,19 +28,19 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Erreur lors de la mise à jour");
|
throw new Error(t("books.actions.markAsUnread.error.update"));
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Succès",
|
title: t("books.actions.markAsUnread.success.title"),
|
||||||
description: "Le tome a été marqué comme non lu",
|
description: t("books.actions.markAsUnread.success.description"),
|
||||||
});
|
});
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur lors de la mise à jour du progresseur de lecture:", error);
|
console.error("Erreur lors de la mise à jour du progresseur de lecture:", error);
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur",
|
title: t("books.actions.markAsUnread.error.title"),
|
||||||
description: "Impossible de marquer le tome comme non lu",
|
description: t("books.actions.markAsUnread.error.description"),
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -53,7 +55,7 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
|
|||||||
onClick={handleMarkAsUnread}
|
onClick={handleMarkAsUnread}
|
||||||
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
|
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
aria-label="Marquer comme non lu"
|
aria-label={t("books.actions.markAsUnread.button")}
|
||||||
>
|
>
|
||||||
{isLoading ? <Loader2 className="h-5 w-5 animate-spin" /> : <BookX className="h-5 w-5" />}
|
{isLoading ? <Loader2 className="h-5 w-5 animate-spin" /> : <BookX className="h-5 w-5" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -146,7 +146,18 @@
|
|||||||
"available": "Available",
|
"available": "Available",
|
||||||
"unavailable": "Unavailable"
|
"unavailable": "Unavailable"
|
||||||
},
|
},
|
||||||
"lastUpdated": "Last updated: {date}"
|
"lastUpdated": "Last updated: {date}",
|
||||||
|
"refresh": {
|
||||||
|
"button": "Refresh library",
|
||||||
|
"success": {
|
||||||
|
"title": "Library refreshed",
|
||||||
|
"description": "The library has been refreshed successfully"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error",
|
||||||
|
"description": "An error occurred"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"series": {
|
"series": {
|
||||||
"empty": "No series available",
|
"empty": "No series available",
|
||||||
@@ -180,7 +191,9 @@
|
|||||||
"favorite": {
|
"favorite": {
|
||||||
"add": "Added to favorites",
|
"add": "Added to favorites",
|
||||||
"remove": "Removed from favorites"
|
"remove": "Removed from favorites"
|
||||||
}
|
},
|
||||||
|
"toggleSidebar": "Toggle sidebar",
|
||||||
|
"toggleTheme": "Toggle theme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"books": {
|
"books": {
|
||||||
@@ -200,7 +213,33 @@
|
|||||||
"showAll": "Show all",
|
"showAll": "Show all",
|
||||||
"unread": "Unread"
|
"unread": "Unread"
|
||||||
},
|
},
|
||||||
"loading": "Loading..."
|
"loading": "Loading...",
|
||||||
|
"actions": {
|
||||||
|
"markAsRead": {
|
||||||
|
"button": "Mark as read",
|
||||||
|
"success": {
|
||||||
|
"title": "Success",
|
||||||
|
"description": "Book has been marked as read"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error",
|
||||||
|
"description": "Unable to mark book as read",
|
||||||
|
"update": "Error updating status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"markAsUnread": {
|
||||||
|
"button": "Mark as unread",
|
||||||
|
"success": {
|
||||||
|
"title": "Success",
|
||||||
|
"description": "Book has been marked as unread"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error",
|
||||||
|
"description": "Unable to mark book as unread",
|
||||||
|
"update": "Error updating status"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"page": {
|
"page": {
|
||||||
@@ -278,7 +317,30 @@
|
|||||||
"fullscreen": {
|
"fullscreen": {
|
||||||
"enter": "Enter fullscreen",
|
"enter": "Enter fullscreen",
|
||||||
"exit": "Exit fullscreen"
|
"exit": "Exit fullscreen"
|
||||||
}
|
},
|
||||||
|
"close": "Close",
|
||||||
|
"previousPage": "Previous page",
|
||||||
|
"nextPage": "Next page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"title": "DEBUG",
|
||||||
|
"entries": "entry",
|
||||||
|
"entries_plural": "entries",
|
||||||
|
"noRequests": "No recorded requests",
|
||||||
|
"actions": {
|
||||||
|
"refresh": "Refresh logs",
|
||||||
|
"maximize": "Maximize",
|
||||||
|
"minimize": "Minimize",
|
||||||
|
"clear": "Clear logs"
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"cache": "Cache: {{type}}",
|
||||||
|
"mongodb": "MongoDB: {{operation}}",
|
||||||
|
"pageRender": "Page Render: {{page}}",
|
||||||
|
"apiCall": "API Call",
|
||||||
|
"logTime": "Log time",
|
||||||
|
"mongoAccess": "MongoDB access time"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,7 +146,18 @@
|
|||||||
"available": "Disponible",
|
"available": "Disponible",
|
||||||
"unavailable": "Non disponible"
|
"unavailable": "Non disponible"
|
||||||
},
|
},
|
||||||
"lastUpdated": "Dernière mise à jour : {date}"
|
"lastUpdated": "Dernière mise à jour : {date}",
|
||||||
|
"refresh": {
|
||||||
|
"button": "Rafraîchir la bibliothèque",
|
||||||
|
"success": {
|
||||||
|
"title": "Bibliothèque rafraîchie",
|
||||||
|
"description": "La bibliothèque a été rafraîchie avec succès"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Erreur",
|
||||||
|
"description": "Une erreur est survenue"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"series": {
|
"series": {
|
||||||
"empty": "Aucune série disponible",
|
"empty": "Aucune série disponible",
|
||||||
@@ -200,7 +211,33 @@
|
|||||||
"showAll": "Afficher tout",
|
"showAll": "Afficher tout",
|
||||||
"unread": "À lire"
|
"unread": "À lire"
|
||||||
},
|
},
|
||||||
"loading": "Chargement..."
|
"loading": "Chargement...",
|
||||||
|
"actions": {
|
||||||
|
"markAsRead": {
|
||||||
|
"button": "Marquer comme lu",
|
||||||
|
"success": {
|
||||||
|
"title": "Succès",
|
||||||
|
"description": "Le tome a été marqué comme lu"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Erreur",
|
||||||
|
"description": "Impossible de marquer le tome comme lu",
|
||||||
|
"update": "Erreur lors de la mise à jour"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"markAsUnread": {
|
||||||
|
"button": "Marquer comme non lu",
|
||||||
|
"success": {
|
||||||
|
"title": "Succès",
|
||||||
|
"description": "Le tome a été marqué comme non lu"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Erreur",
|
||||||
|
"description": "Impossible de marquer le tome comme non lu",
|
||||||
|
"update": "Erreur lors de la mise à jour"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"page": {
|
"page": {
|
||||||
@@ -278,7 +315,34 @@
|
|||||||
"fullscreen": {
|
"fullscreen": {
|
||||||
"enter": "Plein écran",
|
"enter": "Plein écran",
|
||||||
"exit": "Quitter le plein écran"
|
"exit": "Quitter le plein écran"
|
||||||
}
|
},
|
||||||
|
"close": "Fermer",
|
||||||
|
"previousPage": "Page précédente",
|
||||||
|
"nextPage": "Page suivante"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"toggleSidebar": "Afficher/masquer le menu latéral",
|
||||||
|
"toggleTheme": "Changer le thème"
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"title": "DEBUG",
|
||||||
|
"entries": "entrée",
|
||||||
|
"entries_plural": "entrées",
|
||||||
|
"noRequests": "Aucune requête enregistrée",
|
||||||
|
"actions": {
|
||||||
|
"refresh": "Rafraîchir les logs",
|
||||||
|
"maximize": "Agrandir",
|
||||||
|
"minimize": "Minimiser",
|
||||||
|
"clear": "Effacer les logs"
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"cache": "Cache: {{type}}",
|
||||||
|
"mongodb": "MongoDB: {{operation}}",
|
||||||
|
"pageRender": "Page Render: {{page}}",
|
||||||
|
"apiCall": "API Call",
|
||||||
|
"logTime": "Heure du log",
|
||||||
|
"mongoAccess": "Temps d'accès MongoDB"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user