diff --git a/src/components/library/LibraryGrid.tsx b/src/components/library/LibraryGrid.tsx index c00856f..0ec2246 100644 --- a/src/components/library/LibraryGrid.tsx +++ b/src/components/library/LibraryGrid.tsx @@ -1,35 +1,38 @@ import { Book } from "lucide-react"; import { Cover } from "@/components/ui/cover"; import { KomgaLibrary } from "@/types/komga"; +import { useTranslate } from "@/hooks/useTranslate"; interface LibraryGridProps { libraries: KomgaLibrary[]; onLibraryClick?: (library: KomgaLibrary) => void; } -// Fonction utilitaire pour formater la date de manière sécurisée -const formatDate = (dateString: string): string => { +// Utility function to format date safely +const formatDate = (dateString: string, locale: string): string => { try { const date = new Date(dateString); if (isNaN(date.getTime())) { - return "Date non disponible"; + return "Date unavailable"; } - return new Intl.DateTimeFormat("fr-FR", { + return new Intl.DateTimeFormat(locale, { year: "numeric", month: "long", day: "numeric", }).format(date); } catch (error) { - console.error("Erreur lors du formatage de la date:", error); - return "Date non disponible"; + console.error("Error formatting date:", error); + return "Date unavailable"; } }; export function LibraryGrid({ libraries, onLibraryClick }: LibraryGridProps) { + const { t } = useTranslate(); + if (!libraries.length) { return (
-

Aucune bibliothèque disponible

+

{t("library.empty")}

); } @@ -49,18 +52,20 @@ interface LibraryCardProps { } function LibraryCard({ library, onClick }: LibraryCardProps) { + const { t, i18n } = useTranslate(); + return (
- {/* Indicateur de chargement */} + {/* Loading indicator */} {isChangingPage && (
- Chargement... + {t("sidebar.libraries.loading")}
)} - {/* Grille avec animation de transition */} + {/* Grid with transition animation */}

- Page {currentPage} sur {totalPages} + {t("series.display.page", { current: currentPage, total: totalPages })}

{ +export const SearchInput = ({ placeholder }: SearchInputProps) => { const router = useRouter(); const searchParams = useSearchParams(); const [isPending, startTransition] = useTransition(); @@ -42,7 +42,7 @@ export const SearchInput = ({ placeholder = "Rechercher une série..." }: Search className="pl-9" defaultValue={searchParams.get("search") ?? ""} onChange={(e) => handleSearch(e.target.value)} - aria-label="Rechercher une série" + aria-label={placeholder} /> {isPending && (
diff --git a/src/components/library/SeriesGrid.tsx b/src/components/library/SeriesGrid.tsx index 9bd04ff..bc4b171 100644 --- a/src/components/library/SeriesGrid.tsx +++ b/src/components/library/SeriesGrid.tsx @@ -4,47 +4,52 @@ import { KomgaSeries } from "@/types/komga"; import { useRouter } from "next/navigation"; import { cn } from "@/lib/utils"; import { Cover } from "@/components/ui/cover"; +import { useTranslate } from "@/hooks/useTranslate"; interface SeriesGridProps { series: KomgaSeries[]; } -// Fonction utilitaire pour obtenir les informations de statut de lecture -const getReadingStatusInfo = (series: KomgaSeries) => { +// Utility function to get reading status info +const getReadingStatusInfo = (series: KomgaSeries, t: (key: string, options?: any) => string) => { if (series.booksCount === 0) { return { - label: "Pas de tomes", + label: t("series.status.noBooks"), className: "bg-yellow-500/10 text-yellow-500", }; } if (series.booksCount === series.booksReadCount) { return { - label: "Lu", + label: t("series.status.read"), className: "bg-green-500/10 text-green-500", }; } if (series.booksReadCount > 0) { return { - label: `${series.booksReadCount}/${series.booksCount}`, + label: t("series.status.progress", { + read: series.booksReadCount, + total: series.booksCount, + }), className: "bg-blue-500/10 text-blue-500", }; } return { - label: "Non lu", + label: t("series.status.unread"), className: "bg-yellow-500/10 text-yellow-500", }; }; export function SeriesGrid({ series }: SeriesGridProps) { const router = useRouter(); + const { t } = useTranslate(); if (!series.length) { return (
-

Aucune série disponible

+

{t("series.empty")}

); } @@ -63,7 +68,7 @@ export function SeriesGrid({ series }: SeriesGridProps) { - {getReadingStatusInfo(series).label} + {getReadingStatusInfo(series, t).label} - {series.booksCount} tome{series.booksCount > 1 ? "s" : ""} + {t("series.books", { count: series.booksCount })}
diff --git a/src/components/providers/I18nProvider.tsx b/src/components/providers/I18nProvider.tsx index a4a3a10..ebd9df1 100644 --- a/src/components/providers/I18nProvider.tsx +++ b/src/components/providers/I18nProvider.tsx @@ -9,7 +9,7 @@ export function I18nProvider({ children, locale }: PropsWithChildren<{ locale: s // Synchroniser la langue avec celle du cookie côté client if (typeof window !== "undefined") { const localeCookie = document.cookie.split("; ").find((row) => row.startsWith("NEXT_LOCALE=")); - console.log(localeCookie); + if (localeCookie) { const locale = localeCookie.split("=")[1]; if (i18n.language !== locale) { diff --git a/src/hooks/useTranslate.ts b/src/hooks/useTranslate.ts index 319c9a1..ddba140 100644 --- a/src/hooks/useTranslate.ts +++ b/src/hooks/useTranslate.ts @@ -1,12 +1,32 @@ import { useTranslation } from "react-i18next"; export function useTranslate() { - const { t, i18n } = useTranslation("common"); + const { t: tBase, i18n } = useTranslation("common"); const changeLanguage = (lang: string) => { i18n.changeLanguage(lang); }; + const t = (translationKey: string, values?: { [key: string]: number | string }) => { + if (values && Object.keys(values).length > 0) { + const translatedText = tBase(translationKey, values); + + const placeholderRegex = new RegExp(`(\{${Object.keys(values).join("}|{")}\})`, "g"); + + const parts = translatedText.split(placeholderRegex); + return parts + .map((part) => { + const key = part.replace(/[{}]/g, ""); + if (values[key] !== undefined) { + return values[key]; + } + return part; + }) + .join(""); + } + return tBase(translationKey, values); + }; + return { t, i18n, diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index 1d0de25..b4d2904 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -36,6 +36,10 @@ if (!i18n.isInitialized) { maxAge: 365 * 24 * 60 * 60, // 1 an }, }, + react: { + transSupportBasicHtmlNodes: true, // Permet l'utilisation de balises HTML de base + transKeepBasicHtmlNodesFor: ["br", "strong", "i", "p", "span"], // Liste des balises autorisées + }, }); } diff --git a/src/i18n/messages/en/common.json b/src/i18n/messages/en/common.json index b1105e0..6c7b541 100644 --- a/src/i18n/messages/en/common.json +++ b/src/i18n/messages/en/common.json @@ -111,5 +111,35 @@ "cleared": "Server cache cleared successfully" } } + }, + "library": { + "empty": "No libraries available", + "coverAlt": "Cover of {name}", + "status": { + "available": "Available", + "unavailable": "Unavailable" + }, + "lastUpdated": "Last updated: {date}" + }, + "series": { + "empty": "No series available", + "coverAlt": "Cover of {title}", + "status": { + "noBooks": "No books", + "read": "Read", + "unread": "Unread", + "progress": "{read}/{total}" + }, + "books": "{count} book | {count} books", + "filters": { + "title": "Filters", + "showAll": "Show all", + "unread": "Unread", + "search": "Search series..." + }, + "display": { + "showing": "Showing series {start} to {end} of {total}", + "page": "Page {current} of {total}" + } } } diff --git a/src/i18n/messages/fr/common.json b/src/i18n/messages/fr/common.json index 96af925..57923b5 100644 --- a/src/i18n/messages/fr/common.json +++ b/src/i18n/messages/fr/common.json @@ -111,5 +111,35 @@ "cleared": "Cache serveur supprimé avec succès" } } + }, + "library": { + "empty": "Aucune bibliothèque disponible", + "coverAlt": "Couverture de {name}", + "status": { + "available": "Disponible", + "unavailable": "Non disponible" + }, + "lastUpdated": "Dernière mise à jour : {date}" + }, + "series": { + "empty": "Aucune série disponible", + "coverAlt": "Couverture de {title}", + "status": { + "noBooks": "Pas de tomes", + "read": "Lu", + "unread": "Non lu", + "progress": "{read}/{total}" + }, + "books": "{count} tome | {count} tomes", + "filters": { + "title": "Filtres", + "showAll": "Afficher tout", + "unread": "À lire", + "search": "Rechercher une série..." + }, + "display": { + "showing": "Affichage des séries {start} à {end} sur {total}", + "page": "Page {current} sur {total}" + } } }