From 2289753b844407abd336a13625d0c87d3a59afbc Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 2 Mar 2025 06:31:41 +0100 Subject: [PATCH] feat(i18n): components pack translated --- src/components/debug/DebugInfo.tsx | 37 ++++++---- src/components/layout/Header.tsx | 8 ++- src/components/library/RefreshButton.tsx | 13 ++-- .../reader/components/ControlButtons.tsx | 6 +- src/components/ui/mark-as-read-button.tsx | 14 ++-- src/components/ui/mark-as-unread-button.tsx | 14 ++-- src/i18n/messages/en/common.json | 70 +++++++++++++++++-- src/i18n/messages/fr/common.json | 70 ++++++++++++++++++- 8 files changed, 190 insertions(+), 42 deletions(-) diff --git a/src/components/debug/DebugInfo.tsx b/src/components/debug/DebugInfo.tsx index b2a0c2e..d941402 100644 --- a/src/components/debug/DebugInfo.tsx +++ b/src/components/debug/DebugInfo.tsx @@ -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() { >
-

DEBUG

+

{t("debug.title")}

{!isMinimized && ( - {sortedLogs.length} entrée{sortedLogs.length > 1 ? "s" : ""} + {sortedLogs.length} {t("debug.entries", { count: sortedLogs.length })} )}
@@ -111,7 +113,7 @@ export function DebugInfo() { @@ -136,32 +138,40 @@ export function DebugInfo() { {!isMinimized && (
{sortedLogs.length === 0 ? ( -

Aucune requête enregistrée

+

{t("debug.noRequests")}

) : ( sortedLogs.map((log, index) => (
{log.fromCache && ( -
+
)} {log.mongoAccess && (
)} {log.pageRender && ( -
+
)} {!log.fromCache && !log.mongoAccess && !log.pageRender && ( -
+
)} @@ -170,7 +180,10 @@ export function DebugInfo() {
-
+
{formatTime(log.timestamp)}
@@ -188,7 +201,7 @@ export function DebugInfo() { {formatDuration(log.duration)}ms {log.mongoAccess && ( - + +{formatDuration(log.mongoAccess.duration)}ms )} diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index cbdcc71..1c74b42 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -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) {
diff --git a/src/components/library/RefreshButton.tsx b/src/components/library/RefreshButton.tsx index 60a424e..f747308 100644 --- a/src/components/library/RefreshButton.tsx +++ b/src/components/library/RefreshButton.tsx @@ -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")} > diff --git a/src/components/reader/components/ControlButtons.tsx b/src/components/reader/components/ControlButtons.tsx index 9fd4c2b..70942a5 100644 --- a/src/components/reader/components/ControlButtons.tsx +++ b/src/components/reader/components/ControlButtons.tsx @@ -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")} > @@ -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")} > @@ -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")} > diff --git a/src/components/ui/mark-as-read-button.tsx b/src/components/ui/mark-as-read-button.tsx index 49333c2..7260f7a 100644 --- a/src/components/ui/mark-as-read-button.tsx +++ b/src/components/ui/mark-as-read-button.tsx @@ -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 ? : } diff --git a/src/components/ui/mark-as-unread-button.tsx b/src/components/ui/mark-as-unread-button.tsx index fc577aa..941a602 100644 --- a/src/components/ui/mark-as-unread-button.tsx +++ b/src/components/ui/mark-as-unread-button.tsx @@ -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 ? : } diff --git a/src/i18n/messages/en/common.json b/src/i18n/messages/en/common.json index 7d693e7..86f31cb 100644 --- a/src/i18n/messages/en/common.json +++ b/src/i18n/messages/en/common.json @@ -146,7 +146,18 @@ "available": "Available", "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": { "empty": "No series available", @@ -180,7 +191,9 @@ "favorite": { "add": "Added to favorites", "remove": "Removed from favorites" - } + }, + "toggleSidebar": "Toggle sidebar", + "toggleTheme": "Toggle theme" } }, "books": { @@ -200,7 +213,33 @@ "showAll": "Show all", "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": { "page": { @@ -278,7 +317,30 @@ "fullscreen": { "enter": "Enter 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" } } } diff --git a/src/i18n/messages/fr/common.json b/src/i18n/messages/fr/common.json index 5f0ee50..c0a9fc4 100644 --- a/src/i18n/messages/fr/common.json +++ b/src/i18n/messages/fr/common.json @@ -146,7 +146,18 @@ "available": "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": { "empty": "Aucune série disponible", @@ -200,7 +211,33 @@ "showAll": "Afficher tout", "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": { "page": { @@ -278,7 +315,34 @@ "fullscreen": { "enter": "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" } } }