diff --git a/frontend/src/components/admin/AdminHeader.tsx b/frontend/src/components/admin/AdminHeader.tsx index 092d80ec..5b7e3794 100644 --- a/frontend/src/components/admin/AdminHeader.tsx +++ b/frontend/src/components/admin/AdminHeader.tsx @@ -3,7 +3,6 @@ import { useNavigate } from 'react-router-dom'; import { Menu, User, LogOut, Settings, Bell, Lock, CheckCircle, Trash2, Sun, Moon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useLocalizedDate } from '../../hooks/useLocalizedDate'; -import { useLocalizedTimeAgo } from '../../hooks/useLocalizedTimeAgo'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useAdminAuth } from '../../contexts'; @@ -25,8 +24,7 @@ export const AdminHeader: React.FC = ({ onMenuClick }) => { const { user, logout } = useAdminAuth(); const { isDark, toggle: toggleDarkMode, forcedMode } = useAdminDarkMode(); const { t } = useTranslation(); - const { format } = useLocalizedDate(); - const { formatTimeAgo } = useLocalizedTimeAgo(); + const { format, formatDistanceToNow } = useLocalizedDate(); const [showUserMenu, setShowUserMenu] = useState(false); const [showNotifications, setShowNotifications] = useState(false); const [showPasswordModal, setShowPasswordModal] = useState(false); @@ -190,7 +188,7 @@ export const AdminHeader: React.FC = ({ onMenuClick }) => { {notificationsService.formatNotificationMessage(notification)}

- {formatTimeAgo(notification.createdAt)} + {formatDistanceToNow(notification.createdAt, { addSuffix: true })}

diff --git a/frontend/src/components/admin/BackupDashboard.jsx b/frontend/src/components/admin/BackupDashboard.jsx index 24dadcdb..2623f900 100644 --- a/frontend/src/components/admin/BackupDashboard.jsx +++ b/frontend/src/components/admin/BackupDashboard.jsx @@ -106,7 +106,7 @@ export const BackupDashboard = ({ status, config, onRunBackup, isBackupRunning }

{t('backup.dashboard.health.title')}

- {health.status.charAt(0).toUpperCase() + health.status.slice(1)} + {t(`backup.dashboard.healthStatus.${health.status}`)}
diff --git a/frontend/src/components/common/LanguageSelector.tsx b/frontend/src/components/common/LanguageSelector.tsx index 6a7d2f98..56598ba8 100644 --- a/frontend/src/components/common/LanguageSelector.tsx +++ b/frontend/src/components/common/LanguageSelector.tsx @@ -46,19 +46,28 @@ const NLFlag: React.FC<{ className?: string }> = ({ className = "w-5 h-5" }) => ); -const languages = [ +const FRFlag: React.FC<{ className?: string }> = ({ className = "w-5 h-5" }) => ( + + + + + +); + +export const SUPPORTED_LANGUAGES = [ { code: 'en', name: 'English', Flag: GBFlag }, { code: 'de', name: 'Deutsch', Flag: DEFlag }, { code: 'ru', name: 'Русский', Flag: RUFlag }, { code: 'pt', name: 'Português', Flag: PTBRFlag }, { code: 'nl', name: 'Nederlands', Flag: NLFlag }, + { code: 'fr', name: 'Français', Flag: FRFlag }, ]; export const LanguageSelector: React.FC = () => { const { i18n } = useTranslation(); const [isOpen, setIsOpen] = React.useState(false); - const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0]; + const currentLanguage = SUPPORTED_LANGUAGES.find(lang => lang.code === i18n.language) || SUPPORTED_LANGUAGES[0]; const handleLanguageChange = (languageCode: string) => { i18n.changeLanguage(languageCode); @@ -78,7 +87,7 @@ export const LanguageSelector: React.FC = () => { {isOpen && (
- {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');