- {/* 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}"
+ }
}
}