- {languages.map((language) => (
+ {SUPPORTED_LANGUAGES.map((language) => (
{showSortMenu && (
diff --git a/frontend/src/features/settings/tabs/GeneralTab.tsx b/frontend/src/features/settings/tabs/GeneralTab.tsx
index 2760fe5f..d5e6b4d3 100644
--- a/frontend/src/features/settings/tabs/GeneralTab.tsx
+++ b/frontend/src/features/settings/tabs/GeneralTab.tsx
@@ -4,6 +4,7 @@ import { Button, Card, Input, Loading } from '../../../components/common';
import { useTranslation } from 'react-i18next';
import type { GeneralSettings } from '../hooks/useSettingsState';
import { MAX_FILES_PER_UPLOAD_LIMIT } from '../hooks/useSettingsState';
+import { SUPPORTED_LANGUAGES } from "../../../components/common/LanguageSelector.tsx";
interface GeneralTabProps {
generalSettings: GeneralSettings;
@@ -244,11 +245,9 @@ export const GeneralTab: React.FC
= ({
onChange={(e) => setGeneralSettings(prev => ({ ...prev, default_language: e.target.value }))}
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
>
-
-
-
-
-
+ {SUPPORTED_LANGUAGES.map(lang => (
+
+ ))}
{t('settings.general.defaultLanguageHelp')}
diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts
index 0e2ac578..9a8ed8f1 100644
--- a/frontend/src/hooks/index.ts
+++ b/frontend/src/hooks/index.ts
@@ -1,6 +1,5 @@
export * from './useSessionTimeout';
export * from './useOnClickOutside';
export * from './useLocalizedDate';
-export * from './useLocalizedTimeAgo';
export * from './usePermission';
export * from './usePublicSettings';
\ No newline at end of file
diff --git a/frontend/src/hooks/useLocalizedDate.ts b/frontend/src/hooks/useLocalizedDate.ts
index 9ad2b3f0..5a8c6a5f 100644
--- a/frontend/src/hooks/useLocalizedDate.ts
+++ b/frontend/src/hooks/useLocalizedDate.ts
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next';
import { format as dateFnsFormat, formatDistanceToNow as dateFnsFormatDistanceToNow } from 'date-fns';
-import { de, enUS, ptBR } from 'date-fns/locale';
+import { de, enUS, ptBR, fr } from 'date-fns/locale';
import { usePublicSettings } from './usePublicSettings';
// Convert old date format strings to new date-fns format
@@ -17,9 +17,17 @@ export const useLocalizedDate = () => {
const { data: settings } = usePublicSettings();
const getLocale = () => {
- if (i18n.language === 'de') return de;
- if (i18n.language === 'pt' || i18n.language === 'pt-BR') return ptBR;
- return enUS;
+ switch (i18n.language) {
+ case 'de':
+ return de;
+ case 'pt':
+ case 'pt-BR':
+ return ptBR;
+ case 'fr':
+ return fr;
+ default:
+ return enUS;
+ }
};
const format = (date: Date | string, formatStr?: string) => {
diff --git a/frontend/src/hooks/useLocalizedTimeAgo.ts b/frontend/src/hooks/useLocalizedTimeAgo.ts
deleted file mode 100644
index 7e798724..00000000
--- a/frontend/src/hooks/useLocalizedTimeAgo.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useTranslation } from 'react-i18next';
-
-export const useLocalizedTimeAgo = () => {
- const { i18n } = useTranslation();
-
- const formatTimeAgo = (date: Date | string): string => {
- const dateObj = typeof date === 'string' ? new Date(date) : date;
- const now = new Date();
- const seconds = Math.floor((now.getTime() - dateObj.getTime()) / 1000);
-
- const isGerman = i18n.language === 'de';
-
- // Less than a minute
- if (seconds < 60) {
- return isGerman ? 'gerade eben' : 'just now';
- }
-
- // Minutes
- const minutes = Math.floor(seconds / 60);
- if (minutes < 60) {
- if (minutes === 1) {
- return isGerman ? 'vor 1 Minute' : '1 minute ago';
- }
- return isGerman ? `vor ${minutes} Minuten` : `${minutes} minutes ago`;
- }
-
- // Hours
- const hours = Math.floor(minutes / 60);
- if (hours < 24) {
- if (hours === 1) {
- return isGerman ? 'vor 1 Stunde' : '1 hour ago';
- }
- return isGerman ? `vor ${hours} Stunden` : `${hours} hours ago`;
- }
-
- // Days
- const days = Math.floor(hours / 24);
- if (days < 7) {
- if (days === 1) {
- return isGerman ? 'vor 1 Tag' : '1 day ago';
- }
- return isGerman ? `vor ${days} Tagen` : `${days} days ago`;
- }
-
- // Weeks
- const weeks = Math.floor(days / 7);
- if (weeks < 4) {
- if (weeks === 1) {
- return isGerman ? 'vor 1 Woche' : '1 week ago';
- }
- return isGerman ? `vor ${weeks} Wochen` : `${weeks} weeks ago`;
- }
-
- // Months
- const months = Math.floor(days / 30);
- if (months < 12) {
- if (months === 1) {
- return isGerman ? 'vor 1 Monat' : '1 month ago';
- }
- return isGerman ? `vor ${months} Monaten` : `${months} months ago`;
- }
-
- // Years
- const years = Math.floor(days / 365);
- if (years === 1) {
- return isGerman ? 'vor 1 Jahr' : '1 year ago';
- }
- return isGerman ? `vor ${years} Jahren` : `${years} years ago`;
- };
-
- return { formatTimeAgo };
-};
\ No newline at end of file
diff --git a/frontend/src/i18n/config.ts b/frontend/src/i18n/config.ts
index d1aca46e..b9087af5 100644
--- a/frontend/src/i18n/config.ts
+++ b/frontend/src/i18n/config.ts
@@ -3,11 +3,14 @@ import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';
-import enTranslations from './locales/en.json';
-import deTranslations from './locales/de.json';
-import ruTranslations from './locales/ru.json';
-import ptTranslations from './locales/pt.json';
-import nlTranslations from './locales/nl.json';
+const localeFiles = import.meta.glob>('./locales/*.json', { eager: true, import: 'default' });
+
+const resources = Object.fromEntries(
+ Object.entries(localeFiles).map(([path, translations]) => {
+ const lang = path.match(/\/(\w+)\.json$/)?.[1];
+ return [lang, { translation: translations }];
+ })
+);
i18n
.use(HttpBackend)
@@ -17,23 +20,7 @@ i18n
fallbackLng: 'en',
debug: false,
- resources: {
- en: {
- translation: enTranslations,
- },
- de: {
- translation: deTranslations,
- },
- ru: {
- translation: ruTranslations,
- },
- pt: {
- translation: ptTranslations,
- },
- nl: {
- translation: nlTranslations,
- },
- },
+ resources,
interpolation: {
escapeValue: false,
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
new file mode 100644
index 00000000..c57e9751
--- /dev/null
+++ b/frontend/src/i18n/locales/fr.json
@@ -0,0 +1,2302 @@
+{
+ "userManagement": {
+ "title": "Gestion des utilisateurs",
+ "subtitle": "Gérer les administrateurs et les invitations",
+ "loading": "Chargement des utilisateurs...",
+ "loadError": "Échec du chargement des utilisateurs. Veuillez réessayer.",
+ "inviteUser": "Inviter un utilisateur",
+ "createInvitation": "Créer une invitation",
+ "email": "Adresse e-mail",
+ "emailPlaceholder": "utilisateur@exemple.com",
+ "role": "Rôle",
+ "selectRole": "Sélectionner un rôle",
+ "sendInvitation": "Envoyer l'invitation",
+ "editUser": "Modifier l'utilisateur",
+ "editingUser": "Modification de l'utilisateur",
+ "saveChanges": "Enregistrer les modifications",
+ "deactivateUser": "Désactiver l'utilisateur",
+ "cancelInvitation": "Annuler l'invitation",
+ "invitationSent": "Invitation envoyée avec succès",
+ "invitationError": "Échec de l'envoi de l'invitation",
+ "invitationCancelled": "Invitation annulée",
+ "cancelInvitationError": "Échec de l'annulation de l'invitation",
+ "userUpdated": "Utilisateur mis à jour avec succès",
+ "updateUserError": "Échec de la mise à jour de l'utilisateur",
+ "userDeactivated": "Utilisateur désactivé avec succès",
+ "deactivateUserError": "Échec de la désactivation de l'utilisateur",
+ "noRole": "Aucun rôle",
+ "neverLoggedIn": "Jamais connecté",
+ "expired": "Expiré",
+ "deactivate": "Désactiver",
+ "cancel": "Annuler",
+ "tabs": {
+ "users": "Utilisateurs",
+ "invitations": "Invitations"
+ },
+ "stats": {
+ "totalUsers": "Total Utilisateurs",
+ "activeUsers": "Utilisateurs actifs",
+ "pendingInvitations": "Invitations en attente",
+ "inactiveUsers": "Utilisateurs inactifs"
+ },
+ "status": {
+ "active": "Actif",
+ "inactive": "Inactif"
+ },
+ "table": {
+ "user": "Utilisateur",
+ "email": "E-mail",
+ "role": "Rôle",
+ "status": "Statut",
+ "lastLogin": "Dernière connexion",
+ "actions": "Actions",
+ "invitedBy": "Invité par",
+ "expires": "Expire le"
+ },
+ "validation": {
+ "emailRequired": "L'e-mail est requis",
+ "emailInvalid": "Format d'e-mail invalide",
+ "roleRequired": "Le rôle est requis"
+ },
+ "searchUsersPlaceholder": "Rechercher des utilisateurs...",
+ "searchInvitationsPlaceholder": "Rechercher des invitations...",
+ "noUsers": "Aucun utilisateur trouvé",
+ "noUsersFound": "Aucun utilisateur ne correspond à votre recherche",
+ "noInvitations": "Aucune invitation en attente",
+ "noInvitationsFound": "Aucune invitation ne correspond à votre recherche",
+ "confirmDeactivate": {
+ "title": "Désactiver l'utilisateur",
+ "message": "Êtes-vous sûr de vouloir désactiver {{name}} ? Il ne pourra plus se connecter."
+ },
+ "confirmCancelInvitation": {
+ "title": "Annuler l'invitation",
+ "message": "Êtes-vous sûr de vouloir annuler l'invitation pour {{email}} ?"
+ }
+ },
+ "common": {
+ "loading": "Chargement...",
+ "error": "Erreur",
+ "save": "Enregistrer",
+ "cancel": "Annuler",
+ "delete": "Supprimer",
+ "edit": "Modifier",
+ "add": "Ajouter",
+ "search": "Rechercher",
+ "filter": "Filtrer",
+ "sortBy": "Trier par",
+ "yes": "Oui",
+ "no": "Non",
+ "back": "Retour",
+ "next": "Suivant",
+ "previous": "Précédent",
+ "close": "Fermer",
+ "logout": "Déconnexion",
+ "menu": "Menu",
+ "change": "Modifier",
+ "remove": "Retirer",
+ "download": "Télécharger",
+ "downloadAll": "Tout télécharger",
+ "uploading": "Téléversement...",
+ "uploaded": "Téléversé",
+ "photo": "photo",
+ "photos": "photos",
+ "video": "vidéo",
+ "videos": "vidéos",
+ "media": "média",
+ "restore": "Restaurer",
+ "actions": "Actions",
+ "refresh": "Actualiser",
+ "preview": "Aperçu",
+ "processing": "Traitement...",
+ "upload": "Téléverser",
+ "days": "jours",
+ "customize": "Personnaliser",
+ "hide": "Masquer",
+ "unknown": "Inconnu",
+ "notSet": "Non défini",
+ "of": "sur",
+ "up": "Haut",
+ "select": "Sélectionner",
+ "selected": "Sélectionné",
+ "chunk": "Segment",
+ "optional": "optionnel"
+ },
+ "upload": {
+ "photoCategory": "Catégorie de photo",
+ "noCategory": "Aucune catégorie",
+ "eventSpecific": "(Spécifique à l'événement)",
+ "clickToUpload": "Cliquez pour téléverser ou glissez-déposez",
+ "fileRequirements": "JPEG, PNG ou WebP (max 50 Mo par fichier, {{limit}} fichiers par envoi)",
+ "fileRequirementsMedia": "Images JPEG, PNG ou WebP, plus vidéos MP4/MOV/WEBM (max 50 Mo par fichier, {{limit}} fichiers par envoi)",
+ "unsupportedFiles": "Certains fichiers ont été ignorés car le format n'est pas supporté (utilisez JPEG/PNG/WebP/MP4/MOV/WEBM).",
+ "selectedFiles": "Fichiers sélectionnés",
+ "uploading": "Téléversement...",
+ "uploadComplete": "Téléversement terminé !",
+ "uploadFailed": "Échec du téléversement",
+ "someFilesFailed": "Certains fichiers n'ont pas pu être téléversés",
+ "uploadPhotos": "Téléverser des photos",
+ "uploadMedia": "Téléverser photos et vidéos",
+ "importExternal": "Importer depuis un dossier externe",
+ "externalImportInfo": "Toutes les images du dossier sélectionné seront importées.",
+ "selectExternalFolder": "Sélectionnez le dossier externe sous /external-media",
+ "importFromSelectedFolder": "Importer depuis le dossier sélectionné",
+ "maxFilesReached": "Maximum de {{limit}} fichiers autorisés",
+ "someFilesSkipped": "Seuls {{allowed}} fichiers supplémentaires peuvent être ajoutés (limite de {{limit}})",
+ "tooManyFiles": "Un maximum de {{limit}} fichiers peuvent être téléversés à la fois",
+ "limitInfo": "{{selected}} sur {{limit}} fichiers sélectionnés ({{remaining}} restants)",
+ "limitReached": "Limite de téléversement atteinte ({{limit}} fichiers par lot)",
+ "uploadingChunks": "Téléversement de {{count}} fichiers en {{total}} lots...",
+ "mediaCategory": "Catégorie de média",
+ "uploadAction": "Téléverser {{count}} fichiers"
+ },
+ "navigation": {
+ "dashboard": "Tableau de bord",
+ "events": "Événements",
+ "archives": "Archives",
+ "settings": "Paramètres",
+ "eventTypes": "Types d'événements",
+ "branding": "Identité visuelle",
+ "analytics": "Analytique",
+ "emailSettings": "Paramètres e-mail",
+ "backup": "Sauvegarde et Restauration",
+ "cmsPages": "Pages CMS",
+ "users": "Utilisateurs"
+ },
+ "archives": {
+ "title": "Archives",
+ "subtitle": "Gérer les galeries photo archivées",
+ "loadingArchives": "Chargement des archives...",
+ "totalArchives": "Total des archives",
+ "storageUsed": "Espace disque utilisé",
+ "totalPhotos": "Total des photos",
+ "avgArchiveSize": "Taille moyenne d'archive",
+ "searchPlaceholder": "Rechercher des archives...",
+ "allTypes": "Tous les types",
+ "wedding": "Mariage",
+ "birthday": "Anniversaire",
+ "corporate": "Entreprise",
+ "other": "Autre",
+ "sortByDate": "Trier par date",
+ "sortByName": "Trier par nom",
+ "sortBySize": "Trier par taille",
+ "tableHeaders": {
+ "event": "Événement",
+ "type": "Type",
+ "archivedDate": "Date d'archivage",
+ "size": "Taille",
+ "photos": "Photos",
+ "actions": "Actions"
+ },
+ "noArchivesFound": "Aucune archive trouvée",
+ "eventDateNA": "Date de l'événement : N/A",
+ "processing": "Traitement...",
+ "download": "Télécharger",
+ "restore": "Restaurer",
+ "delete": "Supprimer",
+ "showing": "Affichage de {{from}} à {{to}} sur {{total}} archives",
+ "page": "Page {{current}} sur {{total}}",
+ "storageManagement": "Gestion du stockage",
+ "storageInfo": "Les archives sont conservées de façon permanente sauf suppression manuelle. Envisagez de mettre en œuvre une politique de rétention pour gérer les coûts de stockage.",
+ "confirmRestore": "Êtes-vous sûr de vouloir restaurer cette archive ? L'événement redeviendra actif.",
+ "confirmDelete": "Êtes-vous sûr de vouloir supprimer définitivement cette archive ? Cette action est irréversible.",
+ "restoreSuccess": "Archive restaurée avec succès",
+ "deleteSuccess": "Archive supprimée définitivement"
+ },
+ "auth": {
+ "login": "Connexion",
+ "password": "Mot de passe",
+ "enterPassword": "Entrer le mot de passe de la galerie",
+ "passwordPlaceholder": "Saisissez le mot de passe de la galerie",
+ "invalidPassword": "Mot de passe invalide",
+ "wrongPassword": "Mot de passe incorrect. Veuillez vérifier et réessayer.",
+ "tooManyAttempts": "Trop de tentatives de connexion échouées. Veuillez réessayer plus tard.",
+ "sessionExpired": "Session expirée",
+ "pleaseEnterPassword": "Veuillez entrer un mot de passe",
+ "passwordHint": "Le mot de passe a été fourni par l'organisateur de l'événement. Contactez-le si vous ne l'avez pas."
+ },
+ "gallery": {
+ "title": "Galerie Photo",
+ "welcomeMessage": "Message de bienvenue",
+ "expiresOn": "Expire le",
+ "expires": "Expire",
+ "expired": "Expirée",
+ "daysRemaining": "{{days}} jours restants",
+ "dayRemaining": "1 jour restant",
+ "hoursRemaining": "{{hours}} heures restantes",
+ "expiredMessage": "Cette galerie a expiré le {{date}}",
+ "contactOrganizer": "Veuillez contacter l'organisateur si vous avez besoin d'accéder à ces photos",
+ "searchPhotos": "Rechercher par nom de fichier...",
+ "sortByDate": "Trier par date",
+ "sortByName": "Trier par nom",
+ "sortBySize": "Trier par taille",
+ "allPhotos": "Toutes les photos",
+ "filter": "Filtrer",
+ "feedbackFilter": "Filtre d'avis",
+ "all": "Tout",
+ "liked": "Aimé",
+ "favorited": "Favori",
+ "favorites": "Favoris",
+ "downloadSelected": "Télécharger la sélection",
+ "shareGallery": "Partager la galerie",
+ "needHelp": "Besoin d'aide ? Contactez-nous à",
+ "noPhotosFound": "Aucune photo trouvée",
+ "failedToLoad": "Échec du chargement des photos",
+ "tryAgain": "Réessayer",
+ "loading": "Chargement de la galerie...",
+ "expiredOn": "Cette galerie a expiré le {{date}}.",
+ "expiresIn": "La galerie expire dans {{count}} jour",
+ "expiresIn_plural": "La galerie expire dans {{count}} jours",
+ "downloadBefore": "Téléchargez vos photos avant qu'elles ne soient plus disponibles.",
+ "publicGalleryTitle": "Cette galerie est accessible publiquement",
+ "publicGallerySubtitle": "Chargement des photos...",
+ "viewGallery": "Voir la galerie",
+ "downloadAll": "Tout télécharger",
+ "downloading": "Téléchargement de {{count}} photo...",
+ "downloading_plural": "Téléchargement de {{count}} photos...",
+ "downloadedPhotos": "{{count}} photo téléchargée !",
+ "downloadedPhotos_plural": "{{count}} photos téléchargées !",
+ "downloadError": "Échec du téléchargement de certaines photos",
+ "selectPhotos": "Sélectionner des photos",
+ "cancelSelection": "Annuler la sélection",
+ "photosSelected": "{{count}} sélectionnée(s)",
+ "selectAll": "Tout sélectionner",
+ "deselectAll": "Tout désélectionner",
+ "downloadSelected": "Télécharger les {{count}} sélectionnés",
+ "deleteSelected": "Supprimer la sélection",
+ "photosCount": "{{count}} photo",
+ "photosCount_plural": "{{count}} photos",
+ "searchByFilename": "Rechercher par nom de fichier...",
+ "uncategorized": "Non catégorisé",
+ "sortAscending": "Tri croissant",
+ "sortDescending": "Tri décroissant",
+ "remaining": "restant",
+ "selectPhotosHint": "Astuce : Utilisez Ctrl+Clic (Cmd+Clic sur Mac) pour sélectionner rapidement plusieurs photos",
+ "filters": "Filtres",
+ "openFilters": "Ouvrir les filtres",
+ "toggleSidebar": "Basculer la barre latérale",
+ "toggleMenu": "Basculer le menu",
+ "allCategories": "Toutes les catégories",
+ "categories": "Catégories",
+ "mediaType": "Média",
+ "allMedia": "Tous les médias",
+ "photosOnly": "Photos uniquement",
+ "videosOnly": "Vidéos uniquement",
+ "download": "Télécharger",
+ "noMedia": "Aucun média téléversé pour le moment",
+ "searchPlaceholder": "Rechercher des photos...",
+ "sortBy": "Trier par",
+ "sortByDate": "Trier par date",
+ "sortByCaptureDate": "Trier par date de capture",
+ "sortByName": "Trier par nom",
+ "sortBySize": "Trier par taille",
+ "sortByRating": "Trier par note",
+ "photoGallery": "Galerie Photo",
+ "photos": "Photos",
+ "allRightsReserved": "Tous droits réservés",
+ "searchMemories": "Rechercher des souvenirs...",
+ "thankYou": "Merci",
+ "thankYouMessage": "D'avoir fait partie de notre histoire et d'avoir rendu cette journée inoubliable.",
+ "feedback": {
+ "title": "Avis",
+ "shareThoughts": "Partagez votre avis sur \"{{name}}\"",
+ "rateThisMoment": "Notez ce moment",
+ "comments": "Commentaires",
+ "noComments": "Pas encore de commentaires. Soyez le premier !",
+ "yourName": "Votre nom",
+ "yourEmail": "Votre e-mail",
+ "writeLovelyNote": "Écrivez un petit mot...",
+ "postComment": "Publier le commentaire",
+ "anonymous": "Anonyme"
+ }
+ },
+ "categories": {
+ "title": "Catégories de photos",
+ "global": "Catégories globales",
+ "eventSpecific": "Catégories spécifiques à l'événement",
+ "addCategory": "Ajouter une catégorie",
+ "organizationInfo": "Organisez vos photos en catégories. Les catégories aident les invités à naviguer et à trouver des types de photos spécifiques.",
+ "eventSpecificCategories": "Catégories spécifiques à l'événement",
+ "noEventSpecificCategories": "Aucune catégorie spécifique. Les catégories globales sont disponibles par défaut.",
+ "globalCategoriesAlwaysAvailable": "Catégories globales (toujours disponibles) :",
+ "deleteCategoryTitle": "Supprimer la catégorie",
+ "categoryCreatedSuccess": "Catégorie créée avec succès",
+ "categoryDeletedSuccess": "Catégorie supprimée avec succès",
+ "failedToCreateCategory": "Échec de la création de la catégorie",
+ "failedToDeleteCategory": "Échec de la suppression de la catégorie",
+ "categoryName": "Nom de la catégorie",
+ "noCategory": "Aucune catégorie",
+ "noCategoriesYet": "Pas encore de catégories. Créez votre première catégorie pour organiser les photos.",
+ "deleteConfirm": "Êtes-vous sûr de vouloir supprimer \"{{name}}\" ?",
+ "cannotDelete": "Impossible de supprimer une catégorie contenant des photos. Veuillez réassigner les photos d'abord.",
+ "setCoverPhoto": "Définir comme photo de couverture",
+ "removeCoverPhoto": "Retirer la photo de couverture",
+ "coverPhotoSet": "Photo de couverture définie avec succès",
+ "coverPhotoRemoved": "Photo de couverture retirée",
+ "failedToSetCoverPhoto": "Échec de la définition de la photo de couverture",
+ "categoryHeroHint": "Si aucune photo de couverture n'est définie pour une catégorie, la photo principale par défaut sera utilisée."
+ },
+ "events": {
+ "title": "Événements",
+ "create": "Créer",
+ "createEvent": "Créer un événement",
+ "totalViews": "Total des vues",
+ "totalDownloads": "Total des téléchargements",
+ "uniqueVisitors": "Visiteurs uniques",
+ "createNewEvent": "Créer un nouvel événement",
+ "setupNewGallery": "Configurez une nouvelle galerie photo pour votre événement",
+ "createNewEventSubtitle": "Configurez une nouvelle galerie photo pour votre événement",
+ "eventNamePlaceholder": "ex: Mariage de Julie & Thomas",
+ "welcomeMessageOptional": "Message de bienvenue (Optionnel)",
+ "welcomeMessagePlaceholder": "Bienvenue à notre journée spéciale ! N'hésitez pas à télécharger et partager ces souvenirs...",
+ "hostEmailPlaceholder": "client@exemple.com",
+ "adminEmailPlaceholder": "admin@exemple.com",
+ "securityAndAccess": "Sécurité et Accès",
+ "accessAndSecurity": "Accès et Sécurité",
+ "enterPassword": "Entrer le mot de passe",
+ "passwordPlaceholder": "Entrer un mot de passe sécurisé",
+ "confirmPasswordPlaceholder": "Confirmer le mot de passe",
+ "galleryExpiresOn": "La galerie expirera le {{date}}",
+ "guestsWillReceiveWarning": "Les invités recevront un e-mail d'avertissement 7 jours avant l'expiration.",
+ "types": {
+ "wedding": "Mariage",
+ "birthday": "Anniversaire",
+ "corporate": "Entreprise",
+ "other": "Autre"
+ },
+ "themes": {
+ "default": "Par défaut",
+ "oceanBlue": "Bleu Océan",
+ "royalPurple": "Violet Royal",
+ "roseGold": "Or Rose",
+ "sunsetAmber": "Ambre Coucher de Soleil"
+ },
+ "eventDetails": "Détails de l'événement",
+ "eventName": "Nom de l'événement",
+ "eventType": "Type d'événement",
+ "eventDate": "Date de l'événement",
+ "hostEmail": "E-mail du client",
+ "hostName": "Nom du client",
+ "hostNamePlaceholder": "Jean Dupont",
+ "adminEmail": "E-mail de l'admin",
+ "adminNotificationEmail": "E-mail de notification admin",
+ "expirationDate": "Date d'expiration",
+ "active": "Actif",
+ "archived": "Archivé",
+ "photoCount": "{{count}} photos",
+ "totalSize": "Taille totale",
+ "shareLink": "Lien de partage",
+ "copyLink": "Copier le lien",
+ "linkCopied": "Lien copié !",
+ "viewGallery": "Voir la galerie",
+ "uploadPhotos": "Téléverser des photos",
+ "archiveEvent": "Archiver l'événement",
+ "archiveConfirm": "Êtes-vous sûr de vouloir archiver cet événement ? Cette action est irréversible.",
+ "extendExpiration": "Prolonger de {{days}} jours",
+ "backToEvents": "Retour aux événements",
+ "loadingEventDetails": "Chargement des détails...",
+ "saveChanges": "Enregistrer les modifications",
+ "eventExpired": "Cet événement a expiré",
+ "eventExpiresIn": "Cet événement expire dans {{days}} jours",
+ "guestsNoAccess": "Les invités ne peuvent plus accéder à la galerie. Envisagez d'archiver cet événement.",
+ "warningEmailsSent": "Des e-mails d'avertissement ont été envoyés au client.",
+ "overview": "Aperçu",
+ "photos": "Photos",
+ "categories": "Catégories",
+ "eventInformation": "Informations sur l'événement",
+ "sourceMode": "Mode source",
+ "sourceModeManaged": "Géré (téléverser sur PicPeak)",
+ "sourceModeReference": "Référence dossier externe",
+ "sourceModeHelp": "Utilisez le mode géré pour les téléversements directs ou pointez vers un dossier /external-media monté pour un stockage local.",
+ "externalFolder": "Dossier externe",
+ "externalFolderHint": "Ces dossiers sont lus depuis le montage /external-media dans votre conteneur ou hôte.",
+ "externalFolderRequired": "Veuillez sélectionner un dossier externe avant d'enregistrer.",
+ "welcomeMessage": "Message de bienvenue",
+ "noWelcomeMessage": "Aucun message de bienvenue défini",
+ "created": "Créé",
+ "expires": "Expire le",
+ "shareWithGuests": "Partagez ce lien avec vos invités. Ils auront besoin du mot de passe pour accéder à la galerie.",
+ "shareWithGuestsPublic": "Partagez ce lien avec vos invités. Aucun mot de passe n'est requis pour cette galerie.",
+ "resetGalleryPassword": "Réinitialiser le mot de passe",
+ "resendCreationEmail": "Renvoyer l'e-mail de création",
+ "creationEmailResent": "L'e-mail de création a été mis en file d'attente",
+ "failedToResendEmail": "Échec du renvoi de l'e-mail de création",
+ "photoStatistics": "Statistiques des photos",
+ "totalPhotos": "Total des photos",
+ "managePhotos": "Gérer les photos",
+ "actions": "Actions",
+ "archivingInfo": "L'archivage créera un fichier ZIP de toutes les photos et supprimera l'accès public à la galerie.",
+ "statistics": "Statistiques",
+ "views": "Vues",
+ "downloads": "Téléchargements",
+ "noStatistics": "Aucune statistique disponible pour le moment",
+ "archiveStatus": "Statut de l'archive",
+ "archivedOn": "Archivé le",
+ "downloadArchive": "Télécharger l'archive",
+ "loadingPhotos": "Chargement des photos...",
+ "photoCategories": "Catégories de photos",
+ "organizeCategoriesInfo": "Organisez vos photos en catégories. Les catégories aident les invités à naviguer.",
+ "categoriesTip": "Astuce : Les catégories sont spécifiques à chaque événement. Vous pouvez aussi créer des catégories globales dans les Paramètres.",
+ "contactInformation": "Informations de contact",
+ "hostEmailHelp": "Le client recevra les notifications de création et d'expiration de la galerie",
+ "adminEmailHelp": "Recevra les notifications système et les confirmations d'archivage",
+ "securityAccess": "Sécurité et Accès",
+ "galleryPassword": "Mot de passe de la galerie",
+ "requirePasswordToggle": "Exiger un mot de passe pour cette galerie",
+ "requirePasswordToggleHelp": "Désactivez cette option pour partager la galerie sans mot de passe. Toute personne disposant du lien pourra voir les photos.",
+ "publicGalleryWarning": "Les galeries publiques sont accessibles à tous ceux qui ont le lien. Envisagez d'activer les filigranes et de surveiller l'activité.",
+ "passwordHelperText": "Vous pouvez utiliser des dates comme \"04.07.2025\" ou n'importe quel texte de plus de 6 caractères",
+ "confirmPassword": "Confirmer le mot de passe",
+ "showPasswords": "Afficher les mots de passe",
+ "newPasswordLabel": "Nouveau mot de passe de la galerie",
+ "gallerySettings": "Paramètres de la galerie",
+ "themeAndStyle": "Thème et Style",
+ "colorTheme": "Thème de couleur",
+ "galleryExpiration": "Expiration de la galerie",
+ "galleryExpiresIn": "La galerie expire dans",
+ "daysAfterEvent": "jours après la date de l'événement",
+ "galleryWillExpireOn": "La galerie expirera le {{date}}",
+ "expirationWarning": "Les invités recevront un e-mail d'avertissement 7 jours avant l'expiration.",
+ "noExpiration": "Pas d'expiration",
+ "noExpirationHelp": "Cette galerie restera active jusqu'à son archivage manuel.",
+ "userUploads": "Paramètres de téléversement invité",
+ "allowUserUploads": "Autoriser les invités à téléverser des photos",
+ "allowUserUploadsHelp": "Permet aux invités d'ajouter leurs propres photos à cette galerie",
+ "allowUserUploadsDescription": "Permet aux invités d'ajouter leurs propres photos à cette galerie",
+ "uploadCategory": "Catégorie de téléversement",
+ "selectCategory": "Sélectionner une catégorie pour les envois invités",
+ "uploadCategoryHelp": "Toutes les photos envoyées par les invités seront ajoutées à cette catégorie",
+ "userUploadWarning": "Les envois invités seront modérés et peuvent être supprimés par les admins à tout moment.",
+ "allowDownloads": "Autoriser le téléchargement des photos",
+ "allowDownloadsHelp": "Autorise les invités à télécharger les photos de cette galerie",
+ "downloadPermissions": "Permissions de téléchargement",
+ "downloadsEnabled": "Téléchargements activés",
+ "downloadsDisabled": "Téléchargements désactivés",
+ "downloadProtection": "Protection contre le téléchargement",
+ "disableRightClick": "Bloquer le clic droit",
+ "watermarkDownloads": "Ajouter un filigrane aux téléchargements",
+ "enableDevtoolsProtection": "Détecter les outils de développement",
+ "useCanvasRendering": "Rendu Canvas (protection avancée)",
+ "protectionInfo": "Les fonctions de protection aident à prévenir les téléchargements non autorisés mais ne peuvent bloquer toutes les méthodes.",
+ "protectionLevelBasic": "Basique - Blocage clic droit uniquement",
+ "protectionLevelStandard": "Standard - Raccourcis clavier bloqués",
+ "protectionLevelEnhanced": "Amélioré - Détection de capture d'écran",
+ "protectionLevelMaximum": "Maximum - Détection DevTools et rendu Canvas",
+ "heroLogoSettings": "Paramètres du logo principal",
+ "heroLogoVisible": "Afficher le logo dans la section principale",
+ "heroLogoSize": "Taille du logo",
+ "heroLogoSizeSmall": "Petit",
+ "heroLogoSizeMedium": "Moyen",
+ "heroLogoSizeLarge": "Grand",
+ "heroLogoSizeXLarge": "Très Grand",
+ "heroLogoPosition": "Position du logo",
+ "heroLogoPositionTop": "Haut (au-dessus du titre)",
+ "heroLogoPositionCenter": "Centre (entre titre et dates)",
+ "heroLogoPositionBottom": "Bas (sous les dates)",
+ "heroLogoInfo": "Ces paramètres s'appliquent lorsque la galerie utilise la mise en page 'Hero'. Vous pouvez masquer le logo ou personnaliser sa taille et sa position.",
+ "eventCustomLogo": "Logo personnalisé de l'événement",
+ "eventCustomLogoDescription": "Téléchargez un logo spécifique pour cet événement. Il remplace le logo global pour cette galerie uniquement.",
+ "uploadEventLogo": "Téléverser le logo",
+ "replaceLogo": "Remplacer",
+ "removeLogo": "Retirer",
+ "eventLogoUploaded": "Logo de l'événement téléversé avec succès",
+ "eventLogoUploadFailed": "Échec du téléversement du logo",
+ "eventLogoRemoved": "Logo de l'événement retiré avec succès",
+ "eventLogoRemoveFailed": "Échec du retrait du logo",
+ "heroLogoVisibleLabel": "Logo visible",
+ "heroLogoSizeLabel": "Taille",
+ "heroLogoPositionLabel": "Position",
+ "heroLogoHidden": "Logo masqué",
+ "heroPhoto": "Photo principale (Hero)",
+ "heroPhotoHelp": "Sélectionnez une photo principale par défaut. Vous pouvez modifier cela par catégorie.",
+ "selectHeroPhoto": "Sélectionner la photo Hero",
+ "noHeroPhotoSelected": "Aucune photo Hero sélectionnée",
+ "heroPhotoSelected": "Photo Hero sélectionnée",
+ "heroImageAnchor": "Position de recadrage de l'image Hero",
+ "heroImageAnchorDescription": "Cliquez sur l'image pour définir le point focal du recadrage.",
+ "heroImageAnchorTop": "Haut",
+ "heroImageAnchorCenter": "Centre",
+ "heroImageAnchorBottom": "Bas",
+ "heroPreview": "Aperçu Hero",
+ "noPhotosAvailable": "Aucune photo disponible",
+ "processingRequest": "Traitement de votre demande...",
+ "eventTypeWedding": "Mariage",
+ "eventTypeBirthday": "Anniversaire",
+ "eventTypeCorporate": "Entreprise",
+ "eventTypeOther": "Autre",
+ "days30": "30 jours",
+ "days60": "60 jours",
+ "days90": "90 jours",
+ "days365": "1 an",
+ "inactive": "Inactif",
+ "daysLeft": "{{count}}j restant",
+ "daysLeft_plural": "{{count}}j restants",
+ "subtitle": "Gérez vos galeries photo et vos événements",
+ "loadingEvents": "Chargement des événements...",
+ "failedToLoadEvents": "Échec du chargement des événements",
+ "bulkArchiveSuccess": "Archivage réussi de {{count}} événements",
+ "bulkArchivePartial": "{{success}} événements archivés, {{failed}} échecs",
+ "searchEventsPlaceholder": "Rechercher des événements...",
+ "all": "Tout",
+ "expiring": "Expire bientôt",
+ "eventsSelected": "{{count}} événement sélectionné",
+ "eventsSelected_plural": "{{count}} événements sélectionnés",
+ "clear": "Effacer",
+ "archiveSelected": "Archiver la sélection",
+ "publicAccess": "Accès public",
+ "passwordProtected": "Protégé par mot de passe",
+ "newPasswordRequired": "Veuillez définir un mot de passe avant d'activer la protection.",
+ "event": "Événement",
+ "type": "Type",
+ "date": "Date",
+ "status": "Statut",
+ "noEventsFound": "Aucun événement trouvé",
+ "stats": {
+ "totalEvents": "Total Événements",
+ "activeEvents": "Événements Actifs",
+ "totalPhotos": "Total Photos",
+ "expiringEvents": "Expire Bientôt"
+ },
+ "viewDetails": "Voir les détails",
+ "archiveEventAction": "Archiver l'événement",
+ "downloadArchiveAction": "Télécharger l'archive",
+ "deleteEvent": "Supprimer l'événement",
+ "deleteEventConfirm": "Êtes-vous sûr de vouloir supprimer cet événement ?",
+ "downloadArchiveSoon": "Téléchargement d'archive bientôt disponible",
+ "eventExpiredMessage": "Cet événement a expiré",
+ "guestsCannotAccessGallery": "Les invités ne peuvent plus accéder à la galerie.",
+ "warningEmailsHaveBeenSent": "Les e-mails d'avertissement ont été envoyés au client.",
+ "extendSevenDays": "Prolonger de 7 jours",
+ "welcomeMessageLabel": "Message de bienvenue",
+ "noWelcomeMessageSet": "Aucun message de bienvenue défini",
+ "createdOn": "Créé le",
+ "daysLeft": "({{count}} jour restant)",
+ "daysLeft_plural": "({{count}} jours restants)",
+ "copy": "Copier",
+ "copied": "Copié !",
+ "organizingPhotosInfo": "Organisez vos photos en catégories pour aider vos invités.",
+ "categoriesTip": "Astuce : Créez des catégories comme \"Cérémonie\", \"Réception\", \"Portraits\", etc.",
+ "archiveStatusTitle": "Statut de l'archive",
+ "downloadingArchive": "Téléchargement de l'archive {{name}}...",
+ "downloadStarted": "Téléchargement démarré",
+ "failedToDownloadArchive": "Échec du téléchargement de l'archive",
+ "statisticsNotAvailable": "Statistiques non disponibles pour le moment",
+ "photoFilters": "Filtres photo",
+ "noStatisticsAvailableYet": "Pas encore de statistiques disponibles",
+ "addPlus": "Ajouter+",
+ "galleryTheme": "Thème de la galerie",
+ "customizeTheme": "Personnaliser le thème",
+ "noThemeSet": "Aucun thème configuré",
+ "customizingTheme": "Personnalisation du thème de la galerie",
+ "customizingThemeFor": "Personnalisation du thème pour {{event}}",
+ "customCssTemplate": "Modèle CSS personnalisé",
+ "customCssTemplateDesc": "Appliquez un modèle CSS personnalisé pour un style unique.",
+ "noTemplate": "Aucun modèle",
+ "useThemeOnly": "Utiliser uniquement le thème prédéfini",
+ "customTemplate": "Modèle personnalisé",
+ "photoCap": "Limite de photos",
+ "photoCapHelp": "Nombre maximum de photos autorisé. 0 = illimité",
+ "rename": {
+ "button": "Renommer",
+ "title": "Renommer l'événement",
+ "validating": "Validation du nouveau nom...",
+ "renamingFiles": "Renommage des fichiers...",
+ "complete": "Terminé !",
+ "failed": "Échec du renommage",
+ "filesRenamed": "{{count}} fichiers mis à jour",
+ "confirm": "Renommer l'événement"
+ }
+ },
+ "settings": {
+ "title": "Paramètres système",
+ "subtitle": "Configurer les paramètres et préférences globaux",
+ "loadingSettings": "Chargement des paramètres...",
+ "general": {
+ "title": "Général",
+ "siteConfiguration": "Configuration du site",
+ "siteUrl": "URL du site",
+ "siteUrlHelp": "Utilisé pour générer les liens des galeries dans les e-mails",
+ "defaultExpiration": "Expiration par défaut (jours)",
+ "defaultExpirationHelp": "Durée d'activation par défaut des galeries",
+ "maxFileSize": "Taille max de fichier (Mo)",
+ "maxFileSizeHelp": "Taille maximale par photo téléversée",
+ "maxFilesPerUpload": "Fichiers max par envoi",
+ "maxFilesPerUploadHelp": "Nombre maximum de photos autorisées par lot (1-{{max}}).",
+ "allowedFileTypes": "Types de fichiers autorisés",
+ "allowedFileTypesHelp": "Liste d'extensions séparées par des virgules",
+ "featureToggles": "Fonctionnalités",
+ "enableAnalytics": "Activer le suivi analytique",
+ "enableRegistration": "Autoriser l'auto-inscription des administrateurs",
+ "enableShortGalleryUrls": "Utiliser des URLs de galerie courtes",
+ "enableShortGalleryUrlsHelp": "Supprime le slug de l'événement des nouveaux liens tout en gardant les anciens fonctionnels.",
+ "maintenanceMode": "Activer le mode maintenance",
+ "language": "Langue",
+ "defaultLanguage": "Langue par défaut",
+ "defaultLanguageHelp": "Langue affichée aux invités avant connexion",
+ "defaultWelcomeMessage": "Message de bienvenue par défaut",
+ "welcomeMessage": "Message de bienvenue",
+ "welcomeMessagePlaceholder": "Entrez un message de bienvenue par défaut pour les e-mails de création",
+ "welcomeMessageHelp": "Ce message sera inclus dans tous les e-mails de création, sauf s'il est modifié lors de la création d'un événement",
+ "saveSettings": "Enregistrer les paramètres généraux",
+ "saveGeneralSettings": "Enregistrer les paramètres généraux",
+ "dateTimeFormat": "Format de date et d'heure",
+ "dateFormat": "Format de date",
+ "dateFormatHelp": "Affichage des dates dans les e-mails et l'application",
+ "accountSection": "Compte Administrateur",
+ "accountUsername": "Nom d'utilisateur admin",
+ "accountUsernameHelp": "Affiché dans l'interface et les journaux d'activité.",
+ "accountUsernameRequired": "Le nom d'utilisateur est requis",
+ "accountUsernameLength": "Le nom d'utilisateur doit faire au moins 3 caractères",
+ "accountEmail": "E-mail admin",
+ "accountEmailHelp": "Utilisé pour la connexion et les notifications de sécurité.",
+ "accountEmailRequired": "L'adresse e-mail est requise",
+ "accountEmailInvalid": "Entrez une adresse e-mail valide",
+ "accountSaveButton": "Enregistrer les détails du compte",
+ "accountSaveSuccess": "Détails du compte mis à jour"
+ },
+ "publicSite": {
+ "tabLabel": "Site Public",
+ "badge": "Page d'accueil",
+ "title": "Page d'accueil publique",
+ "subtitle": "Publiez une page d'accueil personnalisée pour les invités visitant votre domaine.",
+ "loading": "Chargement des paramètres...",
+ "enabled": "Activé",
+ "disabled": "Désactivé",
+ "htmlLabel": "HTML de la page d'accueil",
+ "htmlPlaceholder": "Saisissez le balisage HTML pour votre page (hero, sections, appels à l'action).",
+ "htmlHelp": "Le HTML brut est nettoyé à l'enregistrement. Les jetons comme {{company_name}} seront remplacés par les valeurs de l'identité visuelle.",
+ "cssLabel": "CSS personnalisé",
+ "cssPlaceholder": "Surcharges CSS optionnelles pour ajuster la mise en page.",
+ "cssHelp": "Les imports et URLs JavaScript sont retirés. Utilisez des sélecteurs CSS standard.",
+ "saveCta": "Enregistrer le site public",
+ "saving": "Enregistrement...",
+ "saveSuccess": "Paramètres du site public enregistrés.",
+ "saveError": "Échec de l'enregistrement.",
+ "resetCta": "Réinitialiser par défaut",
+ "resetting": "Réinitialisation...",
+ "resetSuccess": "Modèle réinitialisé.",
+ "resetError": "Échec de la réinitialisation.",
+ "previewTitle": "Aperçu en direct",
+ "previewSandboxed": "Aperçu sécurisé",
+ "previewDisabled": "Activez le site public pour voir l'aperçu.",
+ "sanitizationNotice": "Les scripts et balises non sécurisées sont supprimés avant publication.",
+ "htmlRequired": "Fournissez un contenu HTML avant d'activer le site."
+ },
+ "storage": {
+ "title": "Stockage",
+ "overview": "Aperçu du stockage",
+ "totalUsed": "Total utilisé",
+ "archiveStorage": "Stockage des archives",
+ "storageLimit": "Limite de stockage",
+ "storageLimitHelper": "Définissez une limite de stockage. Cette limite est indicative.",
+ "softLimitInputLabel": "Limite (Go)",
+ "softLimitHelper": "Saisissez l'usage maximum souhaité en gigaoctets.",
+ "recommendedSoftLimit": "Limite suggérée",
+ "diskCapacity": "Capacité disque",
+ "diskCapacityReported": "Capacité disque (déclarée)",
+ "diskAvailable": "Disponible",
+ "diskAvailableReported": "Disponible (déclarée)",
+ "diskFree": "Libre",
+ "diskFreeReported": "Libre (déclarée)",
+ "diskMetricsUnavailable": "Les mesures disque ne sont pas disponibles dans Docker Desktop ou les environnements virtualisés.",
+ "applyRecommended": "Utiliser la recommandation",
+ "applyAvailable": "Faire correspondre au disponible",
+ "invalidSoftLimit": "Entrez un nombre valide.",
+ "saveSoftLimit": "Enregistrer la limite",
+ "limitNotEnforced": "Cette limite est purement indicative et n'est pas appliquée automatiquement.",
+ "overrideTitle": "Surcharge manuelle de capacité",
+ "diskOverrideEnvNote": "La capacité disque est contrôlée par des variables d'environnement.",
+ "diskOverrideSettingsHelp": "Définissez des valeurs personnalisées si Docker rapporte des chiffres irréalistes.",
+ "overrideCapacityLabel": "Capacité totale (Go)",
+ "overrideCapacityHelper": "Laissez vide pour la lecture automatique.",
+ "overrideAvailableLabel": "Espace disponible (Go)",
+ "overrideAvailableHelper": "Optionnel. Laissez vide pour calculer automatiquement.",
+ "saveOverride": "Enregistrer la surcharge",
+ "capacityRequiredForAvailable": "Entrez une capacité totale avant l'espace disponible.",
+ "availableExceedsCapacity": "L'espace disponible ne peut excéder la capacité totale.",
+ "storageUsage": "Usage du stockage",
+ "storageByEvent": "Stockage par événement",
+ "storageManagement": "Gestion du stockage",
+ "storageManagementHelp": "Envisagez d'archiver ou supprimer d'anciens événements pour libérer de l'espace.",
+ "noEventsUsingStorage": "Aucun événement n'utilise de stockage",
+ "unlimited": "Illimité"
+ },
+ "security": {
+ "title": "Sécurité",
+ "passwordSettings": "Paramètres de mot de passe",
+ "minPasswordLength": "Longueur minimale du mot de passe",
+ "minPasswordLengthHelp": "Nombre minimum de caractères pour les galeries",
+ "passwordComplexity": "Complexité du mot de passe",
+ "passwordComplexityHelp": "Niveau de sécurité requis",
+ "complexitySimple": "Simple (6+ car., n'importe quel texte)",
+ "complexityModerate": "Modéré (8+ car., mélange maj/min/chiffres)",
+ "complexityStrong": "Fort (12+ car., maj/min/chiffres)",
+ "complexityVeryStrong": "Très Fort (12+ car., tous types de caractères)",
+ "sessionAuth": "Session et Authentification",
+ "sessionTimeout": "Expiration de session (minutes)",
+ "sessionTimeoutHelp": "Expiration de la session admin",
+ "maxLoginAttempts": "Tentatives de connexion max",
+ "maxLoginAttemptsHelp": "Échecs autorisés par IP avant blocage",
+ "attemptWindowMinutes": "Fenêtre de tentative (minutes)",
+ "attemptWindowMinutesHelp": "Durée de surveillance des échecs",
+ "lockoutDurationMinutes": "Durée du blocage (minutes)",
+ "lockoutDurationMinutesHelp": "Durée du verrouillage après trop d'échecs",
+ "enable2FA": "Activer l'authentification à deux facteurs pour les admins",
+ "recaptchaSettings": "Paramètres reCAPTCHA",
+ "enableRecaptcha": "Activer reCAPTCHA sur les formulaires",
+ "siteKey": "Clé du site",
+ "siteKeyHelp": "Votre clé publique reCAPTCHA v2",
+ "secretKey": "Clé secrète",
+ "secretKeyHelp": "Votre clé secrète reCAPTCHA v2 (garder privé)",
+ "recaptchaHelp": "Obtenez vos clés reCAPTCHA sur",
+ "saveSettings": "Enregistrer les paramètres de sécurité",
+ "saveSecuritySettings": "Enregistrer les paramètres de sécurité"
+ },
+ "categories": {
+ "title": "Catégories",
+ "about": "À propos des catégories",
+ "aboutText": "Les catégories globales sont disponibles pour tous les événements. Vous pouvez aussi créer des catégories spécifiques par événement."
+ },
+ "systemStatus": {
+ "title": "Statut du système",
+ "storageOverview": "Aperçu du stockage",
+ "systemInfo": "Informations système",
+ "databaseInfo": "Informations base de données",
+ "platform": "Plateforme",
+ "nodeVersion": "Version Node",
+ "uptime": "Temps de fonctionnement",
+ "cpuCores": "Cœurs CPU",
+ "memoryUsage": "Usage mémoire",
+ "memoryUsed": "Mémoire utilisée",
+ "photos": "Photos",
+ "admins": "Admins",
+ "dbSize": "Taille base de données",
+ "services": "Services d'arrière-plan",
+ "fileWatcher": "Observateur de fichiers",
+ "fileWatcherDesc": "Surveille les nouvelles photos",
+ "expirationChecker": "Vérificateur d'expiration",
+ "expirationCheckerDesc": "Archive les galeries expirées",
+ "emailProcessor": "Processeur d'e-mails",
+ "emailProcessorDesc": "Envoie les e-mails en file d'attente",
+ "emailQueue": "Statut de la file d'e-mails",
+ "pending": "En attente",
+ "sent": "Envoyés",
+ "failed": "Échoués",
+ "lastUpdate": "Dernière mise à jour"
+ },
+ "photoDimensions": {
+ "title": "Dimensions des photos",
+ "totalPhotos": "Total photos",
+ "withDimensions": "Avec dimensions",
+ "missingDimensions": "Dimensions manquantes",
+ "repairButton": "Réparer les dimensions",
+ "repairing": "Réparation...",
+ "alreadyRunning": "La réparation est déjà en cours",
+ "started": "Réparation lancée pour {{count}} photos",
+ "noneToRepair": "Toutes les photos ont déjà leurs dimensions",
+ "resultSuccess": "Dernière réparation : {{success}} mis à jour, {{failed}} échecs",
+ "description": "Complète les dimensions manquantes (largeur/hauteur) pour les photos anciennes. Requis pour les mises en page Masonry et Mosaic."
+ },
+ "updateNotifications": {
+ "title": "Notifications de mise à jour",
+ "description": "Recevez des e-mails quand une nouvelle version de PicPeak est disponible.",
+ "enableEmails": "Activer les notifications par e-mail",
+ "enableEmailsDesc": "Informer les admins des nouvelles versions",
+ "recipients": "Destinataires",
+ "recipientsPlaceholder": "admin@exemple.com, autre@exemple.com",
+ "recipientsHelper": "Adresses séparées par des virgules. Laissez vide pour tous les admins.",
+ "lastNotified": "Dernière notification pour la version : {{version}}",
+ "checkNow": "Vérifier et notifier",
+ "sendTest": "Envoyer un e-mail test",
+ "saved": "Paramètres enregistrés",
+ "saveError": "Échec de l'enregistrement",
+ "emailSent": "E-mail de notification envoyé à {{count}} destinataires",
+ "emailFailed": "Échec de l'envoi",
+ "checkSuccess": "Notification envoyée pour la nouvelle version",
+ "checkNoAction": "Aucune notification nécessaire : {{reason}}",
+ "checkError": "Échec de la vérification"
+ },
+ "events": {
+ "title": "Création d'événement",
+ "requiredFields": "Champs requis",
+ "requiredFieldsDescription": "Configurez les champs obligatoires lors de la création d'événements.",
+ "requireCustomerName": "Exiger le nom du client",
+ "requireCustomerNameHelp": "Le nom du client doit être fourni",
+ "requireCustomerEmail": "Exiger l'e-mail du client",
+ "requireCustomerEmailHelp": "L'e-mail du client doit être fourni",
+ "customerEmailWarning": "Requis pour l'envoi des invitations",
+ "requireAdminEmail": "Exiger l'e-mail admin",
+ "requireAdminEmailHelp": "L'e-mail admin doit être fourni",
+ "adminEmailWarning": "Requis pour les notifications d'événement",
+ "requireEventDate": "Exiger la date de l'événement",
+ "requireEventDateHelp": "La date doit être fournie à la création",
+ "eventDateWarning": "Les URLs utiliseront des identifiants aléatoires sans date",
+ "requireExpiration": "Exiger une date d'expiration",
+ "requireExpirationHelp": "Les galeries doivent avoir une expiration",
+ "expirationWarning": "Sans expiration, les galeries restent actives jusqu'à archivage manuel",
+ "saveSettings": "Enregistrer les paramètres d'événement",
+ "noteTitle": "Note",
+ "noteText": "Ces paramètres n'affectent que les nouveaux événements. Par défaut, tous les champs sont requis."
+ },
+ "imageSecurity": {
+ "title": "Protection de l'image",
+ "saveSuccess": "Paramètres de sécurité enregistrés",
+ "saveError": "Échec de l'enregistrement",
+ "loadError": "Échec du chargement des paramètres de sécurité",
+ "defaultProtection": "Paramètres de protection par défaut",
+ "defaultProtectionHelp": "S'appliquent à tous les nouveaux événements.",
+ "protectionLevel": "Niveau de protection par défaut",
+ "imageQuality": "Qualité d'image par défaut",
+ "fragmentationLevel": "Niveau de fragmentation",
+ "enableDevtools": "Activer la détection DevTools par défaut",
+ "enableCanvas": "Activer le rendu Canvas par défaut (protection avancée)",
+ "rateLimiting": "Limitation de débit",
+ "rateLimitingHelp": "Limite le nombre de requêtes d'images pour éviter le siphonnage.",
+ "requestsPerMinute": "Requêtes par minute",
+ "requestsPer5Minutes": "Requêtes par 5 min",
+ "requestsPerHour": "Requêtes par heure",
+ "securityMonitoring": "Surveillance de sécurité",
+ "suspiciousThreshold": "Seuil d'activité suspecte",
+ "autoBlockThreshold": "Seuil de blocage automatique",
+ "enableMonitoring": "Activer la surveillance",
+ "blockSuspiciousIps": "Bloquer automatiquement les IPs suspectes",
+ "logEvents": "Journaliser les événements de sécurité",
+ "infoTitle": "À propos de la protection",
+ "infoText": "Ces fonctionnalités freinent le téléchargement sauvage mais ne peuvent tout bloquer. Envisagez l'usage de filigranes."
+ },
+ "moderation": {
+ "title": "Modération",
+ "wordFilters": "Filtres de mots",
+ "description": "Gérer les mots à filtrer ou bloquer dans les commentaires",
+ "addFilter": "Ajouter un filtre",
+ "enterWord": "Mot à filtrer",
+ "searchFilters": "Rechercher...",
+ "filterAdded": "Filtre ajouté avec succès",
+ "filterExists": "Ce filtre existe déjà",
+ "addError": "Échec de l'ajout",
+ "filterUpdated": "Filtre mis à jour",
+ "updateError": "Échec de la mise à jour",
+ "filterDeleted": "Filtre supprimé",
+ "deleteError": "Échec de la suppression",
+ "wordRequired": "Veuillez entrer un mot",
+ "confirmDelete": "Supprimer ce filtre ?",
+ "loading": "Chargement...",
+ "noMatchingFilters": "Aucun filtre correspondant",
+ "noFilters": "Aucun filtre configuré",
+ "severityLow": "Faible",
+ "severityModerate": "Modéré",
+ "severityHigh": "Élevé",
+ "severityBlock": "Bloquer",
+ "severityLevels": "Niveaux de gravité",
+ "lowDescription": "Signalé mais non bloqué",
+ "moderateDescription": "Approbation manuelle requise",
+ "highDescription": "Masqué automatiquement, revue admin requise",
+ "blockDescription": "Rejet immédiat du commentaire"
+ },
+ "styling": {
+ "title": "CSS Personnalisé"
+ },
+ "thumbnails": {
+ "title": "Vignettes",
+ "dimensionsTitle": "Dimensions et qualité des vignettes",
+ "dimensionsHelp": "Configurez la taille et la qualité. Des valeurs plus hautes augmentent la charge et le stockage.",
+ "width": "Largeur (px)",
+ "widthHelp": "50-1000 pixels",
+ "height": "Hauteur (px)",
+ "heightHelp": "50-1000 pixels",
+ "quality": "Qualité",
+ "qualityHelp": "1-100, plus élevé = meilleure qualité",
+ "format": "Format",
+ "fit": "Mode d'ajustement",
+ "fitHelp": "Comment l'image est redimensionnée. 'Cover' remplit, 'Contain' ajuste dans le cadre.",
+ "fit_cover": "Cover (remplir et couper)",
+ "fit_contain": "Contain (ajuster à l'intérieur)",
+ "fit_fill": "Fill (étirer)",
+ "fit_inside": "Inside (réduire pour ajuster)",
+ "fit_outside": "Outside (étendre pour couvrir)",
+ "regenerateTitle": "Régénérer les vignettes",
+ "regenerateHelp": "Lancez ceci après avoir changé les réglages. S'exécute en arrière-plan.",
+ "regenerateButton": "Régénérer toutes les vignettes",
+ "regenerateStarted": "Régénération démarrée",
+ "regenerateError": "Échec du lancement",
+ "saveSuccess": "Paramètres enregistrés",
+ "saveError": "Échec de l'enregistrement",
+ "loadError": "Échec du chargement",
+ "infoTitle": "À propos des vignettes",
+ "infoText": "Ce sont des aperçus réduits de vos originaux."
+ },
+ "seo": {
+ "title": "SEO et Robots",
+ "indexingTitle": "Indexation moteurs de recherche",
+ "allowIndexing": "Autoriser l'indexation",
+ "allowIndexingHelp": "Si désactivé, les robots sont bloqués via robots.txt. Recommandé pour le privé.",
+ "sitemapUrl": "URL du Sitemap",
+ "sitemapUrlHelp": "Optionnel. Ajouté au robots.txt.",
+ "aiBlockingTitle": "Blocage IA et Robots",
+ "blockAiCrawlers": "Bloquer les robots d'IA/LLM",
+ "blockAiCrawlersHelp": "Empêche l'entraînement des IA sur vos contenus.",
+ "blockedAgents": "Agents IA bloqués",
+ "addAgentPlaceholder": "Nom de l'agent...",
+ "blockSocialBots": "Bloquer les robots de réseaux sociaux",
+ "blockSocialBotsHelp": "Empêche les aperçus sur Twitter, FB, etc.",
+ "metaTagsTitle": "Meta Tags et Règles",
+ "metaNoindex": "Ajouter meta noindex",
+ "metaNoindexHelp": "Complète le robots.txt au niveau HTML.",
+ "metaNofollow": "Ajouter meta nofollow",
+ "metaNofollowHelp": "Dit aux moteurs de ne pas suivre les liens.",
+ "metaNoai": "Ajouter meta noai/noimageai",
+ "metaNoaiHelp": "Signale que le contenu ne doit pas servir à l'IA.",
+ "customRules": "Règles robots.txt personnalisées",
+ "ruleAgentPlaceholder": "User-agent",
+ "rulePathPlaceholder": "Chemin interdit",
+ "showPreview": "Aperçu robots.txt",
+ "hidePreview": "Masquer l'aperçu",
+ "saveSettings": "Enregistrer SEO"
+ },
+ "analytics": {
+ "title": "Analytique",
+ "umamiIntegration": "Intégration Umami",
+ "enableUmami": "Activer Umami Analytics",
+ "umamiUrl": "URL Umami",
+ "umamiUrlHelp": "Ex: https://analytics.votredomaine.com",
+ "websiteId": "ID du site",
+ "websiteIdHelp": "Trouvé dans votre tableau de bord Umami",
+ "shareUrl": "URL de partage (Optionnel)",
+ "shareUrlHelp": "Pour intégrer le tableau de bord complet",
+ "umamiInfo": "À propos d'Umami",
+ "umamiInfoText": "Plateforme open-source respectueuse de la vie privée, sans cookies.",
+ "learnMore": "En savoir plus sur Umami",
+ "saveAnalyticsSettings": "Enregistrer les paramètres",
+ "backendAnalytics": "Analytique Backend",
+ "backendAnalyticsText": "Le système suit des mesures de base pour la sécurité.",
+ "tracked": "Mesures suivies",
+ "galleryViews": "Vues des galeries",
+ "photoDownloads": "Téléchargements (unité et groupe)",
+ "uniqueVisitors": "Visiteurs uniques par IP",
+ "deviceTypes": "Types d'appareils",
+ "privacy": "Vie privée",
+ "privacyText": "Les adresses IP sont hachées. Rétention : 90 jours."
+ }
+ },
+ "analytics": {
+ "title": "Tableau de bord analytique",
+ "titleSimple": "Analytique",
+ "subtitle": "Suivi des performances et de l'engagement",
+ "detailedSubtitle": "Analyses détaillées via Umami",
+ "loadingAnalytics": "Chargement...",
+ "showSummaryView": "Vue Résumé",
+ "fullDashboard": "Tableau de bord complet",
+ "refresh": "Actualiser",
+ "last7Days": "7 derniers jours",
+ "last30Days": "30 derniers jours",
+ "last90Days": "90 derniers jours",
+ "pageViews": "Vues de pages",
+ "uniqueVisitors": "Visiteurs uniques",
+ "totalDownloads": "Total téléchargements",
+ "topGallery": "Meilleure galerie",
+ "topPages": "Pages les plus vues",
+ "views": "vues",
+ "visitors": "visiteurs uniques",
+ "topDownloadsByGallery": "Téléchargements par galerie",
+ "deviceBreakdown": "Répartition par appareil",
+ "desktop": "Ordinateur",
+ "mobile": "Mobile",
+ "tablet": "Tablette",
+ "storageUsage": "Usage stockage",
+ "used": "Utilisé",
+ "of": "sur",
+ "totalPhotos": "Total photos",
+ "activeEvents": "Événements actifs",
+ "notConfigured": "Umami non configuré",
+ "configureInstructions": "Configurez Umami dans vos variables d'environnement.",
+ "noData": "Aucune donnée disponible",
+ "percentChange": "{{percent}}% par rapport à la période précédente"
+ },
+ "branding": {
+ "title": "Identité et Thèmes",
+ "titleFull": "Personnalisation de marque",
+ "subtitle": "Personnalisez l'apparence de vos galeries",
+ "loadingBranding": "Chargement...",
+ "themeAndStyle": "Thème et Style",
+ "companyInfo": "Informations entreprise",
+ "companyName": "Nom de l'entreprise",
+ "companyNameHelp": "Affiché dans les en-têtes et e-mails",
+ "companyTagline": "Slogan",
+ "companyTaglineHelp": "Courte description de votre activité",
+ "supportEmail": "E-mail de support",
+ "supportEmailHelp": "Contact pour l'aide aux invités",
+ "footerText": "Texte de pied de page",
+ "footerTextHelp": "Affiché en bas des galeries",
+ "logo": "Logo",
+ "currentLogo": "Logo actuel",
+ "uploadLogo": "Téléverser logo",
+ "removeLogo": "Retirer logo",
+ "logoHelp": "Taille recommandée : 200x60px, PNG ou JPEG",
+ "favicon": "Favicon",
+ "currentFavicon": "Favicon actuel",
+ "uploadFavicon": "Téléverser favicon",
+ "removeFavicon": "Retirer favicon",
+ "faviconHelp": "Format PNG ou ICO, 32x32px recommandé",
+ "watermark": "Filigrane",
+ "watermarkSettings": "Paramètres filigrane",
+ "enableWatermarks": "Activer les filigranes",
+ "watermarkHelp": "Ajoute le nom de votre entreprise sur les photos téléchargées",
+ "watermarkLogo": "Logo de filigrane",
+ "currentWatermark": "Filigrane actuel",
+ "uploadWatermarkLogo": "Téléverser logo filigrane",
+ "watermarkPosition": "Position filigrane",
+ "topLeft": "Haut Gauche",
+ "topRight": "Haut Droite",
+ "center": "Centre",
+ "bottomLeft": "Bas Gauche",
+ "bottomRight": "Bas Droite",
+ "watermarkOpacity": "Opacité filigrane",
+ "watermarkSize": "Taille filigrane",
+ "theme": "Thème",
+ "galleryTheme": "Thème galerie",
+ "themeCustomization": "Personnalisation thème",
+ "selectPreset": "Sélectionner un préréglage",
+ "colors": "Couleurs",
+ "primaryColor": "Couleur primaire",
+ "secondaryColor": "Couleur secondaire",
+ "accentColor": "Couleur d'accentuation",
+ "backgroundColor": "Couleur de fond",
+ "textColor": "Couleur de texte",
+ "colorMode": "Mode de couleur",
+ "colorModeLight": "Clair",
+ "colorModeDark": "Sombre",
+ "colorModeAuto": "Auto",
+ "colorModeHelp": "Auto suit les préférences système de l'invité.",
+ "customCSS": "CSS personnalisé",
+ "preview": "Aperçu",
+ "previewInNewTab": "Aperçu (nouvel onglet)",
+ "reset": "Réinitialiser",
+ "saveChanges": "Enregistrer",
+ "applyLivePreview": "Appliquer immédiatement (Aperçu direct)",
+ "eventSpecificThemes": "Thèmes spécifiques par événement",
+ "eventThemesInfo": "Vous pouvez surcharger ces paramètres globaux pour chaque événement.",
+ "themePresets": "Préréglages de thème",
+ "galleryLayout": "Mise en page",
+ "layoutDescriptions": {
+ "grid": "Grille classique, tailles uniformes",
+ "masonry": "Style Pinterest, hauteurs variables",
+ "carousel": "Diaporama plein écran",
+ "timeline": "Photos triées par date",
+ "hero": "Image mise en avant avec grille en dessous",
+ "mosaic": "Mosaïque artistique, tailles mixtes",
+ "justified": "Lignes justifiées préservant les ratios",
+ "gallery-premium": "Thème clair élégant avec hero et masonry (Beta)",
+ "gallery-story": "Thème sombre cinématique avec sections (Beta)"
+ },
+ "layoutSettings": "Paramètres de mise en page",
+ "photoSpacing": "Espacement des photos",
+ "spacing": {
+ "tight": "Serré",
+ "normal": "Normal",
+ "relaxed": "Espacé"
+ },
+ "photoAnimation": "Animations photos",
+ "animation": {
+ "none": "Aucune",
+ "fade": "Fondu",
+ "scale": "Zoom",
+ "slide": "Glissement"
+ },
+ "columns": "Colonnes",
+ "mobile": "Mobile",
+ "tablet": "Tablette",
+ "desktop": "Ordinateur",
+ "enableAutoplay": "Lecture auto",
+ "autoplayInterval": "Intervalle lecture auto (s)",
+ "groupPhotosBy": "Grouper par",
+ "grouping": {
+ "day": "Jour",
+ "week": "Semaine",
+ "month": "Mois"
+ },
+ "masonryMode": "Mode de mise en page",
+ "masonryModeOptions": {
+ "columns": "Colonnes (Pinterest)",
+ "rows": "Lignes (Justifié personnalisé)",
+ "flickr": "Flickr (Justifié robuste)",
+ "justified": "Google Photos (Algorithme Knuth-Plass)"
+ },
+ "masonryModeHint": {
+ "columns": "Colonnes verticales style Pinterest",
+ "rows": "Lignes justifiées personnalisées",
+ "flickr": "Algorithme de Flickr pour le justifié",
+ "justified": "Lignes style Google Photos pour des coupures optimales"
+ },
+ "targetRowHeight": "Hauteur de ligne cible",
+ "targetRowHeightHint": "En pixels (150-400). Les photos s'adapteront aux lignes.",
+ "lastRowBehavior": "Alignement dernière ligne",
+ "lastRowOptions": {
+ "left": "Aligné à gauche",
+ "center": "Centré",
+ "justify": "Justifié (étiré)"
+ },
+ "showHeroSection": "Afficher la section Hero",
+ "showHeroSectionHint": "Affiche une image principale au-dessus de la galerie",
+ "heroHeight": "Hauteur section Hero",
+ "heroHeightOptions": {
+ "small": "Petit (40-50%)",
+ "medium": "Moyen (50-70%)",
+ "large": "Grand (60-80%)"
+ },
+ "heroOverlayOpacity": "Opacité superposition Hero",
+ "heroOverlayHint": "Assombrit l'image pour la lisibilité du texte",
+ "typographyAndStyle": "Typographie et Style",
+ "bodyFont": "Police de corps",
+ "headingFont": "Police de titre",
+ "sameAsBody": "Identique au corps",
+ "fontSize": "Taille de police",
+ "fontSizes": {
+ "small": "Petit",
+ "normal": "Normal",
+ "large": "Grand"
+ },
+ "borderRadius": "Arrondi des coins",
+ "borderRadiusOptions": {
+ "none": "Aucun",
+ "small": "Petit",
+ "medium": "Moyen",
+ "large": "Grand"
+ },
+ "shadowStyle": "Style d'ombre",
+ "shadowOptions": {
+ "none": "Aucune",
+ "subtle": "Subtile",
+ "normal": "Normale",
+ "dramatic": "Prononcée"
+ },
+ "backgroundPattern": "Fond",
+ "backgroundOptions": {
+ "none": "Uni",
+ "dots": "Points",
+ "grid": "Grille",
+ "waves": "Vagues"
+ },
+ "customCSSHelp": "Avancé : Ajoutez du CSS pour personnaliser davantage",
+ "cssInstructions": {
+ "title": "Usage du CSS personnalisé",
+ "variables": "Variables CSS du thème",
+ "variablesDesc": "Utilisez ces variables pour correspondre au thème :",
+ "layouts": "Éléments de mise en page",
+ "layoutsDesc": "Ciblez les éléments avec ces sélecteurs :",
+ "glassEffect": "Effet Glassmorphism",
+ "glassEffectDesc": "Pour des effets de verre modernes :",
+ "tip": "Astuce",
+ "tipText": "Utilisez les modèles CSS depuis les Paramètres pour des designs comme Apple Liquid Glass."
+ },
+ "resetToDefault": "Réinitialiser",
+ "applyTheme": "Appliquer le thème",
+ "customTheme": "Thème personnalisé",
+ "customizeTheme": "Personnaliser le thème",
+ "saveTheme": "Enregistrer le thème",
+ "previewLayout": "Aperçu mise en page",
+ "livePreview": "Aperçu direct",
+ "heroPlaceholderText": "L'image Hero de l'événement apparaîtra ici",
+ "whiteLabel": "Marque blanche",
+ "hidePoweredBy": "Masquer la mention \"Powered by PicPeak\"",
+ "hidePoweredByHelp": "Retire l'attribution PicPeak du pied de page",
+ "logoCustomization": "Personnalisation du logo",
+ "changeLogo": "Changer le logo",
+ "logoSizeSmall": "Petit (32px)",
+ "logoSizeMedium": "Moyen (48px)",
+ "logoSizeLarge": "Grand (64px)",
+ "logoSizeXLarge": "Très Grand (96px)",
+ "logoSizeCustom": "Personnalisé",
+ "logoMaxHeight": "Hauteur maximale (pixels)",
+ "logoMaxHeightHelp": "Hauteur personnalisée (20-200 pixels)",
+ "logoPosition": "Position du logo dans l'en-tête",
+ "positionLeft": "Gauche",
+ "positionCenter": "Centre",
+ "positionRight": "Droite",
+ "logoDisplayMode": "Mode d'affichage",
+ "logoOnly": "Logo uniquement",
+ "textOnly": "Nom d'entreprise uniquement",
+ "logoAndText": "Logo et Nom d'entreprise",
+ "showLogoInHeader": "Logo dans l'en-tête",
+ "showLogoInHeaderHelp": "Afficher le logo dans la barre d'en-tête",
+ "showLogoInHero": "Logo dans la section Hero",
+ "showLogoInHeroHelp": "Afficher le logo dans la section principale",
+ "headerStyle": "Style d'en-tête",
+ "headerStyleDescription": "Choisissez l'apparence de l'en-tête.",
+ "headerStyleOptions": {
+ "hero": "Image Hero",
+ "standard": "Bannière standard",
+ "minimal": "Minimaliste",
+ "none": "Aucun en-tête"
+ },
+ "headerStyleDescriptions": {
+ "hero": "Image pleine hauteur avec infos superposées",
+ "standard": "Bannière classique avec détails",
+ "minimal": "En-tête compact avec infos essentielles",
+ "none": "Masquer complètement l'en-tête"
+ },
+ "heroDividerStyle": "Style de séparateur",
+ "heroDividerDescription": "Forme de la transition sous l'image Hero.",
+ "dividerOptions": {
+ "wave": "Vague",
+ "straight": "Droit",
+ "angle": "Angle",
+ "curve": "Courbe",
+ "none": "Aucun"
+ },
+ "controlsStyle": "Style des contrôles",
+ "controlsStyleDescription": "Affichage des filtres et contrôles.",
+ "controlsStyleOptions": {
+ "classic": "Classique",
+ "sidebar": "Barre latérale"
+ },
+ "controlsStyleDescriptions": {
+ "classic": "Barre de filtres sous l'en-tête",
+ "sidebar": "Bouton menu ouvrant un volet latéral"
+ },
+ "controlsStyleHeroWarning": "La barre latérale est recommandée pour les en-têtes Hero."
+ },
+ "admin": {
+ "title": "Panneau d'administration",
+ "welcome": "Bon retour, {{name}}",
+ "recentActivity": "Activité récente",
+ "systemStatus": "Statut système",
+ "totalEvents": "Total événements",
+ "activeGalleries": "Galeries actives",
+ "storageUsed": "Stockage utilisé",
+ "totalPhotos": "Total photos",
+ "storagePercent": "{{percent}}% de la limite {{limit}}",
+ "storageSoftLimitConfigured": "Limite configurée : {{limit}}",
+ "storageSoftLimitRecommended": "Limite suggérée : {{limit}}",
+ "version": "Version",
+ "archivedEvents": "Événements archivés",
+ "systemHealth": "Santé système",
+ "health": {
+ "healthy": "Sain",
+ "warning": "Attention",
+ "error": "Erreur",
+ "checking": "Vérification..."
+ },
+ "updates": {
+ "available": "Mise à jour disponible",
+ "newVersion": "La version {{version}} est disponible",
+ "currentVersion": "Actuelle : {{version}}",
+ "channel": "Canal : {{channel}}",
+ "channelStable": "Stable",
+ "channelBeta": "Beta",
+ "beta": "BETA",
+ "viewReleaseNotes": "Notes de version",
+ "updateAvailableShort": "v{{version}} disponible",
+ "checkForUpdates": "Vérifier les mises à jour",
+ "upToDate": "À jour",
+ "lastChecked": "Vérifié à : {{time}}",
+ "updateNow": "Mettre à jour",
+ "updateDialog": {
+ "title": "Mettre à jour PicPeak",
+ "detectedEnv": "Environnement détecté",
+ "beforeUpdating": "Avant de mettre à jour :",
+ "updateCommands": "Commandes :",
+ "afterUpdating": "Après mise à jour :",
+ "copyAllCommands": "Copier les commandes",
+ "completeChecklist": "Complétez la liste avant de lancer",
+ "error": "Échec du chargement des instructions"
+ }
+ },
+ "notifications": "Notifications",
+ "viewAllNotifications": "Toutes les notifications",
+ "noNotifications": "Aucune nouvelle notification",
+ "markAllRead": "Tout marquer comme lu",
+ "clearAll": "Tout effacer",
+ "close": "Fermer",
+ "noNotificationsMessage": "Pas de notifications",
+ "notificationMessages": {
+ "eventCreated": "Nouvel événement \"{{eventName}}\" créé",
+ "eventArchived": "Événement \"{{eventName}}\" archivé",
+ "eventUpdated": "Événement \"{{eventName}}\" mis à jour",
+ "eventDeleted": "Événement \"{{eventName}}\" supprimé",
+ "photosUploaded": "{{count}} photos téléversées dans \"{{eventName}}\"",
+ "photoDeleted": "Photo supprimée de \"{{eventName}}\"",
+ "photosBulkDeleted": "{{count}} photos supprimées de \"{{eventName}}\"",
+ "eventExpiring": "L'événement \"{{eventName}}\" expire dans {{days}} jours",
+ "eventExpired": "L'événement \"{{eventName}}\" a expiré",
+ "passwordChanged": "Mot de passe changé par {{actorName}}",
+ "passwordReset": "Réinitialisation du mot de passe pour \"{{eventName}}\"",
+ "settingsUpdated": "Paramètres {{type}} mis à jour",
+ "emailTemplateUpdated": "Modèle d'e-mail \"{{template}}\" mis à jour",
+ "bulkDownload": "{{count}} photos téléchargées depuis \"{{eventName}}\"",
+ "storageWarning": "Usage stockage à {{percentage}}%",
+ "adminLogout": "Déconnexion admin {{actorName}}",
+ "categoryCreated": "Catégorie \"{{name}}\" créée pour \"{{eventName}}\"",
+ "categoryUpdated": "Catégorie \"{{name}}\" mise à jour",
+ "categoryDeleted": "Catégorie \"{{name}}\" supprimée",
+ "cmsPageUpdated": "Page CMS \"{{slug}}\" mise à jour",
+ "emailConfigUpdated": "Configuration e-mail mise à jour",
+ "faviconUploaded": "Favicon téléversé",
+ "brandingUpdated": "Paramètres d'identité mis à jour",
+ "generalSettingsUpdated": "Paramètres généraux mis à jour",
+ "securitySettingsUpdated": "Paramètres de sécurité mis à jour",
+ "themeUpdated": "Thème mis à jour",
+ "archiveDownloaded": "Archive téléchargée pour \"{{eventName}}\"",
+ "archiveDeleted": "Archive supprimée pour \"{{eventName}}\"",
+ "archiveRestored": "Archive restaurée pour \"{{eventName}}\"",
+ "systemActivity": "Activité système : {{type}}",
+ "adminProfileUpdated": "Profil admin mis à jour par {{actorName}}"
+ },
+ "notificationToasts": {
+ "markedAllRead": "Toutes les notifications sont lues",
+ "clearedAll": "{{count}} notifications effacées",
+ "profileUpdated": "Profil admin mis à jour"
+ },
+ "markAsRead": "Marquer comme lu",
+ "markAllAsRead": "Tout marquer comme lu",
+ "notificationSettings": "Paramètres de notification",
+ "changePassword": "Changer le mot de passe",
+ "darkMode": "Passer au mode sombre",
+ "lightMode": "Passer au mode clair",
+ "accountSettings": {
+ "title": "Compte admin",
+ "description": "Modifier les accès de connexion à PicPeak.",
+ "username": "Nom d'utilisateur",
+ "usernamePlaceholder": "Admin",
+ "email": "E-mail",
+ "emailPlaceholder": "admin@exemple.com",
+ "updateButton": "Mettre à jour le profil"
+ },
+ "profileUpdateError": "Impossible de mettre à jour le profil. Réessayez.",
+ "loadingDashboard": "Chargement du tableau de bord...",
+ "activeEvents": "Événements actifs",
+ "expiringSoon": "Expire bientôt",
+ "next7Days": "7 prochains jours",
+ "totalViews": "Vues totales",
+ "downloads": "Téléchargements",
+ "percentFromLastWeek": "{{percent}}% depuis la semaine dernière",
+ "dashboardSubtitle": "Bon retour ! Voici l'état de vos galeries.",
+ "eventsExpiringSoon": "Événements expirant bientôt",
+ "noEventsExpiring": "Aucun événement n'expire dans les 7 prochains jours",
+ "daysLeft": "{{count}} jour restant",
+ "daysLeft_plural": "{{count}} jours restants",
+ "viewAllExpiringEvents": "Voir les {{count}} événements expirants",
+ "noRecentActivity": "Aucune activité récente",
+ "viewAllActivity": "Toute l'activité",
+ "quickActions": "Actions rapides",
+ "viewArchives": "Voir les archives",
+ "analytics": "Analytique",
+ "activities": {
+ "event_created": "Nouvel événement : {{eventName}}",
+ "photos_uploaded": "{{count}} photos téléversées : {{eventName}}",
+ "event_archived": "Événement archivé : {{eventName}}",
+ "archive_restored": "Archive restaurée : {{eventName}}",
+ "archive_deleted": "Archive supprimée : {{eventName}}",
+ "archive_downloaded": "Archive téléchargée : {{eventName}}",
+ "email_config_updated": "Configuration e-mail mise à jour",
+ "email_template_updated": "Modèle d'e-mail mis à jour : {{template}}",
+ "branding_updated": "Identité visuelle mise à jour",
+ "theme_updated": "Thème mis à jour",
+ "bulk_download": "{{count}} photos téléchargées : {{eventName}}",
+ "gallery_password_entry": "Mot de passe saisi pour {{eventName}}",
+ "expiration_warning_viewed": "Avertissement d'expiration vu pour {{eventName}}",
+ "feedback_settings_updated": "Paramètres d'avis mis à jour",
+ "feedback_moderated": "Avis modéré",
+ "feedback_deleted": "Avis supprimé",
+ "photo_like": "Photo aimée dans {{eventName}}",
+ "photo_favorite": "Photo mise en favoris dans {{eventName}}",
+ "photo_rating": "Photo notée dans {{eventName}}",
+ "photo_comment": "Commentaire sur photo dans {{eventName}}",
+ "guest_feedback_like": "Un invité a aimé une photo dans {{eventName}}",
+ "guest_feedback_favorite": "Un invité a mis une photo en favoris dans {{eventName}}",
+ "guest_feedback_rating": "Un invité a noté une photo dans {{eventName}}",
+ "guest_feedback_comment": "Un invité a commenté une photo dans {{eventName}}",
+ "word_filter_added": "Filtre de mot ajouté",
+ "external_import_completed": "Import externe terminé ({{imported}} importés, {{skipped}} ignorés)",
+ "bulk_archive_completed": "Archivage groupé terminé",
+ "event_activated": "Événement activé : {{eventName}}",
+ "event_deactivated": "Événement désactivé : {{eventName}}",
+ "photo_deleted": "Photo supprimée de {{eventName}}",
+ "photos_bulk_deleted": "{{count}} photos supprimées de {{eventName}}",
+ "settings_updated": "Paramètres mis à jour",
+ "event_updated": "Événement mis à jour : {{eventName}}",
+ "event_renamed": "Événement renommé : {{eventName}}",
+ "event_deleted": "Événement supprimé : {{eventName}}",
+ "password_changed": "Mot de passe changé",
+ "email_resent": "E-mail de création renvoyé pour : {{eventName}}",
+ "category_created": "Catégorie créée : {{categoryName}}",
+ "category_updated": "Catégorie mise à jour : {{categoryName}}",
+ "category_deleted": "Catégorie supprimée : {{categoryName}}",
+ "general_settings_updated": "Paramètres généraux mis à jour",
+ "favicon_uploaded": "Favicon téléversé",
+ "analytics_settings_updated": "Paramètres analytiques mis à jour",
+ "cms_page_updated": "Page CMS mise à jour : {{page}}",
+ "security_settings_updated": "Paramètres de sécurité mis à jour",
+ "password_reset": "Mot de passe réinitialisé pour : {{eventName}}",
+ "admin_logout": "Déconnexion admin {{actorName}}",
+ "system_activity": "Activité système : {{type}}",
+ "unknown": "Activité inconnue"
+ },
+ "userManagement": "Gestion utilisateurs",
+ "inviteUser": "Inviter",
+ "pendingInvitations": "Invitations en attente",
+ "roles": {
+ "super_admin": "Super Admin",
+ "admin": "Admin",
+ "editor": "Éditeur",
+ "viewer": "Spectateur"
+ },
+ "userStatus": {
+ "active": "Actif",
+ "inactive": "Inactif"
+ },
+ "inviteForm": {
+ "email": "Adresse e-mail",
+ "role": "Rôle",
+ "send": "Envoyer l'invitation"
+ },
+ "acceptInvite": {
+ "title": "Accepter l'invitation admin",
+ "username": "Nom d'utilisateur",
+ "password": "Mot de passe",
+ "submit": "Créer le compte"
+ }
+ },
+ "permissions": {
+ "insufficient": "Permissions insuffisantes pour cette action",
+ "viewOnly": "Lecture seule"
+ },
+ "acceptInvitation": {
+ "title": "Accepter l'invitation",
+ "subtitle": "Créez votre compte administrateur",
+ "validating": "Validation de l'invitation...",
+ "invalidToken": "Invitation invalide",
+ "invalidTokenMessage": "Ce lien est invalide ou a expiré. Contactez l'administrateur.",
+ "expiredToken": "Invitation expirée",
+ "expiredTokenMessage": "Cette invitation a expiré.",
+ "alreadyUsed": "Invitation déjà utilisée",
+ "alreadyUsedMessage": "Cette invitation a déjà servi à créer un compte.",
+ "invitedAs": "Vous avez été invité en tant que",
+ "expiresAt": "L'invitation expire",
+ "usernameLabel": "Nom d'utilisateur",
+ "usernamePlaceholder": "Choisissez un nom d'utilisateur",
+ "usernameHelp": "3-50 car., lettres, chiffres, underscores et tirets uniquement",
+ "passwordLabel": "Mot de passe",
+ "passwordPlaceholder": "Créez un mot de passe robuste",
+ "confirmPasswordLabel": "Confirmer le mot de passe",
+ "confirmPasswordPlaceholder": "Confirmez le mot de passe",
+ "passwordStrength": "Force du mot de passe",
+ "requirements": {
+ "title": "Exigences :",
+ "minLength": "Au moins 12 caractères",
+ "uppercase": "Au moins une majuscule",
+ "lowercase": "Au moins une minuscule",
+ "number": "Au moins un chiffre",
+ "special": "Au moins un caractère spécial"
+ },
+ "strength": {
+ "weak": "Faible",
+ "fair": "Passable",
+ "good": "Bon",
+ "strong": "Robuste"
+ },
+ "createAccount": "Créer le compte",
+ "creating": "Création du compte...",
+ "success": "Compte créé !",
+ "successMessage": "Compte créé avec succès. Vous pouvez maintenant vous connecter.",
+ "redirecting": "Redirection vers la connexion dans {{seconds}}...",
+ "goToLogin": "Aller à la connexion",
+ "contactAdminMessage": "Veuillez contacter l'administrateur pour une nouvelle invitation.",
+ "passwordsMatch": "Les mots de passe correspondent",
+ "alreadyHaveAccount": "Déjà un compte ?",
+ "signIn": "Se connecter",
+ "errors": {
+ "usernameRequired": "Nom d'utilisateur requis",
+ "usernameTooShort": "Minimum 3 caractères",
+ "usernameTooLong": "Maximum 50 caractères",
+ "usernameInvalid": "Caractères invalides",
+ "passwordRequired": "Mot de passe requis",
+ "passwordTooShort": "Minimum 12 caractères",
+ "passwordsDoNotMatch": "Les mots de passe ne correspondent pas",
+ "confirmPasswordRequired": "Veuillez confirmer le mot de passe",
+ "usernameTaken": "Ce nom d'utilisateur est déjà pris",
+ "emailTaken": "Un compte existe déjà avec cet e-mail",
+ "genericError": "Échec de la création. Réessayez."
+ }
+ },
+ "errors": {
+ "notFound": "Non trouvé",
+ "galleryNotFound": "Galerie non trouvée",
+ "galleryNotFoundMessage": "Cette galerie n'existe pas ou a été supprimée.",
+ "galleryArchived": "Galerie archivée",
+ "galleryArchivedMessage": "Cette galerie a été archivée et n'est plus accessible.",
+ "unauthorized": "Non autorisé",
+ "forbidden": "Interdit",
+ "serverError": "Erreur serveur",
+ "somethingWentWrong": "Un problème est survenu",
+ "tryAgainLater": "Veuillez réessayer plus tard",
+ "refreshPage": "Actualiser la page",
+ "oopsSomethingWentWrong": "Oups ! Un problème est survenu",
+ "unexpectedError": "Erreur inattendue. Vos données sont en sécurité.",
+ "goToHomepage": "Retour à l'accueil",
+ "errorDetails": "Détails de l'erreur",
+ "requiredFields": "Veuillez remplir tous les champs requis",
+ "enterTestEmail": "Veuillez entrer une adresse e-mail de test",
+ "failedToCreateEvent": "Échec de la création de l'événement",
+ "eventCreationFailed": "Échec de la création de l'événement",
+ "networkError": "Erreur réseau. Vérifiez votre connexion.",
+ "sessionExpired": "Session expirée. Veuillez vous reconnecter."
+ },
+ "validation": {
+ "eventNameRequired": "Nom de l'événement requis",
+ "hostEmailRequired": "E-mail client requis",
+ "hostNameRequired": "Nom client requis",
+ "adminEmailRequired": "E-mail admin requis",
+ "invalidEmailFormat": "Format d'e-mail invalide",
+ "passwordRequired": "Mot de passe requis",
+ "passwordMinLength": "Le mot de passe doit faire au moins 6 caractères",
+ "passwordsDoNotMatch": "Les mots de passe ne correspondent pas",
+ "passwordSecurityRequirements": "Le mot de passe ne respecte pas les critères de sécurité",
+ "expirationRange": "L'expiration doit être entre 1 et 365 jours"
+ },
+ "legal": {
+ "impressum": "Mentions Légales",
+ "datenschutz": "Politique de Confidentialité",
+ "termsOfService": "Conditions Générales d'Utilisation",
+ "cookiePolicy": "Politique de Cookies"
+ },
+ "toast": {
+ "saveSuccess": "Modifications enregistrées",
+ "saveError": "Échec de l'enregistrement",
+ "deleteSuccess": "Suppression réussie",
+ "deleteError": "Échec de la suppression",
+ "uploadSuccess": "Téléversement réussi",
+ "uploadError": "Échec du téléversement",
+ "loginSuccess": "Connexion réussie",
+ "loginError": "Échec de la connexion",
+ "passwordChanged": "Mot de passe modifié",
+ "linkCopied": "Lien copié dans le presse-papier",
+ "eventCreated": "Événement créé",
+ "eventUpdated": "Événement mis à jour",
+ "eventArchived": "Événement archivé",
+ "settingsSaved": "Paramètres enregistrés",
+ "themeUpdated": "Thème mis à jour",
+ "brandingUpdated": "Identité visuelle mise à jour",
+ "categoryAdded": "Catégorie ajoutée",
+ "categoryDeleted": "Catégorie supprimée",
+ "categoryUpdated": "Catégorie mise à jour",
+ "emailConfigSaved": "Configuration e-mail enregistrée",
+ "testEmailSent": "E-mail de test envoyé",
+ "pageUpdated": "Page mise à jour",
+ "archiveRestored": "Archive restaurée",
+ "archiveDeleted": "Archive supprimée définitivement"
+ },
+ "email": {
+ "title": "Configuration E-mail",
+ "subtitle": "Paramétrer les e-mails de notification",
+ "loadingSettings": "Chargement...",
+ "smtpConfiguration": "Configuration SMTP",
+ "smtpHost": "Hôte SMTP",
+ "smtpHostHelp": "Nom d'hôte du serveur e-mail",
+ "smtpPort": "Port SMTP",
+ "smtpPortHelp": "Généralement 587 (TLS), 465 (SSL) ou 25",
+ "smtpSecure": "Utiliser SSL/TLS",
+ "smtpSecureHelp": "Activer pour une transmission sécurisée",
+ "smtpUsername": "Nom d'utilisateur SMTP",
+ "smtpUsernameHelp": "Compte utilisé pour l'envoi",
+ "smtpPassword": "Mot de passe SMTP",
+ "smtpPasswordHelp": "Mot de passe du compte e-mail",
+ "fromDetails": "Expéditeur",
+ "fromEmail": "E-mail expéditeur",
+ "fromEmailHelp": "Adresse apparaissant comme expéditeur",
+ "fromName": "Nom expéditeur",
+ "fromNameHelp": "Nom apparaissant comme expéditeur",
+ "testConfiguration": "Tester la configuration",
+ "testEmail": "E-mail de test",
+ "testEmailHelp": "Envoyer un e-mail pour vérifier les paramètres",
+ "sendTestEmail": "Envoyer l'e-mail test",
+ "saveConfiguration": "Enregistrer la configuration",
+ "emailTemplates": "Modèles d'e-mails",
+ "templateVariables": "Variables disponibles",
+ "previewTemplate": "Aperçu du modèle",
+ "smtpSettings": "Paramètres SMTP",
+ "testEmailSuccess": "E-mail test envoyé",
+ "saveSmtpSettings": "Enregistrer SMTP",
+ "testEmailSection": "E-mail de test",
+ "beforeTesting": "Avant de tester :",
+ "saveSmtpFirst": "Enregistrez d'abord vos paramètres",
+ "ensureFirewall": "Vérifiez que votre pare-feu autorise le SMTP sortant",
+ "gmailAppPassword": "Pour Gmail, utilisez un mot de passe d'application",
+ "testEmailAddressLabel": "Adresse e-mail de test",
+ "sendTestEmailButton": "Envoyer e-mail test",
+ "commonSmtpSettings": "Paramètres SMTP courants :",
+ "editTemplate": "Modifier le modèle",
+ "templateName": "Nom du modèle",
+ "subjectLine": "Objet de l'e-mail",
+ "emailBody": "Corps de l'e-mail",
+ "preview": "Aperçu",
+ "saveChanges": "Enregistrer",
+ "templates": "Modèles",
+ "variableHelp": "Utilisez ces variables. Elles seront remplacées lors de l'envoi.",
+ "port": "Port",
+ "security": "Sécurité",
+ "username": "Utilisateur",
+ "password": "Mot de passe",
+ "enterPassword": "Saisir le mot de passe",
+ "required": "requis",
+ "ignoreSslErrors": "Ignorer les erreurs de certificat SSL/TLS",
+ "ignoreSslWarning": "Attention : Désactiver la vérification rend la connexion vulnérable aux attaques man-in-the-middle. N'activez ceci que si vous avez confiance en votre serveur SMTP."
+ },
+ "cms": {
+ "title": "Pages CMS",
+ "subtitle": "Gérer les pages légales et d'information",
+ "loadingPages": "Chargement...",
+ "pages": "Pages",
+ "previewLinks": "Liens d'aperçu",
+ "englishVersion": "Version Anglaise",
+ "germanVersion": "Version Allemande",
+ "editPage": "Modifier {{page}}",
+ "pageTitle": "Titre de la page",
+ "pageContent": "Contenu de la page",
+ "pageTitlePlaceholder": "Titre...",
+ "saveChanges": "Enregistrer",
+ "lastUpdated": "Dernière mise à jour :",
+ "impressum": "Mentions Légales",
+ "datenschutz": "Politique de Confidentialité",
+ "pageUpdated": "Page mise à jour"
+ },
+ "eventTypes": {
+ "title": "Types d'événements",
+ "subtitle": "Personnaliser les types d'événements et leurs thèmes par défaut",
+ "loading": "Chargement...",
+ "loadError": "Échec du chargement",
+ "createNew": "Nouveau type",
+ "edit": "Modifier le type",
+ "created": "Type créé",
+ "updated": "Type mis à jour",
+ "deleted": "Type supprimé",
+ "createError": "Échec de la création",
+ "updateError": "Échec de la mise à jour",
+ "deleteError": "Échec de la suppression",
+ "searchPlaceholder": "Rechercher...",
+ "showInactive": "Afficher inactifs",
+ "noResults": "Aucun résultat",
+ "empty": "Aucun type d'événement",
+ "system": "Système",
+ "table": {
+ "type": "Type",
+ "slugPrefix": "Préfixe URL",
+ "theme": "Thème par défaut",
+ "status": "Statut",
+ "actions": "Actions"
+ },
+ "form": {
+ "name": "Nom d'affichage",
+ "namePlaceholder": "ex: Shooting Famille",
+ "slugPrefix": "Préfixe URL",
+ "slugPrefixPlaceholder": "ex: famille",
+ "slugPreview": "Exemple d'URL :",
+ "emoji": "Icône",
+ "themePreset": "Thème par défaut",
+ "isActive": "Actif (visible à la création)"
+ },
+ "validation": {
+ "slugFormat": "Lettres, chiffres et tirets uniquement"
+ },
+ "slugInfo": {
+ "title": "Infos préfixe URL :",
+ "description": "Le préfixe sert à générer les URLs de galerie. Exemple avec 'famille' : famille-dupond-2025-01-22"
+ },
+ "deleteConfirm": {
+ "title": "Supprimer le type",
+ "message": "Voulez-vous supprimer",
+ "warning": "Action irréversible. Vérifiez qu'aucun événement ne l'utilise."
+ }
+ },
+ "backup": {
+ "external": {
+ "warning": {
+ "title": "Média externes exclus",
+ "body": "Cette installation utilise des photos de /external-media. Ces originaux sont exclus des sauvegardes. La base de données et les vignettes sont sauvegardées."
+ }
+ },
+ "title": "Gestion des sauvegardes",
+ "subtitle": "Gérer les sauvegardes système et configurer les restaurations.",
+ "tabs": {
+ "dashboard": "Tableau de bord",
+ "configuration": "Configuration",
+ "history": "Historique",
+ "restore": "Restaurer"
+ },
+ "status": {
+ "inProgress": "Sauvegarde en cours...",
+ "lastBackup": "Dernière sauvegarde",
+ "noBackups": "Aucune sauvegarde",
+ "nextBackup": "Prochaine sauvegarde",
+ "notScheduled": "Non planifiée",
+ "enabled": "Activée",
+ "disabled": "Désactivée"
+ },
+ "actions": {
+ "runBackupNow": "Lancer maintenant",
+ "starting": "Démarrage...",
+ "running": "Exécution...",
+ "testConnection": "Tester la connexion",
+ "save": "Enregistrer",
+ "delete": "Supprimer",
+ "view": "Détails",
+ "download": "Télécharger",
+ "refresh": "Actualiser"
+ },
+ "dashboard": {
+ "backupHealth": "Santé des sauvegardes",
+ "healthStatus": {
+ "excellent": "Excellente",
+ "good": "Bonne",
+ "warning": "Attention",
+ "critical": "Critique"
+ },
+ "health": {
+ "title": "Santé Sauvegarde"
+ },
+ "healthMessages": {
+ "noBackups": "Aucune sauvegarde trouvée",
+ "lastBackupFailed": "Échec de la dernière sauvegarde",
+ "upToDate": "À jour",
+ "recent": "Récente",
+ "gettingOld": "Commence à dater",
+ "outdated": "Obsolète"
+ },
+ "stats": {
+ "totalBackups": "Total sauvegardes",
+ "backupSize": "Taille sauvegarde",
+ "lastDuration": "Dernière durée",
+ "backupStatus": "Statut",
+ "last": "Dernier",
+ "files": "fichiers",
+ "minutes": "{{count}}m",
+ "active": "Actif",
+ "inactive": "Inactif",
+ "noBackupsYet": "Pas encore de sauvegardes"
+ },
+ "recentActivity": {
+ "title": "Activité récente"
+ },
+ "notConfigured": {
+ "title": "Non configuré",
+ "message": "Configurez les paramètres avant de lancer une sauvegarde."
+ },
+ "actions": {
+ "runBackupNow": "Sauvegarder maintenant",
+ "running": "En cours..."
+ },
+ "coverage": {
+ "title": "Contenu sauvegardé",
+ "database": "Base de données",
+ "photos": "Photos",
+ "archives": "Archives",
+ "systemFiles": "Fichiers système",
+ "included": "Inclus",
+ "excluded": "Exclu",
+ "optional": "Optionnel"
+ },
+ "storageDestination": "Destination",
+ "nextScheduledBackup": "Prochaine planification",
+ "backupType": "Sauvegarde {{type}}",
+ "noDestinationSet": "Pas de destination"
+ },
+ "configuration": {
+ "enableBackup": "Activer les sauvegardes auto",
+ "enableBackupHelp": "Sauvegardes automatiques selon le calendrier",
+ "destinationType": "Destination",
+ "destinationTypes": {
+ "local": {
+ "name": "Stockage local",
+ "description": "Sur le serveur de l'application"
+ },
+ "rsync": {
+ "name": "Serveur distant (Rsync)",
+ "description": "Sync via SSH/Rsync"
+ },
+ "s3": {
+ "name": "Stockage S3",
+ "description": "Amazon S3 ou compatible"
+ }
+ },
+ "fields": {
+ "destinationPath": "Chemin de destination",
+ "destinationPathHelp": "Répertoire local",
+ "destinationPathPlaceholder": "/chemin/vers/sauvegarde",
+ "rsyncHost": "Hôte distant",
+ "rsyncHostHelp": "IP ou nom d'hôte SSH",
+ "rsyncHostPlaceholder": "sauvegarde.exemple.com",
+ "rsyncUser": "Utilisateur SSH",
+ "rsyncUserHelp": "Pour la connexion SSH",
+ "rsyncUserPlaceholder": "backup-user",
+ "rsyncPath": "Chemin distant",
+ "rsyncPathHelp": "Répertoire sur le serveur distant",
+ "rsyncPathPlaceholder": "/home/backup/photos",
+ "rsyncSshKey": "Clé privée SSH",
+ "rsyncSshKeyHelp": "Optionnel",
+ "rsyncSshKeyPlaceholder": "-----BEGIN RSA PRIVATE KEY-----",
+ "s3Endpoint": "Point de terminaison S3",
+ "s3EndpointHelp": "URL API S3",
+ "s3EndpointPlaceholder": "https://s3.amazonaws.com",
+ "s3Bucket": "Nom du Bucket",
+ "s3BucketHelp": "Bucket de stockage",
+ "s3BucketPlaceholder": "mon-bucket-backup",
+ "s3AccessKey": "ID Clé d'accès",
+ "s3AccessKeyHelp": "Identifiant S3",
+ "s3AccessKeyPlaceholder": "AKIA...",
+ "s3SecretKey": "Clé secrète S3",
+ "s3SecretKeyHelp": "Secret S3",
+ "s3SecretKeyPlaceholder": "wJal...",
+ "s3Region": "Région",
+ "s3RegionHelp": "Ex: us-east-1",
+ "s3RegionPlaceholder": "us-east-1"
+ },
+ "schedule": {
+ "title": "Calendrier",
+ "scheduleType": "Type de calendrier",
+ "scheduleOptions": {
+ "hourly": "Toutes les heures",
+ "daily": "Quotidien",
+ "weekly": "Hebdomadaire",
+ "custom": "Expression Cron personnalisée"
+ },
+ "options": {
+ "hourly": "Heure",
+ "daily": "Jour",
+ "weekly": "Semaine",
+ "custom": "Cron"
+ },
+ "customCron": "Expression Cron",
+ "customCronHelp": "Expression valide (ex: 0 3 * * *)",
+ "retention": "Politique de rétention",
+ "retentionDays": "Conserver pendant",
+ "retentionHelp": "jours (les plus vieux seront effacés)"
+ },
+ "whatToBackup": {
+ "title": "Éléments à sauvegarder",
+ "database": "Base de données",
+ "databaseHelp": "Événements, réglages et comptes",
+ "photos": "Photos",
+ "photosHelp": "Toutes les photos des galeries actives",
+ "archives": "Archives",
+ "archivesHelp": "Fichiers ZIP archivés",
+ "thumbnails": "Vignettes",
+ "thumbnailsHelp": "Aperçus (peuvent être régénérés)",
+ "tempFiles": "Fichiers temporaires",
+ "tempFilesHelp": "En cours de traitement"
+ },
+ "advancedOptions": {
+ "title": "Options avancées",
+ "compression": "Activer la compression",
+ "compressionHelp": "Économise de l'espace",
+ "encryption": "Activer le chiffrement",
+ "encryptionHelp": "Sécurité supplémentaire",
+ "encryptionPassphrase": "Phrase secrète de chiffrement",
+ "encryptionPassphraseHelp": "Nécessaire pour restaurer",
+ "confirmPassphrase": "Confirmer la phrase secrète",
+ "passphrasesDontMatch": "Les phrases ne correspondent pas"
+ },
+ "validation": {
+ "requiredFields": "Remplissez les champs requis",
+ "invalidCron": "Expression Cron invalide",
+ "connectionTestFailed": "Échec du test de connexion",
+ "connectionTestSuccess": "Connexion réussie !"
+ },
+ "messages": {
+ "requiredFields": "Champs requis manquants",
+ "connectionSuccess": "Connexion réussie !",
+ "connectionFailed": "La connexion a échoué"
+ },
+ "testingConnection": "Test en cours...",
+ "saveSettings": "Enregistrer la configuration",
+ "savingSettings": "Enregistrement..."
+ },
+ "history": {
+ "searchPlaceholder": "Rechercher...",
+ "allStatus": "Tous les statuts",
+ "status": {
+ "completed": "Terminé",
+ "failed": "Échoué",
+ "running": "En cours",
+ "partial": "Partiel"
+ },
+ "deleteConfirm": "Supprimer la sauvegarde du {{date}} ?",
+ "noBackups": "Aucune sauvegarde",
+ "tableHeaders": {
+ "date": "Date",
+ "type": "Type",
+ "status": "Statut",
+ "size": "Taille",
+ "duration": "Durée",
+ "actions": "Actions"
+ },
+ "columns": {
+ "status": "Statut",
+ "dateTime": "Date et Heure",
+ "type": "Type",
+ "size": "Taille",
+ "duration": "Durée",
+ "actions": "Actions"
+ },
+ "details": "Détails",
+ "statistics": "Statistiques",
+ "errors": "Erreurs",
+ "backupDetails": {
+ "backupId": "ID Sauvegarde",
+ "startTime": "Heure de début",
+ "endTime": "Heure de fin",
+ "destination": "Destination",
+ "filesProcessed": "Fichiers traités",
+ "totalSize": "Taille totale",
+ "compressionRatio": "Taux de compression",
+ "errorLog": "Journal d'erreurs",
+ "noErrors": "Aucune erreur détectée"
+ },
+ "pagination": {
+ "showing": "Affichage {{from}}-{{to}} sur {{total}}",
+ "previous": "Précédent",
+ "next": "Suivant"
+ },
+ "filter": {
+ "allStatus": "Tous",
+ "completed": "Réussis",
+ "failed": "Échoués",
+ "running": "En cours",
+ "partial": "Partiels"
+ },
+ "noBackupsFound": "Aucune sauvegarde trouvée",
+ "backupsWillAppear": "Les sauvegardes apparaîtront ici",
+ "messages": {
+ "deleteSuccess": "Sauvegarde supprimée"
+ },
+ "details": {
+ "backupDetails": "Détails de la sauvegarde",
+ "destination": "Destination",
+ "started": "Début",
+ "completed": "Fin",
+ "contentBackedUp": "Contenu sauvegardé",
+ "errorDetails": "Détails d'erreur",
+ "manifest": "Manifeste"
+ }
+ },
+ "restore": {
+ "steps": {
+ "selectSource": "Source",
+ "chooseBackup": "Sauvegarde",
+ "restoreOptions": "Options",
+ "reviewConfirm": "Confirmation",
+ "progress": "Progression"
+ },
+ "source": {
+ "title": "Source de restauration",
+ "subtitle": "D'où provient la sauvegarde ?",
+ "local": {
+ "name": "Local",
+ "description": "Fichiers sur le serveur"
+ },
+ "s3": {
+ "name": "S3",
+ "description": "Stockage S3"
+ },
+ "upload": {
+ "name": "Téléverser",
+ "description": "Envoyer un fichier",
+ "comingSoon": "Bientôt disponible"
+ },
+ "configuration": {
+ "s3": "Configuration S3",
+ "endpoint": "URL S3",
+ "bucket": "Nom du Bucket",
+ "accessKey": "Clé d'accès",
+ "secretKey": "Clé secrète"
+ }
+ },
+ "backup": {
+ "title": "Choisir la sauvegarde",
+ "subtitle": "Sélectionnez un point de restauration",
+ "noBackupsFound": "Aucune sauvegarde trouvée",
+ "encrypted": "Sauvegarde chiffrée",
+ "encryptedMessage": "Phrase secrète requise.",
+ "enterPassphrase": "Entrez la phrase secrète",
+ "at": "à"
+ },
+ "restoreTypes": {
+ "full": {
+ "name": "Complète",
+ "description": "Base, photos et archives",
+ "warning": "Remplace toutes les données actuelles"
+ },
+ "database": {
+ "name": "Base de données",
+ "description": "Paramètres, événements, comptes",
+ "warning": "La base actuelle sera remplacée"
+ },
+ "files": {
+ "name": "Fichiers",
+ "description": "Photos et archives uniquement",
+ "warning": "Les fichiers existants peuvent être écrasés"
+ },
+ "selective": {
+ "name": "Sélective",
+ "description": "Éléments au choix",
+ "warning": "Seuls les éléments choisis seront restaurés"
+ }
+ },
+ "options": {
+ "title": "Options de restauration",
+ "subtitle": "Précisez vos choix",
+ "additionalOptions": {
+ "title": "Options supplémentaires",
+ "skipPreBackup": "Ignorer sauvegarde pré-restauration",
+ "skipPreBackupHelp": "Évite de créer une sauvegarde avant l'écrasement.",
+ "force": "Forcer la restauration",
+ "forceHelp": "Outrepasser les avertissements de sécurité (prudence)"
+ }
+ },
+ "confirmation": {
+ "title": "Vérification",
+ "subtitle": "Vérifiez votre configuration",
+ "validation": {
+ "passed": "Validation réussie",
+ "failed": "Échec de validation",
+ "checking": "Vérification en cours..."
+ },
+ "spaceCheck": {
+ "title": "Espace disque",
+ "required": "Requis",
+ "available": "Disponible",
+ "insufficient": "Espace insuffisant"
+ },
+ "summary": {
+ "title": "Résumé",
+ "source": "Source",
+ "backupDate": "Date de sauvegarde",
+ "restoreType": "Type de restauration",
+ "preBackup": "Pré-sauvegarde",
+ "enabled": "Activée",
+ "skipped": "Ignorée"
+ },
+ "warning": {
+ "title": "Avis important",
+ "message": "Cette opération va écraser les données. Assurez-vous d'avoir une sauvegarde. Irréversible."
+ }
+ },
+ "progress": {
+ "title": "Progression",
+ "inProgress": "Restauration en cours...",
+ "completed": "Restauration terminée",
+ "overallProgress": "Progression globale",
+ "current": "Actuel",
+ "statusDetails": "Détails du statut",
+ "restoreLogs": "Journaux",
+ "steps": {
+ "completed": "Terminé",
+ "running": "En cours",
+ "failed": "Échoué",
+ "pending": "En attente"
+ },
+ "success": {
+ "title": "Restauration réussie",
+ "message": "Vos données ont été restaurées."
+ }
+ },
+ "actions": {
+ "back": "Retour",
+ "next": "Suivant",
+ "startRestore": "Lancer la restauration",
+ "starting": "Démarrage...",
+ "validating": "Validation...",
+ "startNewRestore": "Nouvelle restauration"
+ },
+ "messages": {
+ "restoreStarted": "Restauration lancée"
+ }
+ },
+ "messages": {
+ "backupStarted": "Sauvegarde lancée",
+ "backupFailed": "Échec du lancement",
+ "configUpdated": "Configuration mise à jour",
+ "configUpdateFailed": "Échec de mise à jour",
+ "backupDeleted": "Sauvegarde supprimée",
+ "deleteFailed": "Échec de suppression",
+ "testEmailSent": "Connexion réussie !",
+ "testEmailFailed": "La connexion a échoué"
+ }
+ },
+ "cssTemplates": {
+ "title": "Modèles CSS personnalisés",
+ "template": "Modèle",
+ "templateName": "Nom du modèle",
+ "enableTemplate": "Activer ce modèle",
+ "enableHint": "Les modèles activés seront proposées à la création d'événement",
+ "cssContent": "Contenu CSS",
+ "cssHint": "Ciblez avec .gallery-page. Variables : --gallery-bg, --gallery-text, --gallery-accent",
+ "securityNotice": "Sécurité",
+ "securityText": "Le CSS est filtré. Les URLs externes, @import et JavaScript sont interdits.",
+ "resetToDefault": "Réinitialiser",
+ "resetConfirm": "Réinitialiser par défaut ? Vos changements seront perdus.",
+ "unsavedChanges": "Changements non enregistrés",
+ "saveTemplate": "Enregistrer le modèle",
+ "lastUpdated": "Mis à jour le",
+ "saved": "Modèle enregistré",
+ "saveFailed": "Échec de l'enregistrement",
+ "sanitizationWarning": "Certains motifs CSS ont été bloqués pour sécurité",
+ "reset": "Modèle réinitialisé",
+ "resetFailed": "Échec de réinitialisation"
+ },
+ "maintenance": {
+ "title": "Maintenance système",
+ "message": "Maintenance en cours. Nous revenons bientôt.",
+ "expectedCompletion": "Heure de fin prévue :",
+ "checkBackLater": "Réessayez plus tard",
+ "urgentMatters": "Pour une urgence, contactez"
+ },
+ "passwordChange": {
+ "title": "Changer le mot de passe",
+ "currentPassword": "Mot de passe actuel",
+ "newPassword": "Nouveau mot de passe",
+ "confirmPassword": "Confirmer le nouveau mot de passe",
+ "currentPasswordPlaceholder": "Saisissez l'actuel",
+ "newPasswordPlaceholder": "Saisissez le nouveau",
+ "confirmPasswordPlaceholder": "Confirmez le nouveau",
+ "requirements": "Exigences :",
+ "minLength": "6 caractères minimum",
+ "mustDiffer": "Doit être différent de l'actuel",
+ "success": "Mot de passe changé",
+ "failed": "Échec du changement",
+ "currentRequired": "Le mot de passe actuel est requis",
+ "newRequired": "Le nouveau mot de passe est requis",
+ "minLengthError": "Minimum 6 caractères",
+ "confirmRequired": "Veuillez confirmer",
+ "noMatch": "Les mots de passe ne correspondent pas",
+ "mustBeDifferent": "Doit différer du précédent",
+ "cancel": "Annuler"
+ },
+ "mandatoryPasswordChange": {
+ "title": "Changement de mot de passe requis",
+ "description": "Par sécurité, vous devez changer votre mot de passe avant d'accéder au panneau admin.",
+ "success": "Mot de passe changé ! Vous pouvez accéder à l'administration.",
+ "minLength": "12 caractères minimum",
+ "mustContainUpperLower": "Majuscules et minuscules requises",
+ "mustContainNumbers": "Chiffres requis",
+ "mustContainSpecial": "Caractère spécial requis (!@#$%^&*)",
+ "minLengthError": "Minimum 12 caractères",
+ "mustContainLowercase": "Lettre minuscule requise",
+ "mustContainUppercase": "Lettre majuscule requise",
+ "mustContainNumbersError": "Chiffre requis",
+ "mustContainSpecialError": "Caractère spécial requis"
+ },
+ "offline": {
+ "backOnline": "De nouveau en ligne",
+ "noConnection": "Pas de connexion internet"
+ },
+ "download": {
+ "downloading": "Téléchargement...",
+ "percentComplete": "% terminé"
+ },
+ "passwordGenerator": {
+ "weak": "Faible",
+ "fair": "Passable",
+ "good": "Bon",
+ "strong": "Robuste",
+ "generating": "Génération...",
+ "generatePassword": "Générer un mot de passe",
+ "showSuggestions": "Voir suggestions",
+ "moreOptions": "Plus d'options",
+ "suggestions": "Suggestions",
+ "characters": "caractères",
+ "copyPassword": "Copier",
+ "use": "Utiliser",
+ "pattern": "Modèle :",
+ "patternDescription": "Générés selon le nom et la date. Ex: 'Venue2024$August' pour faciliter la mémoire."
+ },
+ "feedback": {
+ "comments": "Commentaires",
+ "addComment": "Ajouter un commentaire",
+ "yourName": "Votre nom",
+ "yourEmail": "Votre e-mail",
+ "writeComment": "Écrire un commentaire...",
+ "submit": "Envoyer",
+ "commentSubmitted": "Commentaire envoyé",
+ "commentError": "Échec de l'envoi",
+ "rateLimited": "Veuillez patienter avant de commenter à nouveau",
+ "commentRequired": "Le commentaire est requis",
+ "nameRequired": "Le nom est requis",
+ "emailRequired": "L'e-mail est requis",
+ "anonymous": "Anonyme",
+ "pendingApproval": "En attente d'approbation",
+ "noComments": "Soyez le premier à commenter !",
+ "rating": "Note",
+ "ratePhoto": "Noter cette photo",
+ "yourRating": "Votre note",
+ "averageRating": "Note moyenne",
+ "totalRatings": "notes",
+ "likes": "J'aime",
+ "favorites": "Favoris",
+ "likePhoto": "Aimer cette photo",
+ "favoritePhoto": "Ajouter aux favoris",
+ "photoFeedback": "Avis sur la photo",
+ "hasFeedback": "Contient des avis",
+ "hasComments": "Contient des commentaires",
+ "hasRating": "Contient des notes",
+ "settings": {
+ "title": "Paramètres des avis",
+ "enableFeedback": "Activer les avis",
+ "feedbackTypes": "Types d'avis",
+ "ratings": "Évaluation (étoiles)",
+ "ratingsDesc": "Notes de 1 à 5 étoiles",
+ "likes": "J'aime",
+ "likesDesc": "Bouton J'aime simple",
+ "comments": "Commentaires",
+ "commentsDesc": "Commentaires textuels",
+ "favorites": "Favoris",
+ "favoritesDesc": "Mise en favoris"
+ }
+ },
+ "filter": {
+ "feedbackFilters": "Filtres d'avis",
+ "clear": "Effacer",
+ "rating": "Note",
+ "allPhotos": "Toutes les photos",
+ "anyRating": "Toutes les notes",
+ "oneStarPlus": "1+ Étoile",
+ "twoStarsPlus": "2+ Étoiles",
+ "threeStarsPlus": "3+ Étoiles",
+ "fourStarsPlus": "4+ Étoiles",
+ "fiveStarsOnly": "5 Étoiles uniquement",
+ "hasLikes": "Avec J'aime",
+ "hasFavorites": "Avec Favoris",
+ "hasComments": "Avec Commentaires"
+ },
+ "adminLogin": {
+ "title": "Connexion Admin",
+ "subtitle": "Connectez-vous pour gérer les galeries",
+ "sessionExpired": "Session expirée. Reconnectez-vous.",
+ "emailLabel": "Adresse e-mail",
+ "emailPlaceholder": "admin@exemple.com",
+ "emailRequired": "L'e-mail est requis",
+ "invalidEmail": "Format invalide",
+ "passwordLabel": "Mot de passe",
+ "passwordPlaceholder": "Saisissez votre mot de passe",
+ "passwordRequired": "Le mot de passe est requis",
+ "passwordMinLength": "Minimum 6 caractères",
+ "rememberMe": "Se souvenir de moi",
+ "forgotPassword": "Mot de passe oublié ?",
+ "signIn": "Se connecter",
+ "loginSuccess": "Connexion réussie !",
+ "networkError": "Erreur réseau. Vérifiez votre connexion.",
+ "tooManyAttempts": "Trop de tentatives. Réessayez plus tard.",
+ "invalidCredentials": "E-mail ou mot de passe invalide",
+ "generalError": "Une erreur est survenue",
+ "needHelp": "Besoin d'aide ? Contactez",
+ "poweredBy": "Propulsé par PicPeak",
+ "devModeHint": "Mode Développement : admin@exemple.com / admin123"
+ },
+ "photoSort": {
+ "defaultSort": "Tri par défaut des photos",
+ "uploadDateNewest": "Date d'envoi (plus récent d'abord)",
+ "uploadDateOldest": "Date d'envoi (plus ancien d'abord)",
+ "captureDateNewest": "Date de prise de vue (plus récente d'abord)",
+ "captureDateOldest": "Date de prise de vue (plus ancienne d'abord)",
+ "filenameAZ": "Nom de fichier (A-Z)",
+ "filenameZA": "Nom de fichier (Z-A)",
+ "dateTaken": "Date de prise de vue"
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/pages/admin/EmailConfigPage.tsx b/frontend/src/pages/admin/EmailConfigPage.tsx
index 60814535..e7d06a6b 100644
--- a/frontend/src/pages/admin/EmailConfigPage.tsx
+++ b/frontend/src/pages/admin/EmailConfigPage.tsx
@@ -23,14 +23,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { emailService, type EmailConfig, type EmailTemplate, type EmailTemplateTranslation } from '../../services/email.service';
import { settingsService } from '../../services/settings.service';
import { useTranslation } from 'react-i18next';
-
-const SUPPORTED_LANGUAGES = [
- { code: 'en', name: 'English', flag: '🇬🇧' },
- { code: 'de', name: 'Deutsch', flag: '🇩🇪' },
- { code: 'nl', name: 'Nederlands', flag: '🇳🇱' },
- { code: 'pt', name: 'Português', flag: '🇧🇷' },
- { code: 'ru', name: 'Русский', flag: '🇷🇺' },
-];
+import { SUPPORTED_LANGUAGES } from "../../components/common/LanguageSelector.tsx";
const defaultTemplateKeys = [
{
@@ -795,7 +788,7 @@ export const EmailConfigPage: React.FC = () => {
: 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200'
}`}
>
- {lang.flag}
+
{lang.name}
{!hasContent && lang.code !== 'en' && (
diff --git a/frontend/src/pages/admin/UserManagementPage.tsx b/frontend/src/pages/admin/UserManagementPage.tsx
index ead1906f..deab0a35 100644
--- a/frontend/src/pages/admin/UserManagementPage.tsx
+++ b/frontend/src/pages/admin/UserManagementPage.tsx
@@ -17,11 +17,12 @@ import {
CheckCircle,
XCircle,
} from 'lucide-react';
-import { parseISO, formatDistanceToNow, isPast } from 'date-fns';
+import { parseISO, isPast } from 'date-fns';
import { Button, Input, Card, Loading } from '../../components/common';
import { userManagementService } from '../../services/userManagement.service';
import type { AdminUser, AdminRole, AdminInvitation } from '../../types';
+import { useLocalizedDate } from "../../hooks";
type TabType = 'users' | 'invitations';
@@ -368,6 +369,7 @@ const ConfirmDialog: React.FC = ({
export const UserManagementPage: React.FC = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
+ const { formatDistanceToNow } = useLocalizedDate()
// State
const [activeTab, setActiveTab] = useState('users');