From 1fa222e9c4c26e525c7899e368988c6b0b08da85 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Mon, 16 Mar 2026 17:22:44 +0100 Subject: [PATCH] feat: add photo cap per event and Portuguese (pt-BR) locale - Add photo_cap column to events table (migration 074) to limit photos per event - Enforce photo cap in upload route, returning 400 when limit exceeded - Pass photo_cap through all event CRUD routes and frontend forms - Add complete Portuguese (pt-BR) translation (2300+ strings) - Register pt locale in i18n config, language selector, date formatting - Add photoCap/photoCapHelp translation keys to all locale files (en, de, ru, pt) --- backend/migrations/core/074_add_photo_cap.js | 13 + backend/src/routes/adminEvents-enhanced.js | 6 +- backend/src/routes/adminEvents.js | 8 +- backend/src/routes/adminPhotos.js | 25 +- backend/src/routes/auth.js | 6 +- backend/src/services/eventService.js | 8 +- backend/src/utils/dateFormatter.js | 2 + .../components/common/LanguageSelector.tsx | 10 + .../src/features/settings/tabs/GeneralTab.tsx | 1 + frontend/src/hooks/useLocalizedDate.ts | 6 +- frontend/src/i18n/config.ts | 4 + frontend/src/i18n/locales/de.json | 2 + frontend/src/i18n/locales/en.json | 2 + frontend/src/i18n/locales/pt.json | 2309 +++++++++++++++++ frontend/src/i18n/locales/ru.json | 2 + frontend/src/pages/admin/CreateEventPage.tsx | 27 +- frontend/src/pages/admin/EventDetailsPage.tsx | 27 + frontend/src/services/events.service.ts | 2 + frontend/src/types/index.ts | 3 + 19 files changed, 2451 insertions(+), 12 deletions(-) create mode 100644 backend/migrations/core/074_add_photo_cap.js create mode 100644 frontend/src/i18n/locales/pt.json diff --git a/backend/migrations/core/074_add_photo_cap.js b/backend/migrations/core/074_add_photo_cap.js new file mode 100644 index 00000000..d992fe66 --- /dev/null +++ b/backend/migrations/core/074_add_photo_cap.js @@ -0,0 +1,13 @@ +const { addColumnIfNotExists } = require('../helpers'); + +exports.up = async function(knex) { + console.log('Running migration: 074_add_photo_cap'); + await addColumnIfNotExists(knex, 'events', 'photo_cap', (table) => { + table.integer('photo_cap').nullable().defaultTo(null); + }); + console.log('Migration 074_add_photo_cap completed'); +}; + +exports.down = async function(knex) { + console.log('Rollback: 074_add_photo_cap'); +}; diff --git a/backend/src/routes/adminEvents-enhanced.js b/backend/src/routes/adminEvents-enhanced.js index 672df8ab..8d97018d 100644 --- a/backend/src/routes/adminEvents-enhanced.js +++ b/backend/src/routes/adminEvents-enhanced.js @@ -42,7 +42,8 @@ router.post('/', adminAuth, requirePermission('events.create'), [ color_theme = null, expiration_days = 30, allow_user_uploads = false, - upload_category_id = null + upload_category_id = null, + photo_cap = null } = req.body; // Validate password strength for gallery @@ -105,7 +106,8 @@ router.post('/', adminAuth, requirePermission('events.create'), [ expires_at: expires_at.toISOString(), created_at: new Date().toISOString(), allow_user_uploads, - upload_category_id + upload_category_id, + photo_cap: photo_cap || null }).returning('id'); // Handle both PostgreSQL (returns array of objects) and SQLite (returns array of IDs) diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index 0f460ed2..07d86368 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -256,7 +256,9 @@ router.post('/', adminAuth, requirePermission('events.create'), [ header_style = 'standard', hero_divider_style = 'wave', // Hero image anchor position (#162) - hero_image_anchor = 'center' + hero_image_anchor = 'center', + // Photo cap + photo_cap = null } = req.body; const customerName = getCustomerNameFromPayload(req.body); @@ -416,7 +418,8 @@ router.post('/', adminAuth, requirePermission('events.create'), [ hero_logo_position: hero_logo_position || 'top', header_style: effectiveHeaderStyle || 'standard', hero_divider_style: effectiveDividerStyle || 'wave', - hero_image_anchor: hero_image_anchor || 'center' + hero_image_anchor: hero_image_anchor || 'center', + photo_cap: photo_cap || null }).returning('id'); // Handle both PostgreSQL (returns array of objects) and SQLite (returns array of IDs) @@ -479,6 +482,7 @@ router.post('/', adminAuth, requirePermission('events.create'), [ customer_name: customerName, customer_email: customerEmail, require_password: requirePassword, + photo_cap: photo_cap || null, share_link: shareUrl, expires_at: expires_at ? expires_at.toISOString() : null, created_at: new Date().toISOString() diff --git a/backend/src/routes/adminPhotos.js b/backend/src/routes/adminPhotos.js index a2c9dfff..f48d2bb5 100644 --- a/backend/src/routes/adminPhotos.js +++ b/backend/src/routes/adminPhotos.js @@ -170,7 +170,30 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), u } return res.status(404).json({ error: 'Event not found' }); } - + + // Enforce photo cap if set + if (event.photo_cap && event.photo_cap > 0) { + const existingPhotoCount = await db('photos') + .where({ event_id: eventId }) + .count('id as count') + .first(); + const currentCount = parseInt(existingPhotoCount.count) || 0; + const newFilesCount = (req.files && req.files.length) || 0; + if (currentCount + newFilesCount > event.photo_cap) { + // Clean up temp files + if (req.tempUploadPath) { + try { + await fs.rm(req.tempUploadPath, { recursive: true, force: true }); + } catch (e) { + console.error('Failed to clean up temp path:', e); + } + } + return res.status(400).json({ + error: `Photo cap exceeded. This event allows a maximum of ${event.photo_cap} photos. Currently ${currentCount} photos exist, and you are trying to upload ${newFilesCount} more.` + }); + } + } + if (!req.files || req.files.length === 0) { console.error('No files in request. req.files:', req.files); console.error('Request body keys:', Object.keys(req.body)); diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index ca1b42c8..bfc8b704 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -280,7 +280,8 @@ router.post('/gallery/verify', [ expires_at: event.expires_at, allow_user_uploads: event.allow_user_uploads, upload_category_id: event.upload_category_id, - require_password: requiresPassword + require_password: requiresPassword, + photo_cap: event.photo_cap } }); } catch (error) { @@ -353,7 +354,8 @@ router.post('/gallery/share-login', [ expires_at: event.expires_at, allow_user_uploads: event.allow_user_uploads, upload_category_id: event.upload_category_id, - require_password: requiresPassword + require_password: requiresPassword, + photo_cap: event.photo_cap } }); } catch (error) { diff --git a/backend/src/services/eventService.js b/backend/src/services/eventService.js index 5676771b..2ca45013 100644 --- a/backend/src/services/eventService.js +++ b/backend/src/services/eventService.js @@ -138,7 +138,9 @@ const createEvent = async (eventData) => { show_feedback_to_guests, // Upload settings allow_user_uploads, - upload_category_id + upload_category_id, + // Photo cap + photo_cap } = eventData; const requirePassword = parseBooleanInput(require_password, true); @@ -207,7 +209,9 @@ const createEvent = async (eventData) => { show_feedback_to_guests: show_feedback_to_guests !== undefined ? formatBoolean(show_feedback_to_guests) : undefined, // Upload settings allow_user_uploads: allow_user_uploads !== undefined ? formatBoolean(allow_user_uploads) : undefined, - upload_category_id: upload_category_id || null + upload_category_id: upload_category_id || null, + // Photo cap + photo_cap: photo_cap || null }; // Remove undefined values diff --git a/backend/src/utils/dateFormatter.js b/backend/src/utils/dateFormatter.js index bc7ff072..5bbd0518 100644 --- a/backend/src/utils/dateFormatter.js +++ b/backend/src/utils/dateFormatter.js @@ -54,6 +54,8 @@ async function formatDate(date, language = 'en') { let locale = dateConfig.locale || 'en-GB'; if (language === 'de') { locale = 'de-DE'; + } else if (language === 'pt') { + locale = 'pt-BR'; } else if (language === 'en' && dateConfig.format === 'MM/DD/YYYY') { locale = 'en-US'; } diff --git a/frontend/src/components/common/LanguageSelector.tsx b/frontend/src/components/common/LanguageSelector.tsx index 00e9e25e..41a926e1 100644 --- a/frontend/src/components/common/LanguageSelector.tsx +++ b/frontend/src/components/common/LanguageSelector.tsx @@ -29,10 +29,20 @@ const RUFlag: React.FC<{ className?: string }> = ({ className = "w-5 h-5" }) => ); +const PTBRFlag: React.FC<{ className?: string }> = ({ className = "w-5 h-5" }) => ( + + + + + + +); + const languages = [ { code: 'en', name: 'English', Flag: GBFlag }, { code: 'de', name: 'Deutsch', Flag: DEFlag }, { code: 'ru', name: 'Русский', Flag: RUFlag }, + { code: 'pt', name: 'Português', Flag: PTBRFlag }, ]; export const LanguageSelector: React.FC = () => { diff --git a/frontend/src/features/settings/tabs/GeneralTab.tsx b/frontend/src/features/settings/tabs/GeneralTab.tsx index 964bbdbe..bd4d6cce 100644 --- a/frontend/src/features/settings/tabs/GeneralTab.tsx +++ b/frontend/src/features/settings/tabs/GeneralTab.tsx @@ -246,6 +246,7 @@ export const GeneralTab: React.FC = ({ > +

{t('settings.general.defaultLanguageHelp')} diff --git a/frontend/src/hooks/useLocalizedDate.ts b/frontend/src/hooks/useLocalizedDate.ts index f086a506..ff29167b 100644 --- a/frontend/src/hooks/useLocalizedDate.ts +++ b/frontend/src/hooks/useLocalizedDate.ts @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { format as dateFnsFormat, formatDistanceToNow as dateFnsFormatDistanceToNow } from 'date-fns'; -import { de, enUS } from 'date-fns/locale'; +import { de, enUS, ptBR } from 'date-fns/locale'; import { useQuery } from '@tanstack/react-query'; import { publicSettingsService } from '../services/publicSettings.service'; @@ -24,7 +24,9 @@ export const useLocalizedDate = () => { }); const getLocale = () => { - return i18n.language === 'de' ? de : enUS; + if (i18n.language === 'de') return de; + if (i18n.language === 'pt' || i18n.language === 'pt-BR') return ptBR; + return enUS; }; const format = (date: Date | string, formatStr?: string) => { diff --git a/frontend/src/i18n/config.ts b/frontend/src/i18n/config.ts index 0963952e..86d2862e 100644 --- a/frontend/src/i18n/config.ts +++ b/frontend/src/i18n/config.ts @@ -6,6 +6,7 @@ import HttpBackend from 'i18next-http-backend'; import enTranslations from './locales/en.json'; import deTranslations from './locales/de.json'; import ruTranslations from './locales/ru.json'; +import ptTranslations from './locales/pt.json'; i18n .use(HttpBackend) @@ -25,6 +26,9 @@ i18n ru: { translation: ruTranslations, }, + pt: { + translation: ptTranslations, + }, }, interpolation: { diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 42d87aac..e0804dc2 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -835,6 +835,8 @@ "expirationWarning": "Gäste erhalten 7 Tage vor Ablauf eine Warn-E-Mail.", "noExpiration": "Kein Ablaufdatum", "noExpirationHelp": "Diese Galerie bleibt aktiv, bis sie manuell archiviert wird.", + "photoCap": "Fotolimit", + "photoCapHelp": "Maximale Anzahl erlaubter Fotos. 0 = unbegrenzt", "userUploads": "Benutzer-Upload-Einstellungen", "allowUserUploads": "Gästen erlauben, Fotos hochzuladen", "allowUserUploadsHelp": "Ermöglichen Sie Gästen, ihre eigenen Fotos in diese Galerie hochzuladen", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 80cbf151..30ec0be9 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -461,6 +461,8 @@ "expirationWarning": "Guests will receive a warning email 7 days before expiration.", "noExpiration": "No Expiration", "noExpirationHelp": "This gallery will remain active until manually archived.", + "photoCap": "Photo Limit", + "photoCapHelp": "Maximum number of photos allowed. 0 = unlimited", "userUploads": "User Upload Settings", "allowUserUploads": "Allow guests to upload photos", "allowUserUploadsHelp": "Enable guests to upload their own photos to this gallery", diff --git a/frontend/src/i18n/locales/pt.json b/frontend/src/i18n/locales/pt.json new file mode 100644 index 00000000..f601c315 --- /dev/null +++ b/frontend/src/i18n/locales/pt.json @@ -0,0 +1,2309 @@ +{ + "userManagement": { + "title": "Gerenciamento de Usuarios", + "subtitle": "Gerenciar usuarios administradores e convites", + "loading": "Carregando usuarios...", + "loadError": "Falha ao carregar usuarios. Por favor, tente novamente.", + "inviteUser": "Convidar Usuario", + "createInvitation": "Criar Convite", + "email": "Endereco de Email", + "emailPlaceholder": "usuario@exemplo.com", + "role": "Funcao", + "selectRole": "Selecione uma funcao", + "sendInvitation": "Enviar Convite", + "editUser": "Editar Usuario", + "editingUser": "Editando usuario", + "saveChanges": "Salvar Alteracoes", + "deactivateUser": "Desativar Usuario", + "cancelInvitation": "Cancelar Convite", + "invitationSent": "Convite enviado com sucesso", + "invitationError": "Falha ao enviar convite", + "invitationCancelled": "Convite cancelado", + "cancelInvitationError": "Falha ao cancelar convite", + "userUpdated": "Usuario atualizado com sucesso", + "updateUserError": "Falha ao atualizar usuario", + "userDeactivated": "Usuario desativado com sucesso", + "deactivateUserError": "Falha ao desativar usuario", + "noRole": "Sem Funcao", + "neverLoggedIn": "Nunca fez login", + "expired": "Expirado", + "deactivate": "Desativar", + "cancel": "Cancelar", + "tabs": { + "users": "Usuarios", + "invitations": "Convites" + }, + "stats": { + "totalUsers": "Total de Usuarios", + "activeUsers": "Usuarios Ativos", + "pendingInvitations": "Convites Pendentes", + "inactiveUsers": "Usuarios Inativos" + }, + "status": { + "active": "Ativo", + "inactive": "Inativo" + }, + "table": { + "user": "Usuario", + "email": "Email", + "role": "Funcao", + "status": "Status", + "lastLogin": "Ultimo Login", + "actions": "Acoes", + "invitedBy": "Convidado Por", + "expires": "Expira" + }, + "validation": { + "emailRequired": "Email e obrigatorio", + "emailInvalid": "Formato de email invalido", + "roleRequired": "Funcao e obrigatoria" + }, + "searchUsersPlaceholder": "Buscar usuarios...", + "searchInvitationsPlaceholder": "Buscar convites...", + "noUsers": "Nenhum usuario encontrado", + "noUsersFound": "Nenhum usuario corresponde a sua busca", + "noInvitations": "Nenhum convite pendente", + "noInvitationsFound": "Nenhum convite corresponde a sua busca", + "confirmDeactivate": { + "title": "Desativar Usuario", + "message": "Tem certeza que deseja desativar {{name}}? Ele(a) nao podera mais fazer login." + }, + "confirmCancelInvitation": { + "title": "Cancelar Convite", + "message": "Tem certeza que deseja cancelar o convite para {{email}}?" + } + }, + "common": { + "loading": "Carregando...", + "error": "Erro", + "save": "Salvar", + "cancel": "Cancelar", + "delete": "Excluir", + "edit": "Editar", + "add": "Adicionar", + "search": "Buscar", + "filter": "Filtrar", + "sortBy": "Ordenar por", + "yes": "Sim", + "no": "Nao", + "back": "Voltar", + "next": "Proximo", + "previous": "Anterior", + "close": "Fechar", + "logout": "Sair", + "menu": "Menu", + "change": "Alterar", + "remove": "Remover", + "download": "Baixar", + "downloadAll": "Baixar Tudo", + "uploading": "Enviando...", + "uploaded": "Enviado", + "photo": "foto", + "photos": "fotos", + "video": "video", + "videos": "videos", + "media": "midia", + "restore": "Restaurar", + "actions": "Acoes", + "refresh": "Atualizar", + "preview": "Visualizar", + "processing": "Processando...", + "upload": "Enviar", + "days": "dias", + "customize": "Personalizar", + "hide": "Ocultar", + "unknown": "Desconhecido", + "notSet": "Nao definido", + "of": "de", + "up": "Acima", + "select": "Selecionar", + "selected": "Selecionado", + "chunk": "Lote", + "optional": "opcional" + }, + "upload": { + "photoCategory": "Categoria da Foto", + "noCategory": "Sem categoria", + "eventSpecific": "(Especifico do evento)", + "clickToUpload": "Clique para enviar ou arraste e solte", + "fileRequirements": "JPEG, PNG ou WebP (max 50MB por arquivo, {{limit}} arquivos por envio)", + "fileRequirementsMedia": "Imagens JPEG, PNG ou WebP, mais videos MP4/MOV/WEBM (max 50MB por arquivo, {{limit}} arquivos por envio)", + "unsupportedFiles": "Alguns arquivos foram ignorados porque o formato nao e suportado (use JPEG/PNG/WebP/MP4/MOV/WEBM).", + "selectedFiles": "Arquivos selecionados", + "uploading": "Enviando...", + "uploadComplete": "Envio concluido!", + "uploadFailed": "Falha no envio", + "someFilesFailed": "Alguns arquivos falharam ao enviar", + "uploadPhotos": "Enviar Fotos", + "uploadMedia": "Enviar Fotos e Videos", + "importExternal": "Importar de Pasta Externa", + "externalImportInfo": "Todas as fotos da pasta selecionada serao importadas.", + "selectExternalFolder": "Selecione pasta externa em /external-media", + "importFromSelectedFolder": "Importar da pasta selecionada", + "maxFilesReached": "Maximo de {{limit}} arquivos permitidos", + "someFilesSkipped": "Apenas mais {{allowed}} arquivos podem ser adicionados (limite {{limit}})", + "tooManyFiles": "Maximo de {{limit}} arquivos podem ser enviados de uma vez", + "limitInfo": "{{selected}} de {{limit}} arquivos selecionados ({{remaining}} restantes)", + "limitReached": "Limite de envio atingido ({{limit}} arquivos por lote)", + "uploadingChunks": "Enviando {{count}} arquivos em {{total}} lotes...", + "mediaCategory": "Categoria de midia", + "uploadAction": "Enviar {{count}} arquivos" + }, + "navigation": { + "dashboard": "Painel", + "events": "Eventos", + "archives": "Arquivos", + "settings": "Configuracoes", + "eventTypes": "Tipos de Evento", + "branding": "Marca", + "analytics": "Analiticos", + "emailSettings": "Config. de Email", + "backup": "Backup e Restauracao", + "cmsPages": "Paginas CMS", + "users": "Usuarios" + }, + "archives": { + "title": "Arquivos", + "subtitle": "Gerenciar galerias de fotos arquivadas", + "loadingArchives": "Carregando arquivos...", + "totalArchives": "Total de Arquivos", + "storageUsed": "Armazenamento Usado", + "totalPhotos": "Total de Fotos", + "avgArchiveSize": "Tamanho Medio do Arquivo", + "searchPlaceholder": "Buscar arquivos...", + "allTypes": "Todos os Tipos", + "wedding": "Casamento", + "birthday": "Aniversario", + "corporate": "Corporativo", + "other": "Outro", + "sortByDate": "Ordenar por Data", + "sortByName": "Ordenar por Nome", + "sortBySize": "Ordenar por Tamanho", + "tableHeaders": { + "event": "Evento", + "type": "Tipo", + "archivedDate": "Data de Arquivamento", + "size": "Tamanho", + "photos": "Fotos", + "actions": "Acoes" + }, + "noArchivesFound": "Nenhum arquivo encontrado", + "eventDateNA": "Data do evento: N/D", + "processing": "Processando...", + "download": "Baixar", + "restore": "Restaurar", + "delete": "Excluir", + "showing": "Mostrando {{from}} a {{to}} de {{total}} arquivos", + "page": "Pagina {{current}} de {{total}}", + "storageManagement": "Gerenciamento de Armazenamento", + "storageInfo": "Arquivos sao armazenados permanentemente a menos que sejam excluidos manualmente. Considere implementar uma politica de retencao para gerenciar custos de armazenamento.", + "confirmRestore": "Tem certeza que deseja restaurar este arquivo? O evento ficara ativo novamente.", + "confirmDelete": "Tem certeza que deseja excluir permanentemente este arquivo? Esta acao nao pode ser desfeita.", + "restoreSuccess": "Arquivo restaurado com sucesso", + "deleteSuccess": "Arquivo excluido permanentemente" + }, + "auth": { + "login": "Entrar", + "password": "Senha", + "enterPassword": "Digite a Senha da Galeria", + "passwordPlaceholder": "Digite a senha da galeria", + "invalidPassword": "Senha invalida", + "wrongPassword": "Senha incorreta. Por favor, verifique sua senha e tente novamente.", + "tooManyAttempts": "Muitas tentativas de login. Por favor, tente novamente mais tarde.", + "sessionExpired": "Sessao expirada", + "pleaseEnterPassword": "Por favor, digite uma senha", + "passwordHint": "A senha foi fornecida pelo organizador do evento. Entre em contato se voce nao a possui." + }, + "gallery": { + "title": "Galeria de Fotos", + "welcomeMessage": "Mensagem de Boas-vindas", + "expiresOn": "Expira em", + "expires": "Expira", + "expired": "Expirado", + "daysRemaining": "{{days}} dias restantes", + "dayRemaining": "1 dia restante", + "hoursRemaining": "{{hours}} horas restantes", + "expiredMessage": "Esta galeria expirou em {{date}}", + "contactOrganizer": "Por favor, entre em contato com o organizador do evento se precisar de acesso a estas fotos.", + "searchPhotos": "Buscar fotos por nome de arquivo...", + "sortByDate": "Ordenar por Data", + "sortByName": "Ordenar por Nome", + "sortBySize": "Ordenar por Tamanho", + "allPhotos": "Todas as Fotos", + "filter": "Filtrar", + "feedbackFilter": "Filtro de Feedback", + "all": "Todos", + "liked": "Curtidos", + "favorited": "Favoritos", + "favorites": "Favoritos", + "downloadSelected": "Baixar {{count}} Selecionados", + "shareGallery": "Compartilhar Galeria", + "needHelp": "Precisa de ajuda? Entre em contato em", + "noPhotosFound": "Nenhuma foto encontrada", + "failedToLoad": "Falha ao carregar fotos", + "tryAgain": "Tentar Novamente", + "loading": "Carregando galeria...", + "expiredOn": "Esta galeria expirou em {{date}}.", + "expiresIn": "A galeria expira em {{count}} dia", + "expiresIn_plural": "A galeria expira em {{count}} dias", + "downloadBefore": "Baixe suas fotos antes que nao estejam mais disponiveis.", + "publicGalleryTitle": "Esta galeria e acessivel publicamente", + "publicGallerySubtitle": "Carregando as fotos agora...", + "viewGallery": "Ver Galeria", + "downloadAll": "Baixar Tudo", + "downloading": "Baixando {{count}} foto...", + "downloading_plural": "Baixando {{count}} fotos...", + "downloadedPhotos": "{{count}} foto baixada!", + "downloadedPhotos_plural": "{{count}} fotos baixadas!", + "downloadError": "Algumas fotos falharam ao baixar", + "selectPhotos": "Selecionar Fotos", + "cancelSelection": "Cancelar Selecao", + "photosSelected": "{{count}} selecionado(s)", + "selectAll": "Selecionar Tudo", + "deselectAll": "Desmarcar Tudo", + "deleteSelected": "Excluir Selecionados", + "photosCount": "{{count}} foto", + "photosCount_plural": "{{count}} fotos", + "searchByFilename": "Buscar por nome de arquivo...", + "uncategorized": "Sem categoria", + "sortAscending": "Ordem crescente", + "sortDescending": "Ordem decrescente", + "remaining": "restante(s)", + "selectPhotosHint": "Dica: Use Ctrl+Clique (Cmd+Clique no Mac) para selecionar varias fotos rapidamente", + "filters": "Filtros", + "openFilters": "Abrir filtros", + "toggleSidebar": "Alternar barra lateral", + "toggleMenu": "Alternar menu", + "allCategories": "Todas as Categorias", + "categories": "Categorias", + "mediaType": "Midia", + "allMedia": "Toda midia", + "photosOnly": "Fotos", + "videosOnly": "Videos", + "download": "Baixar", + "noMedia": "Nenhuma midia enviada ainda", + "searchPlaceholder": "Buscar fotos...", + "sortBy": "Ordenar Por", + "sortByCaptureDate": "Ordenar por Data de Captura", + "sortByRating": "Ordenar por Avaliacao", + "photoGallery": "Galeria de Fotos", + "photos": "Fotos", + "allRightsReserved": "Todos os direitos reservados", + "searchMemories": "Buscar memorias...", + "thankYou": "Obrigado", + "thankYouMessage": "Por fazer parte da nossa historia e tornar nosso dia especial inesquecivel.", + "feedback": { + "title": "Feedback", + "shareThoughts": "Compartilhe seus pensamentos sobre \"{{name}}\"", + "rateThisMoment": "Avalie este momento", + "comments": "Comentarios", + "noComments": "Nenhum comentario ainda. Seja o primeiro!", + "yourName": "Seu nome", + "yourEmail": "Seu email", + "writeLovelyNote": "Escreva uma mensagem carinhosa...", + "postComment": "Publicar Comentario", + "anonymous": "Anonimo" + } + }, + "categories": { + "title": "Categorias de Fotos", + "global": "Categorias Globais", + "eventSpecific": "Categorias do Evento", + "addCategory": "Adicionar Categoria", + "organizationInfo": "Organize suas fotos em categorias. Categorias ajudam os convidados a navegar e encontrar tipos especificos de fotos.", + "eventSpecificCategories": "Categorias Especificas do Evento", + "noEventSpecificCategories": "Nenhuma categoria especifica do evento. Categorias globais estao disponiveis por padrao.", + "globalCategoriesAlwaysAvailable": "Categorias Globais (sempre disponiveis):", + "deleteCategoryTitle": "Excluir categoria", + "categoryCreatedSuccess": "Categoria criada com sucesso", + "categoryDeletedSuccess": "Categoria excluida com sucesso", + "failedToCreateCategory": "Falha ao criar categoria", + "failedToDeleteCategory": "Falha ao excluir categoria", + "categoryName": "Nome da categoria", + "noCategory": "Sem categoria", + "noCategoriesYet": "Nenhuma categoria ainda. Crie sua primeira categoria para organizar fotos.", + "deleteConfirm": "Tem certeza que deseja excluir \"{{name}}\"?", + "cannotDelete": "Nao e possivel excluir categoria com fotos. Por favor, reatribua as fotos primeiro.", + "setCoverPhoto": "Definir Foto de Capa", + "removeCoverPhoto": "Remover Foto de Capa", + "coverPhotoSet": "Foto de capa definida com sucesso", + "coverPhotoRemoved": "Foto de capa removida", + "failedToSetCoverPhoto": "Falha ao definir foto de capa", + "categoryHeroHint": "Se nenhuma foto de capa for definida para uma categoria, a foto hero padrao sera usada." + }, + "events": { + "title": "Eventos", + "create": "Criar", + "createEvent": "Criar Evento", + "totalViews": "Total de Visualizacoes", + "totalDownloads": "Total de Downloads", + "uniqueVisitors": "Visitantes Unicos", + "createNewEvent": "Criar Novo Evento", + "setupNewGallery": "Configure uma nova galeria de fotos para seu evento", + "createNewEventSubtitle": "Configure uma nova galeria de fotos para seu evento", + "eventNamePlaceholder": "ex., Casamento do Joao e Maria", + "welcomeMessageOptional": "Mensagem de Boas-vindas (Opcional)", + "welcomeMessagePlaceholder": "Bem-vindos ao nosso dia especial! Fique a vontade para baixar e compartilhar estas memorias...", + "hostEmailPlaceholder": "cliente@exemplo.com", + "adminEmailPlaceholder": "admin@exemplo.com", + "securityAndAccess": "Seguranca e Acesso", + "accessAndSecurity": "Acesso e Seguranca", + "enterPassword": "Digite a senha", + "passwordPlaceholder": "Digite uma senha segura", + "confirmPasswordPlaceholder": "Confirme a senha", + "galleryExpiresOn": "A galeria expira em {{date}}", + "guestsWillReceiveWarning": "Os convidados receberao um email de aviso 7 dias antes da expiracao.", + "types": { + "wedding": "Casamento", + "birthday": "Aniversario", + "corporate": "Corporativo", + "other": "Outro" + }, + "themes": { + "default": "Padrao", + "oceanBlue": "Azul Oceano", + "royalPurple": "Roxo Real", + "roseGold": "Rose Gold", + "sunsetAmber": "Ambar do Por do Sol" + }, + "eventDetails": "Detalhes do Evento", + "eventName": "Nome do Evento", + "eventType": "Tipo de Evento", + "eventDate": "Data do Evento", + "hostEmail": "Email do Cliente", + "hostName": "Nome do Cliente", + "hostNamePlaceholder": "Joao Silva", + "adminEmail": "Email do Admin", + "adminNotificationEmail": "Email de Notificacao do Admin", + "expirationDate": "Data de Expiracao", + "active": "Ativo", + "archived": "Arquivado", + "photoCount": "{{count}} fotos", + "totalSize": "Tamanho Total", + "shareLink": "Link de Compartilhamento", + "copyLink": "Copiar Link", + "linkCopied": "Link copiado!", + "viewGallery": "Ver Galeria", + "uploadPhotos": "Enviar Fotos", + "archiveEvent": "Arquivar Evento", + "archiveConfirm": "Tem certeza que deseja arquivar este evento? Esta acao nao pode ser desfeita.", + "extendExpiration": "Estender {{days}} Dias", + "backToEvents": "Voltar aos Eventos", + "loadingEventDetails": "Carregando detalhes do evento...", + "saveChanges": "Salvar Alteracoes", + "eventExpired": "Este evento expirou", + "eventExpiresIn": "Este evento expira em {{days}} dias", + "guestsNoAccess": "Os convidados nao podem mais acessar a galeria. Considere arquivar este evento.", + "warningEmailsSent": "Emails de aviso foram enviados ao cliente.", + "overview": "Visao Geral", + "photos": "Fotos", + "categories": "Categorias", + "eventInformation": "Informacoes do Evento", + "sourceMode": "Modo de Origem", + "sourceModeManaged": "Gerenciado (enviar para o PicPeak)", + "sourceModeReference": "Referenciar pasta externa", + "sourceModeHelp": "Use o modo gerenciado para uploads diretos ou aponte para uma pasta /external-media montada ao usar armazenamento local.", + "externalFolder": "Pasta Externa", + "externalFolderHint": "Estas pastas sao lidas da montagem /external-media dentro do seu container ou host.", + "externalFolderRequired": "Por favor, selecione uma pasta externa antes de salvar.", + "welcomeMessage": "Mensagem de Boas-vindas", + "noWelcomeMessage": "Nenhuma mensagem de boas-vindas definida", + "created": "Criado", + "expires": "Expira", + "shareWithGuests": "Compartilhe este link com os convidados. Eles precisarao da senha para acessar a galeria.", + "shareWithGuestsPublic": "Compartilhe este link com os convidados. Nenhuma senha e necessaria para esta galeria.", + "resetGalleryPassword": "Redefinir Senha da Galeria", + "resendCreationEmail": "Reenviar Email de Criacao", + "creationEmailResent": "Email de criacao foi enfileirado para envio", + "failedToResendEmail": "Falha ao reenviar email de criacao", + "photoStatistics": "Estatisticas de Fotos", + "totalPhotos": "Total de Fotos", + "managePhotos": "Gerenciar Fotos", + "actions": "Acoes", + "archivingInfo": "O arquivamento criara um arquivo ZIP de todas as fotos e removera a galeria do acesso publico.", + "statistics": "Estatisticas", + "views": "Visualizacoes", + "downloads": "Downloads", + "noStatistics": "Nenhuma estatistica disponivel ainda", + "archiveStatus": "Status do Arquivo", + "archivedOn": "Arquivado Em", + "downloadArchive": "Baixar Arquivo", + "loadingPhotos": "Carregando fotos...", + "photoCategories": "Categorias de Fotos", + "organizeCategoriesInfo": "Organize suas fotos em categorias. Categorias ajudam os convidados a navegar e encontrar tipos especificos de fotos.", + "categoriesTip": "Dica: Categorias sao especificas para cada evento. Voce tambem pode criar categorias globais em Configuracoes.", + "contactInformation": "Informacoes de Contato", + "hostEmailHelp": "O cliente recebera notificacoes de criacao e expiracao da galeria", + "adminEmailHelp": "Recebera notificacoes do sistema e confirmacoes de arquivamento", + "securityAccess": "Seguranca e Acesso", + "galleryPassword": "Senha da Galeria", + "requirePasswordToggle": "Exigir senha para esta galeria", + "requirePasswordToggleHelp": "Desative isto se quiser compartilhar a galeria sem senha. Qualquer pessoa com o link podera ver as fotos.", + "publicGalleryWarning": "Galerias publicas sao acessiveis a qualquer pessoa com o link. Considere ativar marcas d'agua nos downloads e monitorar a atividade.", + "passwordHelperText": "Voce pode usar datas como \"04.07.2025\" ou qualquer texto com 6+ caracteres", + "confirmPassword": "Confirmar Senha", + "showPasswords": "Mostrar senhas", + "newPasswordLabel": "Nova Senha da Galeria", + "gallerySettings": "Configuracoes da Galeria", + "themeAndStyle": "Tema e Estilo", + "colorTheme": "Tema de Cores", + "galleryExpiration": "Expiracao da Galeria", + "galleryExpiresIn": "Galeria Expira Em", + "daysAfterEvent": "dias apos a data do evento", + "expiresOn": "Expira em", + "galleryWillExpireOn": "A galeria expira em {{date}}", + "expirationWarning": "Os convidados receberao um email de aviso 7 dias antes da expiracao.", + "noExpiration": "Sem Expiracao", + "noExpirationHelp": "Esta galeria permanecera ativa ate ser arquivada manualmente.", + "photoCap": "Limite de Fotos", + "photoCapHelp": "Número máximo de fotos permitidas. 0 = ilimitado", + "userUploads": "Configuracoes de Upload do Usuario", + "allowUserUploads": "Permitir que convidados enviem fotos", + "allowUserUploadsHelp": "Permitir que convidados enviem suas proprias fotos para esta galeria", + "allowUserUploadsDescription": "Permitir que convidados enviem suas proprias fotos para esta galeria", + "uploadCategory": "Categoria de Upload", + "selectCategory": "Selecione uma categoria para uploads dos usuarios", + "uploadCategoryHelp": "Todas as fotos enviadas pelos usuarios serao adicionadas a esta categoria", + "userUploadWarning": "Uploads dos usuarios serao moderados e podem ser removidos pelos administradores a qualquer momento.", + "allowDownloads": "Permitir download de fotos", + "allowDownloadsHelp": "Permitir que convidados baixem fotos desta galeria", + "downloadPermissions": "Permissoes de Download", + "downloadsEnabled": "Downloads Habilitados", + "downloadsDisabled": "Downloads Desabilitados", + "downloadProtection": "Protecao de Download", + "disableRightClick": "Bloquear menu do botao direito", + "watermarkDownloads": "Adicionar marca d'agua aos downloads", + "enableDevtoolsProtection": "Detectar ferramentas de desenvolvedor", + "useCanvasRendering": "Renderizacao canvas (protecao avancada)", + "protectionInfo": "Os recursos de protecao ajudam a prevenir downloads nao autorizados, mas nao podem bloquear todos os metodos.", + "protectionLevelBasic": "Basico - Apenas bloqueio de clique direito", + "protectionLevelStandard": "Padrao - Atalhos de teclado bloqueados", + "protectionLevelEnhanced": "Aprimorado - Deteccao de captura de tela", + "protectionLevelMaximum": "Maximo - Deteccao de DevTools e renderizacao canvas", + "heroLogoSettings": "Configuracoes do Logo Hero", + "heroLogoVisible": "Exibir logo na secao hero", + "heroLogoSize": "Tamanho do Logo", + "heroLogoSizeSmall": "Pequeno", + "heroLogoSizeMedium": "Medio", + "heroLogoSizeLarge": "Grande", + "heroLogoSizeXLarge": "Extra Grande", + "heroLogoPosition": "Posicao do Logo", + "heroLogoPositionTop": "Topo (acima do titulo)", + "heroLogoPositionCenter": "Centro (entre titulo e datas)", + "heroLogoPositionBottom": "Inferior (abaixo das datas)", + "heroLogoInfo": "Estas configuracoes se aplicam quando a galeria usa o layout Hero. Voce pode ocultar o logo ou personalizar seu tamanho e posicao.", + "eventCustomLogo": "Logo Personalizado do Evento", + "eventCustomLogoDescription": "Envie um logo personalizado para este evento. Isto substitui o logo de marca global apenas para esta galeria.", + "uploadEventLogo": "Enviar Logo", + "replaceLogo": "Substituir", + "removeLogo": "Remover", + "eventLogoUploaded": "Logo do evento enviado com sucesso", + "eventLogoUploadFailed": "Falha ao enviar logo do evento", + "eventLogoRemoved": "Logo do evento removido com sucesso", + "eventLogoRemoveFailed": "Falha ao remover logo do evento", + "heroLogoVisibleLabel": "Logo visivel", + "heroLogoSizeLabel": "Tamanho", + "heroLogoPositionLabel": "Posicao", + "heroLogoHidden": "Logo oculto", + "heroPhoto": "Foto Hero", + "heroPhotoHelp": "Selecione uma foto hero padrao para a galeria. Voce pode substituir isso por categoria.", + "selectHeroPhoto": "Selecionar Foto Hero", + "noHeroPhotoSelected": "Nenhuma foto hero selecionada", + "heroPhotoSelected": "Foto hero selecionada", + "heroImageAnchor": "Posicao de Corte da Imagem Hero", + "heroImageAnchorDescription": "Clique na imagem para definir o ponto focal de corte.", + "heroImageAnchorTop": "Topo", + "heroImageAnchorCenter": "Centro", + "heroImageAnchorBottom": "Inferior", + "heroPreview": "Visualizacao hero", + "noPhotosAvailable": "Nenhuma foto disponivel", + "processingRequest": "Processando sua solicitacao...", + "eventTypeWedding": "Casamento", + "eventTypeBirthday": "Aniversario", + "eventTypeCorporate": "Corporativo", + "eventTypeOther": "Outro", + "days30": "30 dias", + "days60": "60 dias", + "days90": "90 dias", + "days365": "1 ano", + "inactive": "Inativo", + "expired": "Expirado", + "daysLeft": "{{count}}d restante(s)", + "daysLeft_plural": "{{count}}d restante(s)", + "subtitle": "Gerencie suas galerias de fotos e eventos", + "loadingEvents": "Carregando eventos...", + "failedToLoadEvents": "Falha ao carregar eventos", + "tryAgain": "Tentar Novamente", + "bulkArchiveSuccess": "{{count}} eventos arquivados com sucesso", + "bulkArchivePartial": "{{success}} eventos arquivados, {{failed}} falharam", + "searchEventsPlaceholder": "Buscar eventos...", + "all": "Todos", + "expiring": "Expirando", + "eventsSelected": "{{count}} evento selecionado", + "eventsSelected_plural": "{{count}} eventos selecionados", + "clear": "Limpar", + "archiveSelected": "Arquivar Selecionados", + "publicAccess": "Acesso publico", + "passwordProtected": "Protegido por senha", + "newPasswordRequired": "Por favor, defina uma senha antes de ativar a protecao.", + "event": "Evento", + "type": "Tipo", + "date": "Data", + "status": "Status", + "noEventsFound": "Nenhum evento encontrado", + "stats": { + "totalEvents": "Total de Eventos", + "activeEvents": "Eventos Ativos", + "totalPhotos": "Total de Fotos", + "expiringEvents": "Expirando em Breve" + }, + "viewDetails": "Ver Detalhes", + "archiveEventAction": "Arquivar Evento", + "downloadArchiveAction": "Baixar Arquivo", + "deleteEvent": "Excluir Evento", + "deleteEventConfirm": "Tem certeza que deseja excluir este evento?", + "downloadArchiveSoon": "Download de arquivo em breve", + "eventExpiredMessage": "Este evento expirou", + "guestsCannotAccessGallery": "Os convidados nao podem mais acessar a galeria. Considere arquivar este evento.", + "warningEmailsHaveBeenSent": "Emails de aviso foram enviados ao cliente.", + "extendSevenDays": "Estender 7 Dias", + "welcomeMessageLabel": "Mensagem de Boas-vindas", + "noWelcomeMessageSet": "Nenhuma mensagem de boas-vindas definida", + "createdOn": "Criado", + "copy": "Copiar", + "copied": "Copiado!", + "organizingPhotosInfo": "Organize suas fotos em categorias. Categorias ajudam os convidados a navegar e encontrar tipos especificos de fotos.", + "archiveStatusTitle": "Status do Arquivo", + "downloadingArchive": "Baixando arquivo {{name}}...", + "downloadStarted": "Download iniciado", + "failedToDownloadArchive": "Falha ao baixar arquivo", + "statisticsNotAvailable": "Nenhuma estatistica disponivel ainda", + "photoFilters": "Filtros de Fotos", + "noStatisticsAvailableYet": "Nenhuma estatistica disponivel ainda", + "addPlus": "Adicionar+", + "galleryTheme": "Tema da Galeria", + "customizeTheme": "Personalizar Tema", + "noThemeSet": "Nenhum tema configurado", + "customizingTheme": "Personalizando tema da galeria", + "customizingThemeFor": "Personalizando tema para {{event}}", + "customCssTemplate": "Template CSS Personalizado", + "customCssTemplateDesc": "Aplique um template CSS personalizado para estilizar a galeria com efeitos visuais unicos.", + "noTemplate": "Sem Template", + "useThemeOnly": "Usar apenas predefinicao de tema", + "customTemplate": "Template Personalizado", + "rename": { + "button": "Renomear", + "title": "Renomear Evento", + "validating": "Validando novo nome...", + "renamingFiles": "Renomeando arquivos...", + "complete": "Concluido!", + "failed": "Falha ao renomear", + "filesRenamed": "{{count}} arquivos atualizados", + "confirm": "Renomear Evento" + } + }, + "settings": { + "title": "Configuracoes do Sistema", + "subtitle": "Configure as configuracoes e preferencias do sistema", + "loadingSettings": "Carregando configuracoes...", + "general": { + "title": "Geral", + "siteConfiguration": "Configuracao do Site", + "siteUrl": "URL do Site", + "siteUrlHelp": "Usado para gerar links de galeria em emails", + "defaultExpiration": "Expiracao Padrao (dias)", + "defaultExpirationHelp": "Quanto tempo as galerias permanecem ativas por padrao", + "maxFileSize": "Tamanho Maximo do Arquivo (MB)", + "maxFileSizeHelp": "Tamanho maximo por foto enviada", + "maxFilesPerUpload": "Max Arquivos por Upload", + "maxFilesPerUploadHelp": "Numero maximo de fotos permitidas em um unico lote de envio (1-{{max}}).", + "allowedFileTypes": "Tipos de Arquivo Permitidos", + "allowedFileTypesHelp": "Lista de extensoes de arquivo separadas por virgula", + "featureToggles": "Recursos", + "enableAnalytics": "Ativar rastreamento de analiticos", + "enableRegistration": "Permitir auto-registro para admins", + "enableShortGalleryUrls": "Usar URLs curtas de galeria", + "enableShortGalleryUrlsHelp": "Remove o slug do evento dos novos links de compartilhamento, mantendo os links existentes funcionando.", + "maintenanceMode": "Ativar modo de manutencao", + "language": "Idioma", + "defaultLanguage": "Idioma Padrao", + "defaultLanguageHelp": "Idioma exibido para convidados antes do login", + "defaultWelcomeMessage": "Mensagem de Boas-vindas Padrao", + "welcomeMessage": "Mensagem de Boas-vindas", + "welcomeMessagePlaceholder": "Digite uma mensagem de boas-vindas padrao que sera incluida nos emails de criacao da galeria", + "welcomeMessageHelp": "Esta mensagem sera incluida em todos os emails de criacao de galeria, a menos que seja substituida ao criar um evento", + "saveSettings": "Salvar Configuracoes Gerais", + "saveGeneralSettings": "Salvar Configuracoes Gerais", + "dateTimeFormat": "Formato de Data e Hora", + "dateFormat": "Formato de Data", + "dateFormatHelp": "Como as datas sao exibidas em emails e em todo o aplicativo", + "accountSection": "Conta do Admin", + "accountUsername": "Nome de Usuario do Admin", + "accountUsernameHelp": "Exibido na interface de administracao e usado em logs de atividade.", + "accountUsernameRequired": "Nome de usuario e obrigatorio", + "accountUsernameLength": "Nome de usuario deve ter pelo menos 3 caracteres", + "accountEmail": "Email do Admin", + "accountEmailHelp": "Usado para login e recebimento de notificacoes de seguranca.", + "accountEmailRequired": "Endereco de email e obrigatorio", + "accountEmailInvalid": "Digite um endereco de email valido", + "accountSaveButton": "Salvar dados da conta", + "accountSaveSuccess": "Dados da conta atualizados" + }, + "publicSite": { + "tabLabel": "Site Publico", + "badge": "Pagina Publica", + "title": "Pagina Publica de Destino", + "subtitle": "Publique uma pagina de destino personalizada para convidados quando visitarem seu dominio.", + "loading": "Carregando configuracoes do site publico...", + "enabled": "Ativado", + "disabled": "Desativado", + "htmlLabel": "HTML da Pagina de Destino", + "htmlPlaceholder": "Forneca a marcacao HTML para o hero, secoes e chamadas para acao da sua pagina de destino.", + "htmlHelp": "O HTML bruto e sanitizado ao salvar. Tags inseguras, scripts inline e atributos nao permitidos sao removidos automaticamente. Tokens como {{company_name}}, {{company_tagline}} e {{support_email}} serao substituidos por valores das configuracoes de Marca.", + "cssLabel": "CSS Personalizado", + "cssPlaceholder": "Substituicoes de CSS opcionais para ajustar layout, cores ou tipografia.", + "cssHelp": "Importacoes e URLs JavaScript sao removidas. Use seletores CSS padrao para substituir o estilo padrao.", + "saveCta": "Salvar site publico", + "saving": "Salvando...", + "saveSuccess": "Configuracoes do site publico salvas.", + "saveError": "Falha ao salvar configuracoes do site publico.", + "resetCta": "Restaurar padrao", + "resetting": "Restaurando...", + "resetSuccess": "Template do site publico restaurado para os padroes.", + "resetError": "Falha ao restaurar template do site publico.", + "previewTitle": "Visualizacao ao Vivo", + "previewSandboxed": "Visualizacao em sandbox", + "previewDisabled": "Ative o site publico para ver a visualizacao ao vivo.", + "sanitizationNotice": "Scripts, iframes e atributos inseguros sao removidos antes da publicacao. Apenas HTML seguro, links e imagens sao permitidos.", + "htmlRequired": "Forneca conteudo HTML antes de ativar o site publico." + }, + "storage": { + "title": "Armazenamento", + "overview": "Visao Geral do Armazenamento", + "totalUsed": "Total Usado", + "archiveStorage": "Armazenamento de Arquivos", + "storageLimit": "Limite de Armazenamento", + "storageLimitHelper": "Defina um limite para o uso de armazenamento de fotos. Este limite e consultivo e sera destacado quando atingido.", + "softLimitInputLabel": "Limite (GB)", + "softLimitHelper": "Digite o uso maximo preferido de armazenamento em gigabytes.", + "recommendedSoftLimit": "Limite Sugerido", + "diskCapacity": "Capacidade do Disco", + "diskCapacityReported": "Capacidade do Disco (reportada)", + "diskAvailable": "Disponivel", + "diskAvailableReported": "Disponivel (reportado)", + "diskFree": "Livre", + "diskFreeReported": "Livre (reportado)", + "diskMetricsUnavailable": "Metricas de disco nao estao disponiveis no Docker Desktop ou ambientes virtualizados.", + "applyRecommended": "Usar recomendado", + "applyAvailable": "Corresponder ao disponivel", + "invalidSoftLimit": "Digite um numero valido para o limite.", + "saveSoftLimit": "Salvar limite", + "limitNotEnforced": "Este limite e apenas consultivo e nao e aplicado automaticamente.", + "overrideTitle": "Substituicao manual de capacidade", + "diskOverrideEnvNote": "A capacidade do disco e controlada por variaveis de ambiente e nao pode ser editada aqui.", + "diskOverrideSettingsHelp": "Defina valores de capacidade personalizados quando o Docker reportar numeros irreais.", + "overrideCapacityLabel": "Capacidade total (GB)", + "overrideCapacityHelper": "Deixe em branco para usar a leitura automatica.", + "overrideAvailableLabel": "Espaco disponivel (GB)", + "overrideAvailableHelper": "Opcional. Deixe em branco para calcular a partir da capacidade total menos o armazenamento usado.", + "saveOverride": "Salvar substituicao de capacidade", + "capacityRequiredForAvailable": "Digite uma capacidade total antes de definir o espaco disponivel.", + "availableExceedsCapacity": "O espaco disponivel nao pode exceder a capacidade total.", + "storageUsage": "Uso de Armazenamento", + "storageByEvent": "Armazenamento por Evento", + "storageManagement": "Gerenciamento de Armazenamento", + "storageManagementHelp": "Considere arquivar ou excluir eventos antigos para liberar espaco de armazenamento. Eventos arquivados sao comprimidos e usam menos armazenamento que galerias ativas.", + "noEventsUsingStorage": "Nenhum evento usando armazenamento", + "unlimited": "Ilimitado" + }, + "security": { + "title": "Seguranca", + "passwordSettings": "Configuracoes de Senha", + "minPasswordLength": "Comprimento Minimo da Senha", + "minPasswordLengthHelp": "Numero minimo de caracteres para senhas de galeria", + "passwordComplexity": "Complexidade da Senha", + "passwordComplexityHelp": "Nivel de seguranca exigido para senhas de galeria", + "complexitySimple": "Simples (6+ caracteres, qualquer texto)", + "complexityModerate": "Moderada (8+ caracteres, maiusculas/minusculas/numeros)", + "complexityStrong": "Forte (12+ caracteres, maiusculas/minusculas/numeros)", + "complexityVeryStrong": "Muito Forte (12+ caracteres, todos os tipos de caracteres)", + "sessionAuth": "Sessao e Autenticacao", + "sessionTimeout": "Tempo Limite da Sessao (minutos)", + "sessionTimeoutHelp": "Tempo limite da sessao do admin em minutos", + "maxLoginAttempts": "Max Tentativas de Login", + "maxLoginAttemptsHelp": "Maximo de tentativas de login falhadas por IP antes do bloqueio", + "attemptWindowMinutes": "Janela de Tentativas (minutos)", + "attemptWindowMinutesHelp": "Quanto tempo olhar para tras ao contar tentativas de login falhadas", + "lockoutDurationMinutes": "Duracao do Bloqueio (minutos)", + "lockoutDurationMinutesHelp": "Quanto tempo a galeria ou conta permanece bloqueada apos muitas falhas", + "enable2FA": "Ativar autenticacao de dois fatores para admins", + "recaptchaSettings": "Configuracoes do reCAPTCHA", + "enableRecaptcha": "Ativar reCAPTCHA para formularios de login", + "siteKey": "Chave do Site", + "siteKeyHelp": "Sua chave de site reCAPTCHA v2 (publica)", + "secretKey": "Chave Secreta", + "secretKeyHelp": "Sua chave secreta reCAPTCHA v2 (manter privada)", + "recaptchaHelp": "Obtenha suas chaves reCAPTCHA em", + "saveSettings": "Salvar Configuracoes de Seguranca", + "saveSecuritySettings": "Salvar Configuracoes de Seguranca" + }, + "categories": { + "title": "Categorias", + "about": "Sobre Categorias de Fotos", + "aboutText": "Categorias globais estao disponiveis para todos os eventos. Voce tambem pode criar categorias especificas ao editar eventos individuais. Categorias ajudam a organizar fotos e permitem que convidados filtrem fotos por tipo na visualizacao da galeria." + }, + "systemStatus": { + "title": "Status do Sistema", + "storageOverview": "Visao Geral do Armazenamento", + "systemInfo": "Informacoes do Sistema", + "databaseInfo": "Informacoes do Banco de Dados", + "platform": "Plataforma", + "nodeVersion": "Versao do Node", + "uptime": "Tempo Ativo", + "cpuCores": "Nucleos da CPU", + "memoryUsage": "Uso de Memoria", + "memoryUsed": "Memoria Usada", + "photos": "Fotos", + "admins": "Admins", + "dbSize": "Tamanho do Banco de Dados", + "services": "Servicos em Segundo Plano", + "fileWatcher": "Monitoramento de Arquivos", + "fileWatcherDesc": "Monitora novas fotos", + "expirationChecker": "Verificador de Expiracao", + "expirationCheckerDesc": "Arquiva galerias expiradas", + "emailProcessor": "Processador de Email", + "emailProcessorDesc": "Envia emails enfileirados", + "emailQueue": "Status da Fila de Email", + "pending": "Pendente", + "sent": "Enviado", + "failed": "Falhou", + "lastUpdate": "Ultima atualizacao" + }, + "photoDimensions": { + "title": "Dimensoes das Fotos", + "totalPhotos": "Total de Fotos", + "withDimensions": "Com Dimensoes", + "missingDimensions": "Dimensoes Ausentes", + "repairButton": "Reparar Dimensoes", + "repairing": "Reparando...", + "alreadyRunning": "O reparo ja esta em execucao", + "started": "Iniciado reparo de {{count}} fotos", + "noneToRepair": "Todas as fotos ja possuem dimensoes", + "resultSuccess": "Ultimo reparo: {{success}} atualizadas, {{failed}} falharam", + "description": "Preencher largura/altura ausentes para fotos enviadas antes do rastreamento de dimensoes ser adicionado. Necessario para layouts Masonry e Mosaic." + }, + "updateNotifications": { + "title": "Notificacoes de Atualizacao", + "description": "Receba notificacoes por email quando novas versoes do PicPeak estiverem disponiveis.", + "enableEmails": "Ativar notificacoes por email", + "enableEmailsDesc": "Enviar email para admins quando uma nova versao estiver disponivel", + "recipients": "Destinatarios de Email", + "recipientsPlaceholder": "admin@exemplo.com, outro@exemplo.com", + "recipientsHelper": "Enderecos de email separados por virgula. Deixe vazio para enviar a todos os usuarios admin.", + "lastNotified": "Ultima notificacao enviada para a versao: {{version}}", + "checkNow": "Verificar e Notificar", + "sendTest": "Enviar Email de Teste", + "saved": "Configuracoes salvas", + "saveError": "Falha ao salvar configuracoes", + "emailSent": "Email de notificacao enviado para {{count}} destinatarios", + "emailFailed": "Falha ao enviar notificacao", + "checkSuccess": "Notificacao enviada para nova versao", + "checkNoAction": "Nenhuma notificacao necessaria: {{reason}}", + "checkError": "Falha ao verificar atualizacoes" + }, + "events": { + "title": "Criacao de Eventos", + "requiredFields": "Campos Obrigatorios", + "requiredFieldsDescription": "Configure quais campos de contato sao obrigatorios ao criar novos eventos.", + "requireCustomerName": "Exigir nome do cliente", + "requireCustomerNameHelp": "Nome do cliente deve ser fornecido para novos eventos", + "requireCustomerEmail": "Exigir email do cliente", + "requireCustomerEmailHelp": "Email do cliente deve ser fornecido para novos eventos", + "customerEmailWarning": "Necessario para enviar convites da galeria", + "requireAdminEmail": "Exigir email do admin", + "requireAdminEmailHelp": "Email do admin deve ser fornecido para novos eventos", + "adminEmailWarning": "Necessario para receber notificacoes do evento", + "requireEventDate": "Exigir data do evento", + "requireEventDateHelp": "Data do evento deve ser fornecida ao criar eventos", + "eventDateWarning": "URLs da galeria usarao identificadores aleatorios em vez de datas", + "requireExpiration": "Exigir data de expiracao", + "requireExpirationHelp": "Galerias devem ter uma data de expiracao", + "expirationWarning": "Galerias sem expiracao permanecerao ativas ate serem arquivadas manualmente", + "saveSettings": "Salvar Configuracoes de Eventos", + "noteTitle": "Nota", + "noteText": "Estas configuracoes afetam apenas a criacao de novos eventos. Eventos existentes nao sao afetados. O comportamento padrao exige todos os campos." + }, + "imageSecurity": { + "title": "Protecao de Imagem", + "saveSuccess": "Configuracoes de seguranca de imagem salvas", + "saveError": "Falha ao salvar configuracoes", + "loadError": "Falha ao carregar configuracoes de seguranca de imagem", + "defaultProtection": "Configuracoes de Protecao Padrao", + "defaultProtectionHelp": "Estas configuracoes se aplicam a todos os novos eventos. Eventos individuais podem substituir estes padroes.", + "protectionLevel": "Nivel de Protecao Padrao", + "imageQuality": "Qualidade de Imagem Padrao", + "fragmentationLevel": "Nivel de Fragmentacao", + "enableDevtools": "Ativar deteccao de DevTools por padrao", + "enableCanvas": "Ativar renderizacao canvas por padrao (protecao avancada)", + "rateLimiting": "Limitacao de Taxa", + "rateLimitingHelp": "Limite quantas imagens podem ser solicitadas para prevenir raspagem.", + "requestsPerMinute": "Solicitacoes por minuto", + "requestsPer5Minutes": "Solicitacoes por 5 min", + "requestsPerHour": "Solicitacoes por hora", + "securityMonitoring": "Monitoramento de Seguranca", + "suspiciousThreshold": "Limite de atividade suspeita", + "autoBlockThreshold": "Limite de bloqueio automatico", + "enableMonitoring": "Ativar monitoramento de seguranca", + "blockSuspiciousIps": "Bloquear automaticamente IPs suspeitos", + "logEvents": "Registrar eventos de seguranca no banco de dados", + "infoTitle": "Sobre Protecao de Imagem", + "infoText": "Estes recursos de protecao ajudam a prevenir download e copia casual, mas nao podem bloquear todos os metodos. Usuarios determinados ainda podem encontrar formas de capturar imagens. Considere usar marcas d'agua e acordos legais para protecao abrangente." + }, + "moderation": { + "title": "Moderacao", + "wordFilters": "Filtros de Palavras", + "description": "Gerencie palavras que devem ser filtradas ou bloqueadas em comentarios", + "addFilter": "Adicionar Novo Filtro", + "enterWord": "Digite a palavra para filtrar", + "searchFilters": "Buscar filtros...", + "filterAdded": "Filtro de palavra adicionado com sucesso", + "filterExists": "Este filtro de palavra ja existe", + "addError": "Falha ao adicionar filtro de palavra", + "filterUpdated": "Filtro de palavra atualizado com sucesso", + "updateError": "Falha ao atualizar filtro de palavra", + "filterDeleted": "Filtro de palavra excluido com sucesso", + "deleteError": "Falha ao excluir filtro de palavra", + "wordRequired": "Por favor, digite uma palavra para filtrar", + "confirmDelete": "Tem certeza que deseja excluir este filtro de palavra?", + "loading": "Carregando filtros de palavras...", + "noMatchingFilters": "Nenhum filtro correspondente encontrado", + "noFilters": "Nenhum filtro de palavra configurado ainda", + "severityLow": "Baixo", + "severityModerate": "Moderado", + "severityHigh": "Alto", + "severityBlock": "Bloquear", + "severityLevels": "Niveis de Severidade", + "lowDescription": "Palavra e marcada para revisao mas nao e bloqueada automaticamente", + "moderateDescription": "Comentario requer aprovacao manual antes de ficar visivel", + "highDescription": "Comentario e automaticamente ocultado e requer revisao do admin", + "blockDescription": "Comentario e rejeitado imediatamente e nao pode ser enviado" + }, + "styling": { + "title": "CSS Personalizado" + }, + "thumbnails": { + "title": "Miniaturas", + "dimensionsTitle": "Dimensoes e Qualidade das Miniaturas", + "dimensionsHelp": "Configure o tamanho e qualidade das miniaturas geradas automaticamente. Valores maiores produzem visualizacoes melhores, mas aumentam o armazenamento e o tempo de carregamento.", + "width": "Largura (px)", + "widthHelp": "50-1000 pixels", + "height": "Altura (px)", + "heightHelp": "50-1000 pixels", + "quality": "Qualidade", + "qualityHelp": "1-100, maior = melhor qualidade mas arquivos maiores", + "format": "Formato", + "fit": "Modo de Ajuste", + "fitHelp": "Como as imagens sao redimensionadas para caber nas dimensoes da miniatura. \"Cobrir\" corta para preencher, \"Conter\" se ajusta dentro dos limites.", + "fit_cover": "Cobrir (cortar para preencher)", + "fit_contain": "Conter (ajustar dentro)", + "fit_fill": "Preencher (esticar)", + "fit_inside": "Dentro (encolher para caber)", + "fit_outside": "Fora (expandir para cobrir)", + "regenerateTitle": "Regenerar Miniaturas", + "regenerateHelp": "Apos alterar as configuracoes de miniaturas, regenere todas as miniaturas existentes para aplicar a nova configuracao. Isto roda em segundo plano e pode demorar para galerias grandes.", + "regenerateButton": "Regenerar Todas as Miniaturas", + "regenerateStarted": "Regeneracao de miniaturas iniciada", + "regenerateError": "Falha ao iniciar regeneracao de miniaturas", + "saveSuccess": "Configuracoes de miniaturas salvas", + "saveError": "Falha ao salvar configuracoes de miniaturas", + "loadError": "Falha ao carregar configuracoes de miniaturas", + "infoTitle": "Sobre Miniaturas", + "infoText": "Miniaturas sao imagens de visualizacao menores geradas a partir dos originais. Aumentar o tamanho ou qualidade melhora a aparencia das fotos na grade da galeria, mas usa mais armazenamento e largura de banda. Apos alterar as configuracoes, use \"Regenerar Todas as Miniaturas\" para atualizar fotos existentes." + }, + "seo": { + "title": "SEO e Robots", + "indexingTitle": "Indexacao por Mecanismos de Busca", + "allowIndexing": "Permitir indexacao por mecanismos de busca", + "allowIndexingHelp": "Quando desativado, todos os rastreadores sao bloqueados via robots.txt. Recomendado desativar para plataformas privadas de fotos.", + "sitemapUrl": "URL do Sitemap", + "sitemapUrlHelp": "Opcional. Adicionado ao robots.txt se fornecido.", + "aiBlockingTitle": "Bloqueio de IA e Bots", + "blockAiCrawlers": "Bloquear rastreadores de IA/LLM", + "blockAiCrawlersHelp": "Impedir que bots de treinamento de IA acessem seu conteudo.", + "blockedAgents": "Agentes de IA bloqueados", + "addAgentPlaceholder": "Digite o nome do agente...", + "blockSocialBots": "Bloquear bots de pre-visualizacao de redes sociais", + "blockSocialBotsHelp": "Impedir pre-visualizacoes de links no Twitter, Facebook, LinkedIn, etc.", + "metaTagsTitle": "Meta Tags e Regras Personalizadas", + "metaNoindex": "Adicionar meta tag noindex", + "metaNoindexHelp": "Informa aos mecanismos de busca para nao indexar paginas (nivel HTML, complementa robots.txt).", + "metaNofollow": "Adicionar meta tag nofollow", + "metaNofollowHelp": "Informa aos mecanismos de busca para nao seguir links nas paginas.", + "metaNoai": "Adicionar meta tag noai/noimageai", + "metaNoaiHelp": "Sinaliza que o conteudo nao deve ser usado para treinamento de IA.", + "customRules": "Regras personalizadas do robots.txt", + "ruleAgentPlaceholder": "User-agent", + "rulePathPlaceholder": "Caminho de bloqueio", + "showPreview": "Mostrar visualizacao do robots.txt", + "hidePreview": "Ocultar visualizacao do robots.txt", + "saveSettings": "Salvar Configuracoes de SEO" + }, + "analytics": { + "title": "Analiticos", + "umamiIntegration": "Integracao com Umami Analytics", + "enableUmami": "Ativar Umami Analytics", + "umamiUrl": "URL do Umami", + "umamiUrlHelp": "A URL da sua instancia Umami (ex., https://analytics.seudominio.com)", + "websiteId": "ID do Site", + "websiteIdHelp": "Seu ID de site Umami (encontrado no painel do Umami)", + "shareUrl": "URL de Compartilhamento (Opcional)", + "shareUrlHelp": "URL publica de compartilhamento para incorporar o painel completo (criar no Umami)", + "umamiInfo": "Sobre Umami Analytics", + "umamiInfoText": "Umami e uma plataforma de analiticos de codigo aberto focada em privacidade. Rastreia visualizacoes de pagina, visitantes unicos e eventos personalizados sem usar cookies.", + "learnMore": "Saiba mais sobre Umami", + "saveAnalyticsSettings": "Salvar Configuracoes de Analiticos", + "backendAnalytics": "Analiticos do Backend", + "backendAnalyticsText": "O sistema tambem rastreia analiticos basicos no servidor para monitoramento de seguranca e desempenho.", + "tracked": "Metricas Rastreadas", + "galleryViews": "Visualizacoes de pagina da galeria", + "photoDownloads": "Downloads individuais e em massa", + "uniqueVisitors": "Visitantes unicos por IP", + "deviceTypes": "Tipos de dispositivo a partir de user agents", + "privacy": "Privacidade", + "privacyText": "Enderecos IP sao hasheados para privacidade. Nenhum dado pessoal e armazenado. Dados de analiticos sao retidos por 90 dias." + } + }, + "analytics": { + "title": "Painel de Analiticos", + "titleSimple": "Analiticos", + "subtitle": "Acompanhe o desempenho da galeria e o engajamento dos visitantes", + "detailedSubtitle": "Analiticos detalhados fornecidos pelo Umami", + "loadingAnalytics": "Carregando analiticos...", + "showSummaryView": "Mostrar Visao Resumida", + "fullDashboard": "Painel Completo", + "refresh": "Atualizar", + "last7Days": "Ultimos 7 dias", + "last30Days": "Ultimos 30 dias", + "last90Days": "Ultimos 90 dias", + "pageViews": "Visualizacoes de Pagina", + "uniqueVisitors": "Visitantes Unicos", + "totalDownloads": "Total de Downloads", + "topGallery": "Galeria Principal", + "topPages": "Paginas Principais", + "views": "visualizacoes", + "visitors": "visitantes unicos", + "topDownloadsByGallery": "Top Downloads por Galeria", + "deviceBreakdown": "Distribuicao de Dispositivos", + "desktop": "Desktop", + "mobile": "Celular", + "tablet": "Tablet", + "storageUsage": "Uso de Armazenamento", + "used": "Usado", + "of": "de", + "totalPhotos": "Total de Fotos", + "activeEvents": "Eventos Ativos", + "notConfigured": "Umami Analytics Nao Configurado", + "configureInstructions": "Para ver dados reais de analiticos, configure o Umami nas variaveis de ambiente e configuracoes do painel admin.", + "noData": "Nenhum dado disponivel", + "percentChange": "{{percent}}% em relacao ao periodo anterior" + }, + "branding": { + "title": "Marca e Temas", + "titleFull": "Marca e Personalizacao", + "subtitle": "Personalize a aparencia das suas galerias", + "loadingBranding": "Carregando configuracoes de marca...", + "themeAndStyle": "Tema e Estilo", + "companyInfo": "Informacoes da Empresa", + "companyName": "Nome da Empresa", + "companyNameHelp": "Exibido nos cabecalhos da galeria e emails", + "companyTagline": "Slogan da Empresa", + "companyTaglineHelp": "Uma breve descricao do seu negocio", + "supportEmail": "Email de Suporte", + "supportEmailHelp": "Email de contato para suporte aos convidados", + "footerText": "Texto do Rodape", + "footerTextHelp": "Exibido na parte inferior das galerias", + "logo": "Logo", + "currentLogo": "Logo atual", + "uploadLogo": "Enviar Logo", + "removeLogo": "Remover Logo", + "logoHelp": "Tamanho recomendado: 200x60px, PNG ou JPEG", + "favicon": "Favicon", + "currentFavicon": "Favicon atual", + "uploadFavicon": "Enviar Favicon", + "removeFavicon": "Remover Favicon", + "faviconHelp": "Formato PNG ou ICO, tamanho recomendado: 32x32px", + "watermark": "Marca d'agua", + "watermarkSettings": "Configuracoes de Marca d'agua", + "enableWatermarks": "Ativar Marcas d'agua", + "watermarkHelp": "Adicione o nome da sua empresa como marca d'agua nas fotos baixadas", + "watermarkLogo": "Logo da Marca d'agua", + "currentWatermark": "Marca d'agua atual", + "uploadWatermarkLogo": "Enviar Logo da Marca d'agua", + "watermarkPosition": "Posicao da Marca d'agua", + "topLeft": "Superior Esquerdo", + "topRight": "Superior Direito", + "center": "Centro", + "bottomLeft": "Inferior Esquerdo", + "bottomRight": "Inferior Direito", + "watermarkOpacity": "Opacidade da Marca d'agua", + "watermarkSize": "Tamanho da Marca d'agua", + "theme": "Tema", + "galleryTheme": "Tema da Galeria", + "themeCustomization": "Personalizacao do Tema", + "selectPreset": "Selecione um tema predefinido", + "colors": "Cores", + "primaryColor": "Cor Primaria", + "secondaryColor": "Cor Secundaria", + "accentColor": "Cor de Destaque", + "backgroundColor": "Cor de Fundo", + "textColor": "Cor do Texto", + "colorMode": "Modo de Cor", + "colorModeLight": "Claro", + "colorModeDark": "Escuro", + "colorModeAuto": "Automatico", + "colorModeHelp": "Automatico segue a preferencia do sistema do visitante.", + "customCSS": "CSS Personalizado", + "preview": "Visualizar", + "previewInNewTab": "Visualizar em Nova Aba", + "reset": "Redefinir", + "saveChanges": "Salvar Alteracoes", + "applyLivePreview": "Aplicar alteracoes imediatamente (Visualizacao ao Vivo)", + "eventSpecificThemes": "Temas Especificos do Evento", + "eventThemesInfo": "Voce pode substituir estas configuracoes globais de tema para eventos individuais ao cria-los ou edita-los.", + "themePresets": "Predefinicoes de Tema", + "galleryLayout": "Layout da Galeria", + "layoutDescriptions": { + "grid": "Layout de grade classico com tamanhos de foto consistentes", + "masonry": "Layout estilo Pinterest com alturas variadas", + "carousel": "Apresentacao em tela cheia com navegacao", + "timeline": "Fotos organizadas por data", + "hero": "Imagem em destaque com grade abaixo", + "mosaic": "Layout artistico com tamanhos variados", + "justified": "Layout baseado em linhas preservando proporcoes", + "gallery-premium": "Tema claro elegante com hero e masonry (Beta)", + "gallery-story": "Tema escuro cinematico com secoes de cena (Beta)" + }, + "layoutSettings": "Configuracoes de Layout", + "photoSpacing": "Espacamento de Fotos", + "spacing": { + "tight": "Apertado", + "normal": "Normal", + "relaxed": "Espaco" + }, + "photoAnimation": "Animacao de Fotos", + "animation": { + "none": "Nenhuma", + "fade": "Desvanecer", + "scale": "Escala", + "slide": "Deslizar" + }, + "columns": "Colunas", + "mobile": "Celular", + "tablet": "Tablet", + "desktop": "Desktop", + "enableAutoplay": "Ativar Reproducao Automatica", + "autoplayInterval": "Intervalo de Reproducao (segundos)", + "groupPhotosBy": "Agrupar Fotos Por", + "grouping": { + "day": "Dia", + "week": "Semana", + "month": "Mes" + }, + "masonryMode": "Modo de Layout", + "masonryModeOptions": { + "columns": "Colunas (estilo Pinterest)", + "rows": "Linhas (justificado personalizado)", + "flickr": "Flickr (justificado consagrado)", + "justified": "Google Photos (algoritmo Knuth-Plass)" + }, + "masonryModeHint": { + "columns": "Colunas verticais estilo Pinterest com alturas variadas", + "rows": "Layout justificado personalizado baseado em linhas", + "flickr": "Algoritmo de layout justificado de codigo aberto do Flickr", + "justified": "Linhas estilo Google Photos usando algoritmo Knuth-Plass para quebras otimas" + }, + "targetRowHeight": "Altura Alvo da Linha", + "targetRowHeightHint": "Altura em pixels (150-400). Fotos serao dimensionadas para caber nas linhas.", + "lastRowBehavior": "Alinhamento da Ultima Linha", + "lastRowOptions": { + "left": "Alinhado a esquerda", + "center": "Centralizado", + "justify": "Justificado (esticar)" + }, + "showHeroSection": "Mostrar Secao Hero", + "showHeroSectionHint": "Exibir uma imagem hero em destaque acima da galeria justificada", + "heroHeight": "Altura da Secao Hero", + "heroHeightOptions": { + "small": "Pequena (40-50%)", + "medium": "Media (50-70%)", + "large": "Grande (60-80%)" + }, + "heroOverlayOpacity": "Opacidade da Sobreposicao Hero", + "heroOverlayHint": "Escurecer a imagem hero para melhorar a legibilidade do texto", + "typographyAndStyle": "Tipografia e Estilo", + "bodyFont": "Fonte do Corpo", + "headingFont": "Fonte do Titulo", + "sameAsBody": "Mesma do corpo", + "fontSize": "Tamanho da Fonte", + "fontSizes": { + "small": "Pequeno", + "normal": "Normal", + "large": "Grande" + }, + "borderRadius": "Raio da Borda", + "borderRadiusOptions": { + "none": "Nenhum", + "small": "Pequeno", + "medium": "Medio", + "large": "Grande" + }, + "shadowStyle": "Estilo de Sombra", + "shadowOptions": { + "none": "Nenhum", + "subtle": "Sutil", + "normal": "Normal", + "dramatic": "Dramatico" + }, + "backgroundPattern": "Fundo", + "backgroundOptions": { + "none": "Nenhum", + "dots": "Pontos", + "grid": "Grade", + "waves": "Ondas" + }, + "customCSSHelp": "Avancado: Adicione CSS personalizado para customizar ainda mais a aparencia", + "cssInstructions": { + "title": "Como usar CSS Personalizado", + "variables": "Variaveis CSS do Tema", + "variablesDesc": "Use estas variaveis CSS para combinar com suas predefinicoes de tema:", + "layouts": "Layouts Personalizados de Galeria", + "layoutsDesc": "Direcione elementos da galeria com estes seletores:", + "glassEffect": "Efeito Glassmorphism", + "glassEffectDesc": "Crie efeitos modernos de vidro:", + "tip": "Dica", + "tipText": "Use Templates CSS em Configuracoes > Templates CSS para designs pre-construidos como Apple Liquid Glass." + }, + "resetToDefault": "Restaurar Padrao", + "applyTheme": "Aplicar Tema", + "customTheme": "Tema Personalizado", + "customizeTheme": "Personalizar Tema", + "saveTheme": "Salvar Tema", + "previewLayout": "Visualizar Layout", + "livePreview": "Visualizacao ao Vivo", + "heroPlaceholderText": "A foto hero do seu evento aparecera aqui", + "whiteLabel": "White Label", + "hidePoweredBy": "Ocultar marca \"Powered by PicPeak\"", + "hidePoweredByHelp": "Remover a atribuicao PicPeak dos rodapes da galeria para uma experiencia totalmente white-label", + "logoCustomization": "Personalizacao do Logo", + "changeLogo": "Alterar Logo", + "logoSizeSmall": "Pequeno (32px)", + "logoSizeMedium": "Medio (48px)", + "logoSizeLarge": "Grande (64px)", + "logoSizeXLarge": "Extra Grande (96px)", + "logoSizeCustom": "Personalizado", + "logoMaxHeight": "Altura Maxima (pixels)", + "logoMaxHeightHelp": "Defina uma altura maxima personalizada para o logo (20-200 pixels)", + "logoPosition": "Posicao do Logo no Cabecalho", + "positionLeft": "Esquerda", + "positionCenter": "Centro", + "positionRight": "Direita", + "logoDisplayMode": "Modo de Exibicao", + "logoOnly": "Apenas Logo", + "textOnly": "Apenas Nome da Empresa", + "logoAndText": "Logo e Nome da Empresa", + "showLogoInHeader": "Mostrar logo no cabecalho da galeria", + "showLogoInHeaderHelp": "Exibir o logo na barra principal do cabecalho", + "showLogoInHero": "Mostrar logo na secao hero", + "showLogoInHeroHelp": "Exibir o logo nas secoes hero (para layouts nao-grade)", + "headerStyle": "Estilo do Cabecalho", + "headerStyleDescription": "Escolha como o cabecalho da galeria aparece. O estilo do cabecalho e independente do layout das fotos.", + "headerStyleOptions": { + "hero": "Imagem Hero", + "standard": "Banner Padrao", + "minimal": "Minimalista", + "none": "Sem Cabecalho" + }, + "headerStyleDescriptions": { + "hero": "Imagem em altura total com sobreposicao de informacoes do evento", + "standard": "Banner classico com detalhes do evento", + "minimal": "Cabecalho compacto com informacoes essenciais", + "none": "Ocultar cabecalho completamente" + }, + "heroDividerStyle": "Estilo do Divisor", + "heroDividerDescription": "Escolha como a transicao entre a imagem hero e o conteudo da galeria aparece.", + "dividerOptions": { + "wave": "Onda", + "straight": "Reto", + "angle": "Angulo", + "curve": "Curva", + "none": "Nenhum" + }, + "controlsStyle": "Estilo dos Controles", + "controlsStyleDescription": "Escolha como os filtros e controles da galeria sao exibidos.", + "controlsStyleOptions": { + "classic": "Classico", + "sidebar": "Barra Lateral" + }, + "controlsStyleDescriptions": { + "classic": "Barra de filtros inline abaixo do cabecalho", + "sidebar": "Botao de menu abre barra lateral com filtros" + }, + "controlsStyleHeroWarning": "Barra lateral e recomendada para cabecalhos hero para evitar que os controles aparecam acima da imagem hero." + }, + "admin": { + "title": "Painel Admin", + "welcome": "Bem-vindo de volta, {{name}}", + "recentActivity": "Atividade Recente", + "systemStatus": "Status do Sistema", + "totalEvents": "Total de Eventos", + "activeGalleries": "Galerias Ativas", + "storageUsed": "Armazenamento Usado", + "totalPhotos": "Total de Fotos", + "storagePercent": "{{percent}}% do limite {{limit}}", + "storageSoftLimitConfigured": "Limite configurado: {{limit}}", + "storageSoftLimitRecommended": "Limite sugerido: {{limit}}", + "version": "Versao", + "archivedEvents": "Eventos Arquivados", + "systemHealth": "Saude do Sistema", + "health": { + "healthy": "Saudavel", + "warning": "Alerta", + "error": "Erro", + "checking": "Verificando..." + }, + "updates": { + "available": "Atualizacao Disponivel", + "newVersion": "Versao {{version}} esta disponivel", + "currentVersion": "Atual: {{version}}", + "channel": "Canal: {{channel}}", + "channelStable": "Estavel", + "channelBeta": "Beta", + "beta": "BETA", + "viewReleaseNotes": "Ver Notas de Lancamento", + "updateAvailableShort": "v{{version}} disponivel", + "checkForUpdates": "Verificar Atualizacoes", + "upToDate": "Voce esta atualizado", + "lastChecked": "Ultima verificacao: {{time}}", + "updateNow": "Atualizar Agora", + "updateDialog": { + "title": "Atualizar PicPeak", + "detectedEnv": "Ambiente Detectado", + "beforeUpdating": "Antes de atualizar:", + "updateCommands": "Comandos de Atualizacao:", + "afterUpdating": "Apos atualizar:", + "copyAllCommands": "Copiar Todos os Comandos", + "completeChecklist": "Complete a lista de verificacao antes de atualizar", + "error": "Falha ao carregar instrucoes de atualizacao" + } + }, + "notifications": "Notificacoes", + "viewAllNotifications": "Ver todas as notificacoes", + "noNotifications": "Nenhuma nova notificacao", + "markAllRead": "Marcar todas como lidas", + "clearAll": "Limpar todas", + "close": "Fechar", + "noNotificationsMessage": "Sem notificacoes", + "notificationMessages": { + "eventCreated": "Novo evento \"{{eventName}}\" foi criado", + "eventArchived": "Evento \"{{eventName}}\" foi arquivado", + "eventUpdated": "Evento \"{{eventName}}\" foi atualizado", + "eventDeleted": "Evento \"{{eventName}}\" foi excluido", + "photosUploaded": "{{count}} fotos enviadas para \"{{eventName}}\"", + "photoDeleted": "Foto excluida de \"{{eventName}}\"", + "photosBulkDeleted": "{{count}} fotos excluidas de \"{{eventName}}\"", + "eventExpiring": "Evento \"{{eventName}}\" expira em {{days}} dias", + "eventExpired": "Evento \"{{eventName}}\" expirou", + "passwordChanged": "Senha alterada por {{actorName}}", + "passwordReset": "Senha redefinida para \"{{eventName}}\"", + "settingsUpdated": "Configuracoes de {{type}} atualizadas", + "emailTemplateUpdated": "Template de email \"{{template}}\" atualizado", + "bulkDownload": "{{count}} fotos baixadas de \"{{eventName}}\"", + "storageWarning": "Uso de armazenamento em {{percentage}}%", + "adminLogout": "Admin {{actorName}} fez logout", + "categoryCreated": "Categoria \"{{name}}\" criada para \"{{eventName}}\"", + "categoryUpdated": "Categoria \"{{name}}\" atualizada para \"{{eventName}}\"", + "categoryDeleted": "Categoria \"{{name}}\" excluida de \"{{eventName}}\"", + "cmsPageUpdated": "Pagina CMS \"{{slug}}\" atualizada", + "emailConfigUpdated": "Configuracao de email atualizada", + "faviconUploaded": "Favicon enviado", + "brandingUpdated": "Configuracoes de marca atualizadas", + "generalSettingsUpdated": "Configuracoes gerais atualizadas", + "securitySettingsUpdated": "Configuracoes de seguranca atualizadas", + "themeUpdated": "Configuracoes de tema atualizadas", + "archiveDownloaded": "Arquivo baixado para \"{{eventName}}\"", + "archiveDeleted": "Arquivo excluido para \"{{eventName}}\"", + "archiveRestored": "Arquivo restaurado para \"{{eventName}}\"", + "systemActivity": "Atividade do sistema: {{type}}", + "adminProfileUpdated": "Perfil admin atualizado por {{actorName}}" + }, + "notificationToasts": { + "markedAllRead": "Todas as notificacoes marcadas como lidas", + "clearedAll": "{{count}} notificacoes limpas", + "profileUpdated": "Perfil admin atualizado" + }, + "markAsRead": "Marcar como lida", + "markAllAsRead": "Marcar todas como lidas", + "notificationSettings": "Configuracoes de Notificacao", + "changePassword": "Alterar Senha", + "darkMode": "Mudar para modo escuro", + "lightMode": "Mudar para modo claro", + "accountSettings": { + "title": "Conta admin", + "description": "Atualize as credenciais usadas para entrar no PicPeak.", + "username": "Nome de usuario", + "usernamePlaceholder": "Admin", + "email": "Email", + "emailPlaceholder": "admin@exemplo.com", + "updateButton": "Atualizar perfil" + }, + "profileUpdateError": "Nao foi possivel atualizar o perfil admin. Por favor, tente novamente.", + "loadingDashboard": "Carregando painel...", + "activeEvents": "Eventos Ativos", + "expiringSoon": "Expirando em Breve", + "next7Days": "Proximos 7 dias", + "totalViews": "Total de Visualizacoes", + "downloads": "Downloads", + "percentFromLastWeek": "{{percent}}% da semana passada", + "dashboardSubtitle": "Bem-vindo de volta! Veja o que esta acontecendo com suas galerias.", + "eventsExpiringSoon": "Eventos Expirando em Breve", + "noEventsExpiring": "Nenhum evento expirando nos proximos 7 dias", + "daysLeft": "{{count}} dia restante", + "daysLeft_plural": "{{count}} dias restantes", + "viewAllExpiringEvents": "Ver todos os {{count}} eventos expirando", + "noRecentActivity": "Nenhuma atividade recente", + "viewAllActivity": "Ver toda atividade", + "quickActions": "Acoes Rapidas", + "viewArchives": "Ver Arquivos", + "analytics": "Analiticos", + "activities": { + "event_created": "Novo evento criado: {{eventName}}", + "photos_uploaded": "{{count}} fotos enviadas para {{eventName}}", + "event_archived": "Evento arquivado: {{eventName}}", + "archive_restored": "Arquivo restaurado: {{eventName}}", + "archive_deleted": "Arquivo excluido: {{eventName}}", + "archive_downloaded": "Arquivo baixado: {{eventName}}", + "email_config_updated": "Configuracao de email atualizada", + "email_template_updated": "Template de email atualizado: {{template}}", + "branding_updated": "Configuracoes de marca atualizadas", + "theme_updated": "Configuracoes de tema atualizadas", + "bulk_download": "{{count}} fotos baixadas de {{eventName}}", + "gallery_password_entry": "Senha inserida para {{eventName}}", + "expiration_warning_viewed": "Aviso de expiracao visualizado para {{eventName}}", + "feedback_settings_updated": "Configuracoes de feedback atualizadas", + "feedback_moderated": "Feedback moderado", + "feedback_deleted": "Feedback excluido", + "photo_like": "Foto curtida em {{eventName}}", + "photo_favorite": "Foto favoritada em {{eventName}}", + "photo_rating": "Foto avaliada em {{eventName}}", + "photo_comment": "Foto comentada em {{eventName}}", + "guest_feedback_like": "Convidado curtiu uma foto em {{eventName}}", + "guest_feedback_favorite": "Convidado favoritou uma foto em {{eventName}}", + "guest_feedback_rating": "Convidado avaliou uma foto em {{eventName}}", + "guest_feedback_comment": "Convidado comentou uma foto em {{eventName}}", + "word_filter_added": "Filtro de palavra adicionado", + "external_import_completed": "Importacao de midia externa concluida ({{imported}} importados, {{skipped}} ignorados)", + "bulk_archive_completed": "Arquivamento em massa concluido", + "event_activated": "Evento ativado: {{eventName}}", + "event_deactivated": "Evento desativado: {{eventName}}", + "photo_deleted": "Foto excluida de {{eventName}}", + "photos_bulk_deleted": "{{count}} fotos excluidas de {{eventName}}", + "settings_updated": "Configuracoes atualizadas", + "event_updated": "Evento atualizado: {{eventName}}", + "event_renamed": "Evento renomeado: {{eventName}}", + "event_deleted": "Evento excluido: {{eventName}}", + "password_changed": "Senha alterada", + "email_resent": "Email de criacao reenviado para: {{eventName}}", + "category_created": "Categoria criada: {{categoryName}}", + "category_updated": "Categoria atualizada: {{categoryName}}", + "category_deleted": "Categoria excluida: {{categoryName}}", + "general_settings_updated": "Configuracoes gerais atualizadas", + "favicon_uploaded": "Favicon enviado", + "analytics_settings_updated": "Configuracoes de analiticos atualizadas", + "cms_page_updated": "Pagina CMS atualizada: {{page}}", + "security_settings_updated": "Configuracoes de seguranca atualizadas", + "password_reset": "Senha redefinida para: {{eventName}}", + "admin_logout": "Admin {{actorName}} fez logout", + "system_activity": "Atividade do sistema: {{type}}", + "unknown": "Atividade desconhecida" + }, + "userManagement": "Gerenciamento de Usuarios", + "inviteUser": "Convidar Usuario", + "pendingInvitations": "Convites Pendentes", + "roles": { + "super_admin": "Super Admin", + "admin": "Admin", + "editor": "Editor", + "viewer": "Visualizador" + }, + "userStatus": { + "active": "Ativo", + "inactive": "Inativo" + }, + "inviteForm": { + "email": "Endereco de Email", + "role": "Funcao", + "send": "Enviar Convite" + }, + "acceptInvite": { + "title": "Aceitar Convite de Admin", + "username": "Escolha um Nome de Usuario", + "password": "Criar Senha", + "submit": "Criar Conta" + } + }, + "permissions": { + "insufficient": "Voce nao tem permissao para realizar esta acao", + "viewOnly": "Apenas Visualizacao" + }, + "acceptInvitation": { + "title": "Aceitar Convite", + "subtitle": "Crie sua conta de admin", + "validating": "Validando convite...", + "invalidToken": "Convite Invalido", + "invalidTokenMessage": "Este link de convite e invalido ou expirou. Por favor, entre em contato com seu administrador para um novo convite.", + "expiredToken": "Convite Expirado", + "expiredTokenMessage": "Este convite expirou. Por favor, solicite um novo convite ao seu administrador.", + "alreadyUsed": "Convite Ja Utilizado", + "alreadyUsedMessage": "Este convite ja foi usado para criar uma conta.", + "invitedAs": "Voce foi convidado como", + "expiresAt": "O convite expira", + "usernameLabel": "Nome de Usuario", + "usernamePlaceholder": "Escolha um nome de usuario", + "usernameHelp": "3-50 caracteres, apenas letras, numeros, sublinhados e hifens", + "passwordLabel": "Senha", + "passwordPlaceholder": "Crie uma senha forte", + "confirmPasswordLabel": "Confirmar Senha", + "confirmPasswordPlaceholder": "Confirme sua senha", + "passwordStrength": "Forca da senha", + "requirements": { + "title": "Requisitos da senha:", + "minLength": "Pelo menos 12 caracteres", + "uppercase": "Pelo menos uma letra maiuscula", + "lowercase": "Pelo menos uma letra minuscula", + "number": "Pelo menos um numero", + "special": "Pelo menos um caractere especial" + }, + "strength": { + "weak": "Fraca", + "fair": "Razoavel", + "good": "Boa", + "strong": "Forte" + }, + "createAccount": "Criar Conta", + "creating": "Criando conta...", + "success": "Conta Criada!", + "successMessage": "Sua conta foi criada com sucesso. Voce ja pode fazer login com suas credenciais.", + "redirecting": "Redirecionando para login em {{seconds}}...", + "goToLogin": "Ir para Login", + "contactAdminMessage": "Por favor, entre em contato com seu administrador para solicitar um novo convite.", + "passwordsMatch": "Senhas conferem", + "alreadyHaveAccount": "Ja possui uma conta?", + "signIn": "Entrar", + "errors": { + "usernameRequired": "Nome de usuario e obrigatorio", + "usernameTooShort": "Nome de usuario deve ter pelo menos 3 caracteres", + "usernameTooLong": "Nome de usuario deve ter no maximo 50 caracteres", + "usernameInvalid": "Nome de usuario pode conter apenas letras, numeros, sublinhados e hifens", + "passwordRequired": "Senha e obrigatoria", + "passwordTooShort": "Senha deve ter pelo menos 12 caracteres", + "passwordsDoNotMatch": "Senhas nao conferem", + "confirmPasswordRequired": "Por favor, confirme sua senha", + "usernameTaken": "Este nome de usuario ja esta em uso", + "emailTaken": "Uma conta com este email ja existe", + "genericError": "Falha ao criar conta. Por favor, tente novamente." + } + }, + "errors": { + "notFound": "Nao Encontrado", + "galleryNotFound": "Galeria Nao Encontrada", + "galleryNotFoundMessage": "Esta galeria nao existe ou foi removida.", + "galleryArchived": "Galeria Arquivada", + "galleryArchivedMessage": "Esta galeria foi arquivada e nao esta mais acessivel. Por favor, entre em contato com o organizador do evento se precisar de acesso a estas fotos.", + "unauthorized": "Nao Autorizado", + "forbidden": "Proibido", + "serverError": "Erro do Servidor", + "somethingWentWrong": "Algo deu errado", + "tryAgainLater": "Por favor, tente novamente mais tarde", + "refreshPage": "Atualizar Pagina", + "oopsSomethingWentWrong": "Ops! Algo deu errado", + "unexpectedError": "Encontramos um erro inesperado. Nao se preocupe, seus dados estao seguros.", + "goToHomepage": "Ir para a Pagina Inicial", + "errorDetails": "Detalhes do Erro", + "requiredFields": "Por favor, preencha todos os campos obrigatorios", + "enterTestEmail": "Por favor, digite um endereco de email de teste", + "failedToCreateEvent": "Falha ao criar evento", + "eventCreationFailed": "Falha ao criar evento", + "networkError": "Erro de rede. Por favor, verifique sua conexao e tente novamente.", + "sessionExpired": "Sessao expirada. Por favor, faca login novamente." + }, + "validation": { + "eventNameRequired": "Nome do evento e obrigatorio", + "hostEmailRequired": "Email do cliente e obrigatorio", + "hostNameRequired": "Nome do cliente e obrigatorio", + "adminEmailRequired": "Email do admin e obrigatorio", + "invalidEmailFormat": "Formato de email invalido", + "passwordRequired": "Senha e obrigatoria", + "passwordMinLength": "Senha deve ter pelo menos 6 caracteres", + "passwordsDoNotMatch": "Senhas nao conferem", + "passwordSecurityRequirements": "Senha nao atende aos requisitos de seguranca", + "expirationRange": "Expiracao deve ser entre 1 e 365 dias" + }, + "legal": { + "impressum": "Aviso Legal", + "datenschutz": "Politica de Privacidade", + "termsOfService": "Termos de Servico", + "cookiePolicy": "Politica de Cookies" + }, + "toast": { + "saveSuccess": "Alteracoes salvas com sucesso", + "saveError": "Falha ao salvar alteracoes", + "deleteSuccess": "Excluido com sucesso", + "deleteError": "Falha ao excluir", + "uploadSuccess": "Upload concluido com sucesso", + "uploadError": "Falha no upload", + "loginSuccess": "Login realizado com sucesso", + "loginError": "Falha no login", + "passwordChanged": "Senha alterada com sucesso", + "linkCopied": "Link copiado para a area de transferencia", + "eventCreated": "Evento criado com sucesso", + "eventUpdated": "Evento atualizado com sucesso", + "eventArchived": "Evento arquivado com sucesso", + "settingsSaved": "Configuracoes salvas com sucesso", + "themeUpdated": "Tema atualizado com sucesso", + "brandingUpdated": "Marca atualizada com sucesso", + "categoryAdded": "Categoria adicionada com sucesso", + "categoryDeleted": "Categoria excluida com sucesso", + "categoryUpdated": "Categoria atualizada com sucesso", + "emailConfigSaved": "Configuracao de email salva com sucesso", + "testEmailSent": "Email de teste enviado com sucesso", + "pageUpdated": "Pagina atualizada com sucesso", + "archiveRestored": "Arquivo restaurado com sucesso", + "archiveDeleted": "Arquivo excluido permanentemente" + }, + "email": { + "title": "Configuracao de Email", + "subtitle": "Configure as configuracoes de email para notificacoes", + "loadingSettings": "Carregando configuracoes de email...", + "smtpConfiguration": "Configuracao SMTP", + "smtpHost": "Host SMTP", + "smtpHostHelp": "Nome do host do seu servidor de email", + "smtpPort": "Porta SMTP", + "smtpPortHelp": "Geralmente 587 para TLS, 465 para SSL, 25 para nao criptografado", + "smtpSecure": "Usar SSL/TLS", + "smtpSecureHelp": "Ativar para transmissao segura de email", + "smtpUsername": "Usuario SMTP", + "smtpUsernameHelp": "Nome de usuario da sua conta de email", + "smtpPassword": "Senha SMTP", + "smtpPasswordHelp": "Senha da sua conta de email", + "fromDetails": "Detalhes do Remetente", + "fromEmail": "Email do Remetente", + "fromEmailHelp": "Endereco de email que aparece como remetente", + "fromName": "Nome do Remetente", + "fromNameHelp": "Nome que aparece como remetente", + "testConfiguration": "Testar Configuracao", + "testEmail": "Endereco de Email de Teste", + "testEmailHelp": "Envie um email de teste para verificar as configuracoes", + "sendTestEmail": "Enviar Email de Teste", + "saveConfiguration": "Salvar Configuracao", + "emailTemplates": "Templates de Email", + "templateVariables": "Variaveis Disponiveis", + "previewTemplate": "Visualizar Template", + "smtpSettings": "Configuracoes SMTP", + "testEmailSuccess": "Email de teste enviado com sucesso", + "saveSmtpSettings": "Salvar Configuracoes SMTP", + "testEmailSection": "Email de Teste", + "beforeTesting": "Antes de testar:", + "saveSmtpFirst": "Salve suas configuracoes SMTP primeiro", + "ensureFirewall": "Certifique-se de que seu firewall permite SMTP de saida", + "gmailAppPassword": "Para Gmail, use uma senha especifica do aplicativo", + "testEmailAddressLabel": "Endereco de Email de Teste", + "sendTestEmailButton": "Enviar Email de Teste", + "commonSmtpSettings": "Configuracoes SMTP Comuns:", + "editTemplate": "Editar Template", + "templateName": "Nome do Template", + "subjectLine": "Linha de Assunto", + "emailBody": "Corpo do Email", + "preview": "Visualizar", + "save": "Salvar", + "saveChanges": "Salvar Alteracoes", + "templates": "Templates", + "variableHelp": "Use estas variaveis no seu template. Elas serao substituidas por valores reais quando os emails forem enviados.", + "port": "Porta", + "security": "Seguranca", + "username": "Nome de usuario", + "password": "Senha", + "enterPassword": "Digite a senha", + "required": "obrigatorio", + "ignoreSslErrors": "Ignorar erros de certificado SSL/TLS", + "ignoreSslWarning": "Aviso: Desabilitar verificacao de certificado torna a conexao vulneravel a ataques man-in-the-middle. Ative somente se voce confia no servidor SMTP e entende as implicacoes de seguranca.", + "editor": { + "bold": "Negrito", + "italic": "Italico", + "bulletList": "Lista com Marcadores", + "numberedList": "Lista Numerada", + "blockquote": "Citacao", + "link": "Adicionar Link", + "horizontalRule": "Linha Horizontal", + "alignLeft": "Alinhar a Esquerda", + "alignCenter": "Centralizar", + "alignRight": "Alinhar a Direita", + "clearFormatting": "Limpar Formatacao", + "undo": "Desfazer", + "redo": "Refazer", + "insertVariable": "Inserir Variavel", + "sourceMode": "Codigo", + "visualMode": "Visual", + "enterUrl": "Digite a URL...", + "addLink": "Adicionar", + "cancel": "Cancelar" + } + }, + "cms": { + "title": "Paginas CMS", + "subtitle": "Gerenciar paginas legais e informativas", + "loadingPages": "Carregando paginas...", + "pages": "Paginas", + "previewLinks": "Links de Visualizacao", + "englishVersion": "Versao em Ingles", + "germanVersion": "Versao em Alemao", + "editPage": "Editar {{page}}", + "pageTitle": "Titulo da Pagina", + "pageContent": "Conteudo da Pagina", + "pageTitlePlaceholder": "Digite o titulo da pagina...", + "saveChanges": "Salvar Alteracoes", + "lastUpdated": "Ultima atualizacao:", + "impressum": "Aviso Legal", + "datenschutz": "Politica de Privacidade", + "pageUpdated": "Pagina atualizada com sucesso" + }, + "eventTypes": { + "title": "Tipos de Evento", + "subtitle": "Personalize tipos de evento e seus temas padrao", + "loading": "Carregando tipos de evento...", + "loadError": "Falha ao carregar tipos de evento", + "createNew": "Novo Tipo de Evento", + "edit": "Editar Tipo de Evento", + "created": "Tipo de evento criado com sucesso", + "updated": "Tipo de evento atualizado com sucesso", + "deleted": "Tipo de evento excluido com sucesso", + "createError": "Falha ao criar tipo de evento", + "updateError": "Falha ao atualizar tipo de evento", + "deleteError": "Falha ao excluir tipo de evento", + "searchPlaceholder": "Buscar tipos de evento...", + "showInactive": "Mostrar inativos", + "noResults": "Nenhum tipo de evento encontrado", + "empty": "Nenhum tipo de evento ainda", + "system": "Sistema", + "table": { + "type": "Tipo", + "slugPrefix": "Prefixo da URL", + "theme": "Tema Padrao", + "status": "Status", + "actions": "Acoes" + }, + "form": { + "name": "Nome de Exibicao", + "namePlaceholder": "ex., Ensaio Familiar", + "slugPrefix": "Prefixo da URL", + "slugPrefixPlaceholder": "ex., familia", + "slugPreview": "URL de exemplo:", + "emoji": "Icone", + "themePreset": "Tema Padrao", + "isActive": "Ativo (visivel na criacao de eventos)" + }, + "validation": { + "slugFormat": "Apenas letras, numeros e hifens sao permitidos" + }, + "slugInfo": { + "title": "Info do Prefixo da URL:", + "description": "O prefixo da URL e usado para gerar URLs de galeria. Por exemplo, um tipo de evento com prefixo \"familia\" criara URLs como: familia-silva-familia-2025-01-22" + }, + "deleteConfirm": { + "title": "Excluir Tipo de Evento", + "message": "Tem certeza que deseja excluir", + "warning": "Esta acao nao pode ser desfeita. Certifique-se de que nenhum evento esta usando este tipo." + } + }, + "backup": { + "external": { + "warning": { + "title": "Midia externa excluida", + "body": "Esta instalacao referencia fotos de /external-media. Estes originais sao excluidos dos backups. Miniaturas e banco de dados ainda sao salvos." + } + }, + "title": "Gerenciamento de Backup", + "subtitle": "Gerencie backups do sistema, configure backups automatizados e restaure a partir de backups anteriores.", + "tabs": { + "dashboard": "Painel", + "configuration": "Configuracao", + "history": "Historico de Backup", + "restore": "Restaurar" + }, + "status": { + "inProgress": "Backup em andamento...", + "lastBackup": "Ultimo backup", + "noBackups": "Nenhum backup encontrado", + "nextBackup": "Proximo backup", + "notScheduled": "Nao agendado", + "enabled": "Ativado", + "disabled": "Desativado" + }, + "actions": { + "runBackupNow": "Executar Backup Agora", + "starting": "Iniciando...", + "running": "Executando...", + "testConnection": "Testar Conexao", + "save": "Salvar Configuracao", + "delete": "Excluir", + "view": "Ver Detalhes", + "download": "Baixar", + "refresh": "Atualizar" + }, + "dashboard": { + "backupHealth": "Saude do Backup", + "healthStatus": { + "excellent": "Excelente", + "good": "Bom", + "warning": "Alerta", + "critical": "Critico" + }, + "health": { + "title": "Saude do Backup" + }, + "healthMessages": { + "noBackups": "Nenhum backup encontrado", + "lastBackupFailed": "Ultimo backup falhou", + "upToDate": "Backup esta atualizado", + "recent": "Backup e recente", + "gettingOld": "Backup esta ficando antigo", + "outdated": "Backup esta desatualizado" + }, + "stats": { + "totalBackups": "Total de Backups", + "backupSize": "Tamanho do Backup", + "lastDuration": "Ultima Duracao", + "backupStatus": "Status do Backup", + "last": "Ultimo", + "files": "arquivos", + "minutes": "{{count}}m", + "active": "Ativo", + "inactive": "Inativo", + "noBackupsYet": "Nenhum backup ainda" + }, + "recentActivity": { + "title": "Atividade Recente de Backup" + }, + "notConfigured": { + "title": "Backup Nao Configurado", + "message": "Por favor, configure as configuracoes de backup na aba Configuracao antes de executar backups." + }, + "actions": { + "runBackupNow": "Executar Backup Agora", + "running": "Executando..." + }, + "coverage": { + "title": "Cobertura do Backup", + "database": "Banco de Dados", + "photos": "Fotos", + "archives": "Arquivos", + "systemFiles": "Arquivos do Sistema", + "included": "Incluido", + "excluded": "Excluido", + "optional": "Opcional" + }, + "storageDestination": "Destino de Armazenamento", + "nextScheduledBackup": "Proximo Backup Agendado", + "backupType": "Backup {{type}}", + "noDestinationSet": "Nenhum destino definido" + }, + "configuration": { + "enableBackup": "Ativar Backups Automatizados", + "enableBackupHelp": "Criar backups automaticamente de acordo com o agendamento configurado", + "destinationType": "Destino do Backup", + "destinationTypes": { + "local": { + "name": "Armazenamento Local", + "description": "Armazenar backups no sistema de arquivos local do servidor" + }, + "rsync": { + "name": "Servidor Remoto (Rsync)", + "description": "Sincronizar backups para um servidor remoto via SSH/Rsync" + }, + "s3": { + "name": "Armazenamento Compativel com S3", + "description": "Armazenar backups no Amazon S3 ou armazenamento de objetos compativel" + } + }, + "fields": { + "destinationPath": "Caminho de Destino", + "destinationPathHelp": "Caminho do diretorio local para armazenar backups", + "destinationPathPlaceholder": "/caminho/para/diretorio/backup", + "rsyncHost": "Host Remoto", + "rsyncHostHelp": "Nome do host ou endereco IP SSH", + "rsyncHostPlaceholder": "backup.exemplo.com", + "rsyncUser": "Usuario SSH", + "rsyncUserHelp": "Nome de usuario para conexao SSH", + "rsyncUserPlaceholder": "usuario-backup", + "rsyncPath": "Caminho Remoto", + "rsyncPathHelp": "Caminho do diretorio no servidor remoto", + "rsyncPathPlaceholder": "/home/backup/compartilhamento-fotos", + "rsyncSshKey": "Chave Privada SSH", + "rsyncSshKeyHelp": "Chave privada SSH para autenticacao (opcional)", + "rsyncSshKeyPlaceholder": "-----BEGIN RSA PRIVATE KEY-----", + "s3Endpoint": "Endpoint S3", + "s3EndpointHelp": "Endpoint da API S3 (ex., s3.amazonaws.com)", + "s3EndpointPlaceholder": "https://s3.amazonaws.com", + "s3Bucket": "Nome do Bucket", + "s3BucketHelp": "Bucket S3 para armazenar backups", + "s3BucketPlaceholder": "meu-bucket-backup", + "s3AccessKey": "ID da Chave de Acesso", + "s3AccessKeyHelp": "ID da chave de acesso AWS/S3", + "s3AccessKeyPlaceholder": "AKIAIOSFODNN7EXEMPLO", + "s3SecretKey": "Chave de Acesso Secreta", + "s3SecretKeyHelp": "Chave de acesso secreta AWS/S3", + "s3SecretKeyPlaceholder": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYCHAVEEXEMPLO", + "s3Region": "Regiao", + "s3RegionHelp": "Regiao S3 (ex., us-east-1)", + "s3RegionPlaceholder": "us-east-1" + }, + "schedule": { + "title": "Agendamento de Backup", + "scheduleType": "Tipo de Agendamento", + "scheduleOptions": { + "hourly": "A cada hora", + "daily": "Diario", + "weekly": "Semanal", + "custom": "Expressao cron personalizada" + }, + "options": { + "hourly": "A cada hora", + "daily": "Diario", + "weekly": "Semanal", + "custom": "Expressao cron personalizada" + }, + "customCron": "Expressao Cron", + "customCronHelp": "Digite uma expressao cron valida (ex., 0 3 * * *)", + "retention": "Periodo de Retencao", + "retentionDays": "Manter backups por", + "retentionHelp": "dias (backups mais antigos serao excluidos automaticamente)" + }, + "whatToBackup": { + "title": "O que Fazer Backup", + "database": "Banco de Dados", + "databaseHelp": "Todos os dados de eventos, configuracoes e configuracoes", + "photos": "Fotos", + "photosHelp": "Todas as fotos enviadas em galerias ativas", + "archives": "Arquivos", + "archivesHelp": "Arquivos ZIP de eventos arquivados", + "thumbnails": "Miniaturas", + "thumbnailsHelp": "Imagens de miniatura geradas (podem ser recriadas)", + "tempFiles": "Arquivos Temporarios", + "tempFilesHelp": "Arquivos temporarios de upload e processamento" + }, + "advancedOptions": { + "title": "Opcoes Avancadas", + "compression": "Ativar Compressao", + "compressionHelp": "Comprimir arquivos de backup para economizar espaco", + "encryption": "Ativar Criptografia", + "encryptionHelp": "Criptografar backups para seguranca adicional", + "encryptionPassphrase": "Frase de Criptografia", + "encryptionPassphraseHelp": "Frase forte para criptografia do backup", + "confirmPassphrase": "Confirmar Frase", + "passphrasesDontMatch": "As frases nao conferem" + }, + "validation": { + "requiredFields": "Por favor, preencha todos os campos obrigatorios", + "invalidCron": "Expressao cron invalida", + "connectionTestFailed": "Teste de conexao falhou", + "connectionTestSuccess": "Teste de conexao bem-sucedido!" + }, + "messages": { + "requiredFields": "Por favor, preencha todos os campos obrigatorios", + "connectionSuccess": "Teste de conexao bem-sucedido!", + "connectionFailed": "Teste de conexao falhou" + }, + "testingConnection": "Testando conexao...", + "saveSettings": "Salvar Configuracao", + "savingSettings": "Salvando..." + }, + "history": { + "searchPlaceholder": "Buscar backups...", + "allStatus": "Todos os Status", + "status": { + "completed": "Concluido", + "failed": "Falhou", + "running": "Executando", + "partial": "Parcial" + }, + "deleteConfirm": "Tem certeza que deseja excluir este backup de {{date}}?", + "noBackups": "Nenhum backup encontrado", + "tableHeaders": { + "date": "Data", + "type": "Tipo", + "status": "Status", + "size": "Tamanho", + "duration": "Duracao", + "actions": "Acoes" + }, + "columns": { + "status": "Status", + "dateTime": "Data e Hora", + "type": "Tipo", + "size": "Tamanho", + "duration": "Duracao", + "actions": "Acoes" + }, + "details": { + "backupDetails": "Detalhes do Backup", + "destination": "Destino", + "started": "Iniciado", + "completed": "Concluido", + "contentBackedUp": "Conteudo Salvo", + "errorDetails": "Detalhes do Erro", + "manifest": "Manifesto" + }, + "statistics": "Estatisticas", + "errors": "Erros", + "backupDetails": { + "backupId": "ID do Backup", + "startTime": "Hora de Inicio", + "endTime": "Hora de Termino", + "destination": "Destino", + "filesProcessed": "Arquivos Processados", + "totalSize": "Tamanho Total", + "compressionRatio": "Taxa de Compressao", + "errorLog": "Log de Erros", + "noErrors": "Nenhum erro ocorreu" + }, + "pagination": { + "showing": "Mostrando {{from}}-{{to}} de {{total}} backups", + "previous": "Anterior", + "next": "Proximo" + }, + "filter": { + "allStatus": "Todos os Status", + "completed": "Concluido", + "failed": "Falhou", + "running": "Executando", + "partial": "Parcial" + }, + "noBackupsFound": "Nenhum backup encontrado", + "backupsWillAppear": "Backups aparecerao aqui depois de criados", + "messages": { + "deleteSuccess": "Backup excluido com sucesso" + } + }, + "restore": { + "steps": { + "selectSource": "Selecionar Origem", + "chooseBackup": "Escolher Backup", + "restoreOptions": "Opcoes de Restauracao", + "reviewConfirm": "Revisar e Confirmar", + "progress": "Progresso da Restauracao" + }, + "source": { + "title": "Selecionar Origem do Backup", + "subtitle": "Escolha de onde restaurar o backup", + "local": { + "name": "Backup Local", + "description": "Restaurar do sistema de arquivos local" + }, + "s3": { + "name": "Armazenamento S3", + "description": "Restaurar do bucket S3" + }, + "upload": { + "name": "Enviar Backup", + "description": "Enviar um arquivo de backup", + "comingSoon": "Funcionalidade de upload em breve" + }, + "configuration": { + "s3": "Configuracao S3", + "endpoint": "URL do Endpoint S3", + "bucket": "Nome do Bucket", + "accessKey": "ID da Chave de Acesso", + "secretKey": "Chave de Acesso Secreta" + } + }, + "backup": { + "title": "Escolher Backup para Restaurar", + "subtitle": "Selecione entre os backups disponiveis", + "noBackupsFound": "Nenhum backup encontrado na origem selecionada", + "encrypted": "Backup Criptografado", + "encryptedMessage": "Voce precisara fornecer a frase de criptografia para restaurar este backup.", + "enterPassphrase": "Digite a frase de criptografia", + "at": "as" + }, + "restoreTypes": { + "full": { + "name": "Restauracao Completa", + "description": "Restaurar tudo incluindo banco de dados, fotos e arquivos", + "warning": "Isto substituira todos os dados atuais" + }, + "database": { + "name": "Apenas Banco de Dados", + "description": "Restaurar apenas o banco de dados (configuracoes, eventos, usuarios)", + "warning": "O banco de dados atual sera substituido" + }, + "files": { + "name": "Apenas Arquivos", + "description": "Restaurar apenas fotos e arquivos", + "warning": "Arquivos existentes podem ser sobrescritos" + }, + "selective": { + "name": "Restauracao Seletiva", + "description": "Escolha itens especificos para restaurar", + "warning": "Apenas itens selecionados serao restaurados" + } + }, + "options": { + "title": "Opcoes de Restauracao", + "subtitle": "Escolha o que restaurar", + "additionalOptions": { + "title": "Opcoes Adicionais", + "skipPreBackup": "Pular Backup Pre-Restauracao", + "skipPreBackupHelp": "Por padrao, um backup e criado antes da restauracao. Marque para pular.", + "force": "Forcar Restauracao", + "forceHelp": "Ignorar verificacoes de seguranca e avisos (use com cuidado)" + } + }, + "confirmation": { + "title": "Revisar e Confirmar", + "subtitle": "Por favor, revise sua configuracao de restauracao", + "validation": { + "passed": "Validacao Aprovada", + "failed": "Validacao Falhou", + "checking": "Validando configuracao de restauracao..." + }, + "spaceCheck": { + "title": "Espaco de Armazenamento", + "required": "Necessario", + "available": "Disponivel", + "insufficient": "Espaco de armazenamento insuficiente" + }, + "summary": { + "title": "Resumo da Restauracao", + "source": "Origem", + "backupDate": "Data do Backup", + "restoreType": "Tipo de Restauracao", + "preBackup": "Pre-backup", + "enabled": "Ativado", + "skipped": "Ignorado" + }, + "warning": { + "title": "Aviso Importante", + "message": "Esta operacao de restauracao substituira dados existentes. Certifique-se de ter um backup atual antes de prosseguir. Esta acao nao pode ser desfeita." + } + }, + "progress": { + "title": "Progresso da Restauracao", + "inProgress": "Restauracao em andamento...", + "completed": "Restauracao concluida", + "overallProgress": "Progresso Geral", + "current": "Atual", + "statusDetails": "Detalhes do Status", + "restoreLogs": "Logs de Restauracao", + "steps": { + "completed": "Concluido", + "running": "Executando", + "failed": "Falhou", + "pending": "Pendente" + }, + "success": { + "title": "Restauracao Concluida com Sucesso", + "message": "Seus dados foram restaurados. Por favor, verifique se tudo esta funcionando corretamente." + } + }, + "actions": { + "back": "Voltar", + "next": "Proximo", + "startRestore": "Iniciar Restauracao", + "starting": "Iniciando...", + "validating": "Validando...", + "startNewRestore": "Iniciar Nova Restauracao" + }, + "messages": { + "restoreStarted": "Restauracao iniciada com sucesso" + } + }, + "messages": { + "backupStarted": "Backup iniciado com sucesso", + "backupFailed": "Falha ao iniciar backup", + "configUpdated": "Configuracao de backup atualizada", + "configUpdateFailed": "Falha ao atualizar configuracao", + "backupDeleted": "Backup excluido com sucesso", + "deleteFailed": "Falha ao excluir backup", + "testEmailSent": "Teste de conexao bem-sucedido!", + "testEmailFailed": "Teste de conexao falhou" + } + }, + "cssTemplates": { + "title": "Templates CSS Personalizados", + "template": "Template", + "templateName": "Nome do Template", + "enableTemplate": "Ativar este template", + "enableHint": "Templates ativados podem ser selecionados ao criar eventos", + "cssContent": "Conteudo CSS", + "cssHint": "Use .gallery-page para limitar estilos a galeria. Variaveis disponiveis: --gallery-bg, --gallery-text, --gallery-accent", + "securityNotice": "Aviso de Seguranca", + "securityText": "O CSS e sanitizado para prevenir codigo malicioso. URLs externas, @import e expressoes JavaScript sao bloqueadas.", + "resetToDefault": "Restaurar Padrao", + "resetConfirm": "Restaurar este template para o padrao? Suas alteracoes serao perdidas.", + "unsavedChanges": "Alteracoes nao salvas", + "saveTemplate": "Salvar Template", + "lastUpdated": "Ultima atualizacao", + "saved": "Template salvo com sucesso", + "saveFailed": "Falha ao salvar template", + "sanitizationWarning": "Alguns padroes CSS foram bloqueados por seguranca", + "reset": "Template restaurado para o padrao", + "resetFailed": "Falha ao restaurar template" + }, + "maintenance": { + "title": "Manutencao do Sistema", + "message": "Estamos realizando manutencao programada para melhorar nosso servico. Voltaremos em breve.", + "expectedCompletion": "Previsao de conclusao:", + "checkBackLater": "Por favor, volte mais tarde", + "urgentMatters": "Para assuntos urgentes, entre em contato" + }, + "passwordChange": { + "title": "Alterar Senha", + "currentPassword": "Senha Atual", + "newPassword": "Nova Senha", + "confirmPassword": "Confirmar Nova Senha", + "currentPasswordPlaceholder": "Digite a senha atual", + "newPasswordPlaceholder": "Digite a nova senha", + "confirmPasswordPlaceholder": "Confirme a nova senha", + "requirements": "Requisitos da Senha:", + "minLength": "Pelo menos 6 caracteres", + "mustDiffer": "Deve ser diferente da senha atual", + "success": "Senha alterada com sucesso", + "failed": "Falha ao alterar senha", + "currentRequired": "Senha atual e obrigatoria", + "newRequired": "Nova senha e obrigatoria", + "minLengthError": "Senha deve ter pelo menos 6 caracteres", + "confirmRequired": "Por favor, confirme sua nova senha", + "noMatch": "Senhas nao conferem", + "mustBeDifferent": "Nova senha deve ser diferente da senha atual", + "cancel": "Cancelar" + }, + "mandatoryPasswordChange": { + "title": "Alteracao de Senha Obrigatoria", + "description": "Por razoes de seguranca, voce deve alterar sua senha antes de acessar o painel admin.", + "success": "Senha alterada com sucesso! Voce ja pode acessar o painel admin.", + "minLength": "Pelo menos 12 caracteres", + "mustContainUpperLower": "Deve conter letras maiusculas e minusculas", + "mustContainNumbers": "Deve conter numeros", + "mustContainSpecial": "Deve conter caracteres especiais (!@#$%^&*)", + "minLengthError": "Senha deve ter pelo menos 12 caracteres", + "mustContainLowercase": "Senha deve conter letras minusculas", + "mustContainUppercase": "Senha deve conter letras maiusculas", + "mustContainNumbersError": "Senha deve conter numeros", + "mustContainSpecialError": "Senha deve conter caracteres especiais" + }, + "offline": { + "backOnline": "De volta online", + "noConnection": "Sem conexao com a internet" + }, + "download": { + "downloading": "Baixando...", + "percentComplete": "% concluido" + }, + "passwordGenerator": { + "weak": "Fraca", + "fair": "Razoavel", + "good": "Boa", + "strong": "Forte", + "generating": "Gerando...", + "generatePassword": "Gerar Senha", + "showSuggestions": "Mostrar sugestoes de senha", + "moreOptions": "Mais Opcoes", + "suggestions": "Sugestoes de Senha", + "characters": "caracteres", + "copyPassword": "Copiar senha", + "use": "Usar", + "pattern": "Padrao:", + "patternDescription": "As senhas sao geradas usando o nome e a data do seu evento. Exemplo: \"Local2024$Agosto\" para melhor seguranca e memorabilidade." + }, + "feedback": { + "comments": "Comentarios", + "addComment": "Adicionar Comentario", + "yourName": "Seu nome", + "yourEmail": "Seu email", + "writeComment": "Escreva um comentario...", + "submit": "Enviar", + "commentSubmitted": "Comentario enviado", + "commentError": "Falha ao enviar comentario", + "rateLimited": "Por favor, aguarde antes de comentar novamente", + "commentRequired": "Comentario e obrigatorio", + "nameRequired": "Nome e obrigatorio", + "emailRequired": "Email e obrigatorio", + "anonymous": "Anonimo", + "pendingApproval": "Aguardando aprovacao", + "noComments": "Nenhum comentario ainda. Seja o primeiro a comentar!", + "rating": "Avaliacao", + "ratePhoto": "Avalie esta foto", + "yourRating": "Sua avaliacao", + "averageRating": "Avaliacao media", + "totalRatings": "avaliacoes", + "likes": "Curtidas", + "favorites": "Favoritos", + "likePhoto": "Curtir esta foto", + "favoritePhoto": "Adicionar aos favoritos", + "photoFeedback": "Feedback da Foto", + "hasFeedback": "Tem feedback", + "hasComments": "Tem comentarios", + "hasRating": "Tem avaliacao", + "settings": { + "title": "Configuracoes de Feedback dos Convidados", + "enableFeedback": "Ativar feedback", + "feedbackTypes": "Tipos de Feedback", + "ratings": "Avaliacoes com Estrelas", + "ratingsDesc": "Permitir que convidados avaliem fotos (1-5 estrelas)", + "likes": "Curtidas", + "likesDesc": "Funcionalidade simples de curtir/descurtir", + "comments": "Comentarios", + "commentsDesc": "Comentarios de texto nas fotos", + "favorites": "Favoritos", + "favoritesDesc": "Marcar fotos como favoritas" + } + }, + "filter": { + "feedbackFilters": "Filtros de Feedback", + "clear": "Limpar", + "rating": "Avaliacao", + "allPhotos": "Todas as Fotos", + "anyRating": "Qualquer Avaliacao", + "oneStarPlus": "1+ Estrelas", + "twoStarsPlus": "2+ Estrelas", + "threeStarsPlus": "3+ Estrelas", + "fourStarsPlus": "4+ Estrelas", + "fiveStarsOnly": "5 Estrelas Apenas", + "hasLikes": "Tem curtidas", + "hasFavorites": "Tem favoritos", + "hasComments": "Tem comentarios" + }, + "adminLogin": { + "title": "Login Admin", + "subtitle": "Entre para gerenciar suas galerias de fotos", + "sessionExpired": "Sua sessao expirou. Por favor, faca login novamente.", + "emailLabel": "Endereco de Email", + "emailPlaceholder": "admin@exemplo.com", + "emailRequired": "Email e obrigatorio", + "invalidEmail": "Formato de email invalido", + "passwordLabel": "Senha", + "passwordPlaceholder": "Digite sua senha", + "passwordRequired": "Senha e obrigatoria", + "passwordMinLength": "Senha deve ter pelo menos 6 caracteres", + "rememberMe": "Lembrar de mim", + "forgotPassword": "Esqueceu a senha?", + "signIn": "Entrar", + "loginSuccess": "Login realizado com sucesso!", + "networkError": "Erro de rede. Por favor, verifique sua conexao e tente novamente.", + "tooManyAttempts": "Muitas tentativas de login. Por favor, tente novamente mais tarde.", + "invalidCredentials": "Email ou senha invalidos", + "generalError": "Ocorreu um erro. Por favor, tente novamente.", + "needHelp": "Precisa de ajuda? Entre em contato", + "poweredBy": "Powered by PicPeak", + "devModeHint": "Modo de Desenvolvimento: Use email: admin@example.com, senha: admin123" + } +} diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json index 1318cbf9..095a587a 100644 --- a/frontend/src/i18n/locales/ru.json +++ b/frontend/src/i18n/locales/ru.json @@ -455,6 +455,8 @@ "expirationWarning": "Гости получат предупреждение по email за 7 дней до истечения.", "noExpiration": "Без срока действия", "noExpirationHelp": "Эта галерея будет активна до ручного архивирования.", + "photoCap": "Лимит фото", + "photoCapHelp": "Максимальное количество фотографий. 0 = без ограничений", "userUploads": "Настройки загрузки пользователями", "allowUserUploads": "Разрешить гостям загружать фото", "allowUserUploadsHelp": "Разрешить гостям загружать собственные фотографии в эту галерею", diff --git a/frontend/src/pages/admin/CreateEventPage.tsx b/frontend/src/pages/admin/CreateEventPage.tsx index 39504629..5d8b6052 100644 --- a/frontend/src/pages/admin/CreateEventPage.tsx +++ b/frontend/src/pages/admin/CreateEventPage.tsx @@ -8,7 +8,8 @@ import { ArrowLeft, Palette, Eye, - EyeOff + EyeOff, + Image } from 'lucide-react'; import { addDays } from 'date-fns'; import { toast } from 'react-toastify'; @@ -44,6 +45,7 @@ interface FormData { allow_user_uploads: boolean; upload_category_id: number | null; css_template_id: number | null; + photo_cap: number; feedback_settings: { feedback_enabled: boolean; allow_ratings: boolean; @@ -98,6 +100,7 @@ export const CreateEventPage: React.FC = () => { allow_user_uploads: false, upload_category_id: null, css_template_id: null, + photo_cap: 0, feedback_settings: { feedback_enabled: false, allow_ratings: true, @@ -298,6 +301,7 @@ export const CreateEventPage: React.FC = () => { allow_user_uploads: formData.allow_user_uploads, upload_category_id: formData.upload_category_id, css_template_id: formData.css_template_id, + photo_cap: formData.photo_cap > 0 ? formData.photo_cap : null, feedback_enabled: feedbackSettings.feedback_enabled, allow_ratings: feedbackSettings.allow_ratings, allow_likes: feedbackSettings.allow_likes, @@ -717,6 +721,27 @@ export const CreateEventPage: React.FC = () => { )} + {/* Photo Cap */} +

+ +
+
+ setFormData({ ...formData, photo_cap: parseInt(e.target.value) || 0 })} + min={0} + leftIcon={} + /> +
+ + {t('events.photoCapHelp', 'Maximum number of photos allowed. 0 = unlimited')} + +
+
+ {/* User Upload Settings */}
)} + {/* Photo Cap */} +
+ +
+ setEditForm(prev => ({ ...prev, photo_cap: parseInt(e.target.value) || 0 }))} + min={0} + className="w-24 px-3 py-2 border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" + /> + + {t('events.photoCapHelp', 'Maximum number of photos allowed. 0 = unlimited')} + +
+
+