feat(i18n): components pack translated

This commit is contained in:
Julien Froidefond
2025-03-02 06:31:41 +01:00
parent d94232e531
commit 2289753b84
8 changed files with 190 additions and 42 deletions

View File

@@ -13,6 +13,7 @@ import {
Globe,
} from "lucide-react";
import { CacheType } from "@/lib/services/base-api.service";
import { useTranslation } from "react-i18next";
interface RequestTiming {
url: string;
@@ -49,6 +50,7 @@ export function DebugInfo() {
const [isMinimized, setIsMinimized] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const pathname = usePathname();
const { t } = useTranslation();
const fetchLogs = async () => {
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 gap-2">
<h2 className="font-bold text-lg">DEBUG</h2>
<h2 className="font-bold text-lg">{t("debug.title")}</h2>
{!isMinimized && (
<span className="text-xs text-zinc-400">
{sortedLogs.length} entrée{sortedLogs.length > 1 ? "s" : ""}
{sortedLogs.length} {t("debug.entries", { count: sortedLogs.length })}
</span>
)}
</div>
@@ -111,7 +113,7 @@ export function DebugInfo() {
<button
onClick={fetchLogs}
className="hover:bg-zinc-700 rounded-full p-1.5"
aria-label="Rafraîchir les logs"
aria-label={t("debug.actions.refresh")}
disabled={isRefreshing}
>
<RefreshCw className={`h-5 w-5 ${isRefreshing ? "animate-spin" : ""}`} />
@@ -119,14 +121,14 @@ export function DebugInfo() {
<button
onClick={() => setIsMinimized(!isMinimized)}
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" />}
</button>
<button
onClick={clearLogs}
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" />
</button>
@@ -136,32 +138,40 @@ export function DebugInfo() {
{!isMinimized && (
<div className="space-y-3">
{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) => (
<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 items-center gap-2 min-w-0 flex-1">
{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" />
</div>
)}
{log.mongoAccess && (
<div
title={`MongoDB: ${log.mongoAccess.operation}`}
title={t("debug.tooltips.mongodb", {
operation: log.mongoAccess.operation,
})}
className="flex-shrink-0"
>
<CircleDot className="h-4 w-4 text-blue-400" />
</div>
)}
{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" />
</div>
)}
{!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" />
</div>
)}
@@ -170,7 +180,10 @@ export function DebugInfo() {
</span>
</div>
<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" />
<span>{formatTime(log.timestamp)}</span>
</div>
@@ -188,7 +201,7 @@ export function DebugInfo() {
{formatDuration(log.duration)}ms
</span>
{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
</span>
)}

View File

@@ -1,6 +1,7 @@
import { Menu, Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import LanguageSelector from "@/components/LanguageSelector";
import { useTranslation } from "react-i18next";
interface HeaderProps {
onToggleSidebar: () => void;
@@ -8,6 +9,7 @@ interface HeaderProps {
export function Header({ onToggleSidebar }: HeaderProps) {
const { theme, setTheme } = useTheme();
const { t } = useTranslation();
const toggleTheme = () => {
setTheme(theme === "dark" ? "light" : "dark");
@@ -19,7 +21,7 @@ export function Header({ onToggleSidebar }: HeaderProps) {
<button
onClick={onToggleSidebar}
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"
>
<div className="flex items-center justify-center w-5 h-5">
@@ -39,13 +41,13 @@ export function Header({ onToggleSidebar }: HeaderProps) {
<button
onClick={toggleTheme}
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">
<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" />
</div>
<span className="sr-only">Toggle theme</span>
<span className="sr-only">{t("header.toggleTheme")}</span>
</button>
</nav>
</div>

View File

@@ -5,6 +5,7 @@ import { RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
interface RefreshButtonProps {
libraryId: string;
@@ -14,6 +15,7 @@ interface RefreshButtonProps {
export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps) {
const [isRefreshing, setIsRefreshing] = useState(false);
const { toast } = useToast();
const { t } = useTranslation();
const handleRefresh = async () => {
setIsRefreshing(true);
@@ -22,8 +24,8 @@ export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps)
if (result.success) {
toast({
title: "Bibliothèque rafraîchie",
description: "La bibliothèque a été rafraîchie avec succès",
title: t("library.refresh.success.title"),
description: t("library.refresh.success.description"),
});
} else {
throw new Error(result.error);
@@ -31,8 +33,9 @@ export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps)
} catch (error) {
toast({
variant: "destructive",
title: "Erreur",
description: error instanceof Error ? error.message : "Une erreur est survenue",
title: t("library.refresh.error.title"),
description:
error instanceof Error ? error.message : t("library.refresh.error.description"),
});
} finally {
setIsRefreshing(false);
@@ -46,7 +49,7 @@ export function RefreshButton({ libraryId, refreshLibrary }: RefreshButtonProps)
onClick={handleRefresh}
disabled={isRefreshing}
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")} />
</Button>

View File

@@ -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",
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
)}
aria-label="Fermer"
aria-label={t("reader.controls.close")}
>
<X className="h-6 w-6" />
</button>
@@ -136,7 +136,7 @@ export const ControlButtons = ({
direction === "rtl" ? "right-4" : "left-4",
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" />
</button>
@@ -154,7 +154,7 @@ export const ControlButtons = ({
direction === "rtl" ? "left-4" : "right-4",
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
)}
aria-label="Page suivante"
aria-label={t("reader.controls.nextPage")}
>
<ChevronRight className="h-8 w-8" />
</button>

View File

@@ -5,6 +5,7 @@ import { Button } from "./button";
import { useToast } from "./use-toast";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
import { useState } from "react";
import { useTranslation } from "react-i18next";
interface MarkAsReadButtonProps {
bookId: string;
@@ -23,6 +24,7 @@ export function MarkAsReadButton({
}: MarkAsReadButtonProps) {
const { toast } = useToast();
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const handleMarkAsRead = async (e: React.MouseEvent) => {
e.stopPropagation(); // Empêcher la propagation au parent
@@ -38,19 +40,19 @@ export function MarkAsReadButton({
});
if (!response.ok) {
throw new Error("Erreur lors de la mise à jour");
throw new Error(t("books.actions.markAsRead.error.update"));
}
toast({
title: "Succès",
description: "Le tome a été marqué comme lu",
title: t("books.actions.markAsRead.success.title"),
description: t("books.actions.markAsRead.success.description"),
});
onSuccess?.();
} catch (error) {
console.error("Erreur lors de la mise à jour du progresseur de lecture:", error);
toast({
title: "Erreur",
description: "Impossible de marquer le tome comme lu",
title: t("books.actions.markAsRead.error.title"),
description: t("books.actions.markAsRead.error.description"),
variant: "destructive",
});
} finally {
@@ -65,7 +67,7 @@ export function MarkAsReadButton({
onClick={handleMarkAsRead}
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
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" />}
</Button>

View File

@@ -5,6 +5,7 @@ import { Button } from "./button";
import { useToast } from "./use-toast";
import { ClientOfflineBookService } from "@/lib/services/client-offlinebook.service";
import { useState } from "react";
import { useTranslation } from "react-i18next";
interface MarkAsUnreadButtonProps {
bookId: string;
@@ -15,6 +16,7 @@ interface MarkAsUnreadButtonProps {
export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnreadButtonProps) {
const { toast } = useToast();
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const handleMarkAsUnread = async (e: React.MouseEvent) => {
e.stopPropagation(); // Empêcher la propagation au parent
@@ -26,19 +28,19 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
});
if (!response.ok) {
throw new Error("Erreur lors de la mise à jour");
throw new Error(t("books.actions.markAsUnread.error.update"));
}
toast({
title: "Succès",
description: "Le tome a été marqué comme non lu",
title: t("books.actions.markAsUnread.success.title"),
description: t("books.actions.markAsUnread.success.description"),
});
onSuccess?.();
} catch (error) {
console.error("Erreur lors de la mise à jour du progresseur de lecture:", error);
toast({
title: "Erreur",
description: "Impossible de marquer le tome comme non lu",
title: t("books.actions.markAsUnread.error.title"),
description: t("books.actions.markAsUnread.error.description"),
variant: "destructive",
});
} finally {
@@ -53,7 +55,7 @@ export function MarkAsUnreadButton({ bookId, onSuccess, className }: MarkAsUnrea
onClick={handleMarkAsUnread}
className={`h-8 w-8 p-0 rounded-br-lg rounded-tl-lg ${className}`}
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" />}
</Button>