-
+
{t('upload.clickToUpload')}
diff --git a/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx b/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx
index 2326dd9f..e0bff52d 100644
--- a/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx
+++ b/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx
@@ -36,6 +36,9 @@ export interface BaseGalleryLayoutProps {
// Client visibility controls (#172)
isClient?: boolean;
onToggleVisibility?: (photoId: number, currentVisibility: string) => void;
+ // Mirror of the admin original-filename toggle (#508). Forwarded to the
+ // lightbox by layouts that mount their own (story/premium).
+ showOriginalFilename?: boolean;
}
export abstract class BaseGalleryLayout extends React.Component {
diff --git a/frontend/src/components/gallery/layouts/CarouselGalleryLayout.tsx b/frontend/src/components/gallery/layouts/CarouselGalleryLayout.tsx
index 6cea392c..f20d040e 100644
--- a/frontend/src/components/gallery/layouts/CarouselGalleryLayout.tsx
+++ b/frontend/src/components/gallery/layouts/CarouselGalleryLayout.tsx
@@ -147,7 +147,7 @@ export const CarouselGalleryLayout: React.FC = ({
)}
- {feedbackOptions?.allowLikes && (
+ {feedbackEnabled && feedbackOptions?.allowLikes && (
= ({
useEnhancedProtection = false,
useCanvasRendering = false,
feedbackEnabled = false,
+ allowLikes = false,
index
}) => {
// Note: height is passed but not used as we maintain aspect ratio via width
@@ -113,15 +120,18 @@ const PhotoCard: React.FC = ({
{isSelected && }
- {/* Like Button */}
-
-
-
+ {/* Like Button — #506: only when feedback master is on AND the
+ per-event "allow likes" sub-toggle is on. */}
+ {feedbackEnabled && allowLikes && (
+
+
+
+ )}
{/* Selection Border */}
{isSelected && (
@@ -177,7 +187,8 @@ export const GalleryPremiumLayout: React.FC = ({
feedbackEnabled = false,
feedbackOptions,
heroPhotoOverride,
- onLogout
+ onLogout,
+ showOriginalFilename = false,
}) => {
// These props are passed by parent but we use our own lightbox, so mark as intentionally unused
void _onPhotoClick;
@@ -225,16 +236,20 @@ export const GalleryPremiumLayout: React.FC = ({
}));
}, [filteredPhotos]);
- // Lightbox slides
+ // Lightbox slides. `title` powers the Captions plugin — only emitted
+ // when the admin has flipped the original-filenames toggle (#508).
const slides = useMemo(() => {
return filteredPhotos.map(photo => ({
src: photo.url,
alt: photo.filename,
width: photo.width || 1200,
height: photo.height || 800,
- download: allowDownloads ? photo.url : undefined
+ download: allowDownloads ? photo.url : undefined,
+ title: showOriginalFilename
+ ? (photo.original_filename || photo.filename)
+ : undefined,
}));
- }, [filteredPhotos, allowDownloads]);
+ }, [filteredPhotos, allowDownloads, showOriginalFilename]);
const handleLike = useCallback(async (photo: Photo, e: React.MouseEvent) => {
e.stopPropagation();
@@ -502,6 +517,7 @@ export const GalleryPremiumLayout: React.FC = ({
useEnhancedProtection={useEnhancedProtection}
useCanvasRendering={useCanvasRendering}
feedbackEnabled={feedbackEnabled}
+ allowLikes={!!feedbackOptions?.allowLikes}
index={photoIndex}
/>
);
@@ -528,7 +544,13 @@ export const GalleryPremiumLayout: React.FC = ({
close={() => setLightboxIndex(-1)}
index={lightboxIndex}
slides={slides}
- plugins={allowDownloads ? [Thumbnails, Zoom, Fullscreen, Download] : [Thumbnails, Zoom, Fullscreen]}
+ plugins={[
+ Thumbnails,
+ Zoom,
+ Fullscreen,
+ ...(allowDownloads ? [Download] : []),
+ ...(showOriginalFilename ? [Captions] : []),
+ ]}
animation={{ fade: 300, swipe: 250 }}
styles={{
container: { backgroundColor: 'rgba(0, 0, 0, 0.95)' },
diff --git a/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx b/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx
index 07a69887..8397ec25 100644
--- a/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx
+++ b/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx
@@ -58,7 +58,8 @@ export const GalleryStoryLayout: React.FC = ({
feedbackOptions,
heroPhotoOverride,
welcomeMessage,
- onLogout
+ onLogout,
+ showOriginalFilename = false,
}) => {
// These props are passed by parent but we use our own feedback system, so mark as intentionally unused
void _onPhotoClick;
@@ -379,6 +380,7 @@ export const GalleryStoryLayout: React.FC = ({
useEnhancedProtection={useEnhancedProtection}
useCanvasRendering={useCanvasRendering}
onFeedbackChange={onFeedbackChange}
+ showOriginalFilename={showOriginalFilename}
/>
)}
diff --git a/frontend/src/components/gallery/layouts/MosaicGalleryLayout.tsx b/frontend/src/components/gallery/layouts/MosaicGalleryLayout.tsx
index d4d6dfa4..2335afe2 100644
--- a/frontend/src/components/gallery/layouts/MosaicGalleryLayout.tsx
+++ b/frontend/src/components/gallery/layouts/MosaicGalleryLayout.tsx
@@ -105,7 +105,7 @@ const MosaicPhoto: React.FC = ({
)}
- {feedbackOptions?.allowLikes && (
+ {feedbackEnabled && feedbackOptions?.allowLikes && (
{
diff --git a/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx b/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx
index ca31f687..279fdbb7 100644
--- a/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx
+++ b/frontend/src/components/gallery/layouts/TimelineGalleryLayout.tsx
@@ -144,7 +144,7 @@ export const TimelineGalleryLayout: React.FC = ({
)}
- {feedbackOptions?.allowLikes && (
+ {feedbackEnabled && feedbackOptions?.allowLikes && (
{
diff --git a/frontend/src/features/settings/hooks/useSettingsState.ts b/frontend/src/features/settings/hooks/useSettingsState.ts
index a1ad55c7..420098f6 100644
--- a/frontend/src/features/settings/hooks/useSettingsState.ts
+++ b/frontend/src/features/settings/hooks/useSettingsState.ts
@@ -16,6 +16,8 @@ export interface GeneralSettings {
max_file_size_mb: number;
max_files_per_upload: number;
allowed_file_types: string;
+ // #509 — re-added after the main-into-beta merge dropped it.
+ max_upload_batch_size_mb: number;
enable_analytics: boolean;
enable_registration: boolean;
maintenance_mode: boolean;
@@ -91,6 +93,7 @@ export function useSettingsState() {
max_file_size_mb: 50,
max_files_per_upload: 500,
allowed_file_types: 'jpg,jpeg,png,gif,webp',
+ max_upload_batch_size_mb: 95,
enable_analytics: true,
enable_registration: false,
maintenance_mode: false,
@@ -177,6 +180,7 @@ export function useSettingsState() {
Math.max(1, toNumber(settings.general_max_files_per_upload, 500))
),
allowed_file_types: settings.general_allowed_file_types || 'jpg,jpeg,png,gif,webp',
+ max_upload_batch_size_mb: toNumber(settings.general_max_upload_batch_size_mb, 95),
enable_analytics: toBoolean(settings.general_enable_analytics, true),
enable_registration: toBoolean(settings.general_enable_registration, false),
maintenance_mode: toBoolean(settings.general_maintenance_mode, false),
diff --git a/frontend/src/features/settings/tabs/GeneralTab.tsx b/frontend/src/features/settings/tabs/GeneralTab.tsx
index 8c7bd491..ba27bc3e 100644
--- a/frontend/src/features/settings/tabs/GeneralTab.tsx
+++ b/frontend/src/features/settings/tabs/GeneralTab.tsx
@@ -162,6 +162,28 @@ export const GeneralTab: React.FC = ({
{t('settings.general.maxFilesPerUploadHelp', { max: MAX_FILES_PER_UPLOAD_LIMIT })}
+
+
+ {t('settings.general.maxUploadBatchSize')}
+
+
{
+ const parsed = parseInt(e.target.value, 10);
+ setGeneralSettings(prev => ({
+ ...prev,
+ max_upload_batch_size_mb: Number.isFinite(parsed)
+ ? Math.max(1, parsed)
+ : prev.max_upload_batch_size_mb
+ }));
+ }}
+ min="1"
+ />
+
+ {t('settings.general.maxUploadBatchSizeHelp')}
+
+
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 63925399..6524a6aa 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -1031,6 +1031,8 @@
"maxFileSize": "Max. Dateigröße (MB)",
"maxFilesPerUpload": "Max. Dateien pro Upload",
"maxFilesPerUploadHelp": "Maximale Anzahl an Fotos pro Upload-Vorgang (1-{{max}}).",
+ "maxUploadBatchSize": "Max. Upload-Paketgröße (MB)",
+ "maxUploadBatchSizeHelp": "Maximale Größe pro Upload-Anfrage. Reduzieren Sie diesen Wert bei Nutzung eines Reverse-Proxys mit Größenbeschränkung (z.B. Cloudflare: 100MB).",
"allowedFileTypes": "Erlaubte Dateitypen",
"allowedFileTypesHelp": "Kommagetrennte Liste von Dateierweiterungen",
"featureToggles": "Funktionsschalter",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index e5948b34..4a686dd8 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -670,6 +670,8 @@
"maxFileSize": "Max File Size (MB)",
"maxFilesPerUpload": "Max Files per Upload",
"maxFilesPerUploadHelp": "Maximum number of photos allowed in a single upload batch (1-{{max}}).",
+ "maxUploadBatchSize": "Max Upload Batch Size (MB)",
+ "maxUploadBatchSizeHelp": "Maximum size per upload request. Lower this if behind a reverse proxy with request size limits (e.g. Cloudflare: 100MB).",
"allowedFileTypes": "Allowed File Types",
"allowedFileTypesHelp": "Comma-separated list of file extensions",
"featureToggles": "Feature Toggles",
diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json
new file mode 100644
index 00000000..dc7ea7aa
--- /dev/null
+++ b/frontend/src/i18n/locales/es.json
@@ -0,0 +1,2472 @@
+{
+ "userManagement": {
+ "title": "Gestión de usuarios",
+ "subtitle": "Administrar usuarios administradores e invitaciones",
+ "loading": "Cargando usuarios...",
+ "loadError": "Error al cargar usuarios. Por favor, inténtalo de nuevo.",
+ "inviteUser": "Invitar usuario",
+ "createInvitation": "Crear invitación",
+ "email": "Correo electrónico",
+ "emailPlaceholder": "usuario@ejemplo.com",
+ "role": "Rol",
+ "selectRole": "Seleccionar un rol",
+ "sendInvitation": "Enviar invitación",
+ "editUser": "Editar usuario",
+ "editingUser": "Editando usuario",
+ "saveChanges": "Guardar cambios",
+ "deactivateUser": "Desactivar usuario",
+ "cancelInvitation": "Cancelar invitación",
+ "invitationSent": "Invitación enviada con éxito",
+ "invitationError": "Error al enviar la invitación",
+ "invitationCancelled": "Invitación cancelada",
+ "cancelInvitationError": "Error al cancelar la invitación",
+ "userUpdated": "Usuario actualizado con éxito",
+ "updateUserError": "Error al actualizar el usuario",
+ "userDeactivated": "Usuario desactivado con éxito",
+ "deactivateUserError": "Error al desactivar el usuario",
+ "noRole": "Sin rol",
+ "neverLoggedIn": "Nunca ha iniciado sesión",
+ "expired": "Expirado",
+ "deactivate": "Desactivar",
+ "cancel": "Cancelar",
+ "tabs": {
+ "users": "Usuarios",
+ "invitations": "Invitaciones"
+ },
+ "stats": {
+ "totalUsers": "Usuarios totales",
+ "activeUsers": "Usuarios activos",
+ "pendingInvitations": "Invitaciones pendientes",
+ "inactiveUsers": "Usuarios inactivos"
+ },
+ "status": {
+ "active": "Activo",
+ "inactive": "Inactivo"
+ },
+ "table": {
+ "user": "Usuario",
+ "email": "Correo",
+ "role": "Rol",
+ "status": "Estado",
+ "lastLogin": "Último acceso",
+ "actions": "Acciones",
+ "invitedBy": "Invitado por",
+ "expires": "Expiración"
+ },
+ "validation": {
+ "emailRequired": "El correo es obligatorio",
+ "emailInvalid": "Formato de correo inválido",
+ "roleRequired": "El rol es obligatorio"
+ },
+ "searchUsersPlaceholder": "Buscar usuarios...",
+ "searchInvitationsPlaceholder": "Buscar invitaciones...",
+ "noUsers": "No se encontraron usuarios",
+ "noUsersFound": "Ningún usuario coincide con tu búsqueda",
+ "noInvitations": "No hay invitaciones pendientes",
+ "noInvitationsFound": "Ninguna invitación coincide con tu búsqueda",
+ "confirmDeactivate": {
+ "title": "Desactivar usuario",
+ "message": "¿Seguro que quieres desactivar a {{name}}? Ya no podrá iniciar sesión."
+ },
+ "confirmCancelInvitation": {
+ "title": "Cancelar invitación",
+ "message": "¿Seguro que quieres cancelar la invitación para {{email}}?"
+ }
+ },
+ "common": {
+ "loading": "Cargando...",
+ "error": "Error",
+ "save": "Guardar",
+ "cancel": "Cancelar",
+ "delete": "Eliminar",
+ "edit": "Editar",
+ "add": "Agregar",
+ "search": "Buscar",
+ "filter": "Filtrar",
+ "sortBy": "Ordenar por",
+ "yes": "Sí",
+ "no": "No",
+ "back": "Atrás",
+ "next": "Siguiente",
+ "previous": "Anterior",
+ "close": "Cerrar",
+ "logout": "Cerrar sesión",
+ "menu": "Menú",
+ "change": "Cambiar",
+ "remove": "Quitar",
+ "download": "Descargar",
+ "downloadAll": "Descargar todo",
+ "uploading": "Subiendo...",
+ "uploaded": "Subido",
+ "photo": "foto",
+ "photos": "fotos",
+ "video": "video",
+ "videos": "videos",
+ "media": "medios",
+ "restore": "Restaurar",
+ "actions": "Acciones",
+ "refresh": "Actualizar",
+ "preview": "Vista previa",
+ "processing": "Procesando...",
+ "upload": "Subir",
+ "days": "días",
+ "customize": "Personalizar",
+ "hide": "Ocultar",
+ "unknown": "Desconocido",
+ "notSet": "No establecido",
+ "of": "de",
+ "up": "Arriba",
+ "select": "Seleccionar",
+ "selected": "Seleccionado",
+ "chunk": "Fragmento",
+ "optional": "opcional"
+ },
+ "upload": {
+ "photoCategory": "Categoría de foto",
+ "noCategory": "Sin categoría",
+ "eventSpecific": "(Específico del evento)",
+ "clickToUpload": "Haz clic para subir o arrastra y suelta",
+ "fileRequirements": "JPEG, PNG o WebP (máx. 50MB por archivo, {{limit}} archivos por subida)",
+ "fileRequirementsMedia": "Imágenes JPEG, PNG o WebP y videos MP4/MOV/WEBM (máx. 50MB por archivo, {{limit}} archivos por subida)",
+ "unsupportedFiles": "Algunos archivos se omitieron porque el formato no es compatible (usa JPEG/PNG/WebP/MP4/MOV/WEBM).",
+ "selectedFiles": "Archivos seleccionados",
+ "uploading": "Subiendo...",
+ "transferring": "Transfiriendo",
+ "processing": "Procesando fotos...",
+ "processingHint": "Los archivos están subidos. PicPeak está generando miniaturas y leyendo metadatos. Puedes salir de esta página — el trabajo sigue en segundo plano.",
+ "processingProgress": "{{complete}} de {{total}} completado",
+ "processingFailed": "{{count}} foto(s) fallaron al procesar",
+ "retryFailed": "Reintentar fallo",
+ "uploadComplete": "¡Subida completada!",
+ "uploadFailed": "Error en la subida",
+ "someFilesFailed": "Algunos archivos fallaron al subir",
+ "replaceByName": "Reemplazar fotos existentes con el mismo nombre",
+ "replacedFiles": "{{count}} foto(s) reemplazada(s)",
+ "uploadPhotos": "Subir fotos",
+ "uploadMedia": "Subir fotos y videos",
+ "importExternal": "Importar desde carpeta externa",
+ "externalImportInfo": "Todas las imágenes de la carpeta seleccionada serán importadas.",
+ "selectExternalFolder": "Seleccionar carpeta externa bajo /external-media",
+ "importFromSelectedFolder": "Importar desde la carpeta seleccionada",
+ "maxFilesReached": "Máximo {{limit}} archivos permitidos",
+ "someFilesSkipped": "Solo se pueden agregar {{allowed}} archivos más (límite {{limit}})",
+ "tooManyFiles": "Máximo {{limit}} archivos pueden subirse a la vez",
+ "limitInfo": "{{selected}} de {{limit}} archivos seleccionados ({{remaining}} restantes)",
+ "limitReached": "Límite de subida alcanzado ({{limit}} archivos por lote)",
+ "uploadingChunks": "Subiendo {{count}} archivos en {{total}} lotes...",
+ "mediaCategory": "Categoría de medios",
+ "uploadAction": "Subir {{count}} archivos"
+ },
+ "navigation": {
+ "dashboard": "Panel",
+ "events": "Eventos",
+ "archives": "Archivos",
+ "settings": "Ajustes",
+ "eventTypes": "Tipos de evento",
+ "branding": "Firma",
+ "analytics": "Analíticas",
+ "emailSettings": "Ajustes de correo",
+ "backup": "Copia de seguridad",
+ "cmsPages": "Páginas CMS",
+ "users": "Usuarios"
+ },
+ "archives": {
+ "title": "Archivos",
+ "subtitle": "Administrar galerías de fotos archivadas",
+ "loadingArchives": "Cargando archivos...",
+ "totalArchives": "Archivos totales",
+ "storageUsed": "Almacenamiento usado",
+ "totalPhotos": "Fotos totales",
+ "avgArchiveSize": "Tamaño medio de archivo",
+ "searchPlaceholder": "Buscar archivos...",
+ "allTypes": "Todos los tipos",
+ "wedding": "Boda",
+ "birthday": "Cumpleaños",
+ "corporate": "Corporativo",
+ "other": "Otro",
+ "sortByDate": "Ordenar por fecha",
+ "sortByName": "Ordenar por nombre",
+ "sortBySize": "Ordenar por tamaño",
+ "tableHeaders": {
+ "event": "Evento",
+ "type": "Tipo",
+ "archivedDate": "Fecha archivado",
+ "size": "Tamaño",
+ "photos": "Fotos",
+ "actions": "Acciones"
+ },
+ "noArchivesFound": "No se encontraron archivos",
+ "eventDateNA": "Fecha del evento: N/D",
+ "processing": "Procesando...",
+ "download": "Descargar",
+ "restore": "Restaurar",
+ "delete": "Eliminar",
+ "showing": "Mostrando {{from}} a {{to}} de {{total}} archivos",
+ "page": "Página {{current}} de {{total}}",
+ "storageManagement": "Gestión de almacenamiento",
+ "storageInfo": "Los archivos se almacenan de forma permanente a menos que se eliminen manualmente. Considera implementar una política de retención para gestionar costes de almacenamiento.",
+ "confirmRestore": "¿Seguro que quieres restaurar este archivo? El evento volverá a estar activo.",
+ "confirmDelete": "¿Seguro que quieres eliminar permanentemente este archivo? Esta acción no se puede deshacer.",
+ "restoreSuccess": "Archivo restaurado con éxito",
+ "deleteSuccess": "Archivo eliminado permanentemente"
+ },
+ "auth": {
+ "login": "Iniciar sesión",
+ "password": "Contraseña",
+ "enterPassword": "Introduce la Contraseña de la Galería",
+ "passwordPlaceholder": "Introduce la contraseña de la galería",
+ "invalidPassword": "Contraseña inválida",
+ "wrongPassword": "Contraseña incorrecta. Comprueba tu contraseña e inténtalo de nuevo.",
+ "tooManyAttempts": "Demasiados intentos fallidos. Por favor, inténtalo más tarde.",
+ "sessionExpired": "Sesión expirada",
+ "pleaseEnterPassword": "Por favor, introduce una contraseña",
+ "passwordHint": "La contraseña la proporcionó el organizador del evento. Contacta con ellos si no la tienes."
+ },
+ "gallery": {
+ "title": "Galería de fotos",
+ "welcomeMessage": "Mensaje de bienvenida",
+ "expiresOn": "Expira el",
+ "expires": "Expira",
+ "expired": "Expirado",
+ "daysRemaining": "Quedan {{days}} días",
+ "dayRemaining": "Queda 1 día",
+ "hoursRemaining": "Quedan {{hours}} horas",
+ "expiredMessage": "Esta galería expiró el {{date}}",
+ "contactOrganizer": "Contacta con el organizador del evento si necesitas acceso a estas fotos.",
+ "searchPhotos": "Buscar fotos por nombre de archivo...",
+ "sortByDate": "Ordenar por fecha",
+ "sortByName": "Ordenar por nombre",
+ "sortBySize": "Ordenar por tamaño",
+ "allPhotos": "Todas las fotos",
+ "filter": "Filtrar",
+ "feedbackFilter": "Filtro de comentarios",
+ "all": "Todas",
+ "liked": "Me gusta",
+ "favorited": "Favoritas",
+ "favorites": "Favoritos",
+ "downloadSelected": "Descargar {{count}} seleccionadas",
+ "shareGallery": "Compartir galería",
+ "needHelp": "¿Necesitas ayuda? Contáctanos en",
+ "noPhotosFound": "No se encontraron fotos",
+ "failedToLoad": "Error al cargar las fotos",
+ "tryAgain": "Intentar de nuevo",
+ "loading": "Cargando galería...",
+ "expiredOn": "Esta galería expiró el {{date}}.",
+ "expiresIn": "La galería expira en {{count}} día",
+ "expiresIn_plural": "La galería expira en {{count}} días",
+ "downloadBefore": "Descarga tus fotos antes de que dejen de estar disponibles.",
+ "publicGalleryTitle": "Esta galería es accesible públicamente",
+ "publicGallerySubtitle": "Cargando las fotos...",
+ "viewGallery": "Ver galería",
+ "downloadAll": "Descargar todo",
+ "downloading": "Descargando {{count}} foto...",
+ "downloading_plural": "Descargando {{count}} fotos...",
+ "downloadedPhotos": "¡{{count}} foto descargada!",
+ "downloadedPhotos_plural": "¡{{count}} fotos descargadas!",
+ "downloadError": "Algunas fotos fallaron al descargar",
+ "selectPhotos": "Seleccionar fotos",
+ "cancelSelection": "Cancelar selección",
+ "photosSelected": "{{count}} seleccionadas",
+ "selectAll": "Seleccionar todo",
+ "deselectAll": "Deseleccionar todo",
+ "deleteSelected": "Eliminar seleccionadas",
+ "photosCount": "{{count}} foto",
+ "photosCount_plural": "{{count}} fotos",
+ "searchByFilename": "Buscar por nombre de archivo...",
+ "uncategorized": "Sin categoría",
+ "sortAscending": "Orden ascendente",
+ "sortDescending": "Orden descendente",
+ "remaining": "restantes",
+ "selectPhotosHint": "Consejo: Usa Ctrl+Click (Cmd+Click en Mac) para seleccionar varias fotos rápidamente",
+ "filters": "Filtros",
+ "openFilters": "Abrir filtros",
+ "toggleSidebar": "Alternar barra lateral",
+ "toggleMenu": "Alternar menú",
+ "allCategories": "Todas las categorías",
+ "categories": "Categorías",
+ "mediaType": "Medio",
+ "allMedia": "Todos los medios",
+ "photosOnly": "Fotos",
+ "videosOnly": "Videos",
+ "download": "Descargar",
+ "noMedia": "No se han subido medios aún",
+ "searchPlaceholder": "Buscar fotos...",
+ "sortBy": "Ordenar por",
+ "sortByCaptureDate": "Ordenar por fecha de captura",
+ "sortByRating": "Ordenar por calificación",
+ "photoGallery": "Galería de fotos",
+ "photos": "Fotos",
+ "allRightsReserved": "Todos los derechos reservados",
+ "searchMemories": "Buscar recuerdos...",
+ "thankYou": "Gracias",
+ "thankYouMessage": "Gracias por ser parte de nuestra historia y hacer nuestro día inolvidable.",
+ "feedback": {
+ "title": "Comentarios",
+ "shareThoughts": "Comparte tus pensamientos sobre \"{{name}}\"",
+ "rateThisMoment": "Valora este momento",
+ "comments": "Comentarios",
+ "noComments": "No hay comentarios aún. ¡Sé el primero!",
+ "yourName": "Tu nombre",
+ "yourEmail": "Tu correo",
+ "writeLovelyNote": "Escribe una nota agradable...",
+ "postComment": "Publicar comentario",
+ "anonymous": "Anónimo"
+ },
+ "rated": "Valorado",
+ "commented": "Comentado"
+ },
+ "categories": {
+ "title": "Categorías de fotos",
+ "global": "Categorías globales",
+ "eventSpecific": "Categorías específicas del evento",
+ "addCategory": "Agregar categoría",
+ "organizationInfo": "Organiza tus fotos en categorías. Las categorías ayudan a los invitados a navegar y encontrar tipos específicos de fotos.",
+ "eventSpecificCategories": "Categorías específicas del evento",
+ "noEventSpecificCategories": "No hay categorías específicas del evento. Las categorías globales están disponibles por defecto.",
+ "globalCategoriesAlwaysAvailable": "Categorías globales (siempre disponibles):",
+ "deleteCategoryTitle": "Eliminar categoría",
+ "categoryCreatedSuccess": "Categoría creada con éxito",
+ "categoryDeletedSuccess": "Categoría eliminada con éxito",
+ "failedToCreateCategory": "Error al crear la categoría",
+ "failedToDeleteCategory": "Error al eliminar la categoría",
+ "categoryName": "Nombre de la categoría",
+ "noCategory": "Sin categoría",
+ "noCategoriesYet": "No hay categorías todavía. Crea tu primera categoría para organizar fotos.",
+ "deleteConfirm": "¿Seguro que quieres eliminar \"{{name}}\"?",
+ "cannotDelete": "No se puede eliminar la categoría con fotos. Reasigna las fotos primero.",
+ "setCoverPhoto": "Establecer foto de portada",
+ "removeCoverPhoto": "Quitar foto de portada",
+ "coverPhotoSet": "Foto de portada establecida con éxito",
+ "coverPhotoRemoved": "Foto de portada eliminada",
+ "failedToSetCoverPhoto": "Error al establecer la foto de portada",
+ "categoryHeroHint": "Si no se establece una foto de portada para una categoría, se usará la foto hero por defecto."
+ },
+ "events": {
+ "title": "Eventos",
+ "create": "Crear",
+ "createEvent": "Crear evento",
+ "totalViews": "Vistas totales",
+ "totalDownloads": "Descargas totales",
+ "uniqueVisitors": "Visitantes únicos",
+ "createNewEvent": "Crear nuevo evento",
+ "setupNewGallery": "Configura una nueva galería de fotos para tu evento",
+ "createNewEventSubtitle": "Configura una nueva galería de fotos para tu evento",
+ "eventNamePlaceholder": "p. ej., Boda de Juan y María",
+ "welcomeMessageOptional": "Mensaje de bienvenida (opcional)",
+ "welcomeMessagePlaceholder": "¡Bienvenido a nuestro día especial! Siéntete libre de descargar y compartir estos recuerdos...",
+ "hostEmailPlaceholder": "cliente@ejemplo.com",
+ "adminEmailPlaceholder": "admin@ejemplo.com",
+ "adminEmailPickFromAdmins": "Elegir entre administradores:",
+ "adminEmailCustom": "Correo personalizado",
+ "securityAndAccess": "Seguridad y acceso",
+ "accessAndSecurity": "Acceso y seguridad",
+ "enterPassword": "Introduce contraseña",
+ "passwordPlaceholder": "Introduce una contraseña segura",
+ "confirmPasswordPlaceholder": "Confirmar contraseña",
+ "galleryExpiresOn": "La galería expirará el {{date}}",
+ "guestsWillReceiveWarning": "Los invitados recibirán un email de aviso 7 días antes de la expiración.",
+ "types": {
+ "wedding": "Boda",
+ "birthday": "Cumpleaños",
+ "corporate": "Corporativo",
+ "other": "Otro"
+ },
+ "themes": {
+ "default": "Predeterminado",
+ "oceanBlue": "Azul océano",
+ "royalPurple": "Púrpura real",
+ "roseGold": "Oro rosado",
+ "sunsetAmber": "Ámbar atardecer"
+ },
+ "eventDetails": "Detalles del evento",
+ "eventName": "Nombre del evento",
+ "eventType": "Tipo de evento",
+ "eventDate": "Fecha del evento",
+ "hostEmail": "Correo del cliente",
+ "hostName": "Nombre del cliente",
+ "hostNamePlaceholder": "Antonio Pérez",
+ "adminEmail": "Correo del administrador",
+ "adminNotificationEmail": "Correo de notificación al administrador",
+ "expirationDate": "Fecha de expiración",
+ "active": "Activo",
+ "archived": "Archivado",
+ "photoCount": "{{count}} fotos",
+ "totalSize": "Tamaño total",
+ "shareLink": "Enlace para compartir",
+ "copyLink": "Copiar enlace",
+ "linkCopied": "Enlace copiado!",
+ "viewGallery": "Ver galería",
+ "uploadPhotos": "Subir fotos",
+ "archiveEvent": "Archivar evento",
+ "archiveConfirm": "¿Seguro que quieres archivar este evento? Esta acción no se puede deshacer.",
+ "extendExpiration": "Extender {{days}} días",
+ "backToEvents": "Volver a eventos",
+ "loadingEventDetails": "Cargando detalles del evento...",
+ "saveChanges": "Guardar cambios",
+ "eventExpired": "Este evento ha expirado",
+ "eventExpiresIn": "Este evento expira en {{days}} días",
+ "guestsNoAccess": "Los invitados ya no pueden acceder a la galería. Considera archivar este evento.",
+ "warningEmailsSent": "Se han enviado emails de aviso al cliente.",
+ "overview": "Resumen",
+ "photos": "Fotos",
+ "categories": "Categorías",
+ "eventInformation": "Información del evento",
+ "sourceMode": "Modo de origen",
+ "sourceModeManaged": "Gestionado (subir a PicPeak)",
+ "sourceModeReference": "Referenciar carpeta externa",
+ "sourceModeHelp": "Usa el modo gestionado para subidas directas o apunta a una carpeta /external-media montada cuando uses almacenamiento local.",
+ "externalFolder": "Carpeta externa",
+ "externalFolderHint": "Estas carpetas se leen desde el montaje /external-media dentro de tu contenedor u host.",
+ "externalFolderRequired": "Por favor, selecciona una carpeta externa antes de guardar.",
+ "externalFolderEmpty": "Sin subcarpetas",
+ "clearSelection": "Limpiar",
+ "welcomeMessage": "Mensaje de bienvenida",
+ "noWelcomeMessage": "No hay mensaje de bienvenida",
+ "created": "Creado",
+ "expires": "Expira",
+ "shareWithGuests": "Comparte este enlace con los invitados. Necesitarán la contraseña para acceder a la galería.",
+ "shareWithGuestsPublic": "Comparte este enlace con los invitados. No se requiere contraseña para esta galería.",
+ "resetGalleryPassword": "Restablecer contraseña de la galería",
+ "resendCreationEmail": "Reenviar correo de creación",
+ "creationEmailResent": "El correo de creación se ha puesto en cola para enviar",
+ "failedToResendEmail": "Error al reenviar el correo de creación",
+ "photoStatistics": "Estadísticas de fotos",
+ "totalPhotos": "Total de fotos",
+ "managePhotos": "Gestionar fotos",
+ "actions": "Acciones",
+ "archivingInfo": "Al archivar se creará un archivo ZIP con todas las fotos y se quitará la galería del acceso público.",
+ "statistics": "Estadísticas",
+ "views": "Vistas",
+ "downloads": "Descargas",
+ "noStatistics": "No hay estadísticas disponibles aún",
+ "archiveStatus": "Estado del archivo",
+ "archivedOn": "Archivado el",
+ "downloadArchive": "Descargar archivo",
+ "loadingPhotos": "Cargando fotos...",
+ "photoCategories": "Categorías de fotos",
+ "organizeCategoriesInfo": "Organiza tus fotos en categorías. Las categorías ayudan a los invitados a navegar y encontrar tipos específicos de fotos.",
+ "categoriesTip": "Consejo: Las categorías son específicas para cada evento. Puedes crear categorías como \"Ceremonia\", \"Recepción\", \"Retratos\", etc.",
+ "contactInformation": "Información de contacto",
+ "hostEmailHelp": "El cliente recibirá notificaciones de creación y expiración de la galería",
+ "adminEmailHelp": "Recibirá notificaciones del sistema y confirmaciones de archivo",
+ "securityAccess": "Seguridad y acceso",
+ "galleryPassword": "Contraseña de la galería",
+ "requirePasswordToggle": "Requerir contraseña para esta galería",
+ "requirePasswordToggleHelp": "Desactiva esto si quieres compartir la galería sin contraseña. Cualquiera con el enlace podrá ver las fotos.",
+ "publicGalleryWarning": "Las galerías públicas son accesibles para cualquiera con el enlace. Considera habilitar marcas de agua en descargas y monitorizar la actividad.",
+ "passwordHelperText": "Puedes usar fechas como \"04.07.2025\" o cualquier texto con 6+ caracteres",
+ "confirmPassword": "Confirmar contraseña",
+ "showPasswords": "Mostrar contraseñas",
+ "newPasswordLabel": "Nueva contraseña de la galería",
+ "passwordReset": {
+ "title": "Restablecer contraseña de la galería",
+ "newTitle": "Nueva contraseña",
+ "description": "Establece una nueva contraseña para {{eventName}}, o deja ambos campos vacíos para generar una automáticamente.",
+ "newPasswordLabel": "Nueva contraseña",
+ "placeholder": "Dejar vacío para generar automáticamente",
+ "helperText": "Usa 6+ caracteres, o deja vacío para generar automáticamente",
+ "confirmLabel": "Confirmar contraseña",
+ "sendEmail": "Enviar notificación por correo",
+ "sendEmailHelp": "Notificar al anfitrión sobre el cambio de contraseña",
+ "warning": "Nota: La contraseña anterior dejará de funcionar. Asegúrate de compartir la nueva contraseña con el anfitrión.",
+ "submit": "Restablecer contraseña",
+ "successHeading": "¡Contraseña restablecida con éxito!",
+ "emailSentNote": "Se ha enviado una notificación por correo al anfitrión.",
+ "generatedLabel": "Contraseña generada automáticamente",
+ "saveSecurelyNote": "Importante: Guarda esta contraseña de forma segura. No puede recuperarse una vez cierres esta ventana.",
+ "done": "Listo",
+ "toastSuccess": "Contraseña restablecida con éxito",
+ "toastError": "Error al restablecer la contraseña",
+ "toastCopied": "Contraseña copiada al portapapeles",
+ "errorMinLength": "La contraseña debe tener al menos 6 caracteres",
+ "errorMismatch": "Las contraseñas no coinciden"
+ },
+ "gallerySettings": "Ajustes de la galería",
+ "themeAndStyle": "Tema y estilo",
+ "colorTheme": "Tema de color",
+ "galleryExpiration": "Expiración de la galería",
+ "galleryExpiresIn": "La galería expira en",
+ "daysAfterEvent": "días después de la fecha del evento",
+ "expiresOn": "Expira el",
+ "galleryWillExpireOn": "La galería expirará el {{date}}",
+ "expirationWarning": "Los invitados recibirán un email de aviso 7 días antes de la expiración.",
+ "noExpiration": "Sin expiración",
+ "noExpirationHelp": "Esta galería permanecerá activa hasta que se archive manualmente.",
+ "photoCap": "Límite de fotos",
+ "photoCapHelp": "Número máximo de fotos permitidas. 0 = ilimitado",
+ "userUploads": "Ajustes de subidas de usuarios",
+ "allowUserUploads": "Permitir que los invitados suban fotos",
+ "allowUserUploadsHelp": "Permite a los invitados subir sus propias fotos a esta galería",
+ "allowUserUploadsDescription": "Permite a los invitados subir sus propias fotos a esta galería",
+ "uploadCategory": "Categoría de subida",
+ "selectCategory": "Selecciona una categoría para las subidas de usuarios",
+ "uploadCategoryHelp": "Todas las fotos subidas por usuarios se añadirán a esta categoría",
+ "userUploadWarning": "Las subidas de usuarios serán moderadas y pueden ser eliminadas por los administradores en cualquier momento.",
+ "allowDownloads": "Permitir descargas de fotos",
+ "allowDownloadsHelp": "Permitir a los invitados descargar fotos de esta galería",
+ "downloadPermissions": "Permisos de descarga",
+ "downloadsEnabled": "Descargas habilitadas",
+ "downloadsDisabled": "Descargas deshabilitadas",
+ "downloadProtection": "Protección de descargas",
+ "disableRightClick": "Bloquear menú de clic derecho",
+ "watermarkDownloads": "Agregar marca de agua a las descargas",
+ "enableDevtoolsProtection": "Detectar herramientas de desarrollo",
+ "useCanvasRendering": "Renderizado con canvas (protección avanzada)",
+ "protectionInfo": "Las funciones de protección ayudan a prevenir descargas no autorizadas, pero no pueden bloquear todos los métodos.",
+ "protectionLevelBasic": "Básico - Solo bloqueo de clic derecho",
+ "protectionLevelStandard": "Estándar - Atajos de teclado bloqueados",
+ "protectionLevelEnhanced": "Mejorado - Detección de impresión de pantalla",
+ "protectionLevelMaximum": "Máximo - Detección de DevTools y renderizado con canvas",
+ "heroLogoSettings": "Ajustes del logo hero",
+ "heroLogoVisible": "Mostrar logo en la sección hero",
+ "heroLogoSize": "Tamaño del logo",
+ "heroLogoSizeSmall": "Pequeño",
+ "heroLogoSizeMedium": "Mediano",
+ "heroLogoSizeLarge": "Grande",
+ "heroLogoSizeXLarge": "Extra grande",
+ "heroLogoPosition": "Posición del logo",
+ "heroLogoPositionTop": "Arriba (sobre el título)",
+ "heroLogoPositionCenter": "Centro (entre título y fechas)",
+ "heroLogoPositionBottom": "Abajo (debajo de las fechas)",
+ "heroLogoInfo": "Estos ajustes se aplican cuando la galería usa el diseño Hero. Puedes ocultar el logo o personalizar su tamaño y posición.",
+ "eventCustomLogo": "Logo personalizado del evento",
+ "eventCustomLogoDescription": "Sube un logo personalizado para este evento. Esto sustituye al logo global de marca solo para esta galería.",
+ "uploadEventLogo": "Subir logo",
+ "replaceLogo": "Reemplazar",
+ "removeLogo": "Quitar",
+ "eventLogoUploaded": "Logo del evento subido con éxito",
+ "eventLogoUploadFailed": "Error al subir el logo del evento",
+ "eventLogoRemoved": "Logo del evento eliminado con éxito",
+ "eventLogoRemoveFailed": "Error al eliminar el logo del evento",
+ "heroLogoVisibleLabel": "Logo visible",
+ "heroLogoSizeLabel": "Tamaño",
+ "heroLogoPositionLabel": "Posición",
+ "heroLogoHidden": "Logo oculto",
+ "heroPhoto": "Foto hero",
+ "heroPhotoHelp": "Selecciona una foto hero por defecto para la galería. Puedes sobrescribirla por categoría.",
+ "selectHeroPhoto": "Seleccionar foto hero",
+ "noHeroPhotoSelected": "No se ha seleccionado foto hero",
+ "heroPhotoSelected": "Foto hero seleccionada",
+ "heroImageAnchor": "Posición de recorte de la imagen hero",
+ "heroImageAnchorDescription": "Haz clic en la imagen para establecer el punto focal para el recorte.",
+ "heroImageAnchorTop": "Arriba",
+ "heroImageAnchorCenter": "Centro",
+ "heroImageAnchorBottom": "Abajo",
+ "heroPreview": "Vista previa hero",
+ "noPhotosAvailable": "No hay fotos disponibles",
+ "processingRequest": "Procesando tu solicitud...",
+ "eventTypeWedding": "Boda",
+ "eventTypeBirthday": "Cumpleaños",
+ "eventTypeCorporate": "Corporativo",
+ "eventTypeOther": "Otro",
+ "days30": "30 días",
+ "days60": "60 días",
+ "days90": "90 días",
+ "days365": "1 año",
+ "inactive": "Inactivo",
+ "expired": "Expirado",
+ "draft": "Borrador",
+ "publishAndNotify": "Publicar y notificar al cliente",
+ "publishConfirm": "Esto hará que la galería sea accesible y enviará el correo de notificación al cliente. ¿Continuar?",
+ "publishSuccess": "¡Galería publicada y cliente notificado!",
+ "draftBanner": "Esta galería está en modo borrador. Sube tus fotos y publica cuando estés listo.",
+ "daysLeft": "({{count}} día restante)",
+ "daysLeft_plural": "({{count}} días restantes)",
+ "subtitle": "Gestiona tus galerías de fotos y eventos",
+ "loadingEvents": "Cargando eventos...",
+ "failedToLoadEvents": "Error al cargar eventos",
+ "tryAgain": "Intentar de nuevo",
+ "bulkArchiveSuccess": "Se archivaron correctamente {{count}} eventos",
+ "bulkArchivePartial": "Archivados {{success}} eventos, {{failed}} fallaron",
+ "deleteSelected": "Eliminar seleccionadas",
+ "bulkDelete": {
+ "title": "¿Eliminar permanentemente {{count}} eventos?",
+ "warning": "Esto eliminará permanentemente los eventos seleccionados, todas sus fotos, archivos y registros de auditoría. Esta acción no se puede deshacer.",
+ "passwordLabel": "Vuelve a ingresar tu contraseña para confirmar",
+ "passwordPlaceholder": "Tu contraseña de administrador",
+ "passwordHelp": "Solicitamos tu contraseña como salvaguarda contra eliminaciones masivas accidentales.",
+ "submit": "Eliminar {{count}} eventos",
+ "processing": "Eliminando {{count}} eventos. Esto puede tardar unos minutos — por favor no cierres esta ventana.",
+ "incorrectPassword": "Contraseña incorrecta. No se eliminaron eventos.",
+ "successAll": "Se eliminaron permanentemente {{count}} eventos",
+ "successPartial": "Eliminados {{success}} eventos, {{failed}} fallaron",
+ "errorGeneric": "Error al eliminar eventos"
+ },
+ "searchEventsPlaceholder": "Buscar eventos...",
+ "all": "Todos",
+ "expiring": "A punto de expirar",
+ "eventsSelected": "{{count}} evento seleccionado",
+ "eventsSelected_plural": "{{count}} eventos seleccionados",
+ "clear": "Limpiar",
+ "archiveSelected": "Archivar seleccionados",
+ "publicAccess": "Acceso público",
+ "passwordProtected": "Protegido por contraseña",
+ "newPasswordRequired": "Por favor establece una contraseña antes de habilitar la protección.",
+ "event": "Evento",
+ "type": "Tipo",
+ "date": "Fecha",
+ "status": "Estado",
+ "noEventsFound": "No se encontraron eventos",
+ "stats": {
+ "totalEvents": "Eventos totales",
+ "activeEvents": "Eventos activos",
+ "totalPhotos": "Fotos totales",
+ "expiringEvents": "Próximos a expirar"
+ },
+ "viewDetails": "Ver detalles",
+ "archiveEventAction": "Archivar evento",
+ "downloadArchiveAction": "Descargar archivo",
+ "deleteEvent": "Eliminar evento",
+ "deleteEventConfirm": "¿Seguro que quieres eliminar este evento?",
+ "downloadArchiveSoon": "Descarga de archivo próximamente",
+ "eventExpiredMessage": "Este evento ha expirado",
+ "guestsCannotAccessGallery": "Los invitados ya no pueden acceder a la galería. Considera archivar este evento.",
+ "warningEmailsHaveBeenSent": "Se han enviado correos de aviso al cliente.",
+ "extendSevenDays": "Extender 7 días",
+ "welcomeMessageLabel": "Mensaje de bienvenida",
+ "noWelcomeMessageSet": "No hay mensaje de bienvenida",
+ "createdOn": "Creado",
+ "copy": "Copiar",
+ "copied": "¡Copiado!",
+ "organizingPhotosInfo": "Organiza tus fotos en categorías. Las categorías ayudan a los invitados a navegar y encontrar tipos específicos de fotos.",
+ "archiveStatusTitle": "Estado del archivo",
+ "downloadingArchive": "Descargando archivo {{name}}...",
+ "downloadStarted": "Descarga iniciada",
+ "failedToDownloadArchive": "Error al descargar el archivo",
+ "statisticsNotAvailable": "No hay estadísticas disponibles aún",
+ "photoFilters": "Filtros de fotos",
+ "noStatisticsAvailableYet": "No hay estadísticas disponibles aún",
+ "addPlus": "Agregar+",
+ "galleryTheme": "Tema de la galería",
+ "customizeTheme": "Personalizar tema",
+ "noThemeSet": "No hay tema configurado",
+ "customizingTheme": "Personalizando el tema de la galería",
+ "customizingThemeFor": "Personalizando el tema para {{event}}",
+ "customCssTemplate": "Plantilla CSS personalizada",
+ "customCssTemplateDesc": "Aplica una plantilla CSS personalizada para estilizar la galería con efectos visuales únicos.",
+ "noTemplate": "Sin plantilla",
+ "useThemeOnly": "Usar solo el preset de tema",
+ "customTemplate": "Plantilla personalizada",
+ "rename": {
+ "button": "Renombrar",
+ "title": "Renombrar evento",
+ "validating": "Validando nuevo nombre...",
+ "renamingFiles": "Renombrando archivos...",
+ "complete": "¡Completado!",
+ "failed": "Error al renombrar",
+ "filesRenamed": "{{count}} archivos actualizados",
+ "confirm": "Renombrar evento"
+ },
+ "activeFilter": "Activo",
+ "archivedFilter": "Archivado",
+ "sortByName": "Por nombre",
+ "sortByDate": "Por fecha",
+ "sortByExpiration": "Por expiración",
+ "photosCount": "Fotos",
+ "moreActions": "Más acciones",
+ "copyLinkTooltip": "Copiar enlace",
+ "viewGalleryTooltip": "Ver galería",
+ "uploadPhotosTooltip": "Subir fotos",
+ "editTooltip": "Editar",
+ "archiveTooltip": "Archivar",
+ "noEvents": "No se encontraron eventos",
+ "noEventsDescription": "Crea tu primer evento para comenzar.",
+ "bulkArchive": "Archivar",
+ "confirmBulkArchive": "¿Seguro que quieres archivar {{count}} evento(s)?",
+ "confirmBulkArchiveDescription": "Esta acción no se puede deshacer. Los eventos archivados dejarán de ser accesibles públicamente."
+ },
+ "settings": {
+ "title": "Ajustes del sistema",
+ "subtitle": "Configura ajustes y preferencias a nivel del sistema",
+ "loadingSettings": "Cargando ajustes...",
+ "general": {
+ "title": "General",
+ "siteConfiguration": "Configuración del sitio",
+ "siteUrl": "URL del sitio",
+ "siteUrlHelp": "Se utiliza para generar enlaces de galería en correos",
+ "defaultExpiration": "Expiración por defecto (días)",
+ "defaultExpirationHelp": "Cuánto tiempo permanecen activas las galerías por defecto",
+ "maxFileSize": "Tamaño máximo de archivo (MB)",
+ "maxFileSizeHelp": "Tamaño máximo por foto subida",
+ "maxFilesPerUpload": "Máx. archivos por subida",
+ "maxFilesPerUploadHelp": "Número máximo de fotos permitido en un solo lote de subida (1-{{max}}).",
+ "allowedFileTypes": "Tipos de archivo permitidos",
+ "allowedFileTypesHelp": "Lista separada por comas de extensiones permitidas",
+ "featureToggles": "Activar funciones",
+ "enableAnalytics": "Habilitar seguimiento de analíticas",
+ "enableRegistration": "Permitir auto-registro de administradores",
+ "enableShortGalleryUrls": "Usar URLs cortas para galerías",
+ "enableShortGalleryUrlsHelp": "Elimina el slug del evento en los nuevos enlaces, manteniendo los enlaces existentes.",
+ "maintenanceMode": "Habilitar modo mantenimiento",
+ "language": "Idioma",
+ "defaultLanguage": "Idioma por defecto",
+ "defaultLanguageHelp": "Idioma que se muestra a los invitados antes de iniciar sesión",
+ "defaultWelcomeMessage": "Mensaje de bienvenida por defecto",
+ "welcomeMessage": "Mensaje de bienvenida",
+ "welcomeMessagePlaceholder": "Introduce un mensaje de bienvenida por defecto que se incluirá en los correos de creación de galería",
+ "welcomeMessageHelp": "Este mensaje se incluirá en todos los correos de creación de galería a menos que se sobrescriba al crear un evento",
+ "saveSettings": "Guardar ajustes generales",
+ "saveGeneralSettings": "Guardar ajustes generales",
+ "dateTimeFormat": "Formato de fecha y hora",
+ "dateFormat": "Formato de fecha",
+ "dateFormatHelp": "Cómo se muestran las fechas en correos y en la aplicación",
+ "accountSection": "Cuenta de administrador",
+ "accountUsername": "Usuario administrador",
+ "accountUsernameHelp": "Se muestra en la interfaz de administración y se usa en los registros de actividad.",
+ "accountUsernameRequired": "El nombre de usuario es obligatorio",
+ "accountUsernameLength": "El nombre de usuario debe tener al menos 3 caracteres",
+ "accountEmail": "Correo del administrador",
+ "accountEmailHelp": "Se usa para iniciar sesión y recibir notificaciones de seguridad.",
+ "accountEmailRequired": "Se requiere una dirección de correo",
+ "accountEmailInvalid": "Introduce una dirección de correo válida",
+ "accountSaveButton": "Guardar detalles de la cuenta",
+ "accountSaveSuccess": "Detalles de la cuenta actualizados"
+ },
+ "publicSite": {
+ "tabLabel": "Sitio público",
+ "badge": "Landing pública",
+ "title": "Página de aterrizaje pública",
+ "subtitle": "Publica una página de bienvenida personalizada para los invitados cuando visiten tu dominio.",
+ "loading": "Cargando ajustes del sitio público...",
+ "enabled": "Habilitado",
+ "disabled": "Deshabilitado",
+ "htmlLabel": "HTML de la página de aterrizaje",
+ "htmlPlaceholder": "Proporciona el marcado HTML para la sección hero, secciones y llamadas a la acción.",
+ "htmlHelp": "El HTML bruto se sanea al guardar. Las etiquetas no seguras, scripts inline y atributos no permitidos se eliminan automáticamente. Tokens como {{company_name}}, {{company_tagline}} y {{support_email}} serán sustituidos por valores de Marca.",
+ "cssLabel": "CSS personalizado",
+ "cssPlaceholder": "Overrides CSS opcionales para ajustar diseño, colores o tipografía.",
+ "cssHelp": "Se eliminan imports y URLs JavaScript. Usa selectores CSS estándar para sobrescribir el estilo por defecto.",
+ "saveCta": "Guardar sitio público",
+ "saving": "Guardando...",
+ "saveSuccess": "Ajustes del sitio público guardados.",
+ "saveError": "Error al guardar los ajustes del sitio público.",
+ "resetCta": "Restablecer a predeterminado",
+ "resetting": "Restableciendo...",
+ "resetSuccess": "La plantilla del sitio público se restableció a los valores por defecto.",
+ "resetError": "Error al restablecer la plantilla del sitio público.",
+ "previewTitle": "Vista previa en vivo",
+ "previewSandboxed": "Vista previa en sandbox",
+ "previewDisabled": "Habilita el sitio público para ver la vista previa en vivo.",
+ "sanitizationNotice": "Se eliminan scripts, frames embebidos y atributos inseguros antes de publicar. Solo se permite HTML, enlaces e imágenes seguros.",
+ "htmlRequired": "Proporciona contenido HTML antes de habilitar el sitio público."
+ },
+ "storage": {
+ "title": "Almacenamiento",
+ "overview": "Resumen de almacenamiento",
+ "totalUsed": "Total usado",
+ "archiveStorage": "Almacenamiento de archivos",
+ "storageLimit": "Límite de almacenamiento",
+ "storageLimitHelper": "Establece un límite para el uso de almacenamiento de fotos. Este límite es solo orientativo y se resaltará al alcanzarse.",
+ "softLimitInputLabel": "Límite (GB)",
+ "softLimitHelper": "Introduce el máximo preferido de uso de almacenamiento en gigabytes.",
+ "recommendedSoftLimit": "Límite sugerido",
+ "diskCapacity": "Capacidad del disco",
+ "diskCapacityReported": "Capacidad del disco (reportada)",
+ "diskAvailable": "Disponible",
+ "diskAvailableReported": "Disponible (reportado)",
+ "diskFree": "Libre",
+ "diskFreeReported": "Libre (reportado)",
+ "diskMetricsUnavailable": "Las métricas de disco no están disponibles en Docker Desktop o entornos virtualizados.",
+ "applyRecommended": "Usar recomendado",
+ "applyAvailable": "Igualar disponible",
+ "invalidSoftLimit": "Introduce un número válido para el límite.",
+ "saveSoftLimit": "Guardar límite",
+ "limitNotEnforced": "Este límite es solo orientativo y no se aplica automáticamente.",
+ "overrideTitle": "Anulación manual de capacidad",
+ "diskOverrideEnvNote": "La capacidad de disco se controla mediante variables de entorno y no puede editarse aquí.",
+ "diskOverrideSettingsHelp": "Establece valores de capacidad personalizados cuando Docker reporte cifras poco realistas.",
+ "overrideCapacityLabel": "Capacidad total (GB)",
+ "overrideCapacityHelper": "Deja en blanco para usar la lectura automática.",
+ "overrideAvailableLabel": "Espacio disponible (GB)",
+ "overrideAvailableHelper": "Opcional. Deja en blanco para calcular a partir de la capacidad total menos el almacenamiento usado.",
+ "saveOverride": "Guardar anulación de capacidad",
+ "capacityRequiredForAvailable": "Introduce una capacidad total antes de establecer el espacio disponible.",
+ "availableExceedsCapacity": "El espacio disponible no puede exceder la capacidad total.",
+ "storageUsage": "Uso de almacenamiento",
+ "storageByEvent": "Almacenamiento por evento",
+ "storageManagement": "Gestión de almacenamiento",
+ "storageManagementHelp": "Considera archivar o eliminar eventos antiguos para liberar espacio. Los eventos archivados se comprimen y usan menos almacenamiento que las galerías activas.",
+ "noEventsUsingStorage": "Ningún evento usa almacenamiento",
+ "unlimited": "Ilimitado"
+ },
+ "security": {
+ "title": "Seguridad",
+ "passwordSettings": "Ajustes de contraseña",
+ "minPasswordLength": "Longitud mínima de contraseña",
+ "minPasswordLengthHelp": "Número mínimo de caracteres para contraseñas de galería",
+ "passwordComplexity": "Complejidad de la contraseña",
+ "passwordComplexityHelp": "Nivel de seguridad requerido para contraseñas de galería",
+ "complexitySimple": "Simple (6+ caracteres, cualquier texto)",
+ "complexityModerate": "Moderada (8+ caracteres, mayúsculas/números)",
+ "complexityStrong": "Fuerte (12+ caracteres, mayúsculas/minúsculas/números)",
+ "complexityVeryStrong": "Muy fuerte (12+ caracteres, todos los tipos de caracteres)",
+ "sessionAuth": "Sesión y autenticación",
+ "sessionTimeout": "Tiempo de expiración de sesión (minutos)",
+ "sessionTimeoutHelp": "Tiempo de expiración de sesión de administrador en minutos",
+ "maxLoginAttempts": "Máx. intentos de inicio",
+ "maxLoginAttemptsHelp": "Máximo de intentos fallidos por IP antes del bloqueo",
+ "attemptWindowMinutes": "Ventana de intentos (minutos)",
+ "attemptWindowMinutesHelp": "Tiempo atrás para contar intentos fallidos",
+ "lockoutDurationMinutes": "Duración del bloqueo (minutos)",
+ "lockoutDurationMinutesHelp": "Tiempo que la galería o cuenta permanece bloqueada tras demasiados fallos",
+ "enable2FA": "Habilitar autenticación de dos factores para administradores",
+ "recaptchaSettings": "Ajustes reCAPTCHA",
+ "enableRecaptcha": "Habilitar reCAPTCHA en formularios de inicio",
+ "siteKey": "Clave pública (site key)",
+ "siteKeyHelp": "Tu clave pública reCAPTCHA v2 (pública)",
+ "secretKey": "Clave secreta",
+ "secretKeyHelp": "Tu clave secreta reCAPTCHA v2 (mantener privada)",
+ "recaptchaHelp": "Obtén tus claves reCAPTCHA desde",
+ "saveSettings": "Guardar ajustes de seguridad",
+ "saveSecuritySettings": "Guardar ajustes de seguridad"
+ },
+ "categories": {
+ "title": "Categorías",
+ "about": "Acerca de las categorías de fotos",
+ "aboutText": "Las categorías globales están disponibles para todos los eventos. También puedes crear categorías específicas del evento al editar eventos individuales. Las categorías ayudan a organizar las fotos y permiten a los invitados filtrar por tipo en la galería."
+ },
+ "systemStatus": {
+ "title": "Estado del sistema",
+ "storageOverview": "Resumen de almacenamiento",
+ "systemInfo": "Información del sistema",
+ "databaseInfo": "Información de la base de datos",
+ "platform": "Plataforma",
+ "nodeVersion": "Versión de Node",
+ "uptime": "Tiempo en ejecución",
+ "cpuCores": "Núcleos CPU",
+ "memoryUsage": "Uso de memoria",
+ "memoryUsed": "Memoria usada",
+ "photos": "Fotos",
+ "admins": "Administradores",
+ "dbSize": "Tamaño de la base de datos",
+ "services": "Servicios en segundo plano",
+ "fileWatcher": "Vigilante de archivos",
+ "fileWatcherDesc": "Monitorea nuevas fotos",
+ "expirationChecker": "Comprobador de expiración",
+ "expirationCheckerDesc": "Archiva galerías expiradas",
+ "emailProcessor": "Procesador de correo",
+ "emailProcessorDesc": "Envía correos en cola",
+ "emailQueue": "Estado de la cola de correos",
+ "pending": "Pendiente",
+ "sent": "Enviado",
+ "failed": "Fallado",
+ "lastUpdate": "Última actualización"
+ },
+ "photoDimensions": {
+ "title": "Dimensiones de foto",
+ "totalPhotos": "Fotos totales",
+ "withDimensions": "Con dimensiones",
+ "missingDimensions": "Faltan dimensiones",
+ "repairButton": "Reparar dimensiones",
+ "repairing": "Reparando...",
+ "alreadyRunning": "La reparación ya está en curso",
+ "started": "Reparación iniciada para {{count}} fotos",
+ "noneToRepair": "Todas las fotos ya tienen dimensiones",
+ "resultSuccess": "Última reparación: {{success}} actualizadas, {{failed}} fallidas",
+ "description": "Rellenar ancho/alto faltantes para fotos subidas antes de que se agregara seguimiento de dimensiones. Requerido para diseños Masonry y Mosaic."
+ },
+ "updateNotifications": {
+ "title": "Notificaciones de actualización",
+ "description": "Recibe notificaciones por correo cuando nuevas versiones de PicPeak están disponibles.",
+ "enableEmails": "Habilitar notificaciones por correo",
+ "enableEmailsDesc": "Enviar correo a administradores cuando hay una nueva versión disponible",
+ "recipients": "Destinatarios de correo",
+ "recipientsPlaceholder": "admin@example.com, otro@example.com",
+ "recipientsHelper": "Direcciones de correo separadas por comas. Dejar vacío para enviar a todos los administradores.",
+ "lastNotified": "Última notificación enviada para versión: {{version}}",
+ "checkNow": "Comprobar y notificar",
+ "sendTest": "Enviar correo de prueba",
+ "saved": "Ajustes guardados",
+ "saveError": "Error al guardar ajustes",
+ "emailSent": "Correo de notificación enviado a {{count}} destinatarios",
+ "emailFailed": "Error al enviar notificación",
+ "checkSuccess": "Notificación enviada para nueva versión",
+ "checkNoAction": "Sin necesidad de notificación: {{reason}}",
+ "checkError": "Error al comprobar actualizaciones"
+ },
+ "events": {
+ "title": "Creación de eventos",
+ "requiredFields": "Campos obligatorios",
+ "requiredFieldsDescription": "Configura qué campos de contacto son obligatorios al crear nuevos eventos.",
+ "requireCustomerName": "Requerir nombre del cliente",
+ "requireCustomerNameHelp": "El nombre del cliente debe proporcionarse para nuevos eventos",
+ "requireCustomerEmail": "Requerir correo del cliente",
+ "requireCustomerEmailHelp": "El correo del cliente debe proporcionarse para nuevos eventos",
+ "customerEmailWarning": "Requerido para enviar invitaciones de galería",
+ "requireAdminEmail": "Requerir correo de administrador",
+ "requireAdminEmailHelp": "El correo de administrador debe proporcionarse para nuevos eventos",
+ "adminEmailWarning": "Requerido para recibir notificaciones de eventos",
+ "requireEventDate": "Requerir fecha del evento",
+ "requireEventDateHelp": "La fecha del evento debe proporcionarse al crear eventos",
+ "eventDateWarning": "Las URLs de galería usarán identificadores aleatorios en lugar de fechas",
+ "requireExpiration": "Requerir fecha de expiración",
+ "requireExpirationHelp": "Las galerías deben tener una fecha de expiración",
+ "expirationWarning": "Las galerías sin expiración permanecerán activas hasta ser archivadas manualmente",
+ "saveSettings": "Guardar ajustes de eventos",
+ "noteTitle": "Nota",
+ "noteText": "Estos ajustes solo afectan a la creación de nuevos eventos. Los eventos existentes no se ven afectados. El comportamiento por defecto requiere todos los campos."
+ },
+ "imageSecurity": {
+ "title": "Proteger Imagen",
+ "saveSuccess": "Ajustes de seguridad de imagen guardados",
+ "saveError": "Error al guardar ajustes",
+ "loadError": "Error al cargar ajustes de seguridad de imagen",
+ "defaultProtection": "Ajustes de protección por defecto",
+ "defaultProtectionHelp": "Estos ajustes se aplican a todos los nuevos eventos. Los eventos individuales pueden anular estos valores por defecto.",
+ "protectionLevel": "Nivel de protección por defecto",
+ "imageQuality": "Calidad de imagen por defecto",
+ "fragmentationLevel": "Nivel de fragmentación",
+ "enableDevtools": "Habilitar detección de DevTools por defecto",
+ "enableCanvas": "Habilitar renderizado con canvas por defecto (protección avanzada)",
+ "rateLimiting": "Limitación de velocidad",
+ "rateLimitingHelp": "Limita cuántas imágenes pueden solicitarse para prevenir scraping.",
+ "requestsPerMinute": "Solicitudes por minuto",
+ "requestsPer5Minutes": "Solicitudes por 5 min",
+ "requestsPerHour": "Solicitudes por hora",
+ "securityMonitoring": "Monitoreo de seguridad",
+ "suspiciousThreshold": "Umbral de actividad sospechosa",
+ "autoBlockThreshold": "Umbral de bloqueo automático",
+ "enableMonitoring": "Habilitar monitoreo de seguridad",
+ "blockSuspiciousIps": "Bloquear automáticamente IPs sospechosas",
+ "logEvents": "Registrar eventos de seguridad en la base de datos",
+ "infoTitle": "Acerca de la protección de imágenes",
+ "infoText": "Estas funciones de protección ayudan a prevenir descargas y copias ocasionales, pero no pueden bloquear todos los métodos. Los usuarios determinados aún pueden encontrar formas de capturar imágenes. Considera usar marcas de agua y acuerdos legales para protección integral."
+ },
+ "moderation": {
+ "title": "Moderación",
+ "wordFilters": "Filtros de palabras",
+ "description": "Gestiona palabras que deben filtrarse o bloquearse en comentarios",
+ "addFilter": "Agregar nuevo filtro",
+ "enterWord": "Introduce palabra a filtrar",
+ "searchFilters": "Buscar filtros...",
+ "filterAdded": "Filtro de palabra añadido con éxito",
+ "filterExists": "Este filtro de palabra ya existe",
+ "addError": "Error al añadir filtro de palabra",
+ "filterUpdated": "Filtro de palabra actualizado con éxito",
+ "updateError": "Error al actualizar filtro de palabra",
+ "filterDeleted": "Filtro de palabra eliminado con éxito",
+ "deleteError": "Error al eliminar filtro de palabra",
+ "wordRequired": "Por favor introduce una palabra a filtrar",
+ "confirmDelete": "¿Seguro que quieres eliminar este filtro de palabra?",
+ "loading": "Cargando filtros de palabras...",
+ "noMatchingFilters": "No se encontraron filtros coincidentes",
+ "noFilters": "Aún no hay filtros de palabras configurados",
+ "severityLow": "Bajo",
+ "severityModerate": "Moderado",
+ "severityHigh": "Alto",
+ "severityBlock": "Bloquear",
+ "severityLevels": "Niveles de gravedad",
+ "lowDescription": "Palabra marcada para revisión pero no bloqueada automáticamente",
+ "moderateDescription": "El comentario requiere aprobación manual antes de ser visible",
+ "highDescription": "El comentario se oculta automáticamente y requiere revisión del administrador",
+ "blockDescription": "El comentario se rechaza inmediatamente y no puede enviarse"
+ },
+ "styling": {
+ "title": "CSS personalizado"
+ },
+ "thumbnails": {
+ "title": "Miniaturas",
+ "dimensionsTitle": "Dimensiones y calidad de miniaturas",
+ "dimensionsHelp": "Configura el tamaño y la calidad de las miniaturas generadas automáticamente. Valores más altos producen vistas previas mejores pero aumentan el almacenamiento y los tiempos de carga.",
+ "width": "Ancho (px)",
+ "widthHelp": "50-1000 píxeles",
+ "height": "Alto (px)",
+ "heightHelp": "50-1000 píxeles",
+ "quality": "Calidad",
+ "qualityHelp": "1-100; más alto = mejor calidad pero archivos más grandes",
+ "format": "Formato",
+ "fit": "Modo de ajuste",
+ "fitHelp": "Cómo se redimensionan las imágenes para ajustarse a las dimensiones de la miniatura. \"Cover\" recorta para llenar, \"Contain\" ajusta dentro de los límites.",
+ "fit_cover": "Portada (recortar para llenar)",
+ "fit_contain": "Contener (ajustar dentro)",
+ "fit_fill": "Llenar (estirar)",
+ "fit_inside": "Interior (reducir para ajustar)",
+ "fit_outside": "Exterior (expandir para cubrir)",
+ "regenerateTitle": "Regenerar miniaturas",
+ "regenerateHelp": "Después de cambiar los ajustes de miniaturas, regenera todas las miniaturas existentes para aplicar la nueva configuración. Esto se ejecuta en segundo plano y puede tomar un tiempo para galerías grandes.",
+ "regenerateButton": "Regenerar todas las miniaturas",
+ "regenerateStarted": "Regeneración de miniaturas iniciada",
+ "regenerateError": "Error al iniciar la regeneración de miniaturas",
+ "saveSuccess": "Ajustes de miniaturas guardados",
+ "saveError": "Error al guardar los ajustes de miniaturas",
+ "loadError": "Error al cargar los ajustes de miniaturas",
+ "infoTitle": "Acerca de las miniaturas",
+ "infoText": "Las miniaturas son imágenes de vista previa más pequeñas generadas a partir de tus originales. Aumentar el tamaño o la calidad mejora cómo se ven las fotos en la cuadrícula pero usa más almacenamiento y ancho de banda. Después de cambiar los ajustes, usa \"Regenerar todas las miniaturas\" para actualizar las fotos existentes."
+ },
+ "seo": {
+ "title": "SEO y robots",
+ "indexingTitle": "Indexación en motores de búsqueda",
+ "allowIndexing": "Permitir indexación en motores de búsqueda",
+ "allowIndexingHelp": "Cuando está deshabilitado, todos los rastreadores se bloquean a través de robots.txt. Se recomienda deshabilitado para plataformas privadas de fotos.",
+ "sitemapUrl": "URL del mapa del sitio",
+ "sitemapUrlHelp": "Opcional. Se añade a robots.txt si se proporciona.",
+ "aiBlockingTitle": "Bloqueo de IA y bots",
+ "blockAiCrawlers": "Bloquear rastreadores de IA/LLM",
+ "blockAiCrawlersHelp": "Prevenir que bots de entrenamiento de IA accedan a tu contenido.",
+ "blockedAgents": "Agentes de IA bloqueados",
+ "addAgentPlaceholder": "Introduce nombre del agente...",
+ "blockSocialBots": "Bloquear bots de vista previa de redes sociales",
+ "blockSocialBotsHelp": "Prevenir vistas previas en Twitter, Facebook, LinkedIn, etc.",
+ "metaTagsTitle": "Etiquetas meta y reglas personalizadas",
+ "metaNoindex": "Agregar etiqueta meta noindex",
+ "metaNoindexHelp": "Indica a los motores de búsqueda que no indexen páginas (nivel HTML, complementa robots.txt).",
+ "metaNofollow": "Agregar etiqueta meta nofollow",
+ "metaNofollowHelp": "Indica a los motores de búsqueda que no sigan enlaces en páginas.",
+ "metaNoai": "Agregar etiqueta meta noai/noimageai",
+ "metaNoaiHelp": "Indica que el contenido no debe usarse para entrenamiento de IA.",
+ "customRules": "Reglas personalizadas de robots.txt",
+ "ruleAgentPlaceholder": "Agente de usuario",
+ "rulePathPlaceholder": "Ruta a prohibir",
+ "showPreview": "Mostrar vista previa de robots.txt",
+ "hidePreview": "Ocultar vista previa de robots.txt",
+ "saveSettings": "Guardar ajustes de SEO"
+ },
+ "analytics": {
+ "title": "Analíticas",
+ "umamiIntegration": "Integración de Umami Analytics",
+ "enableUmami": "Habilitar Umami Analytics",
+ "umamiUrl": "URL de Umami",
+ "umamiUrlHelp": "La URL de tu instancia de Umami (p. ej., https://analytics.tudominio.com)",
+ "websiteId": "ID del sitio web",
+ "websiteIdHelp": "Tu ID de sitio web de Umami (encontrado en el panel de Umami)",
+ "shareUrl": "URL de compartición (Opcional)",
+ "shareUrlHelp": "URL de compartición pública para incrustar el panel completo (crear en Umami)",
+ "umamiInfo": "Acerca de Umami Analytics",
+ "umamiInfoText": "Umami es una plataforma de análisis de código abierto y enfocada en privacidad. Realiza seguimiento de vistas de página, visitantes únicos y eventos personalizados sin usar cookies.",
+ "learnMore": "Obtener más información sobre Umami",
+ "saveAnalyticsSettings": "Guardar ajustes de analíticas",
+ "backendAnalytics": "Analíticas de backend",
+ "backendAnalyticsText": "El sistema también realiza un seguimiento de analíticas básicas del lado del servidor para supervisión de seguridad y rendimiento.",
+ "tracked": "Métricas rastreadas",
+ "galleryViews": "Vistas de página de galería",
+ "photoDownloads": "Descargas individuales y en masa",
+ "uniqueVisitors": "Visitantes únicos por IP",
+ "deviceTypes": "Tipos de dispositivo desde agentes de usuario",
+ "privacy": "Privacidad",
+ "privacyText": "Las direcciones IP están hasheadas para privacidad. No se almacena datos personales. Los datos de analíticas se conservan durante 90 días."
+ }
+ },
+ "analytics": {
+ "title": "Panel de analíticas",
+ "titleSimple": "Analíticas",
+ "subtitle": "Seguimiento del rendimiento de las galerías y la interacción de los visitantes",
+ "detailedSubtitle": "Analíticas detalladas impulsadas por Umami",
+ "loadingAnalytics": "Cargando analíticas...",
+ "showSummaryView": "Mostrar resumen",
+ "fullDashboard": "Panel completo",
+ "refresh": "Actualizar",
+ "last7Days": "Últimos 7 días",
+ "last30Days": "Últimos 30 días",
+ "last90Days": "Últimos 90 días",
+ "pageViews": "Páginas vistas",
+ "uniqueVisitors": "Visitantes únicos",
+ "totalDownloads": "Descargas totales",
+ "topGallery": "Galería destacada",
+ "topPages": "Páginas principales",
+ "views": "vistas",
+ "visitors": "visitantes únicos",
+ "topDownloadsByGallery": "Descargas por galería",
+ "deviceBreakdown": "Distribución por dispositivo",
+ "desktop": "Escritorio",
+ "mobile": "Móvil",
+ "tablet": "Tableta",
+ "storageUsage": "Uso de almacenamiento",
+ "used": "Usado",
+ "of": "de",
+ "totalPhotos": "Fotos totales",
+ "activeEvents": "Eventos activos",
+ "notConfigured": "Umami no está configurado",
+ "configureInstructions": "Para ver datos reales, configura Umami en tus variables de entorno y en los ajustes.",
+ "noData": "No hay datos disponibles",
+ "percentChange": "{{percent}}% respecto al periodo anterior"
+ },
+ "branding": {
+ "title": "Marca y temas",
+ "titleFull": "Marca y personalización",
+ "subtitle": "Personaliza la apariencia de tus galerías",
+ "loadingBranding": "Cargando ajustes de firma...",
+ "themeAndStyle": "Tema y estilo",
+ "companyInfo": "Información de la empresa",
+ "companyName": "Nombre de la empresa",
+ "companyNameHelp": "Se muestra en encabezados de galería y correos",
+ "companyTagline": "Lema de la empresa",
+ "companyTaglineHelp": "Una breve descripción de tu negocio",
+ "supportEmail": "Correo de soporte",
+ "supportEmailHelp": "Correo de contacto para soporte a invitados",
+ "footerText": "Texto del pie",
+ "footerTextHelp": "Se muestra al final de las galerías",
+ "logo": "Logo",
+ "currentLogo": "Logo actual",
+ "uploadLogo": "Subir logo",
+ "removeLogo": "Eliminar logo",
+ "logoHelp": "Tamaño recomendado: 200x60px, PNG o JPEG",
+ "favicon": "Favicon",
+ "currentFavicon": "Favicon actual",
+ "uploadFavicon": "Subir favicon",
+ "removeFavicon": "Eliminar favicon",
+ "faviconHelp": "Formato PNG o ICO, tamaño recomendado: 32x32px",
+ "watermark": "Marca de agua",
+ "watermarkSettings": "Ajustes de marca de agua",
+ "enableWatermarks": "Habilitar marcas de agua",
+ "watermarkHelp": "Añade el nombre de tu empresa como marca de agua en las descargas",
+ "watermarkLogo": "Logo de marca de agua",
+ "currentWatermark": "Marca de agua actual",
+ "uploadWatermarkLogo": "Subir logo de marca de agua",
+ "watermarkPosition": "Posición de la marca de agua",
+ "topLeft": "Arriba izquierda",
+ "topRight": "Arriba derecha",
+ "center": "Centro",
+ "bottomLeft": "Abajo izquierda",
+ "bottomRight": "Abajo derecha",
+ "watermarkOpacity": "Opacidad de la marca de agua",
+ "watermarkSize": "Tamaño de la marca de agua",
+ "theme": "Tema",
+ "galleryTheme": "Tema de la galería",
+ "themeCustomization": "Personalización del tema",
+ "selectPreset": "Seleccionar un tema predeterminado",
+ "colors": "Colores",
+ "primaryColor": "Color primario",
+ "secondaryColor": "Color secundario",
+ "accentColor": "Color de acento",
+ "backgroundColor": "Color de fondo",
+ "textColor": "Color del texto",
+ "colorMode": "Modo de color",
+ "colorModeLight": "Claro",
+ "colorModeDark": "Oscuro",
+ "colorModeAuto": "Auto",
+ "colorModeHelp": "Auto sigue la preferencia del sistema del visitante.",
+ "customCSS": "CSS personalizado",
+ "preview": "Vista previa",
+ "previewInNewTab": "Vista previa en nueva pestaña",
+ "reset": "Restablecer",
+ "saveChanges": "Guardar cambios",
+ "applyLivePreview": "Aplicar cambios inmediatamente (Vista previa en vivo)",
+ "eventSpecificThemes": "Temas específicos por evento",
+ "eventThemesInfo": "Puedes sobrescribir estos ajustes globales para eventos individuales al crearlos o editarlos.",
+ "themePresets": "Preajustes de tema",
+ "galleryLayout": "Diseño de galería",
+ "layoutDescriptions": {
+ "grid": "Diseño en cuadrícula clásico con tamaños uniformes",
+ "masonry": "Estilo Pinterest con alturas variables",
+ "carousel": "Presentación a pantalla completa con navegación",
+ "timeline": "Fotos organizadas por fecha",
+ "hero": "Imagen destacada con cuadrícula abajo",
+ "mosaic": "Diseño artístico con tamaños mixtos",
+ "justified": "Filas justificadas preservando proporciones",
+ "gallery-premium": "Tema claro elegante con hero y masonry (Beta)",
+ "gallery-story": "Tema oscuro cinematográfico con secciones (Beta)"
+ },
+ "layoutSettings": "Ajustes de diseño",
+ "photoSpacing": "Espaciado entre fotos",
+ "spacing": {
+ "tight": "Ajustado",
+ "normal": "Normal",
+ "relaxed": "Relajado"
+ },
+ "photoAnimation": "Animación de fotos",
+ "animation": {
+ "none": "Ninguna",
+ "fade": "Desvanecer",
+ "scale": "Escalar",
+ "slide": "Deslizar"
+ },
+ "columns": "Columnas",
+ "mobile": "Móvil",
+ "tablet": "Tableta",
+ "desktop": "Escritorio",
+ "enableAutoplay": "Habilitar reproducción automática",
+ "autoplayInterval": "Intervalo de reproducción (segundos)",
+ "groupPhotosBy": "Agrupar fotos por",
+ "grouping": {
+ "day": "Día",
+ "week": "Semana",
+ "month": "Mes"
+ },
+ "masonryMode": "Modo de layout",
+ "masonryModeOptions": {
+ "columns": "Columnas (estilo Pinterest)",
+ "rows": "Filas (justificado personalizado)",
+ "flickr": "Flickr (justificado probado)",
+ "justified": "Google Photos (algoritmo Knuth-Plass)"
+ },
+ "masonryModeHint": {
+ "columns": "Columnas verticales estilo Pinterest con alturas variables",
+ "rows": "Diseño justificado basado en filas",
+ "flickr": "Algoritmo justificado de Flickr",
+ "justified": "Filas estilo Google Photos usando Knuth-Plass para cortes óptimos"
+ },
+ "targetRowHeight": "Altura objetivo de fila",
+ "targetRowHeightHint": "Altura en píxeles (150-400). Las fotos se escalarán para ajustar las filas.",
+ "lastRowBehavior": "Alineación de la última fila",
+ "lastRowOptions": {
+ "left": "Alineada a la izquierda",
+ "center": "Centrada",
+ "justify": "Justificada (estirar)"
+ },
+ "thumbnailScale": "Escala de miniaturas",
+ "thumbnailScaleOptions": {
+ "xs": "XS — Más fotos",
+ "sm": "SM — Más fotos",
+ "md": "MD — Predeterminado",
+ "lg": "LG — Fotos más grandes",
+ "xl": "XL — Fotos más grandes"
+ },
+ "thumbnailScaleHint": "Ajusta el número de columnas relativo a la cuadrícula base",
+ "showHeroSection": "Mostrar sección Hero",
+ "showHeroSectionHint": "Mostrar imagen destacada encima de la galería justificada",
+ "heroHeight": "Altura de la sección Hero",
+ "heroHeightOptions": {
+ "small": "Pequeña (40-50%)",
+ "medium": "Mediana (50-70%)",
+ "large": "Grande (60-80%)"
+ },
+ "heroOverlayOpacity": "Opacidad de superposición Hero",
+ "heroOverlayHint": "Oscurece la imagen hero para mejorar la legibilidad del texto",
+ "typographyAndStyle": "Tipografía y estilo",
+ "bodyFont": "Fuente del cuerpo",
+ "headingFont": "Fuente de encabezados",
+ "sameAsBody": "Igual que el cuerpo",
+ "fontSize": "Tamaño de fuente",
+ "fontSizes": {
+ "small": "Pequeño",
+ "normal": "Normal",
+ "large": "Grande"
+ },
+ "borderRadius": "Radio de borde",
+ "borderRadiusOptions": {
+ "none": "Ninguno",
+ "small": "Pequeño",
+ "medium": "Medio",
+ "large": "Grande"
+ },
+ "shadowStyle": "Estilo de sombra",
+ "shadowOptions": {
+ "none": "Ninguna",
+ "subtle": "Sutil",
+ "normal": "Normal",
+ "dramatic": "Dramática"
+ },
+ "backgroundPattern": "Fondo",
+ "backgroundOptions": {
+ "none": "Ninguno",
+ "dots": "Puntos",
+ "grid": "Grid",
+ "waves": "Ondas"
+ },
+ "customCSSHelp": "Avanzado: Añade CSS personalizado para ajustar aún más la apariencia",
+ "cssInstructions": {
+ "title": "Cómo usar CSS personalizado",
+ "variables": "Variables CSS del tema",
+ "variablesDesc": "Usa estas variables CSS para coincidir con tus presets de tema:",
+ "layouts": "Layouts personalizados de galería",
+ "layoutsDesc": "Apunta a elementos de la galería con estos selectores:",
+ "glassEffect": "Efecto Glassmorphism",
+ "glassEffectDesc": "Crea efectos modernos de vidrio:",
+ "tip": "Consejo",
+ "tipText": "Usa Plantillas CSS desde Ajustes > Plantillas CSS para diseños preconstruidos como Apple Liquid Glass."
+ },
+ "resetToDefault": "Restablecer a predeterminado",
+ "applyTheme": "Aplicar tema",
+ "customTheme": "Tema personalizado",
+ "customizeTheme": "Personalizar tema",
+ "saveTheme": "Guardar tema",
+ "previewLayout": "Vista previa del diseño",
+ "livePreview": "Vista previa en vivo",
+ "heroPlaceholderText": "La foto hero de tu evento aparecerá aquí",
+ "whiteLabel": "Etiqueta blanca",
+ "hidePoweredBy": "Ocultar la marca \"Powered by PicPeak\"",
+ "hidePoweredByHelp": "Eliminar la atribución de PicPeak del pie de las galerías para una experiencia totalmente white-label",
+ "logoCustomization": "Personalización del logo",
+ "changeLogo": "Cambiar logo",
+ "logoSizeSmall": "Pequeño (32px)",
+ "logoSizeMedium": "Mediano (48px)",
+ "logoSizeLarge": "Grande (64px)",
+ "logoSizeXLarge": "Extra grande (96px)",
+ "logoSizeCustom": "Personalizado",
+ "logoMaxHeight": "Altura máxima (píxels)",
+ "logoMaxHeightHelp": "Establece una altura máxima personalizada para el logo (20-200 píxeles)",
+ "logoPosition": "Posición del logo en el encabezado",
+ "positionLeft": "Izquierda",
+ "positionCenter": "Centro",
+ "positionRight": "Derecha",
+ "logoDisplayMode": "Modo de visualización",
+ "logoOnly": "Solo logo",
+ "textOnly": "Solo nombre de la empresa",
+ "logoAndText": "Logo y nombre",
+ "showLogoInHeader": "Mostrar logo en el encabezado de la galería",
+ "showLogoInHeaderHelp": "Mostrar el logo en la barra de encabezado principal",
+ "showLogoInHero": "Mostrar logo en la sección hero",
+ "showLogoInHeroHelp": "Mostrar el logo en secciones hero (para layouts no grid)",
+ "headerStyle": "Estilo del encabezado",
+ "headerStyleDescription": "Elige cómo aparece el encabezado. El estilo del encabezado es independiente del diseño de fotos.",
+ "headerStyleOptions": {
+ "hero": "Hero",
+ "standard": "Estándar",
+ "banner": "Banner",
+ "minimal": "Minimal",
+ "none": "Sin encabezado"
+ },
+ "headerStyleDescriptions": {
+ "hero": "Imagen de altura completa con información del evento",
+ "standard": "Encabezado compacto con detalles del evento",
+ "banner": "Encabezado estándar con banner de color arriba",
+ "minimal": "Encabezado compacto con info esencial",
+ "none": "Ocultar encabezado por completo"
+ },
+ "heroDividerStyle": "Estilo del divisor",
+ "heroDividerDescription": "Elige la transición entre la imagen hero y el contenido de la galería.",
+ "dividerOptions": {
+ "wave": "Onda",
+ "straight": "Recto",
+ "angle": "Ángulo",
+ "curve": "Curva",
+ "none": "Ninguno"
+ },
+ "controlsStyle": "Estilo de controles",
+ "controlsStyleDescription": "Elige cómo se muestran filtros y controles de la galería.",
+ "controlsStyleOptions": {
+ "classic": "Clásico",
+ "sidebar": "Barra lateral"
+ },
+ "controlsStyleDescriptions": {
+ "classic": "Barra de filtros inline bajo el encabezado",
+ "sidebar": "Botón de menú abre barra lateral con filtros"
+ },
+ "controlsStyleHeroWarning": "Se recomienda la barra lateral para encabezados hero para evitar controles sobre la imagen.",
+ "betaThumbnailWarningTitle": "Resolución de miniaturas baja detectada",
+ "betaThumbnailWarningText": "Tus miniaturas son {{width}}×{{height}}px. Los temas beta muestran fotos más grandes y requieren al menos {{recommended}}×{{recommended}}px para buena calidad. Aumenta las dimensiones en Ajustes > Miniaturas y regenera.",
+ "betaThumbnailWarningLink": "Ir a ajustes de miniaturas"
+ },
+ "admin": {
+ "title": "Panel de administración",
+ "welcome": "Bienvenido de nuevo, {{name}}",
+ "recentActivity": "Actividad reciente",
+ "systemStatus": "Estado del sistema",
+ "totalEvents": "Eventos totales",
+ "activeGalleries": "Galerías activas",
+ "storageUsed": "Almacenamiento usado",
+ "totalPhotos": "Fotos totales",
+ "storagePercent": "{{percent}}% del límite {{limit}}",
+ "storageSoftLimitConfigured": "Límite configurado: {{limit}}",
+ "storageSoftLimitRecommended": "Límite sugerido: {{limit}}",
+ "version": "Versión",
+ "archivedEvents": "Eventos archivados",
+ "systemHealth": "Salud del sistema",
+ "health": {
+ "healthy": "Sano",
+ "warning": "Advertencia",
+ "error": "Error",
+ "checking": "Comprobando..."
+ },
+ "updates": {
+ "available": "Actualización disponible",
+ "newVersion": "La versión {{version}} está disponible",
+ "currentVersion": "Actual: {{version}}",
+ "channel": "Canal: {{channel}}",
+ "channelStable": "Estable",
+ "channelBeta": "Beta",
+ "beta": "BETA",
+ "viewReleaseNotes": "Ver notas de la versión",
+ "updateAvailableShort": "v{{version}} disponible",
+ "checkForUpdates": "Buscar actualizaciones",
+ "upToDate": "Estás al día",
+ "lastChecked": "Última comprobación: {{time}}",
+ "updateNow": "Actualizar ahora",
+ "updateDialog": {
+ "title": "Actualizar PicPeak",
+ "detectedEnv": "Entorno detectado",
+ "beforeUpdating": "Antes de actualizar:",
+ "updateCommands": "Comandos de actualización:",
+ "afterUpdating": "Después de actualizar:",
+ "copyAllCommands": "Copiar todos los comandos",
+ "completeChecklist": "Completa la lista antes de actualizar",
+ "error": "Error al cargar instrucciones de actualización"
+ }
+ },
+ "notifications": "Notificaciones",
+ "viewAllNotifications": "Ver todas las notificaciones",
+ "noNotifications": "No hay notificaciones nuevas",
+ "markAllRead": "Marcar todas como leídas",
+ "clearAll": "Borrar todo",
+ "close": "Cerrar",
+ "noNotificationsMessage": "No hay notificaciones",
+ "notificationMessages": {
+ "eventCreated": "Nuevo evento \"{{eventName}}\" creado",
+ "eventArchived": "Evento \"{{eventName}}\" archivado",
+ "eventUpdated": "Evento \"{{eventName}}\" actualizado",
+ "eventDeleted": "Evento \"{{eventName}}\" eliminado",
+ "photosUploaded": "{{count}} fotos subidas a \"{{eventName}}\"",
+ "photoDeleted": "Foto eliminada de \"{{eventName}}\"",
+ "photosBulkDeleted": "{{count}} fotos eliminadas de \"{{eventName}}\"",
+ "eventExpiring": "El evento \"{{eventName}}\" expira en {{days}} días",
+ "eventExpired": "El evento \"{{eventName}}\" ha expirado",
+ "passwordChanged": "Contraseña cambiada por {{actorName}}",
+ "passwordReset": "Contraseña restablecida para \"{{eventName}}\"",
+ "settingsUpdated": "Ajustes {{type}} actualizados",
+ "emailTemplateUpdated": "Plantilla de correo \"{{template}}\" actualizada",
+ "bulkDownload": "{{count}} fotos descargadas de \"{{eventName}}\"",
+ "storageWarning": "Uso de almacenamiento al {{percentage}}%",
+ "adminLogout": "El administrador {{actorName}} cerró sesión",
+ "categoryCreated": "Categoría \"{{name}}\" creada para \"{{eventName}}\"",
+ "categoryUpdated": "Categoría \"{{name}}\" actualizada en \"{{eventName}}\"",
+ "categoryDeleted": "Categoría \"{{name}}\" eliminada de \"{{eventName}}\"",
+ "cmsPageUpdated": "Página CMS \"{{slug}}\" actualizada",
+ "emailConfigUpdated": "Configuración de correo actualizada",
+ "faviconUploaded": "Favicon subido",
+ "brandingUpdated": "Ajustes de firma actualizados",
+ "generalSettingsUpdated": "Ajustes generales actualizados",
+ "securitySettingsUpdated": "Ajustes de seguridad actualizados",
+ "themeUpdated": "Ajustes de tema actualizados",
+ "archiveDownloaded": "Archivo descargado para \"{{eventName}}\"",
+ "archiveDeleted": "Archivo eliminado para \"{{eventName}}\"",
+ "archiveRestored": "Archivo restaurado para \"{{eventName}}\"",
+ "systemActivity": "Actividad del sistema: {{type}}",
+ "adminProfileUpdated": "Perfil de admin actualizado por {{actorName}}"
+ },
+ "notificationToasts": {
+ "markedAllRead": "Todas las notificaciones marcadas como leídas",
+ "clearedAll": "Se borraron {{count}} notificaciones",
+ "profileUpdated": "Perfil de administrador actualizado"
+ },
+ "markAsRead": "Marcar como leído",
+ "markAllAsRead": "Marcar todo como leído",
+ "notificationSettings": "Ajustes de notificaciones",
+ "changePassword": "Cambiar contraseña",
+ "darkMode": "Cambiar a modo oscuro",
+ "lightMode": "Cambiar a modo claro",
+ "accountSettings": {
+ "title": "Cuenta de administrador",
+ "description": "Actualiza las credenciales usadas para iniciar sesión en PicPeak.",
+ "username": "Usuario",
+ "usernamePlaceholder": "Admin",
+ "email": "Correo",
+ "emailPlaceholder": "admin@example.com",
+ "updateButton": "Actualizar perfil"
+ },
+ "profileUpdateError": "No se pudo actualizar el perfil de admin. Por favor, inténtalo de nuevo.",
+ "loadingDashboard": "Cargando panel...",
+ "activeEvents": "Eventos activos",
+ "expiringSoon": "A punto de expirar",
+ "next7Days": "Próximos 7 días",
+ "totalViews": "Vistas totales",
+ "downloads": "Descargas",
+ "percentFromLastWeek": "{{percent}}% respecto a la semana pasada",
+ "dashboardSubtitle": "¡Bienvenido! Esto es lo que ocurre con tus galerías.",
+ "eventsExpiringSoon": "Eventos próximos a expirar",
+ "noEventsExpiring": "No hay eventos que expiren en los próximos 7 días",
+ "daysLeft": "{{count}} día restante",
+ "daysLeft_plural": "{{count}} días restantes",
+ "viewAllExpiringEvents": "Ver los {{count}} eventos próximos a expirar",
+ "noRecentActivity": "Sin actividad reciente",
+ "viewAllActivity": "Ver toda la actividad",
+ "quickActions": "Accesos rápidos",
+ "viewArchives": "Ver archivos",
+ "analytics": "Analíticas",
+ "activities": {
+ "event_created": "Nuevo evento creado: {{eventName}}",
+ "photos_uploaded": "{{count}} fotos subidas a {{eventName}}",
+ "event_archived": "Evento archivado: {{eventName}}",
+ "archive_restored": "Archivo restaurado: {{eventName}}",
+ "archive_deleted": "Archivo eliminado: {{eventName}}",
+ "archive_downloaded": "Archivo descargado: {{eventName}}",
+ "email_config_updated": "Configuración de correo actualizada",
+ "email_template_updated": "Plantilla de correo actualizada: {{template}}",
+ "branding_updated": "Firma actualizada",
+ "theme_updated": "Tema actualizado",
+ "bulk_download": "{{count}} fotos descargadas de {{eventName}}",
+ "gallery_password_entry": "Contraseña ingresada para {{eventName}}",
+ "expiration_warning_viewed": "Aviso de expiración visto para {{eventName}}",
+ "feedback_settings_updated": "Ajustes de feedback actualizados",
+ "feedback_moderated": "Feedback moderado",
+ "feedback_deleted": "Feedback eliminado",
+ "photo_like": "Foto marcada con me gusta en {{eventName}}",
+ "photo_favorite": "Foto añadida a favoritos en {{eventName}}",
+ "photo_rating": "Foto valorada en {{eventName}}",
+ "photo_comment": "Comentario en foto en {{eventName}}",
+ "guest_feedback_like": "Invitado marcó me gusta en {{eventName}}",
+ "guest_feedback_favorite": "Invitado añadió a favoritos en {{eventName}}",
+ "guest_feedback_rating": "Invitado valoró una foto en {{eventName}}",
+ "guest_feedback_comment": "Invitado comentó en una foto en {{eventName}}",
+ "word_filter_added": "Filtro de palabra añadido",
+ "external_import_completed": "Importación externa completada ({{imported}} importadas, {{skipped}} omitidas)",
+ "bulk_archive_completed": "Archivado masivo completado",
+ "event_activated": "Evento activado: {{eventName}}",
+ "event_deactivated": "Evento desactivado: {{eventName}}",
+ "photo_deleted": "Foto eliminada de {{eventName}}",
+ "photos_bulk_deleted": "{{count}} fotos eliminadas de {{eventName}}",
+ "settings_updated": "Ajustes actualizados",
+ "event_updated": "Evento actualizado: {{eventName}}",
+ "event_renamed": "Evento renombrado: {{eventName}}",
+ "event_deleted": "Evento eliminado: {{eventName}}",
+ "password_changed": "Contraseña cambiada",
+ "email_resent": "Correo de creación reenviado para: {{eventName}}",
+ "category_created": "Categoría creada: {{categoryName}}",
+ "category_updated": "Categoría actualizada: {{categoryName}}",
+ "category_deleted": "Categoría eliminada: {{categoryName}}",
+ "general_settings_updated": "Ajustes generales actualizados",
+ "favicon_uploaded": "Favicon subido",
+ "analytics_settings_updated": "Ajustes de analíticas actualizados",
+ "cms_page_updated": "Página CMS actualizada: {{page}}",
+ "security_settings_updated": "Ajustes de seguridad actualizados",
+ "password_reset": "Contraseña restablecida para: {{eventName}}",
+ "admin_logout": "Administrador {{actorName}} cerró sesión",
+ "system_activity": "Actividad del sistema: {{type}}",
+ "unknown": "Actividad desconocida"
+ },
+ "userManagement": "Gestión de usuarios",
+ "inviteUser": "Invitar usuario",
+ "pendingInvitations": "Invitaciones pendientes",
+ "roles": {
+ "super_admin": "Super administrador",
+ "admin": "Administrador",
+ "editor": "Editor",
+ "viewer": "Espectador"
+ },
+ "userStatus": {
+ "active": "Activo",
+ "inactive": "Inactivo"
+ },
+ "inviteForm": {
+ "email": "Correo electrónico",
+ "role": "Rol",
+ "send": "Enviar invitación"
+ },
+ "acceptInvite": {
+ "title": "Aceptar invitación de admin",
+ "username": "Elige un nombre de usuario",
+ "password": "Crear contraseña",
+ "submit": "Crear cuenta"
+ },
+ "photos": {
+ "hidden": "Ocultas",
+ "hideSelected": "Ocultar",
+ "showSelected": "Mostrar",
+ "hiddenSuccess": "Fotos ocultas a los invitados",
+ "visibleSuccess": "Fotos ahora visibles para invitados"
+ }
+ },
+ "permissions": {
+ "insufficient": "No tienes permiso para realizar esta acción",
+ "viewOnly": "Solo lectura"
+ },
+ "acceptInvitation": {
+ "title": "Aceptar invitación",
+ "subtitle": "Crea tu cuenta de administrador",
+ "validating": "Validando invitación...",
+ "invalidToken": "Invitación inválida",
+ "invalidTokenMessage": "Este enlace de invitación es inválido o ha expirado. Por favor, contacta con tu administrador para una nueva invitación.",
+ "expiredToken": "Invitación expirada",
+ "expiredTokenMessage": "Esta invitación ha expirado. Solicita una nueva invitación a tu administrador.",
+ "alreadyUsed": "Invitación ya usada",
+ "alreadyUsedMessage": "Esta invitación ya fue usada para crear una cuenta.",
+ "invitedAs": "Has sido invitado como",
+ "expiresAt": "La invitación expira",
+ "usernameLabel": "Nombre de usuario",
+ "usernamePlaceholder": "Elige un nombre de usuario",
+ "usernameHelp": "3-50 caracteres; solo letras, números, guiones bajos y guiones",
+ "passwordLabel": "Contraseña",
+ "passwordPlaceholder": "Crea una contraseña segura",
+ "confirmPasswordLabel": "Confirmar contraseña",
+ "confirmPasswordPlaceholder": "Confirma tu contraseña",
+ "passwordStrength": "Robustez de la contraseña",
+ "requirements": {
+ "title": "Requisitos de la contraseña:",
+ "minLength": "Al menos 12 caracteres",
+ "uppercase": "Al menos una letra mayúscula",
+ "lowercase": "Al menos una letra minúscula",
+ "number": "Al menos un número",
+ "special": "Al menos un carácter especial"
+ },
+ "strength": {
+ "weak": "Débil",
+ "fair": "Aceptable",
+ "good": "Buena",
+ "strong": "Fuerte"
+ },
+ "createAccount": "Crear cuenta",
+ "creating": "Creando cuenta...",
+ "success": "¡Cuenta creada!",
+ "successMessage": "Tu cuenta se ha creado correctamente. Ahora puedes iniciar sesión con tus credenciales.",
+ "redirecting": "Redirigiendo al login en {{seconds}}...",
+ "goToLogin": "Ir al inicio de sesión",
+ "contactAdminMessage": "Por favor, contacta con tu administrador para solicitar una nueva invitación.",
+ "passwordsMatch": "Las contraseñas coinciden",
+ "alreadyHaveAccount": "¿Ya tienes una cuenta?",
+ "signIn": "Iniciar sesión",
+ "errors": {
+ "usernameRequired": "El nombre de usuario es obligatorio",
+ "usernameTooShort": "El nombre de usuario debe tener al menos 3 caracteres",
+ "usernameTooLong": "El nombre de usuario debe tener como máximo 50 caracteres",
+ "usernameInvalid": "El nombre de usuario solo puede contener letras, números, guiones bajos y guiones",
+ "passwordRequired": "La contraseña es obligatoria",
+ "passwordTooShort": "La contraseña debe tener al menos 12 caracteres",
+ "passwordsDoNotMatch": "Las contraseñas no coinciden",
+ "confirmPasswordRequired": "Por favor confirma tu contraseña",
+ "usernameTaken": "Este nombre de usuario ya está en uso",
+ "emailTaken": "Ya existe una cuenta con este correo",
+ "genericError": "Error al crear la cuenta. Por favor, inténtalo de nuevo."
+ }
+ },
+ "errors": {
+ "notFound": "No encontrado",
+ "galleryNotFound": "Galería no encontrada",
+ "galleryNotFoundMessage": "Esta galería no existe o ha sido eliminada.",
+ "galleryArchived": "Galería archivada",
+ "galleryArchivedMessage": "Esta galería ha sido archivada y ya no es accesible. Contacta con el organizador si necesitas acceso.",
+ "unauthorized": "No autorizado",
+ "forbidden": "Prohibido",
+ "serverError": "Error del servidor",
+ "somethingWentWrong": "Algo salió mal",
+ "tryAgainLater": "Por favor, inténtalo más tarde",
+ "refreshPage": "Actualizar página",
+ "oopsSomethingWentWrong": "¡Vaya! Algo salió mal",
+ "unexpectedError": "Hemos encontrado un error inesperado. No te preocupes, tus datos están seguros.",
+ "goToHomepage": "Ir a la página principal",
+ "errorDetails": "Detalles del error",
+ "requiredFields": "Por favor rellena todos los campos obligatorios",
+ "enterTestEmail": "Introduce una dirección de correo de prueba",
+ "failedToCreateEvent": "Error al crear el evento",
+ "eventCreationFailed": "Error al crear el evento",
+ "networkError": "Error de red. Comprueba tu conexión e inténtalo de nuevo.",
+ "sessionExpired": "Sesión expirada. Por favor, inicia sesión de nuevo."
+ },
+ "validation": {
+ "eventNameRequired": "El nombre del evento es obligatorio",
+ "hostEmailRequired": "El correo del cliente es obligatorio",
+ "hostNameRequired": "El nombre del cliente es obligatorio",
+ "adminEmailRequired": "El correo del administrador es obligatorio",
+ "invalidEmailFormat": "Formato de correo inválido",
+ "passwordRequired": "La contraseña es obligatoria",
+ "passwordMinLength": "La contraseña debe tener al menos 6 caracteres",
+ "passwordsDoNotMatch": "Las contraseñas no coinciden",
+ "passwordSecurityRequirements": "La contraseña no cumple los requisitos de seguridad",
+ "expirationRange": "La expiración debe ser entre 1 y 365 días"
+ },
+ "legal": {
+ "impressum": "Aviso legal",
+ "datenschutz": "Política de privacidad",
+ "termsOfService": "Términos del servicio",
+ "cookiePolicy": "Política de cookies"
+ },
+ "toast": {
+ "saveSuccess": "Cambios guardados correctamente",
+ "saveError": "Error al guardar cambios",
+ "deleteSuccess": "Eliminado correctamente",
+ "deleteError": "Error al eliminar",
+ "uploadSuccess": "Subida completada correctamente",
+ "uploadError": "Error en la subida",
+ "loginSuccess": "Inicio de sesión correcto",
+ "loginError": "Error al iniciar sesión",
+ "passwordChanged": "Contraseña cambiada con éxito",
+ "linkCopied": "Enlace copiado al portapapeles",
+ "eventCreated": "Evento creado con éxito",
+ "eventUpdated": "Evento actualizado con éxito",
+ "eventArchived": "Evento archivado con éxito",
+ "settingsSaved": "Ajustes guardados con éxito",
+ "themeUpdated": "Tema actualizado con éxito",
+ "brandingUpdated": "Firma actualizada con éxito",
+ "categoryAdded": "Categoría añadida con éxito",
+ "categoryDeleted": "Categoría eliminada con éxito",
+ "categoryUpdated": "Categoría actualizada con éxito",
+ "emailConfigSaved": "Configuración de correo guardada con éxito",
+ "testEmailSent": "Correo de prueba enviado con éxito",
+ "pageUpdated": "Página actualizada con éxito",
+ "archiveRestored": "Archivo restaurado con éxito",
+ "archiveDeleted": "Archivo eliminado permanentemente"
+ },
+ "email": {
+ "title": "Configuración de correo",
+ "subtitle": "Configura los ajustes de correo para notificaciones",
+ "loadingSettings": "Cargando ajustes de correo...",
+ "smtpConfiguration": "Configuración SMTP",
+ "smtpHost": "Host SMTP",
+ "smtpHostHelp": "Hostname de tu servidor de correo",
+ "smtpPort": "Puerto SMTP",
+ "smtpPortHelp": "Normalmente 587 para TLS, 465 para SSL, 25 sin cifrado",
+ "smtpSecure": "Usar SSL/TLS",
+ "smtpSecureHelp": "Habilitar para transmisión segura",
+ "smtpUsername": "Usuario SMTP",
+ "smtpUsernameHelp": "Usuario de la cuenta de correo",
+ "smtpPassword": "Contraseña SMTP",
+ "smtpPasswordHelp": "Contraseña de la cuenta de correo",
+ "fromDetails": "Detalles del remitente",
+ "fromEmail": "Correo del remitente",
+ "fromEmailHelp": "Dirección que aparece como remitente",
+ "fromName": "Nombre del remitente",
+ "fromNameHelp": "Nombre que aparece como remitente",
+ "testConfiguration": "Probar configuración",
+ "testEmail": "Correo de prueba",
+ "testEmailHelp": "Enviar un correo de prueba para verificar ajustes",
+ "sendTestEmail": "Enviar correo de prueba",
+ "saveConfiguration": "Guardar configuración",
+ "emailTemplates": "Plantillas de correo",
+ "templateVariables": "Variables disponibles",
+ "previewTemplate": "Vista previa de la plantilla",
+ "smtpSettings": "Ajustes SMTP",
+ "testEmailSuccess": "Correo de prueba enviado con éxito",
+ "saveSmtpSettings": "Guardar ajustes SMTP",
+ "testEmailSection": "Correo de prueba",
+ "beforeTesting": "Antes de probar:",
+ "saveSmtpFirst": "Guarda primero los ajustes SMTP",
+ "ensureFirewall": "Asegúrate de que el firewall permite SMTP saliente",
+ "gmailAppPassword": "Para Gmail, usa una contraseña de aplicación",
+ "testEmailAddressLabel": "Correo de prueba",
+ "sendTestEmailButton": "Enviar correo de prueba",
+ "commonSmtpSettings": "Ajustes SMTP comunes:",
+ "editTemplate": "Editar plantilla",
+ "templateName": "Nombre de la plantilla",
+ "subjectLine": "Asunto",
+ "emailBody": "Cuerpo del correo",
+ "preview": "Vista previa",
+ "save": "Guardar",
+ "saveChanges": "Guardar cambios",
+ "templates": "Plantillas",
+ "variableHelp": "Usa estas variables en tu plantilla. Se reemplazarán por valores reales al enviar.",
+ "port": "Puerto",
+ "security": "Seguridad",
+ "username": "Usuario",
+ "password": "Contraseña",
+ "enterPassword": "Introduce la contraseña",
+ "required": "obligatorio",
+ "ignoreSslErrors": "Ignorar errores SSL/TLS",
+ "ignoreSslWarning": "Aviso: Desactivar verificación de certificados hace la conexión vulnerable a ataques man-in-the-middle. Habilita solo si confías en el servidor SMTP.",
+ "brandingTitle": "Firma de correo",
+ "brandingDescription": "Personaliza colores usados en plantillas de correo. Los cambios afectan barra de encabezado, botones, enlaces y fondo del pie.",
+ "primaryColor": "Color primario",
+ "primaryColorHint": "Usado para encabezado, botones y enlaces",
+ "secondaryColor": "Fondo del pie",
+ "secondaryColorHint": "Usado para el fondo del pie",
+ "saveEmailColors": "Guardar colores de correo",
+ "editor": {
+ "bold": "Negrita",
+ "italic": "Cursiva",
+ "bulletList": "Lista con viñetas",
+ "numberedList": "Lista numerada",
+ "blockquote": "Cita",
+ "link": "Agregar enlace",
+ "horizontalRule": "Regla horizontal",
+ "alignLeft": "Alinear a la izquierda",
+ "alignCenter": "Centrar",
+ "alignRight": "Alinear a la derecha",
+ "clearFormatting": "Quitar formato",
+ "undo": "Deshacer",
+ "redo": "Rehacer",
+ "insertVariable": "Insertar variable",
+ "sourceMode": "Fuente",
+ "visualMode": "Visual",
+ "enterUrl": "Introduce la URL...",
+ "addLink": "Agregar",
+ "cancel": "Cancelar"
+ },
+ "copiedFromLanguage": "Contenido copiado desde {{language}}",
+ "noTranslation": "Sin traducción aún",
+ "noTranslationYet": "No existe traducción para este idioma. Copia desde otro idioma para empezar:",
+ "copyFrom": "Copiar desde"
+ },
+ "cms": {
+ "title": "Páginas CMS",
+ "subtitle": "Gestiona páginas legales e informativas",
+ "loadingPages": "Cargando páginas...",
+ "pages": "Páginas",
+ "previewLinks": "Enlaces de vista previa",
+ "englishVersion": "Versión en inglés",
+ "germanVersion": "Versión en alemán",
+ "editPage": "Editar {{page}}",
+ "pageTitle": "Título de la página",
+ "pageContent": "Contenido",
+ "pageTitlePlaceholder": "Introduce el título...",
+ "saveChanges": "Guardar cambios",
+ "lastUpdated": "Última actualización:",
+ "impressum": "Aviso legal",
+ "datenschutz": "Política de privacidad",
+ "pageUpdated": "Página actualizada con éxito",
+ "useExternalUrl": "Usar URL externa",
+ "useExternalUrlHelp": "Redirige a los visitantes a una página externa en lugar de mostrar el contenido interno. El título y contenido interno se guardan como fallback.",
+ "externalUrl": "URL externa",
+ "externalUrlPlaceholder": "https://example.com/impressum",
+ "externalUrlInvalid": "Debe ser una URL https:// válida",
+ "externalUrlActive": "La URL externa está activa — el contenido interno se conserva pero no se muestra."
+ },
+ "eventTypes": {
+ "title": "Tipos de evento",
+ "subtitle": "Personaliza tipos de evento y sus temas por defecto",
+ "loading": "Cargando tipos de evento...",
+ "loadError": "Error al cargar tipos de evento",
+ "createNew": "Nuevo tipo de evento",
+ "edit": "Editar tipo de evento",
+ "created": "Tipo de evento creado con éxito",
+ "updated": "Tipo de evento actualizado con éxito",
+ "deleted": "Tipo de evento eliminado con éxito",
+ "createError": "Error al crear el tipo de evento",
+ "updateError": "Error al actualizar el tipo de evento",
+ "deleteError": "Error al eliminar el tipo de evento",
+ "searchPlaceholder": "Buscar tipos de evento...",
+ "showInactive": "Mostrar inactivos",
+ "noResults": "No se encontraron tipos de evento",
+ "empty": "Aún no hay tipos de evento",
+ "system": "Sistema",
+ "table": {
+ "type": "Tipo",
+ "slugPrefix": "Prefijo URL",
+ "theme": "Tema por defecto",
+ "status": "Estado",
+ "actions": "Acciones"
+ },
+ "form": {
+ "name": "Nombre visible",
+ "namePlaceholder": "p. ej., Sesión familiar",
+ "slugPrefix": "Prefijo URL",
+ "slugPrefixPlaceholder": "p. ej., familia",
+ "slugPreview": "URL de ejemplo:",
+ "emoji": "Icono",
+ "themePreset": "Tema por defecto",
+ "isActive": "Activo (visible al crear eventos)"
+ },
+ "validation": {
+ "slugFormat": "Solo se permiten letras, números y guiones"
+ },
+ "slugInfo": {
+ "title": "Info del prefijo URL:",
+ "description": "El prefijo se usa para generar URLs de galería. Por ejemplo, un tipo con prefijo \"family\" generará URLs como: family-smith-family-2025-01-22"
+ },
+ "deleteConfirm": {
+ "title": "Eliminar tipo de evento",
+ "message": "¿Seguro que quieres eliminarlo?",
+ "warning": "Esta acción no se puede deshacer. Asegúrate de que ningún evento esté usando este tipo."
+ }
+ },
+ "backup": {
+ "external": {
+ "warning": {
+ "title": "Medios externos excluidos",
+ "body": "Esta instalación referencia fotos desde /external-media. Los originales se excluyen de las copias. Se respaldan miniaturas y base de datos."
+ }
+ },
+ "title": "Gestión de copias de seguridad",
+ "subtitle": "Gestiona copias, configura automatizaciones y restaura versiones anteriores.",
+ "tabs": {
+ "dashboard": "Panel",
+ "configuration": "Configuración",
+ "history": "Historial",
+ "restore": "Restaurar"
+ },
+ "status": {
+ "inProgress": "Copia en progreso...",
+ "lastBackup": "Última copia",
+ "noBackups": "No se encontraron copias",
+ "nextBackup": "Siguiente copia",
+ "notScheduled": "No programada",
+ "enabled": "Habilitado",
+ "disabled": "Deshabilitado"
+ },
+ "actions": {
+ "runBackupNow": "Ejecutar copia ahora",
+ "starting": "Iniciando...",
+ "running": "En ejecución...",
+ "testConnection": "Probar conexión",
+ "save": "Guardar configuración",
+ "delete": "Eliminar",
+ "view": "Ver detalles",
+ "download": "Descargar",
+ "refresh": "Actualizar"
+ },
+ "dashboard": {
+ "backupHealth": "Salud de copias",
+ "healthStatus": {
+ "excellent": "Excelente",
+ "good": "Buena",
+ "warning": "Advertencia",
+ "critical": "Crítica"
+ },
+ "health": {
+ "title": "Estado de las copias"
+ },
+ "healthMessages": {
+ "noBackups": "No se encontraron copias",
+ "lastBackupFailed": "La última copia falló",
+ "upToDate": "La copia está actualizada",
+ "recent": "Copia reciente",
+ "gettingOld": "La copia empieza a envejecer",
+ "outdated": "Copia obsoleta",
+ "failed": "La última copia falló",
+ "old": "La copia empieza a envejecer"
+ },
+ "stats": {
+ "totalBackups": "Copias totales",
+ "backupSize": "Tamaño de copia",
+ "lastDuration": "Última duración",
+ "backupStatus": "Estado",
+ "last": "Última",
+ "files": "archivos",
+ "minutes": "{{count}}m",
+ "active": "Activa",
+ "inactive": "Inactiva",
+ "noBackupsYet": "Aún no hay copias"
+ },
+ "recentActivity": {
+ "title": "Actividad reciente de copias"
+ },
+ "notConfigured": {
+ "title": "Copias no configuradas",
+ "message": "Configura las copias en la pestaña Configuración antes de ejecutar."
+ },
+ "actions": {
+ "runBackupNow": "Ejecutar copia ahora",
+ "running": "En ejecución..."
+ },
+ "coverage": {
+ "title": "Cobertura de copia",
+ "database": "Base de datos",
+ "photos": "Fotos",
+ "archives": "Archivos",
+ "systemFiles": "Archivos del sistema",
+ "included": "Incluido",
+ "excluded": "Excluido",
+ "optional": "Opcional"
+ },
+ "storageDestination": "Destino de almacenamiento",
+ "nextScheduledBackup": "Próxima copia programada",
+ "backupType": "Copia {{type}}",
+ "noDestinationSet": "No se ha definido destino"
+ },
+ "configuration": {
+ "enableBackup": "Habilitar copias automáticas",
+ "enableBackupHelp": "Crear copias automáticamente según la programación",
+ "destinationType": "Destino de copia",
+ "destinationTypes": {
+ "local": {
+ "name": "Almacenamiento local",
+ "description": "Guardar copias en el sistema de archivos local"
+ },
+ "rsync": {
+ "name": "Servidor remoto (Rsync)",
+ "description": "Sincronizar copias a un servidor remoto via SSH/Rsync"
+ },
+ "s3": {
+ "name": "Almacenamiento compatible S3",
+ "description": "Guardar copias en Amazon S3 o almacenamiento compatible"
+ }
+ },
+ "fields": {
+ "destinationPath": "Ruta de destino",
+ "destinationPathHelp": "Ruta local para almacenar copias",
+ "destinationPathPlaceholder": "/ruta/a/copia/seguridad",
+ "rsyncHost": "Host remoto",
+ "rsyncHostHelp": "Hostname o IP SSH",
+ "rsyncHostPlaceholder": "backup.example.com",
+ "rsyncUser": "Usuario SSH",
+ "rsyncUserHelp": "Usuario para conexión SSH",
+ "rsyncUserPlaceholder": "backup-user",
+ "rsyncPath": "Ruta remota",
+ "rsyncPathHelp": "Ruta en el servidor remoto",
+ "rsyncPathPlaceholder": "/home/backup/photo-sharing",
+ "rsyncSshKey": "Clave privada SSH",
+ "rsyncSshKeyHelp": "Clave privada SSH para autenticación (opcional)",
+ "rsyncSshKeyPlaceholder": "-----BEGIN RSA PRIVATE KEY-----",
+ "s3Endpoint": "Endpoint S3",
+ "s3EndpointHelp": "Endpoint API S3 (p. ej., s3.amazonaws.com)",
+ "s3EndpointPlaceholder": "https://s3.amazonaws.com",
+ "s3Bucket": "Nombre del bucket",
+ "s3BucketHelp": "Bucket S3 para almacenar copias",
+ "s3BucketPlaceholder": "mi-bucket-backup",
+ "s3AccessKey": "Access Key ID",
+ "s3AccessKeyHelp": "ID de clave de acceso AWS/S3",
+ "s3AccessKeyPlaceholder": "AKIAIOSFODNN7EXAMPLE",
+ "s3SecretKey": "Secret Access Key",
+ "s3SecretKeyHelp": "Clave secreta AWS/S3",
+ "s3SecretKeyPlaceholder": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
+ "s3Region": "Región",
+ "s3RegionHelp": "Región S3 (p. ej., us-east-1)",
+ "s3RegionPlaceholder": "us-east-1"
+ },
+ "schedule": {
+ "title": "Programación",
+ "scheduleType": "Tipo de programación",
+ "scheduleOptions": {
+ "hourly": "Cada hora",
+ "daily": "Diaria",
+ "weekly": "Semanal",
+ "custom": "Cron personalizado"
+ },
+ "options": {
+ "hourly": "Cada hora",
+ "daily": "Diaria",
+ "weekly": "Semanal",
+ "custom": "Cron personalizado"
+ },
+ "customCron": "Expresión cron",
+ "customCronHelp": "Introduce una expresión cron válida (p. ej., 0 3 * * *)",
+ "retention": "Periodo de retención",
+ "retentionDays": "Conservar copias por",
+ "retentionHelp": "días (las copias antiguas se eliminarán automáticamente)"
+ },
+ "whatToBackup": {
+ "title": "Qué respaldar",
+ "database": "Base de datos",
+ "databaseHelp": "Todos los datos de eventos, ajustes y configuraciones",
+ "photos": "Fotos",
+ "photosHelp": "Todas las fotos subidas en galerías activas",
+ "archives": "Archivos",
+ "archivesHelp": "ZIPs de eventos archivados",
+ "thumbnails": "Miniaturas",
+ "thumbnailsHelp": "Miniaturas generadas (pueden recrearse)",
+ "tempFiles": "Archivos temporales",
+ "tempFilesHelp": "Archivos temporales de subida y procesamiento"
+ },
+ "advancedOptions": {
+ "title": "Opciones avanzadas",
+ "compression": "Habilitar compresión",
+ "compressionHelp": "Comprimir archivos para ahorrar espacio",
+ "encryption": "Habilitar cifrado",
+ "encryptionHelp": "Encriptar copias para mayor seguridad",
+ "encryptionPassphrase": "Frase de cifrado",
+ "encryptionPassphraseHelp": "Frase fuerte para cifrar las copias",
+ "confirmPassphrase": "Confirmar frase",
+ "passphrasesDontMatch": "Las frases no coinciden"
+ },
+ "validation": {
+ "requiredFields": "Por favor rellena los campos obligatorios",
+ "invalidCron": "Expresión cron inválida",
+ "connectionTestFailed": "Falló la prueba de conexión",
+ "connectionTestSuccess": "¡Prueba de conexión exitosa!"
+ },
+ "messages": {
+ "requiredFields": "Por favor rellena los campos obligatorios",
+ "connectionSuccess": "¡Prueba de conexión exitosa!",
+ "connectionFailed": "Falló la prueba de conexión"
+ },
+ "testingConnection": "Probando conexión...",
+ "saveSettings": "Guardar configuración",
+ "savingSettings": "Guardando..."
+ },
+ "history": {
+ "searchPlaceholder": "Buscar copias...",
+ "allStatus": "Todos los estados",
+ "status": {
+ "completed": "Completada",
+ "failed": "Fallida",
+ "running": "En ejecución",
+ "partial": "Parcial"
+ },
+ "deleteConfirm": "¿Seguro que quieres eliminar esta copia de {{date}}?",
+ "noBackups": "No se encontraron copias",
+ "tableHeaders": {
+ "date": "Fecha",
+ "type": "Tipo",
+ "status": "Estado",
+ "size": "Tamaño",
+ "duration": "Duración",
+ "actions": "Acciones"
+ },
+ "columns": {
+ "status": "Estado",
+ "dateTime": "Fecha y hora",
+ "type": "Tipo",
+ "size": "Tamaño",
+ "duration": "Duración",
+ "actions": "Acciones"
+ },
+ "details": {
+ "backupDetails": "Detalles de la copia",
+ "destination": "Destino",
+ "started": "Iniciada",
+ "completed": "Completada",
+ "contentBackedUp": "Contenido respaldado",
+ "errorDetails": "Detalles del error",
+ "manifest": "Manifiesto"
+ },
+ "statistics": "Estadísticas",
+ "errors": "Errores",
+ "backupDetails": {
+ "backupId": "ID de copia",
+ "startTime": "Inicio",
+ "endTime": "Fin",
+ "destination": "Destino",
+ "filesProcessed": "Archivos procesados",
+ "totalSize": "Tamaño total",
+ "compressionRatio": "Ratio de compresión",
+ "errorLog": "Registro de errores",
+ "noErrors": "No hubo errores"
+ },
+ "pagination": {
+ "showing": "Mostrando {{from}}-{{to}} de {{total}} copias",
+ "previous": "Anterior",
+ "next": "Siguiente"
+ },
+ "filter": {
+ "allStatus": "Todos los estados",
+ "completed": "Completada",
+ "failed": "Fallida",
+ "running": "En ejecución",
+ "partial": "Parcial"
+ },
+ "noBackupsFound": "No se encontraron copias",
+ "backupsWillAppear": "Las copias aparecerán aquí cuando se creen",
+ "messages": {
+ "deleteSuccess": "Copia eliminada con éxito"
+ }
+ },
+ "restore": {
+ "steps": {
+ "selectSource": "Seleccionar origen",
+ "chooseBackup": "Elegir copia",
+ "restoreOptions": "Opciones de restauración",
+ "reviewConfirm": "Revisar y confirmar",
+ "progress": "Progreso"
+ },
+ "source": {
+ "title": "Selecciona origen de copia",
+ "subtitle": "Elige desde dónde restaurar",
+ "local": {
+ "name": "Copia local",
+ "description": "Restaurar desde el filesystem local"
+ },
+ "s3": {
+ "name": "Almacenamiento S3",
+ "description": "Restaurar desde un bucket S3"
+ },
+ "upload": {
+ "name": "Subir copia",
+ "description": "Subir un archivo de copia",
+ "comingSoon": "Funcionalidad de subida próximamente"
+ },
+ "configuration": {
+ "s3": "Configuración S3",
+ "endpoint": "URL del endpoint S3",
+ "bucket": "Nombre del bucket",
+ "accessKey": "Access Key ID",
+ "secretKey": "Secret Access Key"
+ }
+ },
+ "backup": {
+ "title": "Elegir copia a restaurar",
+ "subtitle": "Selecciona entre las copias disponibles",
+ "noBackupsFound": "No se encontraron copias en el origen seleccionado",
+ "encrypted": "Copia cifrada",
+ "encryptedMessage": "Necesitarás la frase de cifrado para restaurar esta copia.",
+ "enterPassphrase": "Introduce la frase de cifrado",
+ "at": "en"
+ },
+ "restoreTypes": {
+ "full": {
+ "name": "Restauración completa",
+ "description": "Restaurar todo incluyendo base de datos, fotos y archivos",
+ "warning": "Esto reemplazará todos los datos actuales"
+ },
+ "database": {
+ "name": "Solo base de datos",
+ "description": "Restaurar solo la base de datos (ajustes, eventos, usuarios)",
+ "warning": "La base de datos actual será reemplazada"
+ },
+ "files": {
+ "name": "Solo archivos",
+ "description": "Restaurar solo fotos y archivos",
+ "warning": "Los archivos existentes pueden sobrescribirse"
+ },
+ "selective": {
+ "name": "Restauración selectiva",
+ "description": "Elegir elementos específicos para restaurar",
+ "warning": "Solo los elementos seleccionados se restaurarán"
+ }
+ },
+ "options": {
+ "title": "Opciones de restauración",
+ "subtitle": "Elige qué restaurar",
+ "additionalOptions": {
+ "title": "Opciones adicionales",
+ "skipPreBackup": "Omitir copia previa",
+ "skipPreBackupHelp": "Por defecto se crea una copia previa antes de restaurar. Marca para omitir.",
+ "force": "Forzar restauración",
+ "forceHelp": "Omitir comprobaciones de seguridad (usar con precaución)"
+ }
+ },
+ "confirmation": {
+ "title": "Revisar y confirmar",
+ "subtitle": "Revisa tu configuración de restauración",
+ "validation": {
+ "passed": "Validación superada",
+ "failed": "Validación fallida",
+ "checking": "Validando configuración..."
+ },
+ "spaceCheck": {
+ "title": "Espacio de almacenamiento",
+ "required": "Requerido",
+ "available": "Disponible",
+ "insufficient": "Espacio insuficiente"
+ },
+ "summary": {
+ "title": "Resumen de restauración",
+ "source": "Origen",
+ "backupDate": "Fecha de la copia",
+ "restoreType": "Tipo de restauración",
+ "preBackup": "Copia previa",
+ "enabled": "Habilitada",
+ "skipped": "Omitida"
+ },
+ "warning": {
+ "title": "Aviso importante",
+ "message": "Esta restauración reemplazará los datos existentes. Asegúrate de tener una copia actual antes de proceder. Esta acción no se puede deshacer."
+ }
+ },
+ "progress": {
+ "title": "Progreso de restauración",
+ "inProgress": "Restauración en curso...",
+ "completed": "Restauración completada",
+ "overallProgress": "Progreso general",
+ "current": "Actual",
+ "statusDetails": "Detalles",
+ "restoreLogs": "Registros de restauración",
+ "steps": {
+ "completed": "Completado",
+ "running": "En ejecución",
+ "failed": "Fallido",
+ "pending": "Pendiente"
+ },
+ "success": {
+ "title": "Restauración completada con éxito",
+ "message": "Tus datos han sido restaurados. Verifica que todo funcione correctamente."
+ }
+ },
+ "actions": {
+ "back": "Atrás",
+ "next": "Siguiente",
+ "startRestore": "Iniciar restauración",
+ "starting": "Iniciando...",
+ "validating": "Validando...",
+ "startNewRestore": "Iniciar nueva restauración"
+ },
+ "messages": {
+ "restoreStarted": "Restauración iniciada con éxito"
+ }
+ },
+ "messages": {
+ "backupStarted": "Copia iniciada con éxito",
+ "backupFailed": "Error al iniciar la copia",
+ "configUpdated": "Configuración de copias actualizada",
+ "configUpdateFailed": "Error al actualizar la configuración",
+ "backupDeleted": "Copia eliminada con éxito",
+ "deleteFailed": "Error al eliminar la copia",
+ "testEmailSent": "¡Prueba de conexión exitosa!",
+ "testEmailFailed": "Falló la prueba de conexión"
+ }
+ },
+ "cssTemplates": {
+ "title": "Plantillas CSS personalizadas",
+ "template": "Plantilla",
+ "templateName": "Nombre de la plantilla",
+ "enableTemplate": "Habilitar esta plantilla",
+ "enableHint": "Las plantillas habilitadas pueden seleccionarse al crear eventos",
+ "cssContent": "Contenido CSS",
+ "cssHint": "Usa .gallery-page para aplicar estilos a la galería. Variables disponibles: --gallery-bg, --gallery-text, --gallery-accent",
+ "securityNotice": "Aviso de seguridad",
+ "securityText": "El CSS se sanea para evitar código malicioso. URLs externas, @import y expresiones JavaScript se bloquean.",
+ "resetToDefault": "Restablecer a predeterminado",
+ "resetConfirm": "¿Restablecer esta plantilla al predeterminado? Se perderán los cambios.",
+ "unsavedChanges": "Cambios sin guardar",
+ "saveTemplate": "Guardar plantilla",
+ "lastUpdated": "Última actualización",
+ "saved": "Plantilla guardada con éxito",
+ "saveFailed": "Error al guardar la plantilla",
+ "sanitizationWarning": "Algunos patrones CSS fueron bloqueados por seguridad",
+ "reset": "Plantilla restablecida al predeterminado",
+ "resetFailed": "Error al restablecer la plantilla"
+ },
+ "maintenance": {
+ "title": "Mantenimiento del sistema",
+ "message": "Estamos realizando mantenimiento programado para mejorar el servicio. Volveremos pronto.",
+ "expectedCompletion": "Hora estimada de finalización:",
+ "checkBackLater": "Por favor, vuelve a comprobar más tarde",
+ "urgentMatters": "Para asuntos urgentes, contacta con"
+ },
+ "passwordChange": {
+ "title": "Cambiar contraseña",
+ "currentPassword": "Contraseña actual",
+ "newPassword": "Nueva contraseña",
+ "confirmPassword": "Confirmar nueva contraseña",
+ "currentPasswordPlaceholder": "Introduce la contraseña actual",
+ "newPasswordPlaceholder": "Introduce la nueva contraseña",
+ "confirmPasswordPlaceholder": "Confirma la nueva contraseña",
+ "requirements": "Requisitos de la contraseña:",
+ "minLength": "Al menos 6 caracteres",
+ "mustDiffer": "Debe ser diferente de la contraseña actual",
+ "success": "Contraseña cambiada con éxito",
+ "failed": "Error al cambiar la contraseña",
+ "currentRequired": "La contraseña actual es obligatoria",
+ "newRequired": "La nueva contraseña es obligatoria",
+ "minLengthError": "La contraseña debe tener al menos 6 caracteres",
+ "confirmRequired": "Por favor confirma tu nueva contraseña",
+ "noMatch": "Las contraseñas no coinciden",
+ "mustBeDifferent": "La nueva contraseña debe ser diferente de la actual",
+ "cancel": "Cancelar"
+ },
+ "mandatoryPasswordChange": {
+ "title": "Cambio de contraseña obligatorio",
+ "description": "Por seguridad, debes cambiar tu contraseña antes de acceder al panel.",
+ "success": "¡Contraseña cambiada con éxito! Ya puedes acceder al panel.",
+ "minLength": "Al menos 12 caracteres",
+ "mustContainUpperLower": "Debe contener mayúsculas y minúsculas",
+ "mustContainNumbers": "Debe contener números",
+ "mustContainSpecial": "Debe contener caracteres especiales (!@#$%^&*)",
+ "minLengthError": "La contraseña debe tener al menos 12 caracteres",
+ "mustContainLowercase": "La contraseña debe contener minúsculas",
+ "mustContainUppercase": "La contraseña debe contener mayúsculas",
+ "mustContainNumbersError": "La contraseña debe contener números",
+ "mustContainSpecialError": "La contraseña debe contener caracteres especiales"
+ },
+ "offline": {
+ "backOnline": "De vuelta en línea",
+ "noConnection": "Sin conexión a internet"
+ },
+ "download": {
+ "downloading": "Descargando...",
+ "percentComplete": "% completado"
+ },
+ "passwordGenerator": {
+ "weak": "Débil",
+ "fair": "Aceptable",
+ "good": "Buena",
+ "strong": "Fuerte",
+ "generating": "Generando...",
+ "generatePassword": "Generar contraseña",
+ "showSuggestions": "Mostrar sugerencias",
+ "moreOptions": "Más opciones",
+ "suggestions": "Sugerencias de contraseña",
+ "characters": "caracteres",
+ "copyPassword": "Copiar contraseña",
+ "use": "Usar",
+ "pattern": "Patrón:",
+ "patternDescription": "Las contraseñas se generan usando el nombre y la fecha del evento. Ejemplo: \"Venue2024$August\" para mayor seguridad y memorización."
+ },
+ "feedback": {
+ "comments": "Comentarios",
+ "addComment": "Añadir comentario",
+ "yourName": "Tu nombre",
+ "yourEmail": "Tu correo",
+ "writeComment": "Escribe un comentario...",
+ "submit": "Enviar",
+ "commentSubmitted": "Comentario enviado",
+ "commentError": "Error al enviar el comentario",
+ "rateLimited": "Espera antes de volver a comentar",
+ "commentRequired": "El comentario es obligatorio",
+ "nameRequired": "El nombre es obligatorio",
+ "emailRequired": "El correo es obligatorio",
+ "anonymous": "Anónimo",
+ "pendingApproval": "Pendiente de aprobación",
+ "noComments": "Aún no hay comentarios. ¡Sé el primero!",
+ "rating": "Valoración",
+ "ratePhoto": "Valorar esta foto",
+ "yourRating": "Tu valoración",
+ "averageRating": "Valoración media",
+ "totalRatings": "valoraciones",
+ "likes": "Me gusta",
+ "favorites": "Favoritos",
+ "likePhoto": "Marcar me gusta",
+ "favoritePhoto": "Añadir a favoritos",
+ "photoFeedback": "Feedback de la foto",
+ "hasFeedback": "Tiene feedback",
+ "hasComments": "Tiene comentarios",
+ "hasRating": "Tiene valoración",
+ "settings": {
+ "title": "Ajustes de feedback",
+ "enableFeedback": "Habilitar feedback",
+ "feedbackTypes": "Tipos de feedback",
+ "ratings": "Valoraciones (estrellas)",
+ "ratingsDesc": "Permitir valorar fotos (1-5 estrellas)",
+ "likes": "Me gusta",
+ "likesDesc": "Funcionalidad simple de me gusta",
+ "comments": "Comentarios",
+ "commentsDesc": "Comentarios de texto en fotos",
+ "favorites": "Favoritos",
+ "favoritesDesc": "Marcar fotos como favoritas",
+ "identityMode": "Modo de identidad",
+ "identityModeSimple": "Feedback simple",
+ "identityModeSimpleDesc": "Anónimo, basado en dispositivo. Visitantes del mismo dispositivo comparten estado.",
+ "identityModeGuest": "Por invitado",
+ "identityModeGuestDesc": "Cada visitante introduce su nombre. Habilita rastreo por invitado e insights admin."
+ }
+ },
+ "filter": {
+ "feedbackFilters": "Filtros de feedback",
+ "clear": "Limpiar",
+ "rating": "Valoración",
+ "allPhotos": "Todas las fotos",
+ "anyRating": "Cualquier valoración",
+ "oneStarPlus": "1+ estrellas",
+ "twoStarsPlus": "2+ estrellas",
+ "threeStarsPlus": "3+ estrellas",
+ "fourStarsPlus": "4+ estrellas",
+ "fiveStarsOnly": "Solo 5 estrellas",
+ "hasLikes": "Con me gusta",
+ "hasFavorites": "Con favoritos",
+ "hasComments": "Con comentarios",
+ "showingPhotos": "Fotos totales",
+ "withRatings": "Con valoraciones"
+ },
+ "adminLogin": {
+ "title": "Acceso admin",
+ "subtitle": "Inicia sesión para gestionar tus galerías",
+ "sessionExpired": "Tu sesión ha expirado. Por favor, inicia sesión de nuevo.",
+ "emailLabel": "Correo electrónico",
+ "emailPlaceholder": "admin@example.com",
+ "emailRequired": "El correo es obligatorio",
+ "invalidEmail": "Formato de correo inválido",
+ "passwordLabel": "Contraseña",
+ "passwordPlaceholder": "Introduce tu contraseña",
+ "passwordRequired": "La contraseña es obligatoria",
+ "passwordMinLength": "La contraseña debe tener al menos 6 caracteres",
+ "rememberMe": "Recordarme",
+ "forgotPassword": "¿Olvidaste la contraseña?",
+ "signIn": "Iniciar sesión",
+ "loginSuccess": "Inicio de sesión correcto!",
+ "networkError": "Error de red. Comprueba tu conexión e inténtalo de nuevo.",
+ "tooManyAttempts": "Demasiados intentos. Por favor, inténtalo más tarde.",
+ "invalidCredentials": "Correo o contraseña inválidos",
+ "generalError": "Ocurrió un error. Por favor, inténtalo de nuevo.",
+ "needHelp": "¿Necesitas ayuda? Contacta",
+ "poweredBy": "Desarrollado por PicPeak",
+ "devModeHint": "Modo desarrollo: usa admin@example.com, contraseña: admin123"
+ },
+ "clientAccess": {
+ "title": "Acceso cliente",
+ "description": "Revisa fotos y gestiona visibilidad antes de compartir la galería con invitados.",
+ "pinLabel": "PIN del cliente",
+ "pinPlaceholder": "Introduce tu PIN",
+ "enterPin": "Introduce el PIN del cliente",
+ "invalidPin": "PIN inválido. Inténtalo de nuevo.",
+ "loginFailed": "Autenticación fallida. Inténtalo de nuevo.",
+ "loginButton": "Acceder a la galería",
+ "guestHint": "¿Buscas la vista de invitados?",
+ "guestLink": "Ir a la vista de invitados",
+ "banner": "Modo cliente",
+ "visibleCount": "{{visible}} de {{total}} fotos visibles para invitados",
+ "hideSelected": "Ocultar seleccionadas",
+ "showSelected": "Mostrar seleccionadas",
+ "adminTitle": "Acceso cliente",
+ "enableToggle": "Habilitar acceso cliente",
+ "enableDescription": "Permitir que clientes revisen y oculten fotos antes de compartir.",
+ "pinHelperText": "El cliente usará este PIN para acceder a la revisión.",
+ "pinUpdated": "PIN del cliente actualizado",
+ "setPin": "Establecer PIN",
+ "linkLabel": "Enlace de acceso cliente",
+ "regenerateToken": "Regenerar enlace",
+ "tokenRegenerated": "Enlace de acceso regenerado"
+ },
+ "export": {
+ "button": "Exportar",
+ "success": "Exportación descargada con éxito",
+ "error": "Error en la exportación: ",
+ "exportSelected": "Exportar {{count}} seleccionadas",
+ "exportFiltered": "Exportar fotos filtradas",
+ "hint": "Selecciona fotos o aplica filtros para exportar"
+ },
+ "photoSort": {
+ "defaultSort": "Orden por defecto",
+ "uploadDateNewest": "Fecha de subida (más recientes)",
+ "uploadDateOldest": "Fecha de subida (más antiguas)",
+ "captureDateNewest": "Fecha de captura (más reciente)",
+ "captureDateOldest": "Fecha de captura (más antigua)",
+ "filenameAZ": "Nombre de archivo (A-Z)",
+ "filenameZA": "Nombre de archivo (Z-A)",
+ "dateTaken": "Fecha de captura"
+ }
+}
diff --git a/frontend/src/pages/admin/CustomerDetailPage.tsx b/frontend/src/pages/admin/CustomerDetailPage.tsx
index 27e9ec57..5b509168 100644
--- a/frontend/src/pages/admin/CustomerDetailPage.tsx
+++ b/frontend/src/pages/admin/CustomerDetailPage.tsx
@@ -20,6 +20,7 @@ import {
import { format } from 'date-fns';
import { Button, Card, Input, Loading } from '../../components/common';
+import { SUPPORTED_LANGUAGES } from '../../components/common/LanguageSelector';
import { AssignedEventsDialog } from '../../components/admin/AssignedEventsDialog';
import {
customerAdminService,
@@ -227,11 +228,12 @@ export const CustomerDetailPage: React.FC = () => {
onChange={setField('preferredLanguage')}
className="input"
>
- English
- Deutsch
- Nederlands
- Português
- Русский
+ {/* Drive the option list from SUPPORTED_LANGUAGES so adding a
+ locale (#510 added es; fr was already missing here) only
+ needs to touch LanguageSelector. */}
+ {SUPPORTED_LANGUAGES.map((lang) => (
+ {lang.name}
+ ))}