Files
picpeak/frontend/src/i18n/locales/en.json
T
Paul NothaftandPaul Nothaft 0732a160b5 fix(gallery): keep videos playable under enhanced and maximum protection (stable) (#1408)
Stable twin of the main-branch fix.

Once an event left `standard` protection, both halves of the video path were
routed through /api/secure-images, and neither half can carry a video: the
lightbox drops the emitted `{{token}}` template straight into a <video>
element and nothing substitutes the placeholder, while the secure-images route
pipes every byte through sharp, which throws on an mp4. The /photo/:photoId
route bounced to that same endpoint before reaching its own video branch —
isVideo was computed and then ignored — so there was no way through.

Videos now keep the JWT route at every protection level, on both sides. Not a
new exposure: thumbnails of those same videos have always been served from it,
and a valid gallery token is still required. Still images are unaffected and
keep bouncing to the secure endpoint.

VideoPlayer had no `error` listener, so all of this rendered as a poster
frozen at "0:00 / 0:00" behind a play button that did nothing —
indistinguishable from a codec the browser cannot decode, which is the other
common cause (HEVC/H.265 phone footage plays in Safari and nowhere else). It
now surfaces the failure and names the codec case.

The frontend half is identical to main; the backend half is hand-ported
because stable keeps these routes in the monolithic routes/gallery.js.

Relates to issue 1370

Co-authored-by: Paul Nothaft <[email protected]>
2026-09-11 11:25:24 +02:00

5644 lines
260 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"userManagement": {
"title": "User Management",
"subtitle": "Manage admin users and invitations",
"loading": "Loading users...",
"loadError": "Failed to load users. Please try again.",
"inviteUser": "Invite User",
"createInvitation": "Create Invitation",
"email": "Email Address",
"emailPlaceholder": "[email protected]",
"role": "Role",
"selectRole": "Select a role",
"sendInvitation": "Send Invitation",
"editUser": "Edit User",
"editingUser": "Editing user",
"saveChanges": "Save Changes",
"deactivateUser": "Deactivate User",
"cancelInvitation": "Cancel Invitation",
"invitationSent": "Invitation sent successfully",
"invitationError": "Failed to send invitation",
"invitationCancelled": "Invitation cancelled",
"cancelInvitationError": "Failed to cancel invitation",
"userUpdated": "User updated successfully",
"updateUserError": "Failed to update user",
"userDeactivated": "User deactivated successfully",
"deactivateUserError": "Failed to deactivate user",
"noRole": "No Role",
"neverLoggedIn": "Never logged in",
"expired": "Expired",
"deactivate": "Deactivate",
"cancel": "Cancel",
"tabs": {
"users": "Users",
"invitations": "Invitations"
},
"stats": {
"totalUsers": "Total Users",
"activeUsers": "Active Users",
"pendingInvitations": "Pending Invitations",
"inactiveUsers": "Inactive Users"
},
"status": {
"active": "Active",
"inactive": "Inactive"
},
"table": {
"user": "User",
"email": "Email",
"role": "Role",
"status": "Status",
"lastLogin": "Last Login",
"actions": "Actions",
"invitedBy": "Invited By",
"expires": "Expires"
},
"validation": {
"emailRequired": "Email is required",
"emailInvalid": "Invalid email format",
"roleRequired": "Role is required"
},
"searchUsersPlaceholder": "Search users...",
"searchInvitationsPlaceholder": "Search invitations...",
"noUsers": "No users found",
"noUsersFound": "No users match your search",
"noInvitations": "No pending invitations",
"noInvitationsFound": "No invitations match your search",
"confirmDeactivate": {
"title": "Deactivate User",
"message": "Are you sure you want to deactivate {{name}}? They will no longer be able to log in."
},
"confirmCancelInvitation": {
"title": "Cancel Invitation",
"message": "Are you sure you want to cancel the invitation for {{email}}?"
}
},
"systemHealth": {
"title": "System health",
"subtitle": "Background failures that need attention.",
"retry": "Retry",
"dismiss": "Dismiss",
"retriedToast": "Email re-queued.",
"dismissedToast": "Dismissed.",
"stuckEmails": {
"title": "Stuck / failed emails",
"empty": "No stuck or failed emails — all clear.",
"noError": "retries exhausted",
"col": {
"recipient": "Recipient",
"type": "Type",
"error": "Error",
"queued": "Queued",
"actions": "Actions"
}
}
},
"common": {
"loading": "Loading...",
"error": "Error",
"configureInSettings": "Configure defaults in Settings ↗",
"save": "Save",
"saving": "Saving…",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"add": "Add",
"sortBy": "Sort by",
"selectCountry": "Select country…",
"yes": "Yes",
"no": "No",
"back": "Back",
"next": "Next",
"previous": "Previous",
"close": "Close",
"logout": "Logout",
"change": "Change",
"remove": "Remove",
"download": "Download",
"downloadAll": "Download All",
"photo": "photo",
"photos": "photos",
"video": "video",
"media": "media",
"upload": "Upload",
"customize": "Customize",
"hide": "Hide",
"unknown": "Unknown",
"notSet": "Not set",
"of": "of",
"selected": "Selected",
"chunk": "Chunk",
"optional": "optional",
"tryAgain": "Try Again",
"active": "Active",
"inactive": "Inactive",
"create": "Create",
"unknownDate": "Unknown date",
"pageOf": "Page {{current}} of {{total}}",
"collapse": "Collapse",
"expand": "Expand",
"submitting": "Submitting...",
"copy": "Copy",
"copied": "Copied!",
"applying": "Applying...",
"done": "Done",
"retry": "Retry",
"characters": "characters",
"saveChanges": "Save changes",
"resetChanges": "Reset Changes",
"dismiss": "Dismiss",
"discard": "Discard",
"preview": "Preview",
"duplicate": "Duplicate",
"showAll": "Show all",
"confirm": "Confirm",
"show": "Show"
},
"upload": {
"photoCategory": "Photo Category",
"noCategory": "No category",
"eventSpecific": "(Event specific)",
"clickToUpload": "Click to upload or drag and drop",
"fileRequirements": "JPEG, PNG or WebP (max 50MB per file, {{limit}} files per upload)",
"selectedFiles": "Selected files",
"uploading": "Uploading...",
"transferring": "Transferring",
"processing": "Processing photos...",
"processingHint": "Files are uploaded. PicPeak is now generating thumbnails and reading metadata. You can leave this page — work continues in the background.",
"processingProgress": "{{complete}} of {{total}} done",
"retryFailed": "Retry failed",
"uploadComplete": "Upload complete!",
"someFilesFailed": "Some files failed to upload",
"failures": {
"title": "{{count}} file(s) could not be uploaded",
"toast": "{{count}} file(s) could not be uploaded — see the list below.",
"kindRejected": "Rejected",
"kindTransfer": "Transfer failed",
"kindProcessing": "Processing failed",
"transferReason": "Transfer failed",
"unknownReason": "Unknown error",
"unknownFile": "Unknown file"
},
"replaceByName": "Replace existing photos with same name",
"uploadPhotos": "Upload Photos",
"uploadMedia": "Upload Photos & Videos",
"maxFilesReached": "Maximum {{limit}} files allowed",
"someFilesSkipped": "Only {{allowed}} more files can be added (limit {{limit}})",
"tooManyFiles": "Maximum {{limit}} files can be uploaded at once",
"limitInfo": "{{selected}} of {{limit}} files selected ({{remaining}} remaining)",
"limitReached": "Upload limit reached ({{limit}} files per batch)",
"replacedFiles_one": "{{count}} photo replaced",
"replacedFiles_other": "{{count}} photos replaced",
"processingFailed_one": "{{count}} photo failed to process",
"processingFailed_other": "{{count}} photos failed to process",
"uploadingChunks_one": "{{count}} chunk uploading",
"uploadingChunks_other": "{{count}} chunks uploading"
},
"navigation": {
"dashboard": "Dashboard",
"events": "Events",
"archives": "Archives",
"messages": "Messages",
"settings": "Settings",
"systemHealth": "System health",
"eventTypes": "Event Types",
"branding": "Branding",
"emailSettings": "Email Settings",
"backup": "Backup & Restore",
"cmsPages": "CMS Pages",
"users": "Users",
"calendar": "Calendar",
"clients": "CRM",
"accounting": "Accounting",
"workflows": "Workflows",
"betaTag": "Beta"
},
"workflows": {
"title": "Workflows",
"subtitle": "Visual automations — triggers, conditions, gates and actions.",
"new": "New workflow",
"empty": "No workflows yet. Create one to automate your invoicing and booking steps.",
"builtin": "built-in",
"triggerLabel": "Trigger",
"enabled": "Enabled",
"disabled": "Disabled",
"confirmDelete": "Delete this workflow?",
"toggle": {
"confirmDisableBuiltin": "Disabling this built-in reverts to the previous built-in behaviour — it does not turn the automation off. Continue?"
},
"test": {
"title": "Test run",
"hint": "Dry run: walks the whole flow now (waits skipped, gates auto-confirmed) with side effects mocked — no real emails. Optionally give an entity id (e.g. an invoice) so conditions can read it.",
"entityId": "Entity id (optional, e.g. invoice id)",
"run": "Run dry test",
"result": "Result",
"failed": "Test run failed"
},
"toast": {
"createFailed": "Could not create workflow",
"deleted": "Workflow deleted",
"deleteFailed": "Could not delete workflow"
},
"approvals": {
"title": "Approvals",
"subtitle": "Workflow runs waiting on your confirmation.",
"empty": "Nothing waiting for you right now.",
"confirm": "Confirm",
"deny": "Deny",
"recorded": "Response recorded",
"defaultPrompt": "A workflow needs your confirmation.",
"pendingTitle": "Pending approvals",
"viewAll": "View all approvals",
"openEntity": "Open {{type}} #{{id}}",
"acted": "Done"
},
"editor": {
"namePlaceholder": "Workflow name",
"when": "When",
"cleanUp": "Clean up layout",
"textView": "Text",
"canvasView": "Canvas",
"loadText": "Load into editor",
"textLoaded": "Loaded — review and Save",
"textNeedsArrays": "Needs \"nodes\" and \"edges\" arrays",
"textNeedsTrigger": "Needs exactly one trigger node",
"textHint": "The whole flow as JSON — copy it to share or hand to an LLM, or paste a flow and load it into the editor. Click “Clean up layout” after importing.",
"saved": "Workflow saved",
"saveFailed": "Could not save",
"badJson": "Config is not valid JSON",
"daysBefore": "days before event",
"templateGroup": "Reminder template group",
"templateGroupHint": "The exact template is auto-picked per event type within this group: «group»_«eventType» if you authored one, else «group»_default. Blank = event_reminder.",
"showAdvanced": "Advanced (JSON)",
"hideAdvanced": "Hide advanced (JSON)",
"triggerHint": "The trigger is set in the toolbar above (When …).",
"actionLabel": "Action",
"recipient": "Recipient",
"recipientCustomer": "Customer (respects business hours)",
"recipientAdmin": "Admin (sent immediately)",
"emailTemplate": "Email template key",
"webhookUrl": "Webhook URL",
"webhookTarget": "Webhook",
"webhookNone": "— Select a configured webhook —",
"webhookInactive": "(inactive)",
"webhookHint": "Delivered via the webhook pipeline (signing, retries, SSRF checks). Manage endpoints in Settings → Webhooks.",
"condition": "Condition",
"exprField": "Field",
"exprOp": "Operator",
"exprValue": "Value",
"conditionHint": "Routes to the “yes” edge when true, “no” when false.",
"waitType": "Wait",
"waitUntil": "Until a date",
"waitDelay": "A fixed delay",
"waitAnchor": "Wait until",
"days": "Days",
"hours": "Hours",
"minutes": "Min",
"maxIterations": "Repeat at most (times)",
"gatePrompt": "Question for the admin",
"gatePromptPh": "e.g. No payment received — send a reminder?",
"gateTimeout": "Auto-expire after (days, optional)",
"gateHint": "Emails the admin a confirm/deny link; routes to the “confirm” or “deny” edge."
}
},
"archives": {
"title": "Archives",
"subtitle": "Manage archived photo galleries",
"loadingArchives": "Loading archives...",
"totalArchives": "Total Archives",
"storageUsed": "Storage Used",
"totalPhotos": "Total Photos",
"avgArchiveSize": "Avg Archive Size",
"searchPlaceholder": "Search archives...",
"allTypes": "All Types",
"wedding": "Wedding",
"birthday": "Birthday",
"corporate": "Corporate",
"other": "Other",
"sortByDate": "Sort by Date",
"sortByName": "Sort by Name",
"sortBySize": "Sort by Size",
"tableHeaders": {
"event": "Event",
"type": "Type",
"archivedDate": "Archived Date",
"size": "Size",
"photos": "Photos",
"actions": "Actions"
},
"noArchivesFound": "No archives found",
"eventDateNA": "Event date: N/A",
"processing": "Processing...",
"download": "Download",
"restore": "Restore",
"delete": "Delete",
"showing": "Showing {{from}} to {{to}} of {{total}} archives",
"page": "Page {{current}} of {{total}}",
"storageManagement": "Storage Management",
"storageInfo": "Archives are stored permanently unless manually deleted. Consider implementing a retention policy to manage storage costs.",
"confirmRestore": "Are you sure you want to restore this archive? The event will become active again.",
"confirmDelete": "Are you sure you want to permanently delete this archive? This action cannot be undone.",
"restoreSuccess": "Archive restored successfully",
"deleteSuccess": "Archive deleted permanently"
},
"auth": {
"password": "Password",
"enterPassword": "Enter Gallery Password",
"passwordPlaceholder": "Enter the gallery password",
"invalidPassword": "Invalid password",
"wrongPassword": "Incorrect password. Please check your password and try again.",
"tooManyAttempts": "Too many failed login attempts. Please try again later.",
"pleaseEnterPassword": "Please enter a password",
"passwordHint": "The password was provided by the event organizer. Contact them if you don't have it.",
"iab": {
"instagram": {
"title": "Open this link in your browser",
"ios": "Instagram's built-in browser sometimes blocks the password login. Tap the ⋯ menu in the top right, then \"Open in external browser\" (or copy the link and paste into Safari).",
"android": "Instagram's built-in browser sometimes blocks the password login. Tap the ⋮ menu in the top right, then \"Open in external browser\" (or copy the link and paste into Chrome)."
}
}
},
"gallery": {
"expires": "Expires",
"expired": "Expired",
"contactOrganizer": "Please contact the event organizer if you need access to these photos.",
"searchPhotos": "Search photos by filename...",
"sortByDate": "Sort by Date",
"sortByName": "Sort by Name",
"sortBySize": "Sort by Size",
"allPhotos": "All Photos",
"feedbackFilter": "Feedback Filter",
"all": "All",
"liked": "Liked",
"favorited": "Favorited",
"favorites": "Favorites",
"needHelp": "Need help? Contact us at",
"noPhotosFound": "No photos found",
"failedToLoad": "Failed to load photos",
"tryAgain": "Try Again",
"loading": "Loading gallery...",
"expiredOn": "This gallery expired on {{date}}.",
"downloadBefore": "Download your photos before they're no longer available.",
"viewGallery": "View Gallery",
"downloadAll": "Download All",
"downloadError": "Some photos failed to download",
"selectPhotos": "Select Photos",
"cancelSelection": "Cancel Selection",
"selectAll": "Select All",
"deselectAll": "Deselect All",
"deleteSelected": "Delete Selected",
"searchByFilename": "Search by filename...",
"uncategorized": "Uncategorized",
"sortAscending": "Sort ascending",
"sortDescending": "Sort descending",
"remaining": "remaining",
"selectPhotosHint": "Tip: Use Ctrl+Click (Cmd+Click on Mac) to quickly select multiple photos",
"filters": "Filters",
"toggleMenu": "Toggle menu",
"allCategories": "All Categories",
"categories": "Categories",
"mediaType": "Media",
"allMedia": "All media",
"photosOnly": "Photos",
"videosOnly": "Videos",
"download": "Download",
"noMedia": "No media uploaded yet",
"searchPlaceholder": "Search photos...",
"sortBy": "Sort By",
"sortByCaptureDate": "Sort by Capture Date",
"sortByRating": "Sort by Rating",
"photoGallery": "Photo Gallery",
"photos": "Photos",
"allRightsReserved": "All rights reserved",
"searchMemories": "Search memories...",
"thankYou": "Thank You",
"thankYouMessage": "For being part of our story and making our special day unforgettable.",
"feedback": {
"title": "Feedback",
"shareThoughts": "Share your thoughts on \"{{name}}\"",
"rateThisMoment": "Rate this moment",
"comments": "Comments",
"noComments": "No comments yet. Be the first!",
"yourName": "Your name",
"yourEmail": "Your email",
"writeLovelyNote": "Write a lovely note...",
"postComment": "Post Comment",
"anonymous": "Anonymous"
},
"rated": "Rated",
"commented": "Commented",
"expiresIn_one": "Gallery expires in {{count}} day",
"expiresIn_other": "Gallery expires in {{count}} days",
"downloading_one": "Downloading 1 photo...",
"downloading_other": "Downloading {{count}} photos...",
"guestRecovery": {
"invalidEmail": "Enter a valid email address",
"codeSent": "Check your inbox for a verification code.",
"requestError": "Could not send code. Try again.",
"invalidCode": "Enter the 6-digit code",
"verifyError": "Invalid or expired code.",
"back": "Back",
"title": "Recover your picks",
"emailStepDescription": "Enter the email you used before. We will send a 6-digit verification code.",
"codeStepDescription": "Enter the 6-digit code we sent to your email.",
"emailLabel": "Email",
"sendCode": "Send code",
"codeLabel": "Verification code",
"verifyCode": "Verify and continue"
},
"guestPrompt": {
"nameRequired": "Name is required",
"invalidEmail": "Invalid email address",
"emailRequired": "Email is required",
"error": "Registration failed",
"title": "Welcome — what's your name?",
"description": "Your picks will be saved under this name so the photographer knows which photos you love.",
"nameLabel": "Your name",
"namePlaceholder": "Enter your name",
"emailLabelRequired": "Email",
"emailLabel": "Email (optional)",
"emailPlaceholder": "[email protected]",
"submit": "Continue",
"alreadyHere": "I've been here before"
},
"footer": {
"forgetMeConfirm": "Your name and selections will be removed from this gallery.",
"forgetMe": "Forget me ({{name}})",
"socials": "Social media"
},
"photosCount_one": "{{count}} photo",
"photosCount_other": "{{count}} photos",
"poweredBy": "Powered by PicPeak",
"photosSelected_one": "{{count}} photo selected",
"photosSelected_other": "{{count}} photos selected",
"downloadSelected_one": "Download {{count}} photo",
"downloadSelected_other": "Download {{count}} photos",
"videoFormatUnsupported": "This video format cannot be played in this browser. Download it to watch it.",
"videoLoadFailed": "This video could not be loaded."
},
"categories": {
"title": "Photo Categories",
"addCategory": "Add Category",
"eventSpecificCategories": "Event-Specific Categories",
"noEventSpecificCategories": "No event-specific categories. Global categories are available by default.",
"globalCategoriesAlwaysAvailable": "Global Categories (always available):",
"deleteCategoryTitle": "Delete category",
"categoryCreatedSuccess": "Category created successfully",
"categoryDeletedSuccess": "Category deleted successfully",
"failedToCreateCategory": "Failed to create category",
"failedToDeleteCategory": "Failed to delete category",
"categoryName": "Category name",
"noCategoriesYet": "No categories yet. Create your first category to organize photos.",
"deleteConfirm": "Are you sure you want to delete \"{{name}}\"?",
"setCoverPhoto": "Set Cover Photo",
"removeCoverPhoto": "Remove Cover Photo",
"coverPhotoSet": "Cover photo set successfully",
"coverPhotoRemoved": "Cover photo removed",
"failedToSetCoverPhoto": "Failed to set cover photo",
"categoryHeroHint": "If no cover photo is set for a category, the default hero photo will be used.",
"downloadsEnabled": "Downloads enabled for this category",
"downloadsDisabled": "Downloads disabled for this category",
"enableDownloadsTitle": "Click to enable downloads for this category",
"disableDownloadsTitle": "Click to disable downloads for this category",
"failedToToggleDownloads": "Failed to update download permission"
},
"events": {
"title": "Events",
"create": "Create",
"createEvent": "Create Event",
"totalViews": "Total Views",
"totalDownloads": "Total Downloads",
"uniqueVisitors": "Unique Visitors",
"eventNamePlaceholder": "e.g., John & Jane's Wedding",
"welcomeMessagePlaceholder": "Welcome to our special day! Feel free to download and share these memories...",
"hostEmailPlaceholder": "[email protected]",
"adminEmailPlaceholder": "[email protected]",
"adminEmailPickFromAdmins": "Pick from admins:",
"adminEmailCustom": "Custom email",
"accessAndSecurity": "Access & Security",
"enterPassword": "Enter password",
"passwordPlaceholder": "Enter a secure password",
"confirmPasswordPlaceholder": "Confirm password",
"eventDetails": "Event Details",
"eventName": "Event Name",
"eventType": "Event Type",
"eventDate": "Event Date",
"fullDay": "Full day",
"eventTimeStart": "Start time",
"eventTimeEnd": "End time",
"hostEmail": "Customer Email",
"hostName": "Customer Name",
"hostNamePlaceholder": "John Smith",
"adminEmail": "Admin Email",
"expirationDate": "Expiration Date",
"active": "Active",
"archived": "Archived",
"totalSize": "Total Size",
"shareLink": "Share Link",
"copyLink": "Copy Link",
"linkCopied": "Link copied!",
"viewGallery": "View Gallery",
"createInvoice": "Create invoice",
"uploadPhotos": "Upload Photos",
"archiveEvent": "Archive Event",
"archiveConfirm": "Are you sure you want to archive this event? This action cannot be undone.",
"extendExpiration": "Extend {{days}} Days",
"backToEvents": "Back to Events",
"loadingEventDetails": "Loading event details...",
"saveChanges": "Save Changes",
"eventExpiresIn": "This event expires in {{days}} days",
"overview": "Overview",
"photos": "Photos",
"categories": "Categories",
"eventInformation": "Event Information",
"sourceMode": "Source Mode",
"sourceModeManaged": "Managed (upload to PicPeak)",
"sourceModeReference": "Reference external folder",
"sourceModeHelp": "Use managed mode for direct uploads or point to a mounted /external-media folder when using local storage.",
"externalFolder": "External Folder",
"externalFolderHint": "These folders are read from the /external-media mount inside your container or host.",
"externalFolderRequired": "Please select an external folder before saving.",
"externalFolderEmpty": "No subfolders",
"clearSelection": "Clear",
"welcomeMessage": "Welcome Message",
"created": "Created",
"expires": "Expires",
"shareWithGuests": "Share this link with guests. They'll need the password to access the gallery.",
"shareWithGuestsPublic": "Share this link with guests. No password is required for this gallery.",
"shortUrls": {
"title": "Branded short URLs",
"description": "Create memorable links like /s/sofia-graduation that resolve to this gallery. The short URL itself shows the rich social preview when shared — so iMessage, Facebook, WhatsApp etc. see the gallery photo + name even when pasting the short link.",
"slugPlaceholder": "sofia-graduation (optional)",
"slugHelp": "Leave empty to auto-generate from the gallery name. Allowed characters: lowercase letters, digits, hyphens.",
"create": "Create",
"created": "Short URL created",
"deleted": "Short URL deleted",
"empty": "No short URLs yet. Create one above to share this gallery with a memorable link.",
"hits": "{{count}} hits",
"lastHit": "last {{when}}",
"createdAt": "created {{when}}",
"useSuggested": "Use \"{{suggested}}\" instead",
"confirmDelete": "Delete short URL /s/{{slug}}? The link will stop working immediately.",
"errors": {
"slugTaken": "That short URL is already taken — try {{suggested}} instead.",
"invalidSlug": "Short URLs must be lowercase letters, digits, and hyphens (164 chars)."
}
},
"resetGalleryPassword": "Reset Gallery Password",
"resendCreationEmail": "Resend Creation Email",
"creationEmailResent": "Creation email has been queued for sending",
"failedToResendEmail": "Failed to resend creation email",
"photoStatistics": "Photo Statistics",
"totalPhotos": "Total Photos",
"managePhotos": "Manage Photos",
"actions": "Actions",
"archivingInfo": "Archiving will create a ZIP file of all photos and remove the gallery from public access.",
"archivedOn": "Archived On",
"downloadArchive": "Download Archive",
"loadingPhotos": "Loading photos...",
"photoCategories": "Photo Categories",
"organizeCategoriesInfo": "Organize your photos into categories. Categories help guests navigate and find specific types of photos.",
"categoriesTip": "Tip: Categories are specific to each event. You can create custom categories like \"Ceremony\", \"Reception\", \"Portraits\", etc.",
"galleryPassword": "Gallery Password",
"requirePasswordToggle": "Require password for this gallery",
"requirePasswordToggleHelp": "Disable this if you want to share the gallery without a password. Anyone with the link will be able to view the photos.",
"publicGalleryWarning": "Public galleries are accessible to anyone with the link. Consider enabling download watermarks and monitoring activity.",
"passwordHelperText": "You can use dates like \"04.07.2025\" or any text with 6+ characters",
"confirmPassword": "Confirm Password",
"newPasswordLabel": "New Gallery Password",
"passwordReset": {
"title": "Reset Gallery Password",
"newTitle": "New Password",
"description": "Set a new password for {{eventName}}, or leave both fields empty to have one auto-generated.",
"newPasswordLabel": "New password",
"placeholder": "Leave empty to auto-generate",
"helperText": "Use 6+ characters, or leave blank to auto-generate",
"confirmLabel": "Confirm password",
"sendEmail": "Send email notification",
"sendEmailHelp": "Notify the host about the password change",
"warning": "Note: The old password will no longer work. Make sure to share the new password with the host.",
"submit": "Reset Password",
"successHeading": "Password reset successfully!",
"emailSentNote": "An email notification has been sent to the host.",
"generatedLabel": "Auto-generated gallery password",
"saveSecurelyNote": "Important: Save this password securely. It cannot be recovered once you close this window.",
"done": "Done",
"toastSuccess": "Password reset successfully",
"toastError": "Failed to reset password",
"toastCopied": "Password copied to clipboard",
"errorMinLength": "Password must be at least 6 characters",
"errorMismatch": "Passwords do not match"
},
"themeAndStyle": "Theme & Style",
"galleryExpiration": "Gallery Expiration",
"daysAfterEvent": "days after event date",
"expiresOn": "Expires on",
"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",
"allowUserUploadsDescription": "Enable guests to upload their own photos to this gallery",
"uploadCategory": "Upload Category",
"selectCategory": "Select a category for user uploads",
"uploadCategoryHelp": "All user-uploaded photos will be added to this category",
"allowDownloads": "Allow photo downloads",
"downloadsDisabled": "Downloads Disabled",
"downloadProtection": "Download Protection",
"disableRightClick": "Block right-click menu",
"watermarkDownloads": "Add watermark to downloads",
"enableDevtoolsProtection": "Detect developer tools",
"useCanvasRendering": "Canvas rendering (advanced protection)",
"protectionInfo": "Protection features help prevent unauthorized downloads but cannot block all methods.",
"protectionLevelBasic": "Basic - Right-click blocking only",
"protectionLevelStandard": "Standard - Keyboard shortcuts blocked",
"protectionLevelEnhanced": "Enhanced - Print screen detection",
"protectionLevelMaximum": "Maximum - DevTools detection & canvas rendering",
"heroLogoSettings": "Hero Logo Settings",
"heroLogoVisible": "Display logo in hero section",
"heroLogoInherit": "Use branding default",
"heroLogoShow": "Always show",
"heroLogoHide": "Always hide",
"heroLogoSize": "Logo Size",
"heroLogoSizeSmall": "Small",
"heroLogoSizeMedium": "Medium",
"heroLogoSizeLarge": "Large",
"heroLogoSizeXLarge": "Extra Large",
"heroLogoPosition": "Logo Position",
"heroLogoPositionTop": "Top (above title)",
"heroLogoPositionCenter": "Center (between title and dates)",
"heroLogoPositionBottom": "Bottom (below dates)",
"heroLogoInfo": "These settings apply when the gallery uses the Hero layout. You can hide the logo or customize its size and position.",
"eventCustomLogo": "Custom Event Logo",
"eventCustomLogoDescription": "Upload a custom logo for this event. This overrides the global branding logo for this gallery only.",
"uploadEventLogo": "Upload Logo",
"replaceLogo": "Replace",
"removeLogo": "Remove",
"eventLogoUploaded": "Event logo uploaded successfully",
"eventLogoUploadFailed": "Failed to upload event logo",
"eventLogoRemoved": "Event logo removed successfully",
"eventLogoRemoveFailed": "Failed to remove event logo",
"heroLogoVisibleLabel": "Logo visible",
"heroLogoSizeLabel": "Size",
"heroLogoPositionLabel": "Position",
"heroLogoHidden": "Logo hidden",
"heroPhoto": "Hero Photo",
"heroPhotoHelp": "Select a default hero photo for the gallery. You can override this per category.",
"selectHeroPhoto": "Select Hero Photo",
"noHeroPhotoSelected": "No hero photo selected",
"heroPhotoSelected": "Hero photo selected",
"heroImageAnchor": "Hero Image Crop Position",
"heroImageAnchorDescription": "Click on the image to set the focal point for cropping.",
"heroImageAnchorTop": "Top",
"heroImageAnchorCenter": "Center",
"heroImageAnchorBottom": "Bottom",
"heroPreview": "Hero preview",
"noPhotosAvailable": "No photos available",
"inactive": "Inactive",
"expired": "Expired",
"draft": "Draft",
"publishAndNotify": "Publish & Notify Client",
"publishConfirm": "This will make the gallery accessible and send the notification email to the client. Continue?",
"publishSuccess": "Gallery published and client notified!",
"publishDialog": {
"title": "Publish gallery",
"descriptionWithEmail": "Publishing \"{{eventName}}\" makes the gallery accessible and sends the notification email to {{customerEmail}}.",
"descriptionNoEmail": "Publishing \"{{eventName}}\" makes the gallery accessible. No customer email is set, so no notification will be sent.",
"descriptionAssignedAccount": "Publishing \"{{eventName}}\" makes the gallery accessible. The assigned customer account(s) will be notified by email (in their language) that it is available.",
"justPublish": "Publish",
"passwordLabel": "Gallery password",
"passwordPlaceholder": "Enter the gallery password",
"passwordHelp": "Re-type the password set at creation (or pick a new one). The email includes this exact text; the backend re-hashes it so the gallery login still works.",
"errorMinLength": "Password must be at least 6 characters long."
},
"duplicateEvent": "Duplicate gallery",
"duplicateDialog": {
"title": "Duplicate gallery",
"description": "Creates a new draft gallery that inherits the branding, behaviour, feedback, and category configuration from \"{{sourceEventName}}\". Photos, password, and share tokens are NOT carried over.",
"eventNameLabel": "New event name *",
"eventNamePlaceholder": "e.g. Müller Wedding 2026",
"eventDateLabel": "Event date",
"eventDateHelp": "Leave blank to use a random suffix in the gallery URL. Expiration is recomputed from this date plus the source gallery's expiration window.",
"customerNameLabel": "Customer name",
"customerNamePlaceholder": "Optional — fill in later if unknown",
"customerEmailLabel": "Customer email",
"customerEmailPlaceholder": "Optional",
"confirm": "Create duplicate",
"errorNameRequired": "Event name is required.",
"successToast": "Gallery duplicated."
},
"draftBanner": "This gallery is in draft mode. Upload your photos, then publish when ready.",
"subtitle": "Manage your photo galleries and events",
"failedToLoadEvents": "Failed to load events",
"tryAgain": "Try Again",
"bulkArchivePartial": "Archived {{success}} events, {{failed}} failed",
"deleteSelected": "Delete Selected",
"bulkDelete": {
"warning": "This will permanently delete the selected events, all their photos, archives, and audit logs. This action cannot be undone.",
"confirmLabel": "Type {{literal}} to confirm",
"confirmHelp": "A typed confirmation prevents accidental deletions and isn't affected by browser autofill or passkey shortcuts.",
"successPartial": "Deleted {{success}} events, {{failed}} failed",
"errorGeneric": "Failed to delete events",
"successAll_one": "Permanently deleted {{count}} event",
"successAll_other": "Permanently deleted {{count}} events",
"title_one": "Permanently delete {{count}} events?",
"title_other": "Permanently delete {{count}} events?",
"processing_one": "Deleting {{count}} events. This may take a few minutes — please don't close this window.",
"processing_other": "Deleting {{count}} events. This may take a few minutes — please don't close this window.",
"submit_one": "Delete {{count}} events",
"submit_other": "Delete {{count}} events"
},
"searchEventsPlaceholder": "Search events...",
"all": "All",
"expiring": "Expiring",
"clear": "Clear",
"archiveSelected": "Archive Selected",
"publicAccess": "Public access",
"passwordProtected": "Password protected",
"newPasswordRequired": "Please set a password before enabling protection.",
"event": "Event",
"type": "Type",
"date": "Date",
"status": "Status",
"noEventsFound": "No events found",
"stats": {
"totalEvents": "Total Events",
"activeEvents": "Active Events",
"totalPhotos": "Total Photos",
"expiringEvents": "Expiring Soon"
},
"viewDetails": "View Details",
"archiveEventAction": "Archive Event",
"downloadArchiveAction": "Download Archive",
"deleteEvent": "Delete Event",
"deleteEventConfirm": "Are you sure you want to delete this event?",
"downloadArchiveSoon": "Download archive coming soon",
"eventExpiredMessage": "This event has expired",
"guestsCannotAccessGallery": "Guests can no longer access the gallery. Consider archiving this event.",
"warningEmailsHaveBeenSent": "Warning emails have been sent to the customer.",
"extendSevenDays": "Extend 7 Days",
"welcomeMessageLabel": "Welcome Message",
"noWelcomeMessageSet": "No welcome message set",
"copy": "Copy",
"copied": "Copied!",
"archiveStatusTitle": "Archive Status",
"downloadingArchive": "Downloading {{name}} archive...",
"downloadStarted": "Download started",
"failedToDownloadArchive": "Failed to download archive",
"galleryTheme": "Gallery Theme",
"noThemeSet": "No theme configured",
"customizingThemeFor": "Customizing theme for {{event}}",
"customCssTemplate": "Custom CSS Template",
"customCssTemplateDesc": "Apply a custom CSS template to style the gallery with unique visual effects.",
"noTemplate": "No Template",
"useThemeOnly": "Use theme preset only",
"customTemplate": "Custom Template",
"rename": {
"button": "Rename",
"title": "Rename Event",
"validating": "Validating new name...",
"renamingFiles": "Renaming files...",
"complete": "Complete!",
"failed": "Rename failed",
"confirm": "Rename Event",
"success": "Event renamed successfully!",
"filesRenamed_one": "{{count}} files updated",
"filesRenamed_other": "{{count}} files updated",
"newLink": "New Gallery Link",
"currentName": "Current name:",
"newName": "New Event Name",
"enterNewName": "Enter new event name",
"newUrl": "New URL:",
"checkingAvailability": "Checking availability...",
"resendEmail": "Resend invitation email with new gallery link",
"emailTo": "Send updated gallery access email to",
"warningTitle": "Please note:",
"warning1": "The gallery URL will change",
"warning2": "Old URLs will automatically redirect to the new URL",
"warning3": "Photo files may be renamed"
},
"bulkArchiveSuccess_one": "Successfully archived {{count}} event",
"bulkArchiveSuccess_other": "Successfully archived {{count}} events",
"daysLeft_one": "({{count}} day left)",
"daysLeft_other": "({{count}} days left)",
"eventsSelected_one": "{{count}} event selected",
"eventsSelected_other": "{{count}} events selected",
"paginationLabel": "{{from}}{{to}} of {{total}}",
"filtered": "filtered",
"pageOf": "Page {{page}} of {{totalPages}}",
"notFound": "Event not found",
"customerPhone": "Customer Phone",
"customerPhonePlaceholder": "+1 555 555 1234",
"allowPresignedDownload": "Allow direct S3 download (no watermark, S3 mode only)",
"neverExpires": "Never",
"rightClickBlocked": "Right-click blocked",
"devtoolsDetection": "DevTools detection",
"watermarked": "Watermarked",
"importExternal": "Import from External Folder",
"externalImportInfo": "All pictures from the selected folder will be imported.",
"selectExternalFolder": "Select external folder under /external-media",
"importFromSelectedFolder": "Import from selected folder",
"promoBanner": {
"title": "Promotional Banner",
"help": "Choose how this gallery handles the promotional banner. \"Inherit\" uses your global default; \"Custom\" overrides it for this event; \"Off\" hides it entirely.",
"mode_inherit": "Inherit global default",
"mode_custom": "Custom override for this event",
"mode_off": "Off (hide for this event)",
"placeholder": "Markdown content (e.g. **Special offer:** [book your next session](https://example.com))",
"preview": "Preview"
},
"ogShare": {
"title": "Use hero photo as social-share preview",
"help": "When this gallery URL is shared on WhatsApp, Facebook, Slack, etc., the link preview will show the hero photo above. The thumbnail is fetched unauthenticated by link-preview crawlers — anyone with the URL effectively makes this image public. Off by default; pick a hero you are comfortable surfacing publicly before enabling.",
"heroRequired": "Pick a hero photo above first — this option uses it as the WhatsApp / Facebook / Slack preview image."
}
},
"settings": {
"title": "System Settings",
"subtitle": "Configure system-wide settings and preferences",
"loadingSettings": "Loading settings...",
"general": {
"title": "General",
"siteConfiguration": "Site Configuration",
"siteUrl": "Site URL",
"siteUrlHelp": "Used for generating gallery links in emails",
"defaultExpiration": "Default Expiration (days)",
"maxFileSize": "Max File Size (MB)",
"maxFilesPerUpload": "Max Files per Upload",
"maxFilesPerUploadHelp": "Maximum number of photos allowed in a single upload batch (1-{{max}}).",
"maxUploadBatchSize": "Max Upload Batch Size (MB)",
"maxUploadBatchSizeHelp": "Maximum size per upload request. Lower this if behind a reverse proxy with request size limits (e.g. Cloudflare: 100MB).",
"allowedFileTypes": "Allowed File Types",
"allowedFileTypesHelp": "Comma-separated list of file extensions, e.g. jpg,jpeg,png,webp. Applies to every upload path, including guest uploads and the chunked (large-file) API. Videos are off by default: add mp4, mov or webm to accept them.",
"featureToggles": "Feature Toggles",
"enableAnalytics": "Enable analytics tracking",
"enableRegistration": "Allow self-registration for admins",
"enableShortGalleryUrls": "Use short gallery URLs",
"enableShortGalleryUrlsHelp": "Removes the event slug from new share links while keeping existing links working.",
"useOriginalFilenames": "Use original filenames on download",
"useOriginalFilenamesHelp": "When on, single-photo and ZIP downloads use the original camera filename (e.g. DSC_1234.jpg) instead of the sanitized name. Storage is unchanged; duplicates in the same event get a numeric suffix.",
"maintenanceMode": "Enable maintenance mode",
"language": "Language",
"defaultLanguageHelp": "Language shown to guests before login",
"saveGeneralSettings": "Save General Settings",
"dateTimeFormat": "Date & Time Format",
"dateFormat": "Date Format",
"dateFormatHelp": "How dates are displayed in emails and throughout the application",
"accountSection": "Admin Account",
"accountUsername": "Admin Username",
"accountUsernameHelp": "Displayed in the admin interface and used in activity logs.",
"accountUsernameRequired": "Username is required",
"accountUsernameLength": "Username must be at least 3 characters",
"accountEmail": "Admin Email",
"accountEmailHelp": "Used for login and receiving security notifications.",
"accountEmailRequired": "Email address is required",
"accountEmailInvalid": "Enter a valid email address",
"accountSaveButton": "Save account details",
"accountSaveSuccess": "Account details updated"
},
"publicSite": {
"badge": "Public Landing",
"title": "Public Landing Page",
"subtitle": "Publish a customized landing page for guests when they visit your domain.",
"loading": "Loading public site settings...",
"enabled": "Enabled",
"disabled": "Disabled",
"htmlLabel": "Landing Page HTML",
"htmlPlaceholder": "Provide the HTML markup for your landing page hero, sections, and calls to action.",
"htmlHelp": "Raw HTML is sanitized on save. Unsafe tags, inline scripts, and disallowed attributes are removed automatically. Tokens such as {{company_name}}, {{company_tagline}}, and {{support_email}} will be substituted with values from Branding settings.",
"cssLabel": "Custom CSS",
"cssPlaceholder": "Optional CSS overrides to adjust layout, colors, or typography.",
"cssHelp": "Imports and JavaScript URLs are stripped. Use standard CSS selectors to override the default styling.",
"saveCta": "Save public site",
"saving": "Saving...",
"saveSuccess": "Public site settings saved.",
"saveError": "Failed to save public site settings.",
"resetCta": "Reset to default",
"resetting": "Resetting...",
"resetSuccess": "Public site template reset to defaults.",
"resetError": "Failed to reset public site template.",
"previewTitle": "Live Preview",
"previewSandboxed": "Sandboxed preview",
"previewDisabled": "Enable the public site to see the live preview.",
"sanitizationNotice": "Scripts, embedded frames, and unsafe attributes are removed before publishing. Only safe HTML, links, and images are allowed.",
"htmlRequired": "Provide HTML content before enabling the public site."
},
"storage": {
"totalUsed": "Total Used",
"archiveStorage": "Archive Storage",
"storageLimit": "Storage Limit",
"storageLimitHelper": "Set a limit for photo storage usage. This limit is advisory and will highlight when reached.",
"softLimitInputLabel": "Limit (GB)",
"softLimitHelper": "Enter the preferred maximum storage usage in gigabytes.",
"recommendedSoftLimit": "Suggested Limit",
"diskCapacity": "Disk Capacity",
"diskCapacityReported": "Disk Capacity (reported)",
"diskAvailable": "Available",
"diskAvailableReported": "Available (reported)",
"diskFreeReported": "Free (reported)",
"diskMetricsUnavailable": "Disk metrics are not available in Docker Desktop or virtualized environments.",
"applyRecommended": "Use recommended",
"applyAvailable": "Match available",
"invalidSoftLimit": "Enter a valid number for the limit.",
"saveSoftLimit": "Save limit",
"limitNotEnforced": "This limit is advisory only and is not enforced automatically.",
"overrideTitle": "Manual capacity override",
"diskOverrideEnvNote": "Disk capacity is controlled by environment variables and cannot be edited here.",
"diskOverrideSettingsHelp": "Set custom capacity values when Docker reports unrealistic figures.",
"overrideCapacityLabel": "Total capacity (GB)",
"overrideCapacityHelper": "Leave blank to use the automatic reading.",
"overrideAvailableLabel": "Available space (GB)",
"overrideAvailableHelper": "Optional. Leave blank to calculate from total capacity minus used storage.",
"saveOverride": "Save capacity override",
"capacityRequiredForAvailable": "Enter a total capacity before setting available space.",
"availableExceedsCapacity": "Available space cannot exceed the total capacity.",
"storageUsage": "Storage Usage",
"unlimited": "Unlimited"
},
"security": {
"title": "Security",
"passwordSettings": "Password Settings",
"minPasswordLength": "Minimum Password Length",
"passwordComplexity": "Password Complexity",
"passwordComplexityHelp": "Security level required for gallery passwords",
"complexitySimple": "Simple (6+ chars, any text)",
"complexityModerate": "Moderate (8+ chars, mixed case/numbers)",
"complexityStrong": "Strong (12+ chars, uppercase/lowercase/numbers)",
"complexityVeryStrong": "Very Strong (12+ chars, all character types)",
"sessionAuth": "Session & Authentication",
"sessionTimeout": "Session Timeout (minutes)",
"maxLoginAttempts": "Max Login Attempts",
"maxLoginAttemptsHelp": "Maximum failed login attempts per IP before lockout",
"attemptWindowMinutes": "Attempt Window (minutes)",
"attemptWindowMinutesHelp": "How long to look back when counting failed login attempts",
"lockoutDurationMinutes": "Lockout Duration (minutes)",
"lockoutDurationMinutesHelp": "How long the gallery or account stays locked after too many failures",
"recaptchaSettings": "reCAPTCHA Settings",
"enableRecaptcha": "Enable reCAPTCHA for login forms",
"siteKey": "Site Key",
"secretKey": "Secret Key",
"recaptchaHelp": "Get your reCAPTCHA keys from",
"saveSecuritySettings": "Save Security Settings",
"twoFactorTitle": "Two-factor authentication",
"twoFactorNote": "Two-factor authentication is now managed per admin from Settings → General → Admin Account. Each admin enables it for their own login."
},
"categories": {
"title": "Categories",
"about": "About Photo Categories",
"aboutText": "Global categories are available for all events. You can also create event-specific categories when editing individual events. Categories help organize photos and allow guests to filter photos by type in the gallery view."
},
"systemStatus": {
"title": "System Status",
"storageOverview": "Storage Overview",
"systemInfo": "System Information",
"databaseInfo": "Database Information",
"platform": "Platform",
"nodeVersion": "Node Version",
"uptime": "Uptime",
"cpuCores": "CPU Cores",
"memoryUsage": "Memory Usage",
"memoryUsed": "Memory Used",
"photos": "Photos",
"admins": "Admins",
"dbSize": "Database Size",
"services": "Background Services",
"fileWatcher": "File Watcher",
"fileWatcherDesc": "Monitors for new photos",
"expirationChecker": "Expiration Checker",
"expirationCheckerDesc": "Archives expired galleries",
"emailProcessor": "Email Processor",
"emailProcessorDesc": "Sends queued emails",
"emailQueue": "Email Queue Status",
"pending": "Pending",
"sent": "Sent",
"failed": "Failed",
"lastUpdate": "Last update"
},
"photoDimensions": {
"title": "Photo Dimensions",
"totalPhotos": "Total Photos",
"withDimensions": "With Dimensions",
"missingDimensions": "Missing Dimensions",
"repairButton": "Repair Dimensions",
"repairing": "Repairing...",
"noneToRepair": "All photos already have dimensions",
"resultSuccess": "Last repair: {{success}} updated, {{failed}} failed",
"description": "Backfill missing width/height for photos uploaded before dimension tracking was added. Required for Masonry and Mosaic layouts."
},
"updateNotifications": {
"title": "Update Notifications",
"description": "Receive email notifications when new versions of PicPeak are available.",
"enableEmails": "Enable email notifications",
"enableEmailsDesc": "Send email to admins when a new version is available",
"recipients": "Email Recipients",
"recipientsPlaceholder": "[email protected], [email protected]",
"recipientsHelper": "Comma-separated email addresses. Leave empty to send to all admin users.",
"lastNotified": "Last notification sent for version: {{version}}",
"checkNow": "Check & Notify",
"sendTest": "Send Test Email",
"saved": "Settings saved",
"saveError": "Failed to save settings",
"emailFailed": "Failed to send notification",
"checkSuccess": "Notification sent for new version",
"checkNoAction": "No notification needed: {{reason}}",
"checkError": "Failed to check for updates",
"emailSent_one": "Notification email sent to {{count}} recipients",
"emailSent_other": "Notification email sent to {{count}} recipients"
},
"events": {
"title": "Event Creation",
"requiredFields": "Required Fields",
"requiredFieldsDescription": "Configure which contact fields are required when creating new events.",
"requireCustomerName": "Require customer name",
"requireCustomerNameHelp": "Customer name must be provided for new events",
"requireCustomerEmail": "Require customer email",
"requireCustomerEmailHelp": "Customer email must be provided for new events",
"customerEmailWarning": "Required for sending gallery invitations",
"requireAdminEmail": "Require admin email",
"requireAdminEmailHelp": "Admin email must be provided for new events",
"adminEmailWarning": "Required for receiving event notifications",
"requireEventDate": "Require event date",
"requireEventDateHelp": "Event date must be provided when creating events",
"eventDateWarning": "Gallery URLs will use random identifiers instead of dates",
"requireExpiration": "Require expiration date",
"requireExpirationHelp": "Galleries must have an expiration date",
"expirationWarning": "Galleries without expiration will remain active until manually archived",
"saveSettings": "Save Event Settings",
"noteTitle": "Note",
"noteText": "These settings only affect new event creation. Existing events are not affected. Default behavior requires all fields.",
"defaultRequirePassword": "Require password by default",
"defaultRequirePasswordHelp": "Pre-check \"Require password\" when creating new events. Disable for quicker creation of public galleries.",
"showGalleryFilterBar": "Show filter bar in galleries",
"showGalleryFilterBarHelp": "Display the search-by-filename and sort controls above grid-layout galleries. Disable for a cleaner layout.",
"enablePhoneField": "Enable phone number field",
"enablePhoneFieldHelp": "Adds an optional phone number input to the event form. Useful for downstream automations like WhatsApp delivery via n8n. Always optional even when enabled.",
"defaultFeedbackEnabled": "Enable Guest Feedback by default",
"defaultFeedbackEnabledHelp": "Pre-check \"Guest Feedback\" when creating new events. Individual feedback options (likes, ratings, comments) can still be customised per event."
},
"imageSecurity": {
"title": "Image Protection",
"saveSuccess": "Image security settings saved",
"saveError": "Failed to save settings",
"loadError": "Failed to load image security settings",
"defaultProtection": "Default Protection Settings",
"defaultProtectionHelp": "These settings apply to all new events. Individual events can override these defaults.",
"protectionLevel": "Default Protection Level",
"imageQuality": "Default Image Quality",
"fragmentationLevel": "Fragmentation Level",
"enableDevtools": "Enable DevTools detection by default",
"enableCanvas": "Enable canvas rendering by default (advanced protection)",
"rateLimiting": "Rate Limiting",
"rateLimitingHelp": "Limit how many images can be requested to prevent scraping.",
"requestsPerMinute": "Requests per minute",
"requestsPer5Minutes": "Requests per 5 min",
"requestsPerHour": "Requests per hour",
"securityMonitoring": "Security Monitoring",
"suspiciousThreshold": "Suspicious activity threshold",
"autoBlockThreshold": "Auto-block threshold",
"enableMonitoring": "Enable security monitoring",
"blockSuspiciousIps": "Automatically block suspicious IPs",
"logEvents": "Log security events to database",
"infoTitle": "About Image Protection",
"infoText": "These protection features help prevent casual downloading and copying but cannot block all methods. Determined users may still find ways to capture images. Consider using watermarks and legal agreements for comprehensive protection."
},
"moderation": {
"title": "Moderation",
"wordFilters": "Word Filters",
"description": "Manage words that should be filtered or blocked in comments",
"addFilter": "Add New Filter",
"enterWord": "Enter word to filter",
"searchFilters": "Search filters...",
"filterAdded": "Word filter added successfully",
"filterExists": "This word filter already exists",
"addError": "Failed to add word filter",
"filterUpdated": "Word filter updated successfully",
"updateError": "Failed to update word filter",
"filterDeleted": "Word filter deleted successfully",
"deleteError": "Failed to delete word filter",
"wordRequired": "Please enter a word to filter",
"confirmDelete": "Are you sure you want to delete this word filter?",
"loading": "Loading word filters...",
"noMatchingFilters": "No matching filters found",
"noFilters": "No word filters configured yet",
"severityLow": "Low",
"severityModerate": "Moderate",
"severityHigh": "High",
"severityBlock": "Block",
"severityLevels": "Severity Levels",
"lowDescription": "Word is flagged for review but not automatically blocked",
"moderateDescription": "Comment requires manual approval before being visible",
"highDescription": "Comment is automatically hidden and requires admin review",
"blockDescription": "Comment is rejected immediately and cannot be submitted"
},
"styling": {
"title": "Custom CSS"
},
"thumbnails": {
"title": "Thumbnails",
"dimensionsTitle": "Thumbnail Dimensions & Quality",
"dimensionsHelp": "Configure the size and quality of auto-generated thumbnails. Higher values produce better-looking previews but increase storage and load times.",
"width": "Width (px)",
"widthHelp": "50-1000 pixels",
"height": "Height (px)",
"heightHelp": "50-1000 pixels",
"quality": "Quality",
"qualityHelp": "1-100, higher = better quality but larger files",
"format": "Format",
"fit": "Fit Mode",
"fitHelp": "How images are resized to fit the thumbnail dimensions. \"Cover\" crops to fill, \"Contain\" fits within bounds.",
"regenerateTitle": "Regenerate Thumbnails",
"regenerateHelp": "After changing thumbnail settings, regenerate all existing thumbnails to apply the new configuration. This runs in the background and may take a while for large galleries.",
"regenerateButton": "Regenerate All Thumbnails",
"regenerateStarted": "Thumbnail regeneration started",
"regenerateError": "Failed to start thumbnail regeneration",
"lightboxTitle": "Lightbox Preview Tier",
"lightboxHelp": "The lightbox shows an aspect-preserved ~1920px JPEG (typically 200500 KB) rather than the full original (often 512 MB). Originals are still served when guests click Download. Previews cost roughly one extra file per photo on disk and are stored in /previews.",
"lightboxToggle": "Enable eager preview generation",
"lightboxToggleHelp": "Off by default: each preview is built the first time a guest opens that photo. Turning this on unlocks the button below, which builds them all up front.",
"regeneratePreviewsButton": "Regenerate All Previews",
"previewsRegenerateStarted": "Lightbox preview regeneration started",
"previewsRegenerateError": "Failed to start preview regeneration",
"saveSuccess": "Thumbnail settings saved",
"saveError": "Failed to save thumbnail settings",
"loadError": "Failed to load thumbnail settings",
"infoTitle": "About Thumbnails",
"infoText": "Thumbnails are smaller preview images generated from your originals. Increasing the size or quality improves how photos look in the gallery grid but uses more storage and bandwidth. After changing settings, use \"Regenerate All Thumbnails\" to update existing photos.",
"fit_cover": "Cover (crop to fill)",
"fit_contain": "Contain (fit within)",
"fit_fill": "Fill (stretch)",
"fit_inside": "Inside (shrink to fit)",
"fit_outside": "Outside (expand to cover)",
"fitRecommendation": "Recommendation: use \"Inside\" for masonry / mosaic / justified layouts (preserves aspect ratio); \"Cover\" for uniform 1:1 grid tiles."
},
"seo": {
"title": "SEO & Robots",
"indexingTitle": "Search Engine Indexing",
"allowIndexing": "Allow search engine indexing",
"allowIndexingHelp": "When disabled, all crawlers are blocked via robots.txt. Recommended off for private photo platforms.",
"sitemapUrl": "Sitemap URL",
"sitemapUrlHelp": "Optional. Added to robots.txt if provided.",
"aiBlockingTitle": "AI & Bot Blocking",
"blockAiCrawlers": "Block AI/LLM crawlers",
"blockAiCrawlersHelp": "Prevent AI training bots from accessing your content.",
"blockedAgents": "Blocked AI agents",
"addAgentPlaceholder": "Enter agent name...",
"blockSocialBots": "Block social media preview bots",
"blockSocialBotsHelp": "Prevent link previews on Twitter, Facebook, LinkedIn, etc.",
"metaTagsTitle": "Meta Tags & Custom Rules",
"metaNoindex": "Add noindex meta tag",
"metaNoindexHelp": "Tells search engines not to index pages (HTML-level, complements robots.txt).",
"metaNofollow": "Add nofollow meta tag",
"metaNofollowHelp": "Tells search engines not to follow links on pages.",
"metaNoai": "Add noai/noimageai meta tag",
"metaNoaiHelp": "Signals that content should not be used for AI training.",
"customRules": "Custom robots.txt rules",
"ruleAgentPlaceholder": "User-agent",
"rulePathPlaceholder": "Disallow path",
"showPreview": "Show robots.txt preview",
"hidePreview": "Hide robots.txt preview",
"saveSettings": "Save SEO Settings"
},
"analytics": {
"title": "Analytics",
"providerHeading": "Analytics provider",
"providerDescription": "Pick which tracker to use for the public gallery, or paste your own script. The admin dashboard's device-breakdown chart only enriches when you pick Umami or Rybbit — those expose a metrics API. Other providers (Plausible, Matomo, GA4, …) work via the Custom mode below.",
"providerLabel": "Provider",
"provider": {
"none": "None",
"umami": "Umami",
"rybbit": "Rybbit",
"custom": "Custom (paste your own script)"
},
"providerNoneInfo": "No external tracker injected. The admin dashboard still shows summary cards + the daily chart from PicPeak's own access_logs; the device-breakdown chart uses a coarse user-agent heuristic.",
"rybbitUrl": "Rybbit URL",
"rybbitUrlHelp": "Your Rybbit instance URL — `https://app.rybbit.io` for the SaaS, or `https://rybbit.yourdomain.com` for self-hosted.",
"rybbitWebsiteId": "Site ID",
"rybbitWebsiteIdHelp": "Found in Rybbit → Sites → your site → Tracking script.",
"rybbitApiKey": "API key",
"rybbitApiKeyHelp": "Optional. Required only for the device-breakdown chart. Generate in Rybbit → Account → Settings → API Keys. Stored masked as •••••••• once saved.",
"customHeadHtml": "Custom <head> HTML",
"customHeadHtmlHelp": "Paste your tracker's `<head>` snippet (Plausible, Matomo, Pirsch, GA4, GoatCounter, Fathom, Cloudflare Web Analytics, …). Sanitised on save: only `<script>`, `<noscript>`, `<link rel=\"preconnect|dns-prefetch\">` and `<meta>` tags survive, and event-handler attributes are stripped. The admin dashboard's device-breakdown chart falls back to a server-side user-agent heuristic in this mode — pick Umami or Rybbit if you want the tracker-side numbers.",
"customCspWarning": "Content-Security-Policy reminder",
"customCspWarningText": "PicPeak ships with a strict CSP (`script-src 'self'`). If your tracker loads from another domain, add that domain to your reverse-proxy or nginx CSP config — otherwise the browser silently blocks the script.",
"umamiIntegration": "Umami Analytics Integration",
"enableUmami": "Enable Umami Analytics",
"umamiUrl": "Umami URL",
"umamiUrlHelp": "The URL of your Umami instance (e.g., https://analytics.yourdomain.com)",
"websiteId": "Website ID",
"websiteIdHelp": "Your Umami website ID (found in Umami dashboard)",
"shareUrl": "Share URL (Optional)",
"shareUrlHelp": "Public share URL for embedding the full dashboard (create in Umami)",
"umamiInfo": "About Umami Analytics",
"umamiInfoText": "Umami is a privacy-focused, open-source analytics platform. It tracks page views, unique visitors, and custom events without using cookies.",
"learnMore": "Learn more about Umami",
"saveAnalyticsSettings": "Save Analytics Settings",
"backendAnalytics": "Backend Analytics",
"backendAnalyticsText": "The system also tracks basic analytics server-side for security and performance monitoring.",
"tracked": "Tracked Metrics",
"galleryViews": "Gallery page views",
"photoDownloads": "Individual and bulk downloads",
"uniqueVisitors": "Unique visitors by IP",
"deviceTypes": "Device types from user agents",
"privacy": "Privacy",
"privacyText": "IP addresses are hashed for privacy. No personal data is stored. Analytics data is retained for 90 days."
},
"groups": {
"general": "General",
"display": "Display",
"privacySecurity": "Privacy & Security",
"integrations": "Integrations",
"system": "System",
"appearance": "Content & Appearance",
"communication": "Communication",
"crm": "CRM-Settings"
},
"contracts": {
"title": "Contracts"
},
"reminderTemplates": {
"title": "Reminder emails"
},
"apiTokens": {
"title": "API Tokens",
"createError": "Failed to create token",
"revoked": "Token revoked",
"subtitle": "Long-lived bearer tokens for the public /api/v1 surface — n8n integrations, custom apps, scripts. Tokens act as the admin user that minted them, intersected with the chosen scopes.",
"copyNow": "Copy this token now — it will not be shown again.",
"copied": "Copied",
"copyFailed": "Copy failed",
"name": "Name",
"namePlaceholder": "e.g. n8n production",
"scopes": "Scopes",
"generate": "Generate Token",
"scopeHint": "admin > write > read. A read-only token cannot mutate, even if its owner is super_admin.",
"existing": "Existing tokens",
"lastUsed": "Last used",
"created": "Created",
"status": "Status",
"statusRevoked": "Revoked",
"statusExpired": "Expired",
"statusActive": "Active",
"confirmRevoke": "Revoke \"{{name}}\"? Existing integrations using this token will start getting 401.",
"revoke": "Revoke",
"empty": "No tokens yet. Generate one above to get started.",
"preview": "Preview"
},
"webhooks": {
"title": "Webhooks",
"subtitle": "POST event notifications to your URL the moment something happens — gallery published, photo uploaded, event archived, etc. Signed with HMAC-SHA256 in the X-PicPeak-Signature header.",
"piiNotice": "event.* payloads include customer contact info (name, email, phone) and the gallery share token if you have stored them. Only point webhooks at receivers you trust — they have everything needed to message the customer or open the gallery.",
"copyNow": "Copy this signing secret now — it will not be shown again.",
"name": "Name",
"url": "Receiver URL",
"events": "Subscribe to events",
"filter": "Filter (JSON, optional)",
"template": "Template (optional)",
"create": "Create Webhook",
"existing": "Existing webhooks",
"empty": "No webhooks yet. Create one above to start receiving event notifications.",
"namePlaceholder": "e.g. n8n WhatsApp",
"urlPlaceholder": "https://n8n.example.com/webhook/picpeak",
"showAdvanced": "+ Advanced (filter, template)",
"hideAdvanced": " Hide advanced (filter, template)",
"filterHelp": "Dot-path → expected value. All keys must match (AND). Use an array for \"any of\".",
"filterError": "Filter must be valid JSON",
"templatePlaceholder": "New gallery: ${data.event.event_name} → ${data.event.share_url}",
"templateHelp": "Replaces the default JSON envelope as the request body. ${dot.path} substitution from the payload only — no logic, no expressions.",
"copyButton": "Copy",
"dismiss": "Dismiss",
"copied": "Copied",
"copyFailed": "Copy failed",
"colName": "Name",
"colUrl": "URL",
"colEvents": "Events",
"colLastDelivery": "Last delivery",
"colStatus": "Status",
"colActions": "Actions",
"eventsSubscribed_one": "{{count}} subscribed",
"eventsSubscribed_other": "{{count}} subscribed",
"statusActive": "Active",
"statusDisabled": "Disabled",
"toggleToDisable": "Click to disable",
"toggleToEnable": "Click to enable",
"deliveriesLink": "Deliveries",
"delete": "Delete",
"confirmDelete": "Delete \"{{name}}\"? Pending deliveries are also removed.",
"createError": "Failed to create webhook",
"updateError": "Failed to update webhook",
"deletedToast": "Webhook deleted",
"deleteError": "Failed to delete webhook"
},
"sectionLabel": "Settings section",
"navAriaLabel": "Settings navigation",
"eventTypes": {
"title": "Event Types"
},
"cms": {
"title": "CMS Pages"
},
"email": {
"title": "Email Settings"
},
"backup": {
"title": "Backup"
},
"branding": {
"title": "Branding"
},
"features": {
"title": "Features",
"intro": "Turn product surfaces on or off. Enabled features appear in the left navigation and become available to your team. Some features are still in beta — flip them on to try them, off to hide them.",
"notYetAvailable": "Not yet available — this toggle activates when the feature ships.",
"sidebarHidden": "No sidebar item — runs in the background",
"unsavedChanges": "You have unsaved changes",
"preview": {
"title": "Sidebar preview",
"note": "Reflects unsaved changes",
"legend": "Green tinted items are controlled by toggles above."
},
"sections": {
"core": "Core",
"communication": "Communication",
"scheduling": "Scheduling",
"sales": "Sales",
"accounting": "Accounting",
"insights": "Insights & Access",
"customers": "Customers",
"clients": "CRM",
"automation": "Automation"
},
"status": {
"stable": "stable",
"beta": "beta",
"new": "new",
"experimental": "experimental",
"roadmap": "roadmap"
},
"galleries": {
"title": "Galleries",
"description": "The core PicPeak surface. Always available.",
"locked": "Galleries are the foundation of PicPeak and can't be turned off."
},
"incomingMail": {
"title": "Incoming mail",
"description": "Poll a dedicated mailbox (IMAP) every minute and drop invoice attachments into Accounting → Incoming invoices. Configure the mailbox under Settings → Email."
},
"reminderEmails": {
"title": "Reminder Emails",
"description": "Automatic pre-event nudge to customers N days before their event date. Per-category templates (concert, corporate, wedding, …) editable in Settings → Reminder templates; per-event override on the event detail page."
},
"messaging": {
"title": "Messaging",
"description": "In-app threads with guests, attached to a gallery. Email is genuinely fine for most teams — this is for studios that want everything in one place.",
"sidebar": "Messages"
},
"calendar": {
"title": "Calendar",
"description": "Admin-only calendar showing events, logged hours, and pending quotes/contracts in one view. Drag-create hours directly on the calendar.",
"sidebar": "Calendar"
},
"calendarBooking": {
"title": "Customer booking",
"description": "Let customers see free slots on a public calendar and book directly. Coming soon.",
"sidebar": "Booking"
},
"quotes": {
"title": "Quotes",
"description": "Send line-itemed quotes to clients. They can accept or decline from a public link; payment is tracked manually.",
"sidebar": "Quotes"
},
"bills": {
"title": "Invoices",
"description": "Generate an invoice from any accepted quote. Mark paid manually — no payment processor integration.",
"sidebar": "Invoices"
},
"taxReport": {
"title": "Tax export",
"description": "Period-scoped revenue list with net + VAT breakdown grouped by VAT rate. Export as PDF (landscape, company letterhead) or CSV for your accountant. Cancelled invoices stay visible for a gap-free audit trail but are excluded from totals.",
"sidebar": "Tax",
"requiresBills": "Enable Bills first — the tax report reads from your invoices.",
"requiresAccounting": "Enable Accounting first — Tax export lives in the Accounting section."
},
"accounting": {
"title": "Accounting",
"description": "A dedicated Accounting area, separate from CRM. Turn this on, then enable the sub-features below (Tax export, Incoming invoices). VAT / tax treatment is guidance only — verify with your Treuhänder before relying on it.",
"sidebar": "Accounting",
"requiredByBills": "On automatically because Invoices is enabled — invoice VAT settings live in the Accounting section."
},
"incomingInvoices": {
"title": "Incoming invoices",
"description": "Capture received supplier invoices (upload or phone/tablet camera), categorize expenses, and re-bill costs to clients on the relevant event with a contract-driven markup.",
"sidebar": "Incoming",
"requiresAccounting": "Enable Accounting first — Incoming invoices live in the Accounting section."
},
"expenses": {
"title": "Expenses",
"description": "Internal expenses (mileage, per-diem, cash) booked to an event or the company, with optional proof. Separate from incoming supplier invoices.",
"sidebar": "Expenses",
"requiresAccounting": "Enable Accounting first — Expenses live in the Accounting section."
},
"analytics": {
"title": "Analytics",
"description": "Storage usage, gallery views, download counts, and per-event stats."
},
"userManagement": {
"title": "User Management",
"description": "Multi-admin support with role-based permissions. Turn off if you're a single-operator studio.",
"warning": "Existing user accounts stay valid; the admin UI for managing them will be hidden until you re-enable this."
},
"customerPortal": {
"title": "Customer Accounts",
"description": "Persistent customer logins. Recurring clients see all their assigned galleries from one place — no per-event passwords. Customers log in at /customer/login and you manage them under CRM → Accounts."
},
"clients": {
"title": "CRM",
"description": "Master switch for the CRM sidebar section. Off hides the section and every sub-feature below regardless of their individual toggles — re-enable to restore them to whatever you set last."
},
"contracts": {
"title": "Contracts",
"description": "Compose contracts from a library of reusable blocks (image rights, NDA, model release, cancellation, jurisdiction…) and have customers sign in-browser or upload a wet-signed PDF. Seeded block bodies are EXAMPLES ONLY — review with your lawyer before sending.",
"sidebar": "Contracts"
},
"crmDevelopment": {
"title": "CRM developer tools",
"description": "Internal helpers for verifying CRM flows (e.g. fire the admin payment-check email instantly, bypass throttles). Surfaces as a \"Development\" sub-tab under Clients. Strictly opt-in — fires real side effects, use against test data only.",
"sidebar": "Development"
},
"hoursLogging": {
"title": "Hours logging",
"description": "Per-customer time tracking. Admin logs date + start/end times + optional rate override + note. Monthly-mode customers auto-accumulate hours into the running monthly draft; per-event customers see a \"Create draft invoice\" button that mints a standalone draft invoice with one line per entry. Independent of Bills — log hours even before turning the full billing surface on.",
"sidebar": "Hours"
},
"projects": {
"title": "Projects",
"description": "Admin-only grouping layer above events. Bundle several events under one project and open a 360° Project Overview cockpit — milestone timeline plus a dated feed of every email (with the actual sent preview + resend/cancel/retry actions), quote, contract, invoice, gallery and logged hour. Adds a \"book to project\" control when logging hours. Customers never see projects.",
"sidebar": "Overview"
},
"whatsapp": {
"title": "WhatsApp",
"description": "Deliver the gallery-ready notification via WhatsApp Business API in addition to email. Requires a Meta Business Account, an approved message template, and a customer phone number on the event. Configure credentials under Settings → WhatsApp."
},
"slideshow": {
"title": "Live Slideshow",
"description": "A separate fullscreen \"Diashow\" link per event for projectors at live events — auto-picks-up new uploads, with per-event-type presets and global watermark defaults under Settings → Slideshow."
},
"workflows": {
"title": "Workflows",
"description": "Build visual automations on a canvas — triggers, conditions, branches, loops and admin approval gates. Your reminder ladder and booking steps become editable flows. Strictly opt-in.",
"sidebar": "Workflows"
}
},
"customerSurface": {
"brandingTitle": "Customer dashboard header",
"brandingHint": "Controls what shows in the header of /customer/dashboard. Public galleries and admin surfaces are not affected.",
"showLogo": "Show logo in customer header",
"showLogoHint": "Uses the same branding logo configured above.",
"showCompanyName": "Show company name in customer header",
"showCompanyNameHint": "Hide if your logo already includes the company name.",
"save": "Save changes",
"saved": "Customer dashboard branding saved",
"error": "Could not save settings"
},
"businessProfile": {
"title": "Business profile"
},
"crm": {
"title": "CRM behaviour"
},
"accounting": {
"title": "Accounting",
"subtitle": "Default rates, VAT settings and the proof requirement.",
"kmRate": "Mileage rate (CHF / km)",
"kmRateHint": "Default applied to mileage expenses; overridable per entry.",
"perDiemRate": "Daily allowance (CHF / day)",
"perDiemRateHint": "A flat daily allowance booked as an expense (not a client billing rate); overridable per entry.",
"requireProof": "Require a proof file on every expense",
"vat": {
"title": "VAT",
"registered": "VAT-registered (charge output VAT + reclaim input VAT)",
"registeredHint": "Off = small business / under threshold: no VAT charged, input VAT is a cost (not reclaimable).",
"reclaimCountries": "Countries where input VAT is reclaimable",
"reclaimCountriesHint": "Typically your domestic country (CH / LI). Costs from other countries are treated as non-reclaimable foreign VAT. Cmd/Ctrl-click to multi-select.",
"defaultOutputCode": "Default VAT code for new invoices",
"defaultOutputCodeNone": "— none (start at 0%) —",
"defaultOutputCodeHint": "New invoices and quotes start with this VAT code selected. Existing documents are unaffected."
},
"disclaimer": "Rates and VAT/tax treatment are guidance only — verify with your Treuhänder.",
"savedToast": "Accounting settings saved.",
"profileFields": {
"vatLabel": "VAT label (e.g. MwSt., VAT)",
"vatLabelHint": "Printed as the VAT-line label on invoice / quote PDFs. Leave blank to use the document language default.",
"hourlyRate": "Default hourly rate",
"hourlyRatePlaceholder": "e.g. 120.00",
"hourlyRateHint": "Billing fallback used when a customer has no own rate (hours logging). In {{currency}}, major units. Leave blank to require a per-customer or per-entry rate."
}
},
"slideshow": {
"title": "Slideshow",
"subtitle": "Global defaults for the Live Slideshow. Events and event types can override these."
},
"whatsapp": {
"title": "WhatsApp",
"subtitle": "Configure Meta Business credentials to deliver the gallery-ready notification via WhatsApp alongside email.",
"phoneNumberId": "Phone Number ID",
"phoneNumberIdHint": "From Meta Business → WhatsApp → API Setup. The numeric ID Meta assigns to the phone you registered.",
"wabaId": "WABA ID",
"wabaIdHint": "WhatsApp Business Account ID. Reference only (the API call uses the Phone Number ID); helpful for auditing.",
"accessToken": "Access token",
"accessTokenPlaceholder": "EAAB… (system-user token recommended)",
"accessTokenHint": "Stored masked as \"********\" on GET. Leave the masked value to keep the existing token; type a new one to replace it.",
"templateName": "Template name",
"templateNameHint": "Name of the Meta-approved message template. The default `gallery_ready` expects 5 body parameters: customer name, event name, gallery link, password line, expiry date. Approve the template in Meta Business Manager before enabling.",
"templateLanguage": "Template language",
"templateLanguagePlaceholder": "e.g. en_US, de_DE, ar, pt_BR",
"templateLanguageHint": "Meta template language code, exactly as you registered it in Meta Business Manager (`ar`, `en_US`, `de_DE`, `pt_BR`, etc.). Leave empty to fall back to the system default language. Meta returns \"template not found in language\" if this doesn't match a registered template.",
"templateParams": "Template parameters",
"templateParamsHint": "Pick which built-in values are sent as positional template parameters (slot 1, slot 2, …), and arrange them so they match the order in your Meta-registered template body. Unchecked slots are not sent at all. Default matches the built-in `gallery_ready` 5-parameter shape.",
"templateParamsEmpty": "No slots selected — saving will fall back to the default 5-parameter shape.",
"templateParamsPreview": "Your template will receive: {{preview}}",
"paramMoveUp": "Move up",
"paramMoveDown": "Move down",
"params": {
"customer_name": "Customer name",
"event_name": "Event name",
"gallery_link": "Gallery link",
"password_line": "Password line (🔒-prefixed, empty when no password)",
"expiry_date": "Expiry date"
},
"enabled": "Send WhatsApp notifications",
"savedToast": "WhatsApp settings saved.",
"testHeading": "Send a test message",
"testHelp": "Sends a static template message to the phone number below to verify Meta credentials + template approval. Includes country code (e.g. +49…).",
"testSend": "Send test",
"testSending": "Sending…",
"testSentToast": "Test message sent (id: {{id}})."
},
"mfa": {
"title": "Two-factor authentication",
"description": "Add a second step to your admin sign-in using an authenticator app (TOTP).",
"notEnrolled": "Two-factor authentication is not enabled for your account.",
"setUp": "Set up",
"setupScanInstruction": "Scan this QR code with your authenticator app (e.g. Google Authenticator, 1Password, Authy).",
"manualEntry": "Or enter this secret manually:",
"qrAlt": "Two-factor setup QR code",
"enterCodeLabel": "Enter the 6-digit code from your app",
"codePlaceholder": "123456",
"enable": "Enable",
"enabledBadge": "Two-factor authentication is enabled.",
"recoveryCodesRemaining": "{{count}} recovery code remaining.",
"recoveryCodesRemaining_other": "{{count}} recovery codes remaining.",
"regenerate": "Regenerate recovery codes",
"regenerateHelp": "Enter a current authentication code to generate a new set of recovery codes. Your old codes will stop working.",
"regenerateConfirm": "Regenerate",
"disable": "Disable",
"disablePrompt": "Enter a current authentication or recovery code to disable two-factor authentication:",
"disableConfirmTitle": "Disable two-factor authentication?",
"disableConfirmMessage": "Your account will no longer require a second step at sign-in. You can re-enable it at any time.",
"disableConfirmButton": "Disable",
"disabledToast": "Two-factor authentication disabled.",
"codeRequired": "A code is required.",
"recoveryCodesWarning": "Save these recovery codes now. Each can be used once if you lose access to your authenticator app. They will not be shown again.",
"recoveryCodesAck": "I have saved my recovery codes in a safe place.",
"copy": "Copy",
"copied": "Copied",
"copyFailed": "Failed to copy to clipboard.",
"download": "Download .txt",
"done": "Done",
"setupFailed": "Could not start two-factor setup. Please try again.",
"enableFailed": "Could not enable two-factor authentication. Check the code and try again.",
"disableFailed": "Could not disable two-factor authentication. Check the code and try again.",
"regenerateFailed": "Could not regenerate recovery codes. Check the code and try again."
},
"captureDates": {
"title": "Capture Dates",
"totalPhotos": "Total Photos",
"withDates": "With Capture Date",
"missingDates": "Missing Capture Date",
"button": "Backfill Capture Dates",
"running": "Backfilling...",
"noneToFill": "All photos already have a capture date",
"resultSuccess": "Last run: {{success}} updated, {{noExif}} with no date found, {{failed}} unreachable",
"skipped": "{{count}} photo(s) were changed by something else while the run was reading them and were not updated.",
"description": "Backfill \"Date Taken\" from EXIF for photos imported before capture dates were read. External/reference imports never recorded one, so their galleries sort by import order instead of when the photos were taken."
}
},
"analytics": {
"title": "Analytics Dashboard",
"subtitle": "Track gallery performance and visitor engagement",
"detailedSubtitle": "Detailed analytics powered by Umami",
"loadingAnalytics": "Loading analytics...",
"showSummaryView": "Show Summary View",
"fullDashboard": "Full Dashboard",
"refresh": "Refresh",
"last7Days": "Last 7 days",
"last30Days": "Last 30 days",
"last90Days": "Last 90 days",
"pageViews": "Page Views",
"uniqueVisitors": "Unique Visitors",
"totalDownloads": "Total Downloads",
"topGallery": "Top Gallery",
"topPages": "Top Pages",
"views": "views",
"visitors": "unique visitors",
"topDownloadsByGallery": "Top Downloads by Gallery",
"deviceBreakdown": "Device Breakdown",
"desktop": "Desktop",
"mobile": "Mobile",
"tablet": "Tablet",
"storageUsage": "Storage Usage",
"used": "Used",
"of": "of",
"totalPhotos": "Total Photos",
"activeEvents": "Active Events",
"notConfigured": "Umami Analytics Not Configured",
"configureInstructions": "To see real analytics data, configure Umami in your environment variables and admin panel settings.",
"catalogedMedia": "Catalogued media",
"storageUnavailable": "unavailable",
"storageNoMeasurement": "no measurement available"
},
"branding": {
"title": "Branding & Themes",
"subtitle": "Customize the look and feel of your galleries",
"loadingBranding": "Loading branding settings...",
"themeAndStyle": "Theme & Style",
"companyInfo": "Company Information",
"companyName": "Company Name",
"companyNameHelp": "Displayed in gallery headers and emails",
"companyTagline": "Company Tagline",
"companyTaglineHelp": "A short description of your business",
"supportEmail": "Support Email",
"supportEmailHelp": "Contact email for guest support",
"footerText": "Footer Text",
"logo": "Logo",
"uploadLogo": "Upload Logo",
"logoHelp": "Recommended size: 200x60px, PNG or JPEG",
"logoDark": "Dark-mode logo",
"logoDarkHelp": "Optional. Shown on dark themes / dark mode; falls back to the main logo when unset.",
"favicon": "Favicon",
"currentFavicon": "Current favicon",
"uploadFavicon": "Upload Favicon",
"removeFavicon": "Remove Favicon",
"faviconHelp": "PNG, ICO, or SVG format. Use a square image — an SVG (or a 512×512px PNG) gives the crispest display on high-resolution screens; smaller sizes work too.",
"watermarkSettings": "Watermark Settings",
"enableWatermarks": "Enable Watermarks",
"watermarkHelp": "Add your company name as a watermark on downloaded photos",
"watermarkLogo": "Watermark Logo",
"currentWatermark": "Current watermark",
"uploadWatermarkLogo": "Upload Watermark Logo",
"watermarkPosition": "Watermark Position",
"topLeft": "Top Left",
"topRight": "Top Right",
"center": "Center",
"bottomLeft": "Bottom Left",
"bottomRight": "Bottom Right",
"watermarkOpacity": "Watermark Opacity",
"watermarkSize": "Watermark Size",
"theme": "Theme",
"galleryTheme": "Gallery Theme",
"colors": "Colors",
"accentColor": "Accent Color",
"backgroundColor": "Background Color",
"textColor": "Text Color",
"colorMode": "Color Mode",
"colorModeLight": "Light",
"colorModeDark": "Dark",
"colorModeAuto": "Auto",
"colorModeHelp": "Auto follows the visitor's system preference.",
"preview": "Preview",
"saveChanges": "Save Changes",
"applyLivePreview": "Apply changes immediately (Live Preview)",
"eventSpecificThemes": "Event-Specific Themes",
"eventThemesInfo": "You can override these global theme settings for individual events when creating or editing them.",
"themePresets": "Theme Presets",
"galleryLayout": "Gallery Layout",
"layoutSettings": "Layout Settings",
"photoSpacing": "Photo Spacing",
"spacing": {
"tight": "Tight",
"normal": "Normal",
"relaxed": "Relaxed"
},
"photoAnimation": "Photo Animation",
"animation": {
"none": "None",
"fade": "Fade",
"scale": "Scale",
"slide": "Slide"
},
"columns": "Columns",
"mobile": "Mobile",
"tablet": "Tablet",
"desktop": "Desktop",
"enableAutoplay": "Enable Autoplay",
"autoplayInterval": "Autoplay Interval (seconds)",
"groupPhotosBy": "Group Photos By",
"grouping": {
"day": "Day",
"week": "Week",
"month": "Month"
},
"masonryMode": "Layout Mode",
"masonryModeOptions": {
"columns": "Columns (Pinterest-style)",
"rows": "Rows (Custom justified)",
"flickr": "Flickr (Battle-tested justified)",
"justified": "Google Photos (Knuth-Plass algorithm)"
},
"masonryModeHint": {
"columns": "Pinterest-style vertical columns with varied heights",
"rows": "Custom row-based justified layout",
"flickr": "Flickr's open-source justified layout algorithm",
"justified": "Google Photos-style rows using Knuth-Plass algorithm for optimal breaks"
},
"targetRowHeight": "Target Row Height",
"targetRowHeightHint": "Height in pixels (150-400). Photos will scale to fit rows.",
"lastRowBehavior": "Last Row Alignment",
"lastRowOptions": {
"left": "Left aligned",
"center": "Centered",
"justify": "Justified (stretch)"
},
"thumbnailScale": "Thumbnail Scale",
"thumbnailScaleOptions": {
"xs": "XS — Most photos",
"sm": "SM — More photos",
"md": "MD — Default",
"lg": "LG — Larger photos",
"xl": "XL — Largest photos"
},
"thumbnailScaleHint": "Adjusts column count relative to the base grid columns",
"typographyAndStyle": "Typography & Style",
"bodyFont": "Body Font",
"headingFont": "Heading Font",
"sameAsBody": "Same as body",
"fontSize": "Font Size",
"fontSizes": {
"small": "Small",
"normal": "Normal",
"large": "Large"
},
"borderRadius": "Border Radius",
"borderRadiusOptions": {
"none": "None",
"small": "Small",
"medium": "Medium",
"large": "Large"
},
"shadowStyle": "Shadow Style",
"shadowOptions": {
"none": "None",
"subtle": "Subtle",
"normal": "Normal",
"dramatic": "Dramatic"
},
"backgroundPattern": "Background",
"backgroundOptions": {
"none": "None",
"dots": "Dots",
"grid": "Grid",
"waves": "Waves"
},
"customCSSHelp": "Advanced: Add custom CSS to further customize the appearance",
"cssInstructions": {
"title": "How to use Custom CSS",
"variables": "Theme CSS Variables",
"variablesDesc": "Use these CSS variables to match your theme presets:",
"layouts": "Custom Gallery Layouts",
"layoutsDesc": "Target gallery elements with these selectors:",
"glassEffect": "Glassmorphism Effect",
"glassEffectDesc": "Create modern glass effects:",
"tip": "Tip",
"tipText": "Use CSS Templates from Settings > CSS Templates for pre-built designs like Apple Liquid Glass."
},
"resetToDefault": "Reset to Default",
"applyTheme": "Apply Theme",
"saveTheme": "Save Theme",
"previewLayout": "Preview Layout",
"livePreview": "Live Preview",
"pdfTypography": "PDF typography",
"pdfTypographyHelp": "Used for invoice + quote letterheads. Pick one of the bundled fonts, or leave on default to use Helvetica.",
"pdfFontFamily": "Body font",
"pdfFontFamilyDefault": "Use Helvetica (default)",
"pdfFontFamilyMissing": "missing",
"pdfFontSavedToast": "PDF font saved.",
"heroPlaceholderText": "Your event's hero photo will appear here",
"whiteLabel": "White Label",
"hidePoweredBy": "Hide \"Powered by PicPeak\" branding",
"hidePoweredByHelp": "Remove the PicPeak attribution from gallery footers for a fully white-labeled experience",
"logoCustomization": "Logo Customization",
"changeLogo": "Change Logo",
"logoSizeSmall": "Small (32px)",
"logoSizeMedium": "Medium (48px)",
"logoSizeLarge": "Large (64px)",
"logoSizeXLarge": "Extra Large (96px)",
"logoSizeCustom": "Custom",
"logoMaxHeight": "Maximum Height (pixels)",
"logoMaxHeightHelp": "Set a custom maximum height for the logo (20-200 pixels)",
"logoPosition": "Logo Position in Header",
"positionLeft": "Left",
"positionCenter": "Center",
"positionRight": "Right",
"positionSidepanel": "Sidebar",
"positionSidepanelHelp": "The logo appears in the admin sidebar instead of the header. When the sidebar is collapsed, the favicon is shown.",
"logoDisplayMode": "Display Mode",
"logoOnly": "Logo Only",
"textOnly": "Company Name Only",
"logoAndText": "Logo and Company Name",
"showLogoInHeader": "Show logo in gallery header",
"showLogoInHeaderHelp": "Display the logo in the main header bar",
"showLogoInHero": "Show logo in hero section",
"showLogoInHeroHelp": "Display the logo in hero sections (for non-grid layouts)",
"headerStyle": "Header Style",
"headerStyleDescription": "Choose how the gallery header appears. The header style is independent of the photo layout.",
"heroDividerStyle": "Divider Style",
"heroDividerDescription": "Choose how the transition between the hero image and gallery content looks.",
"controlsStyle": "Controls Style",
"controlsStyleDescription": "Choose how gallery filters and controls are displayed.",
"controlsStyleOptions": {
"classic": "Classic",
"sidebar": "Sidebar"
},
"controlsStyleDescriptions": {
"classic": "Inline filter bar below header",
"sidebar": "Menu button opens sidebar with filters"
},
"controlsStyleHeroWarning": "Sidebar is recommended for hero headers to prevent controls appearing above the hero image.",
"betaThumbnailWarningTitle": "Low thumbnail resolution detected",
"betaThumbnailWarningText": "Your thumbnails are currently {{width}}×{{height}}px. Beta themes display photos at larger sizes and require at least {{recommended}}×{{recommended}}px for good quality. Increase the thumbnail dimensions in Settings > Thumbnails and regenerate.",
"betaThumbnailWarningLink": "Go to Thumbnail Settings",
"logoSize": "Logo Size",
"surfaceColor": "Surface",
"elevatedColor": "Elevated",
"borderColor": "Border",
"mutedTextColor": "Muted text",
"accentDarkColor": "Accent (filled)",
"syncFromBranding": "Sync from Branding",
"forceColorMode": "Force color mode",
"forceColorModeHelp": "Lock the entire admin and public site to dark or light. The user-facing dark/light toggle is hidden whenever a lock is active. Per-event themes that try to override the colour mode are also forced to follow.",
"forceColorModeNone": "No force (user choice)",
"forceColorModeDark": "Force dark",
"forceColorModeLight": "Force light",
"forcedModeBrandingHint": "Light/dark is locked site-wide by the Force control below — the per-theme mode picker is hidden because it would have no effect.",
"forcedModeGalleryNote": "A site-wide color lock is active, so this gallery follows the locked light/dark mode. Color and light/dark options are hidden here and cant be overridden per gallery.",
"colorGroupSurfaces": "Surfaces",
"colorGroupSurfacesHelp": "The neutral layers behind your content. Background sits furthest back; Surface and Elevated stack on top.",
"backgroundColorHelp": "The page itself — body background of every gallery, admin page and CMS page.",
"surfaceColorHelp": "Cards, sidebar, header bar and navigation. The first layer above Background.",
"elevatedColorHelp": "Panels that float above cards: image placeholders, hover/active rows, modal headers, code blocks.",
"borderColorHelp": "Dividers, table grid lines, card outlines, input borders.",
"colorGroupText": "Text",
"colorGroupTextHelp": "Foreground text colours. Primary is for everything readers focus on; Secondary is for supporting copy.",
"textColorHelp": "Headlines, body copy, table cells, form input values, navigation labels — the main text colour.",
"mutedTextColorHelp": "Captions, helper text under inputs, table column headers, footer links, dates and metadata.",
"colorGroupAccent": "Accent",
"colorGroupAccentHelp": "Brand colours that highlight interactive elements. Use a strong colour pair — Accent is for outlines/text, Accent Dark is for filled buttons.",
"accentColorHelp": "Links, icons, focus rings, hover states on primary buttons, active sidebar item underline. Should read clearly on both Background and Surface.",
"accentDarkColorHelp": "Filled CTA buttons, active sidebar item background, badges and tags. Needs enough contrast for white text to be readable on top.",
"cssTemplate": "CSS Template",
"cssTemplateDescription": "Select a pre-built CSS template to apply application-wide styling to this gallery. Templates can be managed in Settings > CSS Templates.",
"noTemplate": "No Template",
"noTemplateDescription": "Use only theme settings without a CSS template",
"templateSlot": "Slot {{slot}}",
"eventCustomCSS": "Event-specific Custom CSS",
"layoutDescriptions": {
"grid": "Classic grid layout with consistent photo sizes",
"masonry": "Pinterest-style layout with varied heights",
"carousel": "Full-screen slideshow with navigation",
"timeline": "Photos organized by date",
"mosaic": "Artistic layout with mixed sizes",
"gallery-premium": "Elegant light theme with hero and masonry (Beta)",
"gallery-story": "Cinematic dark theme with scene sections (Beta)"
},
"headerStyleOptions": {
"hero": "Hero Image",
"standard": "Standard",
"banner": "Banner",
"minimal": "Minimal",
"none": "No Header"
},
"headerStyleDescriptions": {
"hero": "Full-height image with event info overlay",
"standard": "Compact inline header with event details",
"banner": "Standard header plus a colored banner above",
"minimal": "Compact header with essential info",
"none": "Hide header completely"
},
"dividerOptions": {
"wave": "Wave",
"straight": "Straight",
"angle": "Angle",
"curve": "Curve",
"none": "None"
},
"loginLogo": {
"title": "Login pages logo",
"subtitle": "Controls only /admin/login and /customer/login. Gallery and admin chrome use the logo settings above.",
"frame": "Show tinted frame behind the logo",
"frameHelp": "When off, the logo sits directly on the page background. Useful for logos that already include their own backdrop.",
"size": "Logo size on login pages",
"sizeSmall": "Small",
"sizeMedium": "Medium (default)",
"sizeLarge": "Large",
"sizeXLarge": "Extra large"
},
"promo": {
"alignment": "Alignment",
"alignLeft": "Left",
"alignCenter": "Center (default — matches footer)",
"alignRight": "Right"
}
},
"admin": {
"title": "Admin Panel",
"recentActivity": "Recent Activity",
"storageUsed": "Storage Used",
"totalPhotos": "Total Photos",
"storagePercent": "{{percent}}% of limit {{limit}}",
"storageSoftLimitConfigured": "Configured limit: {{limit}}",
"storageSoftLimitRecommended": "Suggested limit: {{limit}}",
"version": "Version",
"archivedEvents": "Archived Events",
"systemHealth": "System Health",
"health": {
"healthy": "Healthy",
"warning": "Warning",
"error": "Error",
"checking": "Checking..."
},
"whatsnew": {
"title": "What's new in {{version}}",
"viewAll": "What's new",
"modalTitle": "What's new",
"fullChangelog": "Full changelog",
"gotIt": "Got it"
},
"updates": {
"available": "Update Available",
"newVersion": "Version {{version}} is available",
"currentVersion": "Current: {{version}}",
"newFeatures": "New features include:",
"channel": "Channel: {{channel}}",
"channelStable": "Stable",
"channelBeta": "Beta",
"beta": "BETA",
"viewReleaseNotes": "View Release Notes",
"updateAvailableShort": "v{{version}} available",
"upToDate": "You're up to date",
"updateNow": "Update Now",
"updateDialog": {
"title": "Update PicPeak",
"detectedEnv": "Detected Environment",
"beforeUpdating": "Before updating:",
"updateCommands": "Update Commands:",
"afterUpdating": "After updating:",
"copyAllCommands": "Copy All Commands",
"completeChecklist": "Complete the checklist before updating",
"error": "Failed to load update instructions"
}
},
"notifications": "Notifications",
"markAllRead": "Mark all read",
"clearAll": "Clear all",
"close": "Close",
"noNotificationsMessage": "No notifications",
"notificationMessages": {
"eventCreated": "New event \"{{eventName}}\" was created",
"eventArchived": "Event \"{{eventName}}\" was archived",
"eventUpdated": "Event \"{{eventName}}\" was updated",
"eventDeleted": "Event \"{{eventName}}\" was deleted",
"photoDeleted": "Photo deleted from \"{{eventName}}\"",
"eventExpiring": "Event \"{{eventName}}\" expires in {{days}} days",
"eventExpired": "Event \"{{eventName}}\" has expired",
"passwordChanged": "Password changed by {{actorName}}",
"passwordReset": "Password reset for \"{{eventName}}\"",
"settingsUpdated": "{{type}} settings updated",
"emailTemplateUpdated": "Email template \"{{template}}\" updated",
"storageWarning": "Storage usage at {{percentage}}%",
"adminLogout": "Admin {{actorName}} logged out",
"categoryCreated": "Category \"{{name}}\" created for \"{{eventName}}\"",
"categoryUpdated": "Category \"{{name}}\" updated for \"{{eventName}}\"",
"categoryDeleted": "Category \"{{name}}\" deleted from \"{{eventName}}\"",
"cmsPageUpdated": "CMS page \"{{slug}}\" updated",
"emailConfigUpdated": "Email configuration updated",
"faviconUploaded": "Favicon uploaded",
"brandingUpdated": "Branding settings updated",
"generalSettingsUpdated": "General settings updated",
"securitySettingsUpdated": "Security settings updated",
"themeUpdated": "Theme settings updated",
"archiveDownloaded": "Archive downloaded for \"{{eventName}}\"",
"archiveDeleted": "Archive deleted for \"{{eventName}}\"",
"archiveRestored": "Archive restored for \"{{eventName}}\"",
"systemActivity": "System activity: {{type}}",
"adminProfileUpdated": "Admin profile updated by {{actorName}}",
"photosUploaded_one": "{{count}} photo uploaded to \"{{eventName}}\"",
"photosUploaded_other": "{{count}} photos uploaded to \"{{eventName}}\"",
"photosBulkDeleted_one": "{{count}} photo deleted from \"{{eventName}}\"",
"photosBulkDeleted_other": "{{count}} photos deleted from \"{{eventName}}\"",
"bulkDownload_one": "{{count}} photo downloaded from \"{{eventName}}\"",
"bulkDownload_other": "{{count}} photos downloaded from \"{{eventName}}\"",
"customerLogin": "Customer logged in: {{email}}",
"customerInvitationCreated": "Customer invitation sent to {{email}}",
"customerInvitationAccepted": "Customer accepted invitation: {{email}}",
"customerInvitationCancelled": "Customer invitation cancelled: {{email}}",
"customerPasswordResetRequested": "Password reset requested for customer {{email}}",
"customerPasswordResetApplied": "Customer password reset by admin: {{email}}",
"customerPasswordChange": "Customer changed their password: {{email}}",
"customerSelfProfileUpdate": "Customer updated their profile: {{email}}",
"customerEventAccess": "{{email}} opened {{eventName}}",
"customerUpdated": "Customer account updated: {{email}}",
"customerDeactivated": "Customer account deactivated: {{email}}",
"customerReactivated": "Customer account reactivated: {{email}}",
"customerErased": "Customer account erased (GDPR): {{email}}",
"adminInvitationCreated": "Admin invitation sent to {{email}}",
"adminInvitationAccepted": "Admin accepted invitation: {{email}}",
"adminInvitationCancelled": "Admin invitation cancelled: {{email}}",
"adminUserUpdated": "Admin user updated: {{username}}",
"adminUserDeactivated": "Admin user deactivated: {{username}}",
"webhookCreated": "Webhook created: {{name}}",
"webhookUpdated": "Webhook updated: {{name}}",
"webhookDeleted": "Webhook deleted: {{name}}",
"apiTokenCreated": "API token created: {{name}}",
"apiTokenRevoked": "API token revoked: {{name}}",
"eventTypeCreated": "Event type created: {{name}}",
"eventTypeUpdated": "Event type updated: {{name}}",
"eventTypeDeleted": "Event type deleted: {{name}}",
"eventTypesReordered": "Event types reordered",
"eventPublished": "Event published: {{eventName}}",
"eventLogoUploaded": "Event logo uploaded for {{eventName}}",
"eventLogoRemoved": "Event logo removed for {{eventName}}",
"bulkDeleteCompleted": "Bulk delete completed: {{count}} events removed",
"photoReplaced": "Photo replaced in {{eventName}}",
"photoUploaded": "Photo uploaded to {{eventName}}",
"categoryHeroUpdated": "Category hero photo updated",
"publicSiteResetToDefault": "Public site reset to default",
"cmsPageLogoUploaded": "CMS page logo uploaded: {{slug}}",
"adminPasswordResetByAdmin": "Admin password reset by another admin: {{username}}",
"contractCreated": "Contract created: {{contractNumber}}",
"contractCreatedFromQuote": "Contract created from quote: {{contractNumber}}",
"contractUpdated": "Contract updated: {{contractNumber}}",
"contractSent": "Contract sent: {{contractNumber}}",
"contractResentSigned": "Signed contract resent: {{contractNumber}}",
"contractSignedByCustomer": "Contract signed by customer: {{contractNumber}}",
"contractSignedPdfUploaded": "Signed contract PDF uploaded: {{contractNumber}}",
"contractSignaturesRestamped": "Contract signatures re-stamped: {{contractNumber}}",
"contractCancelled": "Contract cancelled: {{contractNumber}}",
"contractConvertedToEvent": "Contract converted to event: {{contractNumber}}",
"contractConvertedToEmptyEvent": "Contract converted to empty event: {{contractNumber}}",
"contractConvertedToInvoices": "Contract converted to invoices: {{contractNumber}}",
"contractConvertedToEmptyInvoice": "Contract converted to empty invoice: {{contractNumber}}",
"quoteCreated": "Quote created: {{quoteNumber}}",
"quoteSent": "Quote sent: {{quoteNumber}}",
"quoteUpdated": "Quote updated: {{quoteNumber}}",
"quoteAcceptedByAdmin": "Quote accepted: {{quoteNumber}}",
"quoteDeclinedByAdmin": "Quote declined: {{quoteNumber}}",
"quoteConverted": "Quote converted: {{quoteNumber}}",
"quoteConvertedInvoicesOnly": "Quote converted to invoices: {{quoteNumber}}",
"invoiceCreated": "Invoice created: {{invoiceNumber}}",
"invoiceSent": "Invoice sent: {{invoiceNumber}}",
"invoiceScheduled": "Invoice scheduled: {{invoiceNumber}}",
"invoiceCancelled": "Invoice cancelled: {{invoiceNumber}}",
"invoiceCancelledViaStorno": "Invoice cancelled via Storno: {{invoiceNumber}}",
"invoiceReissued": "Invoice reissued: {{invoiceNumber}}",
"invoicePaidAdminNotified": "Invoice marked paid: {{invoiceNumber}}",
"invoicePaymentCheckRecorded": "Payment-check recorded for invoice: {{invoiceNumber}}",
"invoicePaymentCheckSent": "Payment-check sent for invoice: {{invoiceNumber}}",
"invoiceReleasedForDelivery": "Invoice released for delivery: {{invoiceNumber}}",
"invoiceReminderSent": "Invoice reminder sent: {{invoiceNumber}}",
"stornoSent": "Storno sent: {{invoiceNumber}}",
"monthlyBillIssued": "Monthly bill issued for customer",
"monthlyBillSkippedEmpty": "Monthly bill skipped (no entries)",
"monthlyBillTriggeredManually": "Monthly bill triggered manually",
"monthlyBillingItemsQueued": "Monthly billing items queued",
"installmentPlanUpdated": "Installment plan updated",
"expenseCreated": "Expense created",
"expenseUpdated": "Expense updated",
"expensePaid": "Expense marked paid",
"expenseInvoiced": "Expense invoiced",
"hourEntryLogged": "Hour entry logged",
"hourEntryUpdated": "Hour entry updated",
"hourEntryDeleted": "Hour entry deleted",
"hourEntryLoggedToMonthlyDraft": "Hour entry logged to monthly draft",
"hourEntriesBilled": "Hour entries billed to customer",
"incomingInvoiceCaptured": "Incoming invoice captured",
"incomingInvoiceCategorized": "Incoming invoice categorised",
"incomingInvoiceUpdated": "Incoming invoice updated",
"incomingInvoiceRebilled": "Incoming invoice re-billed to customer",
"incomingInvoiceSupplierPayment": "Supplier payment recorded",
"incomingMailConfigUpdated": "Incoming mail configuration updated",
"customerCreatedPassive": "Passive customer created: {{email}}",
"adminUserActivated": "Admin user activated: {{username}}",
"adminUserDeleted": "Admin user deleted: {{username}}",
"adminPasswordReset": "Admin password reset: {{username}}",
"bulkArchiveCompleted": "Bulk archive completed: {{count}} events archived",
"emailQueueFlushed": "Email queue flushed",
"emailResent": "Creation email resent for {{eventName}}",
"emailTemplateCreated": "Email template created: {{template_key}}",
"eventDuplicated": "Event duplicated from {{source_event_name}}",
"feedbackDeleted": "Feedback deleted",
"feedbackModerated": "Feedback moderated",
"feedbackSettingsUpdated": "Feedback settings updated for {{eventName}}",
"wordFilterAdded": "Word filter added: {{word}}",
"whatsappConfigUpdated": "WhatsApp configuration updated"
},
"notificationToasts": {
"markedAllRead": "All notifications marked as read",
"clearedAll_one": "Cleared {{count}} notification",
"clearedAll_other": "Cleared {{count}} notifications"
},
"changePassword": "Change Password",
"darkMode": "Switch to dark mode",
"lightMode": "Switch to light mode",
"loadingDashboard": "Loading dashboard...",
"activeEvents": "Active Events",
"expiringSoon": "Expiring Soon",
"next7Days": "Next 7 days",
"totalViews": "Total Views",
"downloads": "Downloads",
"percentFromLastWeek": "{{percent}}% from last week",
"dashboardSubtitle": "Welcome back! Here's what's happening with your galleries.",
"eventsExpiringSoon": "Events Expiring Soon",
"noEventsExpiring": "No events expiring in the next 7 days",
"noRecentActivity": "No recent activity",
"photos": {
"hidden": "Hidden",
"hideSelected": "Hide",
"showSelected": "Show",
"hiddenSuccess": "Photos hidden from guests",
"visibleSuccess": "Photos now visible to guests",
"processingStatus": "Processing…",
"processingFailed": "Failed",
"retryQueued": "Retry queued",
"viewMode": "View mode",
"gridView": "Grid view",
"listView": "List view",
"columns": {
"photo": "Photo",
"category": "Category",
"uploaded": "Uploaded",
"engagement": "Engagement",
"feedback": "Feedback",
"size": "Size",
"actions": "Actions",
"views": "Views",
"downloads": "Downloads",
"likes": "Likes"
}
},
"events": {
"tabs": {
"guests": "Guests"
}
},
"daysLeft_one": "{{count}} day left",
"daysLeft_other": "{{count}} days left",
"viewAllExpiringEvents_one": "View all {{count}} expiring events",
"viewAllExpiringEvents_other": "View all {{count}} expiring events",
"guests": {
"loading": "Loading...",
"aggregate": {
"empty": "No guest picks yet.",
"description": "Photos sorted by how many distinct guests liked or favorited them."
},
"inviteCreated": "Invite created",
"inviteCreateError": "Failed to create invite",
"inviteRevoked": "Invite revoked",
"inviteRevokeError": "Failed to revoke invite",
"invitesTitle": "Guest invites",
"createInvite": "Create invite",
"inviteName": "Guest name",
"inviteEmail": "Email (optional)",
"generateInvite": "Generate invite link",
"existingInvites": "Existing invites",
"noInvites": "No invites yet",
"copyLink": "Copy link",
"revokeInvite": "Revoke",
"deletedToast": "Guest removed",
"deletedError": "Failed to remove guest",
"mergedToast": "Guests merged",
"mergedError": "Failed to merge guests",
"forgetGuestConfirm": "Remove this guest? Their picks will be anonymized but kept in aggregate totals.",
"exportError": "Export failed",
"mergeSelectAtLeastTwo": "Select at least 2 guests to merge",
"mergeConfirm_one": "Merge {{count}} guests into {{name}}? This cannot be undone.",
"mergeConfirm_other": "Merge {{count}} guests into {{name}}? This cannot be undone.",
"backToList": "Back to list",
"title": "Guests",
"mergeSelected_one": "{{count}} selected",
"mergeSelected_other": "{{count}} selected",
"mergeNow": "Merge selected",
"aggregateView": "By popularity",
"mergeMode": "Merge",
"exportAll": "Export all",
"empty": "No guests have registered yet.",
"columns": {
"name": "Name",
"email": "Email",
"likes": "Likes",
"favorites": "Favorites",
"comments": "Comments",
"ratings": "Ratings",
"lastSeen": "Last seen"
},
"view": "View details",
"export": "Export",
"forgetGuest": "Remove guest",
"loadingDetail": "Loading selections...",
"detail": {
"noComments": "No comments",
"empty": "No selections in this category",
"all": "All",
"liked": "Liked",
"favorited": "Favorited",
"rated": "Rated",
"commented": "Commented"
},
"inviteStatus": {
"pending": "Pending",
"redeemed": "Redeemed",
"revoked": "Revoked"
}
},
"analytics": "Analytics",
"activities": {
"event_created": "New event created: {{eventName}}",
"photos_uploaded": "{{count}} photos uploaded to {{eventName}}",
"event_archived": "Event archived: {{eventName}}",
"archive_restored": "Archive restored: {{eventName}}",
"archive_deleted": "Archive deleted: {{eventName}}",
"archive_downloaded": "Archive downloaded: {{eventName}}",
"email_config_updated": "Email configuration updated",
"email_template_updated": "Email template updated: {{template}}",
"branding_updated": "Branding settings updated",
"theme_updated": "Theme settings updated",
"bulk_download": "{{count}} photos downloaded from {{eventName}}",
"gallery_password_entry": "Password entered for {{eventName}}",
"expiration_warning_viewed": "Expiration warning viewed for {{eventName}}",
"feedback_settings_updated": "Feedback settings updated",
"feedback_moderated": "Feedback moderated",
"feedback_deleted": "Feedback deleted",
"photo_like": "Photo liked in {{eventName}}",
"photo_favorite": "Photo favorited in {{eventName}}",
"photo_rating": "Photo rated in {{eventName}}",
"photo_comment": "Photo commented in {{eventName}}",
"guest_feedback_like": "Guest liked a photo in {{eventName}}",
"guest_feedback_favorite": "Guest favorited a photo in {{eventName}}",
"guest_feedback_rating": "Guest rated a photo in {{eventName}}",
"guest_feedback_comment": "Guest commented on a photo in {{eventName}}",
"word_filter_added": "Word filter added",
"external_import_completed": "External media import completed ({{imported}} imported, {{skipped}} skipped)",
"bulk_archive_completed": "Bulk archive completed",
"event_activated": "Event activated: {{eventName}}",
"event_deactivated": "Event deactivated: {{eventName}}",
"photo_deleted": "Photo deleted from {{eventName}}",
"photos_bulk_deleted": "{{count}} photos deleted from {{eventName}}",
"settings_updated": "Settings updated",
"event_updated": "Event updated: {{eventName}}",
"event_renamed": "Event renamed: {{eventName}}",
"event_deleted": "Event deleted: {{eventName}}",
"password_changed": "Password changed",
"email_resent": "Creation email resent for: {{eventName}}",
"category_created": "Category created: {{categoryName}}",
"category_updated": "Category updated: {{categoryName}}",
"category_deleted": "Category deleted: {{categoryName}}",
"general_settings_updated": "General settings updated",
"favicon_uploaded": "Favicon uploaded",
"analytics_settings_updated": "Analytics settings updated",
"cms_page_updated": "CMS page updated: {{page}}",
"security_settings_updated": "Security settings updated",
"password_reset": "Password reset for: {{eventName}}",
"admin_logout": "Admin {{actorName}} logged out",
"system_activity": "System activity: {{type}}",
"unknown": "Unknown activity",
"feature_enabled": "{{label}} enabled",
"feature_disabled": "{{label}} disabled",
"feature_flags_summary": "{{count}} features updated: {{summary}}",
"feature_flags_noop": "Feature flags reviewed (no changes)",
"customer_login": "Customer logged in: {{email}}",
"customer_invitation_created": "Customer invitation sent to {{email}}",
"customer_invitation_accepted": "Customer accepted invitation: {{email}}",
"customer_invitation_cancelled": "Customer invitation cancelled: {{email}}",
"customer_password_reset_requested": "Password reset requested for customer {{email}}",
"customer_password_reset_applied": "Customer password reset by admin: {{email}}",
"customer_password_change": "Customer changed their password: {{email}}",
"customer_self_profile_update": "Customer updated their profile: {{email}}",
"customer_event_access": "{{email}} opened {{eventName}}",
"customer_updated": "Customer account updated: {{email}}",
"customer_deactivated": "Customer account deactivated: {{email}}",
"customer_reactivated": "Customer account reactivated: {{email}}",
"customer_erased": "Customer account erased (GDPR): {{email}}",
"admin_invitation_created": "Admin invitation sent to {{email}}",
"admin_invitation_accepted": "Admin accepted invitation: {{email}}",
"admin_invitation_cancelled": "Admin invitation cancelled: {{email}}",
"admin_user_updated": "Admin user updated: {{username}}",
"admin_user_deactivated": "Admin user deactivated: {{username}}",
"admin_password_reset": "Admin password reset by another admin: {{username}}",
"admin_profile_updated": "Admin {{actorName}} updated their profile",
"webhook_created": "Webhook created: {{name}}",
"webhook_updated": "Webhook updated: {{name}}",
"webhook_deleted": "Webhook deleted: {{name}}",
"api_token_created": "API token created: {{name}}",
"api_token_revoked": "API token revoked: {{name}}",
"event_type_created": "Event type created: {{name}}",
"event_type_updated": "Event type updated: {{name}}",
"event_type_deleted": "Event type deleted: {{name}}",
"event_types_reordered": "Event types reordered",
"event_published": "Event published: {{eventName}}",
"event_logo_uploaded": "Event logo uploaded for {{eventName}}",
"event_logo_removed": "Event logo removed for {{eventName}}",
"bulk_delete_completed": "Bulk delete completed: {{count}} events removed",
"photo_replaced": "Photo replaced in {{eventName}}",
"photo_uploaded": "Photo uploaded to {{eventName}}",
"category_hero_updated": "Category hero photo updated",
"public_site_reset_to_default": "Public site reset to default",
"cms_page_logo_uploaded": "CMS page logo uploaded: {{slug}}",
"contract_created": "Contract created: {{contractNumber}}",
"contract_created_from_quote": "Contract created from quote: {{contractNumber}}",
"contract_updated": "Contract updated: {{contractNumber}}",
"contract_sent": "Contract sent: {{contractNumber}}",
"contract_resent_signed": "Signed contract resent: {{contractNumber}}",
"contract_signed_by_customer": "Contract signed by customer: {{contractNumber}}",
"contract_signed_pdf_uploaded": "Signed contract PDF uploaded: {{contractNumber}}",
"contract_signatures_restamped": "Contract signatures re-stamped: {{contractNumber}}",
"contract_cancelled": "Contract cancelled: {{contractNumber}}",
"contract_converted_to_event": "Contract converted to event: {{contractNumber}}",
"contract_converted_to_empty_event": "Contract converted to empty event: {{contractNumber}}",
"contract_converted_to_invoices": "Contract converted to invoices: {{contractNumber}}",
"contract_converted_to_empty_invoice": "Contract converted to empty invoice: {{contractNumber}}",
"quote_created": "Quote created: {{quoteNumber}}",
"quote_sent": "Quote sent: {{quoteNumber}}",
"quote_updated": "Quote updated: {{quoteNumber}}",
"quote_accepted_by_admin": "Quote accepted: {{quoteNumber}}",
"quote_declined_by_admin": "Quote declined: {{quoteNumber}}",
"quote_converted": "Quote converted: {{quoteNumber}}",
"quote_converted_invoices_only": "Quote converted to invoices: {{quoteNumber}}",
"invoice_created": "Invoice created: {{invoiceNumber}}",
"invoice_sent": "Invoice sent: {{invoiceNumber}}",
"invoice_scheduled": "Invoice scheduled: {{invoiceNumber}}",
"invoice_cancelled": "Invoice cancelled: {{invoiceNumber}}",
"invoice_cancelled_via_storno": "Invoice cancelled via Storno: {{invoiceNumber}}",
"invoice_reissued": "Invoice reissued: {{invoiceNumber}}",
"invoice_paid_admin_notified": "Invoice marked paid: {{invoiceNumber}}",
"invoice_payment_check_recorded": "Payment-check recorded for invoice: {{invoiceNumber}}",
"invoice_payment_check_sent": "Payment-check sent for invoice: {{invoiceNumber}}",
"invoice_released_for_delivery": "Invoice released for delivery: {{invoiceNumber}}",
"invoice_reminder_sent": "Invoice reminder sent: {{invoiceNumber}}",
"storno_sent": "Storno sent: {{invoiceNumber}}",
"monthly_bill_issued": "Monthly bill issued for customer",
"monthly_bill_skipped_empty": "Monthly bill skipped (no entries)",
"monthly_bill_triggered_manually": "Monthly bill triggered manually",
"monthly_billing_items_queued": "Monthly billing items queued",
"installment_plan_updated": "Installment plan updated",
"expense_created": "Expense created",
"expense_updated": "Expense updated",
"expense_paid": "Expense marked paid",
"expense_invoiced": "Expense invoiced",
"hour_entry_logged": "Hour entry logged",
"hour_entry_updated": "Hour entry updated",
"hour_entry_deleted": "Hour entry deleted",
"hour_entry_logged_to_monthly_draft": "Hour entry logged to monthly draft",
"hour_entries_billed": "Hour entries billed to customer",
"incoming_invoice_captured": "Incoming invoice captured",
"incoming_invoice_categorized": "Incoming invoice categorised",
"incoming_invoice_updated": "Incoming invoice updated",
"incoming_invoice_rebilled": "Incoming invoice re-billed to customer",
"incoming_invoice_supplier_payment": "Supplier payment recorded",
"incoming_mail_config_updated": "Incoming mail configuration updated",
"customer_created_passive": "Passive customer created: {{email}}",
"admin_user_activated": "Admin user activated: {{username}}",
"admin_user_deleted": "Admin user deleted: {{username}}",
"email_queue_flushed": "Email queue flushed",
"email_template_created": "Email template created: {{template_key}}",
"event_duplicated": "Event duplicated from {{source_event_name}}",
"whatsapp_config_updated": "WhatsApp configuration updated"
},
"catalogedMedia": "{{size}} catalogued",
"catalogedMediaOnly": "catalogued — objects live in S3",
"storageUnavailable": "unavailable"
},
"acceptInvitation": {
"title": "Accept Invitation",
"subtitle": "Create your admin account",
"validating": "Validating invitation...",
"invalidToken": "Invalid Invitation",
"invalidTokenMessage": "This invitation link is invalid or has expired. Please contact your administrator for a new invitation.",
"alreadyUsedMessage": "This invitation has already been used to create an account.",
"invitedAs": "You've been invited as",
"expiresAt": "Invitation expires",
"usernameLabel": "Username",
"usernamePlaceholder": "Choose a username",
"usernameHelp": "3-50 characters, letters, numbers, underscores, and hyphens only",
"passwordLabel": "Password",
"passwordPlaceholder": "Create a strong password",
"confirmPasswordLabel": "Confirm Password",
"confirmPasswordPlaceholder": "Confirm your password",
"passwordStrength": "Password strength",
"requirements": {
"title": "Password requirements:",
"minLength": "At least 12 characters",
"uppercase": "At least one uppercase letter",
"lowercase": "At least one lowercase letter",
"number": "At least one number",
"special": "At least one special character"
},
"strength": {
"weak": "Weak",
"fair": "Fair",
"good": "Good",
"strong": "Strong"
},
"createAccount": "Create Account",
"success": "Account Created!",
"successMessage": "Your account has been created successfully. You can now log in with your credentials.",
"redirecting": "Redirecting to login in {{seconds}}...",
"goToLogin": "Go to Login",
"contactAdminMessage": "Please contact your administrator to request a new invitation.",
"passwordsMatch": "Passwords match",
"alreadyHaveAccount": "Already have an account?",
"signIn": "Sign in",
"errors": {
"usernameRequired": "Username is required",
"usernameTooShort": "Username must be at least 3 characters",
"usernameTooLong": "Username must be at most 50 characters",
"usernameInvalid": "Username can only contain letters, numbers, underscores, and hyphens",
"passwordRequired": "Password is required",
"passwordsDoNotMatch": "Passwords do not match",
"confirmPasswordRequired": "Please confirm your password",
"genericError": "Failed to create account. Please try again."
}
},
"errors": {
"galleryNotFound": "Gallery Not Found",
"galleryNotFoundMessage": "This gallery does not exist or has been removed.",
"somethingWentWrong": "Something went wrong",
"tryAgainLater": "Please try again later",
"refreshPage": "Refresh Page",
"oopsSomethingWentWrong": "Oops! Something went wrong",
"unexpectedError": "We encountered an unexpected error. Don't worry, your data is safe.",
"goToHomepage": "Go to Homepage",
"errorDetails": "Error Details",
"requiredFields": "Please fill in all required fields",
"enterTestEmail": "Please enter a test email address",
"eventCreationFailed": "Failed to create event",
"noShareLink": "No share link available",
"copyFailed": "Failed to copy link"
},
"validation": {
"eventNameRequired": "Event name is required",
"hostEmailRequired": "Customer email is required",
"hostNameRequired": "Customer name is required",
"adminEmailRequired": "Admin email is required",
"invalidEmailFormat": "Invalid email format",
"passwordRequired": "Password is required",
"passwordMinLength": "Password must be at least 6 characters",
"passwordsDoNotMatch": "Passwords do not match",
"expirationRange": "Expiration must be between 1 and 365 days",
"required": "This field is required",
"expirationRequired": "Expiration date is required.",
"eventDateRequired": "Event date is required.",
"passwordTooSimple": "Password cannot be just numbers. Consider using a date format like \"04.07.2025\""
},
"legal": {
"impressum": "Legal Notice",
"datenschutz": "Privacy Policy"
},
"toast": {
"saveSuccess": "Changes saved successfully",
"saveError": "Failed to save changes",
"deleteSuccess": "Deleted successfully",
"deleteError": "Failed to delete",
"uploadSuccess": "Upload completed successfully",
"uploadError": "Upload failed",
"linkCopied": "Link copied to clipboard",
"eventCreated": "Event created successfully",
"eventUpdated": "Event updated successfully",
"eventArchived": "Event archived successfully",
"settingsSaved": "Settings saved successfully",
"themeUpdated": "Theme updated successfully",
"brandingUpdated": "Branding updated successfully",
"categoryUpdated": "Category updated successfully",
"emailConfigSaved": "Email configuration saved successfully",
"brandingThemeMissing": "No branding theme has been saved yet.",
"brandingPaletteSynced": "Palette synced from Branding."
},
"email": {
"title": "Email Configuration",
"subtitle": "Configure email settings for notifications",
"loadingSettings": "Loading email settings...",
"smtpConfiguration": "SMTP Configuration",
"smtpHost": "SMTP Host",
"fromEmail": "From Email",
"fromName": "From Name",
"emailTemplates": "Email Templates",
"smtpSettings": "SMTP Settings",
"testEmailSuccess": "Test email sent successfully",
"saveSmtpSettings": "Save SMTP Settings",
"testEmailSection": "Test Email",
"beforeTesting": "Before testing:",
"saveSmtpFirst": "Save your SMTP settings first",
"ensureFirewall": "Ensure your firewall allows outbound SMTP",
"gmailAppPassword": "For Gmail, use an app-specific password",
"testEmailAddressLabel": "Test Email Address",
"sendTestEmailButton": "Send Test Email",
"flushQueue": {
"title": "Send queued emails now",
"help": "Immediately send every pending email, ignoring the business-hours schedule. Useful for draining the queue before maintenance or updates.",
"button": "Send queued emails now",
"success": "Email queue flushed — {{sent}} sent, {{failed}} failed",
"empty": "No pending emails to send"
},
"incoming": {
"title": "Incoming mail (IMAP)",
"subtitle": "A dedicated mailbox polled every minute; attachments land in Accounting → Incoming invoices.",
"host": "IMAP Host",
"test": "Test connection",
"testOk": "Connected to {{folder}} — {{messages}} messages, {{unseen}} unread.",
"testFailed": "Connection failed.",
"roundTrip": "Round-trip test",
"roundTripHint": "Sends a test email via your SMTP settings to this mailbox and confirms it arrives. Save both first.",
"roundTripOk": "Round-trip OK — delivered to {{recipient}} in {{seconds}}s.",
"roundTripFailed": "Round-trip test failed.",
"poll": "Check now",
"pollHint": "Check the mailbox now instead of waiting for the 60-second poll. Ingests unread attachments into Incoming invoices.",
"pollOk": "Checked mailbox — {{count}} new email(s) ingested.",
"pollDisabled": "Incoming mail is turned off — enable it under Settings → Features.",
"pollUnconfigured": "Save the incoming mail settings first.",
"pollBusy": "A poll is already running — try again in a moment.",
"pollFailed": "Mailbox poll failed.",
"port": "Port",
"security": "Security",
"ssl": "SSL/TLS",
"plain": "None / STARTTLS",
"user": "Username",
"pass": "Password",
"folder": "Folder",
"save": "Save Incoming Mail Settings",
"detectFolders": "Detect",
"folderHint": "Enter host, username and password, then Detect to list the mailbox folders.",
"foldersDetected": "{{count}} folders found.",
"noFolders": "No folders returned by the server.",
"detectFailed": "Could not detect folders.",
"requiredFields": "Host, port and username are required.",
"savedToast": "Incoming mail settings saved."
},
"received": {
"tab": "Received emails",
"from": "From",
"subject": "Subject",
"received": "Received",
"status": "Status",
"empty": "No received emails yet. Enable incoming mail and configure the mailbox.",
"inbox": "inbox",
"statusValue": {
"ingested": "Ingested",
"no_attachment": "No attachment",
"duplicate": "Duplicate",
"error": "Error"
}
},
"sentEmails": {
"tab": "Sent emails",
"title": "Sent emails",
"subtitle": "Delivery status of every queued and sent notification.",
"searchPlaceholder": "Search by recipient or type…",
"from": "From",
"to": "To",
"empty": "No emails match these filters.",
"retries": "{{count}} retries",
"pagination": "Page {{page}} of {{total}} · {{count}} emails",
"status": {
"pending": "Pending",
"sent": "Sent",
"failed": "Failed"
},
"col": {
"recipient": "Recipient",
"type": "Type",
"status": "Status",
"created": "Queued",
"sent": "Sent",
"event": "Event"
}
},
"commonSmtpSettings": "Common SMTP Settings:",
"editTemplate": "Edit Template",
"templateName": "Template Name",
"subjectLine": "Subject Line",
"emailBody": "Email Body",
"preview": "Preview",
"save": "Save",
"templates": "Templates",
"port": "Port",
"security": "Security",
"username": "Username",
"password": "Password",
"enterPassword": "Enter password",
"ignoreSslErrors": "Ignore SSL/TLS certificate errors",
"ignoreSslWarning": "Warning: Disabling certificate verification makes the connection vulnerable to man-in-the-middle attacks. Only enable this if you trust the SMTP server and understand the security implications.",
"brandingTitle": "Email Branding",
"brandingDescription": "Customize the colors used in email templates. Changes apply to the header bar, buttons, links, and footer background.",
"primaryColor": "Primary Color",
"secondaryColor": "Footer Background",
"saveEmailColors": "Save Email Colors",
"editor": {
"bold": "Bold",
"italic": "Italic",
"bulletList": "Bullet List",
"numberedList": "Numbered List",
"blockquote": "Blockquote",
"link": "Add Link",
"horizontalRule": "Horizontal Rule",
"alignLeft": "Align Left",
"alignCenter": "Align Center",
"alignRight": "Align Right",
"clearFormatting": "Clear Formatting",
"undo": "Undo",
"redo": "Redo",
"insertVariable": "Insert Variable",
"sourceMode": "Source",
"visualMode": "Visual",
"enterUrl": "Enter URL...",
"addLink": "Add",
"cancel": "Cancel"
},
"copiedFromLanguage": "Copied content from {{language}}",
"noTranslation": "No translation yet",
"noTranslationYet": "No translation exists for this language yet. Copy from an existing language to get started:",
"copyFrom": "Copy from",
"syncedFromBranding": "Email colours synced from Branding. Click Save to apply.",
"syncFromBranding": "Sync from Branding",
"primaryColorHelp": "Header bar, H2 headings, button background, link colour. Maps to Branding → Accent (filled).",
"secondaryColorHelp": "Footer bar background. Maps to Branding → Surface.",
"bodyBgColor": "Page background",
"bodyBgColorHelp": "The wrapper around the email card — what the recipient sees behind the email itself. Maps to Branding → Background.",
"containerBgColor": "Email card",
"containerBgColorHelp": "The white card that holds the email content. Maps to Branding → Surface.",
"listBgColor": "Info panel",
"listBgColorHelp": "Background of the bulleted info panels inside the email body. Maps to Branding → Elevated.",
"bodyTextColor": "Body text",
"bodyTextColorHelp": "Paragraph and bold text colour. Maps to Branding → Primary text.",
"mutedTextColor": "Footer text",
"mutedTextColorHelp": "Footer text and copyright line. Maps to Branding → Secondary text.",
"buttonTextColor": "Button text",
"buttonTextColorHelp": "Text colour on filled buttons. Should contrast cleanly against the Primary colour. No Branding equivalent — usually white.",
"categories": {
"core": "Core",
"customers": "Customers",
"calendar": "Calendar",
"quotes": "Quotes",
"contracts": "Contracts",
"billing": "Billing"
},
"featureOff": "Feature off",
"featureOffTooltip": "The feature this template belongs to is currently disabled. You can still edit the template — it will be used once the feature is re-enabled.",
"subcategories": {
"gallery": "Galleries",
"admin": "Admin accounts",
"backup": "Backup & restore",
"system": "System updates",
"other": "Other"
},
"customerMailbox": {
"title": "Customer mailbox (hello@)",
"subtitle": "A second inbound mailbox for customer conversations. Its mail appears under Messages → Customers; attachments are not routed to Accounting.",
"enabled": "Poll this mailbox every minute",
"outgoing": "Outgoing (SMTP)",
"outgoingHint": "Replies from this mailbox send from here. Leave blank to fall back to the global outgoing address.",
"fromEmail": "From address",
"smtpHost": "SMTP Host",
"smtpUser": "SMTP Username",
"smtpPass": "SMTP Password",
"smtpSsl": "SSL (465)",
"smtpStarttls": "STARTTLS (587)",
"save": "Save Customer Mailbox",
"savedToast": "Customer mailbox saved.",
"requiredFields": "Host, port and username are required.",
"testOk": "Connected to {{folder}} — {{messages}} messages, {{unseen}} unread.",
"testFailed": "Connection failed."
}
},
"cms": {
"title": "CMS Pages",
"subtitle": "Manage legal and informational pages",
"loadingPages": "Loading pages...",
"pages": "Pages",
"previewLinks": "Preview Links",
"englishVersion": "English Version",
"germanVersion": "German Version",
"editPage": "Edit {{page}}",
"pageTitle": "Page Title",
"pageContent": "Page Content",
"pageTitlePlaceholder": "Enter page title...",
"lastUpdated": "Last updated:",
"pageUpdated": "Page updated successfully",
"useExternalUrl": "Use external URL",
"useExternalUrlHelp": "Redirect visitors to an external page instead of showing the internal content. The internal title and content stay saved as a fallback.",
"externalUrl": "External URL",
"externalUrlPlaceholder": "https://example.com/impressum",
"externalUrlInvalid": "Must be a valid https:// URL",
"externalUrlActive": "External URL is active — internal content is preserved but not shown to visitors.",
"logoUploaded": "Logo uploaded",
"logoCleared": "Logo cleared",
"pageLogo": "Page Logo",
"pageLogoHelp": "Optional. If set, used in place of the global branding logo on this page.",
"noLogo": "no override",
"replaceLogo": "Replace Logo",
"uploadLogo": "Upload Logo",
"clearLogo": "Use site default"
},
"eventTypes": {
"title": "Event Types",
"subtitle": "Customize event types and their default themes",
"loading": "Loading event types...",
"loadError": "Failed to load event types",
"createNew": "New Event Type",
"edit": "Edit Event Type",
"created": "Event type created successfully",
"updated": "Event type updated successfully",
"deleted": "Event type deleted successfully",
"createError": "Failed to create event type",
"updateError": "Failed to update event type",
"deleteError": "Failed to delete event type",
"searchPlaceholder": "Search event types...",
"showInactive": "Show inactive",
"noResults": "No event types found",
"empty": "No event types yet",
"system": "System",
"table": {
"type": "Type",
"slugPrefix": "URL Prefix",
"theme": "Default Theme",
"status": "Status",
"actions": "Actions"
},
"form": {
"name": "Display Name",
"namePlaceholder": "e.g., Family Shoot",
"slugPrefix": "URL Prefix",
"slugPrefixPlaceholder": "e.g., family",
"slugPreview": "Example URL:",
"emoji": "Icon",
"themePreset": "Default Theme",
"slideshowPreset": "Slideshow preset",
"slideshowPresetHint": "Default slideshow style for new events of this type.",
"isActive": "Active (visible in event creation)"
},
"validation": {
"slugFormat": "Only letters, numbers, and hyphens allowed"
},
"slugInfo": {
"title": "URL Prefix Info:",
"description": "The URL prefix is used to generate gallery URLs. For example, an event type with prefix \"family\" will create URLs like: family-smith-family-2025-01-22"
},
"deleteConfirm": {
"title": "Delete Event Type",
"message": "Are you sure you want to delete",
"warning": "This action cannot be undone. Make sure no events are using this type."
}
},
"backup": {
"picpeak": {
"restoreIntro": "Upload a .picpeak taken from this or another instance. Restoring a SQLite backup onto a PostgreSQL instance is supported (the upgrade path); other engine combinations must match.",
"crossEngineNote": "Cross-engine restore: a SQLite backup was converted onto this PostgreSQL instance."
},
"title": "Backup Management",
"subtitle": "Manage system backups, configure automated backups, and restore from previous backups.",
"tabs": {
"dashboard": "Dashboard",
"configuration": "Configuration",
"history": "Backup History",
"restore": "Restore",
"integrity": "Integrity",
"coverage": "Coverage"
},
"integrity": {
"title": "Document integrity",
"description": "Verifies every CRM document (quote / contract / invoice / signature) referenced from the database actually exists on disk and — where a hash is stored — its bytes still match. Read-only, on-demand.",
"runNow": "Run check now",
"running": "Checking…",
"error": "Check failed: {{message}}",
"scannedAt": "Last checked: {{when}}",
"emptyState": "No check has been run yet in this session. Click \"Run check now\" to scan the document estate.",
"summary": {
"total": "Total",
"verifiedOk": "Hash-verified",
"existsButNoHash": "Exists only",
"existsButNoHashHint": "File found, but no SHA-256 is stored for it (quote/invoice PDFs, signature drawings). Existence-only is weaker evidence in a dispute.",
"missingFiles": "Missing",
"hashMismatches": "Hash mismatches"
},
"missing": {
"heading": "Missing files",
"caption": "These rows reference a path that does not exist on disk. After a restore, this means the artefact was lost from the backup chain; for fresh installs, it usually means the file was deleted manually."
},
"hashMismatches": {
"heading": "Hash mismatches",
"caption": "The file exists but its current bytes do not match the SHA-256 captured at issue / sign time. Indicates tampering, bit-rot, or a restore that pulled in a different copy than the original."
},
"results": {
"table": "Table",
"rowId": "Row id",
"column": "Column",
"detail": "Detail"
}
},
"coverage": {
"title": "Backup coverage",
"description": "Shows what the next backup will include, skip, or silently miss. The database block confirms the dump strategy. The \"drift\" section flags subdirectories that exist on disk but are not in the backup configuration — usually a sign that a new feature shipped without a matching backup_paths row.",
"refresh": "Refresh",
"error": "Could not load coverage report: {{message}}",
"generatedAt": "Coverage generated: {{when}}",
"fallbackInUse": "The backup_paths table is missing. The walker is using its legacy hard-coded fallback. Migration 108 may not have run — check server logs and re-run migrations.",
"database": {
"title": "Database",
"mode": "Mode",
"modeInline": "Inline dump on every backup",
"modeScheduled": "Scheduled-only (inline opted out)",
"lastDump": "Last dump",
"lastDumpSize": "Size",
"noDump": "No dump on file yet",
"staleLabel": "Status",
"stale": "Stale — older than 26h"
},
"summary": {
"title": "Summary",
"willScan": "Will scan",
"skippedByToggle": "Skipped (toggle off)",
"skippedByFlag": "Skipped (feature flag)",
"skippedBySetting": "Skipped (backup settings)",
"missingOnDisk": "Missing on disk",
"drift": "Unconfigured on disk (drift)"
},
"paths": {
"heading": "Configured paths",
"path": "Path",
"coverage": "Coverage",
"featureFlag": "Feature flag",
"description": "Description"
},
"coverage": {
"willScan": "Will scan",
"skippedByToggle": "Off",
"skippedByFlag": "Gated off",
"skippedBySetting": "Off (backup settings)",
"missingOnDisk": "Missing on disk"
},
"drift": {
"heading": "Drift detected: subdirectories not covered by any backup_paths row",
"caption": "These directories exist on disk but the walker will skip them. Either add a backup_paths row, move the files into a covered location, or — if they are runtime caches — confirm they are safe to exclude.",
"none": "No drift detected — every top-level subdirectory under STORAGE_PATH is either in backup_paths or in the expected non-backup allow-list."
}
},
"status": {
"inProgress": "Backup in progress...",
"lastBackup": "Last backup",
"noBackups": "No backups found",
"nextBackup": "Next backup",
"notScheduled": "Not scheduled",
"enabled": "Enabled",
"disabled": "Disabled"
},
"actions": {
"runBackupNow": "Run Backup Now",
"starting": "Starting...",
"testConnection": "Test Connection",
"delete": "Delete",
"view": "View Details",
"download": "Download"
},
"dashboard": {
"health": {
"title": "Backup Health"
},
"healthMessages": {
"noBackups": "No backups found",
"lastBackupFailed": "Last backup failed",
"upToDate": "Backup is up to date",
"recent": "Backup is recent",
"gettingOld": "Backup is getting old",
"outdated": "Backup is outdated"
},
"stats": {
"totalBackups": "Total Backups",
"backupSize": "Backup Size",
"lastDuration": "Last Duration",
"backupStatus": "Backup Status",
"last": "Last",
"files": "files",
"active": "Active",
"inactive": "Inactive",
"noBackupsYet": "No backups yet"
},
"recentActivity": {
"title": "Recent Backup Activity"
},
"notConfigured": {
"title": "Backup Not Configured",
"message": "Please configure backup settings in the Configuration tab before running backups."
},
"actions": {
"runBackupNow": "Run Backup Now",
"running": "Running..."
},
"coverage": {
"title": "Backup Coverage",
"included": "Included",
"excluded": "Excluded"
},
"storageDestination": "Storage Destination",
"backupType": "{{type}} backup",
"noDestinationSet": "No destination set",
"healthStatus": {
"excellent": "Excellent",
"good": "Good",
"warning": "Warning",
"critical": "Critical"
}
},
"configuration": {
"enableBackup": "Enable Automated Backups",
"enableBackupHelp": "Automatically create backups according to the configured schedule",
"destinationType": "Backup Destination",
"destinationTypes": {
"local": {
"name": "Local Storage",
"description": "Store backups on the local server filesystem"
},
"rsync": {
"name": "Remote Server (Rsync)",
"description": "Sync backups to a remote server via SSH/Rsync"
},
"s3": {
"name": "S3 Compatible Storage",
"description": "Store backups in Amazon S3 or compatible object storage"
}
},
"fields": {
"destinationPath": "Destination Path",
"destinationPathHelp": "Local directory path for storing backups",
"destinationPathPlaceholder": "/path/to/backup/directory",
"rsyncHost": "Remote Host",
"rsyncHostPlaceholder": "backup.example.com",
"rsyncUser": "SSH User",
"rsyncUserPlaceholder": "backup-user",
"rsyncPath": "Remote Path",
"rsyncPathPlaceholder": "/home/backup/photo-sharing",
"rsyncSshKey": "SSH Private Key",
"rsyncSshKeyHelp": "SSH private key for authentication (optional)",
"rsyncSshKeyPlaceholder": "-----BEGIN RSA PRIVATE KEY-----",
"s3Endpoint": "S3 Endpoint",
"s3EndpointHelp": "S3 API endpoint (e.g., s3.amazonaws.com)",
"s3Bucket": "Bucket Name",
"s3AccessKey": "Access Key ID",
"s3SecretKey": "Secret Access Key",
"s3Region": "Region"
},
"schedule": {
"title": "Backup Schedule",
"scheduleType": "Schedule Type",
"options": {
"hourly": "Every hour",
"daily": "Daily",
"weekly": "Weekly",
"custom": "Custom cron expression"
},
"customCron": "Cron Expression",
"customCronHelp": "Enter a valid cron expression (e.g., 0 3 * * *)",
"retention": "Retention Period",
"retentionDays": "Keep backups for",
"retentionHelp": "days (older backups will be automatically deleted)"
},
"whatToBackup": {
"title": "What to Backup",
"database": "Database",
"databaseHelp": "All event data, settings, and configurations",
"photos": "Photos",
"photosHelp": "All uploaded photos in active galleries",
"archives": "Archives",
"archivesHelp": "Archived event ZIP files",
"thumbnails": "Thumbnails",
"thumbnailsHelp": "Generated thumbnail images (can be recreated)"
},
"advancedOptions": {
"title": "Advanced Options",
"compression": "Enable Compression",
"compressionHelp": "Compress backup files to save storage space",
"encryption": "Enable Encryption",
"encryptionHelp": "Encrypt backups for additional security",
"encryptionPassphrase": "Encryption Passphrase",
"encryptionPassphraseHelp": "Strong passphrase for backup encryption"
},
"messages": {
"requiredFields": "Please fill in all required fields",
"connectionSuccess": "Connection test successful!",
"connectionFailed": "Connection test failed"
},
"testingConnection": "Testing connection...",
"saveSettings": "Save Configuration",
"savingSettings": "Saving..."
},
"history": {
"searchPlaceholder": "Search backups...",
"columns": {
"status": "Status",
"dateTime": "Date & Time",
"type": "Type",
"size": "Size",
"duration": "Duration",
"actions": "Actions"
},
"details": {
"backupDetails": "Backup Details",
"destination": "Destination",
"started": "Started",
"completed": "Completed",
"contentBackedUp": "Content Backed Up",
"errorDetails": "Error Details",
"manifest": "Manifest",
"otherFiles": "Business documents & other",
"totalFiles": "Total files"
},
"pagination": {
"showing": "Showing {{from}}-{{to}} of {{total}} backups",
"previous": "Previous",
"next": "Next"
}
},
"restore": {
"steps": {
"selectSource": "Select Source",
"chooseBackup": "Choose Backup",
"restoreOptions": "Restore Options",
"reviewConfirm": "Review & Confirm",
"progress": "Restore Progress"
},
"source": {
"title": "Select Backup Source",
"subtitle": "Choose where to restore the backup from",
"local": {
"name": "Local Backup",
"description": "Restore from local filesystem"
},
"s3": {
"name": "S3 Storage",
"description": "Restore from S3 bucket"
},
"upload": {
"name": "Upload Backup",
"description": "Upload a backup file",
"comingSoon": "Upload functionality coming soon",
"manifestComingSoon": "Manifest Upload functionality coming soon",
"picpeak": {
"name": ".picpeak backup",
"description": "Portable full backup — restores everything (full override, keeps your current account)."
},
"manifest": {
"name": "Manifest + files",
"description": "Upload a manifest and its backup files (legacy format)."
}
},
"configuration": {
"s3": "S3 Configuration",
"endpoint": "S3 Endpoint URL",
"bucket": "Bucket Name",
"accessKey": "Access Key ID",
"secretKey": "Secret Access Key"
}
},
"backup": {
"title": "Choose Backup to Restore",
"subtitle": "Select from available backups",
"noBackupsFound": "No backups found in selected source",
"encrypted": "Encrypted Backup",
"encryptedMessage": "You'll need to provide the encryption passphrase to restore this backup.",
"enterPassphrase": "Enter encryption passphrase",
"at": "at"
},
"restoreTypes": {
"full": {
"name": "Full Restore",
"description": "Restore everything including database, photos, and archives",
"warning": "This will replace all current data"
},
"database": {
"name": "Database Only",
"description": "Restore only the database (settings, events, users)",
"warning": "Current database will be replaced"
},
"files": {
"name": "Files Only",
"description": "Restore only photos and archives",
"warning": "Existing files may be overwritten"
},
"selective": {
"name": "Selective Restore",
"description": "Choose specific items to restore",
"warning": "Only selected items will be restored"
}
},
"options": {
"title": "Restore Options",
"subtitle": "Choose what to restore",
"additionalOptions": {
"title": "Additional Options",
"skipPreBackup": "Skip Pre-Restore Backup",
"skipPreBackupHelp": "By default, a backup is created before restore. Check this to skip it.",
"force": "Force Restore",
"forceHelp": "Override safety checks and warnings (use with caution)"
}
},
"confirmation": {
"title": "Review & Confirm",
"subtitle": "Please review your restore configuration",
"validation": {
"passed": "Validation Passed",
"failed": "Validation Failed",
"checking": "Validating restore configuration..."
},
"spaceCheck": {
"title": "Storage Space",
"required": "Required",
"available": "Available",
"insufficient": "Insufficient storage space"
},
"summary": {
"title": "Restore Summary",
"source": "Source",
"backupDate": "Backup Date",
"restoreType": "Restore Type",
"preBackup": "Pre-backup",
"enabled": "Enabled",
"skipped": "Skipped"
},
"warning": {
"title": "Important Notice",
"message": "This restore operation will replace existing data. Make sure you have a current backup before proceeding. This action cannot be undone."
}
},
"progress": {
"title": "Restore Progress",
"inProgress": "Restore in progress...",
"completed": "Restore completed",
"overallProgress": "Overall Progress",
"current": "Current",
"statusDetails": "Status Details",
"restoreLogs": "Restore Logs",
"success": {
"title": "Restore Completed Successfully",
"message": "Your data has been restored. Please verify everything is working correctly.",
"verifyIntegrity": "Verify document integrity now"
}
},
"actions": {
"back": "Back",
"next": "Next",
"startRestore": "Start Restore",
"starting": "Starting...",
"validating": "Validating...",
"startNewRestore": "Start New Restore"
}
},
"messages": {
"backupStarted": "Backup started successfully",
"backupFailed": "Failed to start backup",
"configUpdated": "Backup configuration updated",
"configUpdateFailed": "Failed to update configuration"
}
},
"cssTemplates": {
"title": "Custom CSS Templates",
"template": "Template",
"templateName": "Template Name",
"enableTemplate": "Enable this template",
"enableHint": "Enabled templates can be selected when creating events",
"cssContent": "CSS Content",
"cssHint": "Use .gallery-page to scope styles to the gallery. Available variables: --gallery-bg, --gallery-text, --gallery-accent",
"securityNotice": "Security Notice",
"securityText": "CSS is sanitized to prevent malicious code. External URLs, @import, and JavaScript expressions are blocked.",
"resetToDefault": "Reset to Default",
"resetConfirm": "Reset this template to the default? Your changes will be lost.",
"unsavedChanges": "Unsaved changes",
"saveTemplate": "Save Template",
"lastUpdated": "Last updated",
"saved": "Template saved successfully",
"saveFailed": "Failed to save template",
"sanitizationWarning": "Some CSS patterns were blocked for security",
"reset": "Template reset to default",
"resetFailed": "Failed to reset template"
},
"maintenance": {
"title": "System Maintenance",
"message": "We're currently performing scheduled maintenance to improve our service. We'll be back online shortly.",
"urgentMatters": "For urgent matters, please contact"
},
"migrationBanner": {
"title": "PicPeak's image registry has moved",
"body": "Update your docker-compose.yml to pull from {{newPath}} — the old path is no longer being updated.",
"link": "See migration notes"
},
"passwordChange": {
"title": "Change Password",
"currentPassword": "Current Password",
"newPassword": "New Password",
"confirmPassword": "Confirm New Password",
"currentPasswordPlaceholder": "Enter current password",
"newPasswordPlaceholder": "Enter new password",
"confirmPasswordPlaceholder": "Confirm new password",
"requirements": "Password Requirements:",
"minLength": "At least 6 characters long",
"mustDiffer": "Must be different from current password",
"success": "Password changed successfully",
"failed": "Failed to change password",
"currentRequired": "Current password is required",
"newRequired": "New password is required",
"minLengthError": "Password must be at least 6 characters",
"confirmRequired": "Please confirm your new password",
"noMatch": "Passwords do not match",
"mustBeDifferent": "New password must be different from current password",
"cancel": "Cancel"
},
"mandatoryPasswordChange": {
"title": "Password Change Required",
"description": "For security reasons, you must change your password before accessing the admin panel.",
"success": "Password changed successfully! You can now access the admin panel.",
"minLength": "At least 12 characters long",
"mustContainUpperLower": "Must contain uppercase and lowercase letters",
"mustContainNumbers": "Must contain numbers",
"mustContainSpecial": "Must contain special characters (!@#$%^&*)",
"minLengthError": "Password must be at least 12 characters",
"mustContainLowercase": "Password must contain lowercase letters",
"mustContainUppercase": "Password must contain uppercase letters",
"mustContainNumbersError": "Password must contain numbers",
"mustContainSpecialError": "Password must contain special characters"
},
"offline": {
"backOnline": "Back online",
"noConnection": "No internet connection"
},
"download": {
"downloading": "Downloading...",
"percentComplete": "% complete"
},
"passwordGenerator": {
"weak": "Weak",
"fair": "Fair",
"good": "Good",
"strong": "Strong",
"generating": "Generating...",
"generatePassword": "Generate Password",
"showSuggestions": "Show password suggestions",
"moreOptions": "More Options",
"suggestions": "Password Suggestions",
"characters": "characters",
"copyPassword": "Copy password",
"use": "Use",
"pattern": "Pattern:",
"patternDescription": "Passwords are generated using your event name and date. Example: \"Venue2024$August\" for better security and memorability."
},
"feedback": {
"comments": "Comments",
"addComment": "Add Comment",
"yourName": "Your name",
"yourEmail": "Your email",
"writeComment": "Write a comment...",
"submit": "Submit",
"commentSubmitted": "Comment submitted",
"commentError": "Failed to submit comment",
"rateLimited": "Please wait before commenting again",
"commentRequired": "Comment is required",
"nameRequired": "Name is required",
"emailRequired": "Email is required",
"anonymous": "Anonymous",
"pendingApproval": "Pending approval",
"noComments": "No comments yet. Be the first to comment!",
"rating": "Rating",
"likes": "Likes",
"settings": {
"title": "Guest Feedback Settings",
"enableFeedback": "Enable feedback",
"feedbackTypes": "Feedback Types",
"ratings": "Star Ratings",
"ratingsDesc": "Allow guests to rate photos (1-5 stars)",
"likes": "Likes",
"likesDesc": "Simple like/unlike functionality",
"comments": "Comments",
"commentsDesc": "Text comments on photos",
"favorites": "Favorites",
"favoritesDesc": "Mark photos as favorites",
"perGuestLimits": "Per-guest limits",
"perGuestLimitsDesc": "Cap how many photos each guest can favorite or like — useful for \"pick your top N for the album\" workflows. Leave at 0 for no limit. Lowering a cap below an existing guest's count keeps their existing rows; only new adds are blocked until they remove some.",
"maxFavoritesPerGuest": "Max favorites per guest",
"maxFavoritesPerGuestHint": "0 = unlimited",
"maxLikesPerGuest": "Max likes per guest",
"maxLikesPerGuestHint": "0 = unlimited",
"identityMode": "Identity Mode",
"identityModeSimple": "Simple feedback",
"identityModeSimpleDesc": "Anonymous, device-based. All visitors on the same device share state.",
"identityModeGuest": "Per-guest selections",
"identityModeGuestDesc": "Each visitor enters their name. Enables per-guest tracking and admin insights.",
"privacyModeration": "Privacy & Moderation",
"requireInfo": "Require Name & Email",
"requireInfoDesc": "Guests must provide name and email to leave feedback",
"moderateComments": "Moderate Comments",
"moderateCommentsDesc": "Comments require approval before being visible",
"showToGuests": "Show Feedback to Guests",
"showToGuestsDesc": "Other guests can see ratings, likes, and approved comments",
"enableRateLimiting": "Enable Rate Limiting",
"rateLimitingDesc": "Prevent spam by limiting feedback frequency",
"timeWindow": "Time Window (minutes)",
"maxRequests": "Max Requests"
},
"settingsUpdated": "Feedback settings updated",
"settingsUpdateError": "Failed to update settings",
"moderated": "Feedback moderated",
"deleted": "Feedback deleted",
"exported": "Feedback exported",
"exportError": "Failed to export feedback",
"title": "Feedback Management",
"exportCSV": "Export CSV",
"exportJSON": "Export JSON",
"tabs": {
"settings": "Settings",
"feedback": "Feedback",
"analytics": "Analytics",
"moderation": "Moderation"
},
"allTypes": "All Types",
"types": {
"rating": "Ratings",
"like": "Likes",
"comment": "Comments",
"favorite": "Favorites"
},
"allStatuses": "All Statuses",
"status": {
"pending": "Pending",
"approved": "Approved",
"hidden": "Hidden"
},
"noFeedback": "No feedback found",
"approve": "Approve",
"hide": "Hide",
"unhide": "Unhide",
"confirmDelete": "Are you sure you want to delete this feedback?",
"avgRating": "Average Rating",
"totalRatings_one": "{{count}} ratings",
"totalRatings_other": "{{count}} ratings",
"totalLikes": "Total Likes",
"totalComments": "Total Comments",
"pendingModeration_one": "{{count}} pending",
"pendingModeration_other": "{{count}} pending",
"totalInteractions": "Total Interactions",
"topRated": "Top Rated Photos",
"recentComments": "Recent Comments",
"wordFilters": "Word Filters",
"wordFiltersDesc": "Manage blocked words for comment moderation",
"manageFilters": "Manage Word Filters",
"manage": "Manage Feedback",
"ratingSubmitted": "Rating submitted",
"ratingError": "Failed to submit rating",
"rateStar_one": "Rate {{count}} stars",
"rateStar_other": "Rate {{count}} stars",
"ratingsCount_one": "{{count}} ratings",
"ratingsCount_other": "{{count}} ratings",
"likeError": "Failed to update like",
"unlike": "Unlike",
"like": "Like",
"favoriteError": "Failed to update favorite",
"unfavorite": "Remove from favorites",
"favorite": "Add to favorites",
"limit": {
"favoriteTitle": "Favorite limit reached",
"favoriteBody": "You can favorite up to {{limit}} photos in this gallery. Remove one to add a new one.",
"likeTitle": "Like limit reached",
"likeBody": "You can like up to {{limit}} photos in this gallery. Remove one to add a new one.",
"counter": "{{current}} of {{limit}} used",
"ok": "Got it"
},
"invalidEmail": "Invalid email address",
"identityRequired": "Your Information Required",
"identityReason": "Please provide your name and email to submit {{type}}.",
"namePlaceholder": "Enter your name",
"emailPlaceholder": "Enter your email",
"submitFeedback": "Submit Feedback",
"moderationSuccess": "Feedback moderated successfully",
"pendingModeration": "Pending Moderation",
"pending": "pending",
"noPendingComments": "No comments pending moderation",
"onPhoto": "On photo",
"showAll_one": "Show all {{count}} pending comments",
"showAll_other": "Show all {{count}} pending comments",
"viewAllFeedback": "View all feedback & settings",
"exportShapeLabel": "Shape",
"exportShapeLong": "Per-action (long)",
"exportShapePivot": "Per-guest (pivot)"
},
"filter": {
"feedbackFilters": "Feedback Filters",
"clear": "Clear",
"rating": "Rating",
"hasLikes": "Has likes",
"hasFavorites": "Has favorites",
"hasComments": "Has comments",
"showingPhotos": "Total photos",
"withRatings": "With ratings",
"combineWith": "Combine with",
"allPhotos": "All Photos",
"anyRating": "Any Rating",
"oneStarPlus": "1+ Stars",
"twoStarsPlus": "2+ Stars",
"threeStarsPlus": "3+ Stars",
"fourStarsPlus": "4+ Stars",
"fiveStarsOnly": "5 Stars Only"
},
"setup": {
"title": "Welcome to PicPeak",
"subtitle": "Create your administrator account to get started",
"tokenStepSubtitle": "First, enter your one-time setup token",
"accountStepSubtitle": "Now create your administrator account",
"usageSubtitle": "How will you use PicPeak?",
"usageAlwaysOn": "Galleries, analytics and user management are always included. Pick any extras below — you can change these anytime in Settings.",
"usageGroupCrm": "Client management",
"usageGroupAccounting": "Accounting",
"usageGroupAutomation": "Automation & delivery",
"usageDepsNote": "Invoices automatically enable the Accounting area.",
"usageSkip": "Continue with galleries only",
"finish": "Finish setup",
"featuresSaveFailed": "Could not save your feature choices — you can set them later in Settings → Features.",
"restoreStepSubtitle": "Restore from a backup",
"restoreEntry": "Migrating from another PicPeak?",
"restoreEntryHint": "Restore a .picpeak backup instead of setting up fresh.",
"restoreIntro": "Upload a .picpeak backup to clone another instance onto this one. This replaces everything except the account you just created.",
"config": {
"subtitle": "Set up your features",
"intro": "A few details for the features you picked. Anything you skip keeps its default and can be set later in Settings.",
"invoicing": "Invoicing details",
"invoicingDisclaimer": "Used on your invoices. Bank/IBAN and VAT details are your responsibility — verify them with your bank and Treuhänder/tax advisor.",
"companyName": "Company / legal name",
"addressLine1": "Street and number",
"postalCode": "Postal code",
"city": "City",
"countryCode": "Country code (e.g. CH)",
"currency": "Currency (e.g. CHF)",
"vatId": "VAT ID (or leave blank)",
"taxId": "Tax number (or VAT ID)",
"iban": "IBAN (for invoice payments)",
"email": "Email delivery (SMTP)",
"emailHint": "Required to send reminders, invoices and notifications.",
"smtpHost": "SMTP host",
"smtpPort": "Port",
"smtpUser": "Username",
"smtpPass": "Password",
"fromEmail": "From address",
"fromName": "From name",
"skip": "Skip for now",
"finish": "Finish setup",
"saveFailed": "Some settings could not be saved — you can finish them in Settings."
},
"community": {
"subtitle": "You're all set",
"mission": "PicPeak exists so photographers can own their galleries and client data — on their own server, without monthly SaaS fees. Thanks for giving it a try.",
"bugTitle": "Found a bug?",
"bugDesc": "Open an issue and we'll take a look.",
"featureTitle": "Want a feature?",
"featureDesc": "Tell us what would make PicPeak better for you.",
"starTitle": "Like it? Spread the word",
"starDesc": "Tell other photographers, share it, and star the project on GitHub.",
"supportTitle": "Support development",
"supportDesc": "Optional — buy me a coffee to help keep PicPeak going.",
"finish": "Finish → Dashboard"
},
"stepOf": "Step {{current}} of {{total}}",
"continue": "Continue",
"back": "Back",
"tokenLabel": "Setup token",
"tokenPlaceholder": "Paste the one-time setup token",
"tokenHint": "Printed to the server logs on first start (also saved to data/SETUP_TOKEN).",
"tokenRequired": "The setup token is required",
"tokenCommandLabel": "Can't find your token? Run this in the project directory:",
"copyCommand": "Copy command",
"tokenRotatedLink": "Logs already rotated away? Read the setup guide",
"tokenLocationHint": "Can't find it? Run: docker compose exec backend cat /app/data/SETUP_TOKEN",
"invalidToken": "That setup token is not valid.",
"passwordRequirements": "Use at least 8 characters with an upper-case letter, a lower-case letter and a number.",
"emailLabel": "Email address",
"emailPlaceholder": "[email protected]",
"emailRequired": "Email is required",
"invalidEmail": "Please enter a valid email address",
"passwordLabel": "Password",
"passwordPlaceholder": "Choose a strong password",
"passwordRequired": "Password is required",
"passwordMinLength": "Password must be at least 8 characters",
"confirmLabel": "Confirm password",
"confirmPlaceholder": "Re-enter your password",
"passwordMismatch": "Passwords do not match",
"submit": "Create admin account",
"success": "Admin account created. Welcome!",
"genericError": "Setup failed. Please try again.",
"tooManyAttempts": "Too many attempts. Please wait a moment and try again."
},
"adminLogin": {
"title": "Admin Login",
"subtitle": "Sign in to manage your photo galleries",
"sessionExpired": "Your session has expired. Please log in again.",
"emailLabel": "Email Address",
"emailPlaceholder": "[email protected]",
"emailRequired": "Email is required",
"invalidEmail": "Invalid email format",
"passwordLabel": "Password",
"passwordPlaceholder": "Enter your password",
"passwordRequired": "Password is required",
"passwordMinLength": "Password must be at least 6 characters",
"rememberMe": "Remember me",
"signIn": "Sign In",
"loginSuccess": "Login successful!",
"networkError": "Network error. Please check your connection and try again.",
"tooManyAttempts": "Too many login attempts. Please try again later.",
"invalidCredentials": "Invalid email or password",
"generalError": "An error occurred. Please try again.",
"needHelp": "Need help? Contact",
"poweredBy": "Powered by PicPeak",
"devModeHint": "Development Mode: Use email: [email protected], password: admin123",
"mfa": {
"title": "Two-factor authentication",
"subtitle": "Enter the 6-digit code from your authenticator app.",
"recoverySubtitle": "Enter one of your recovery codes.",
"codeLabel": "Authentication code",
"codePlaceholder": "123456",
"recoveryCodeLabel": "Recovery code",
"recoveryCodePlaceholder": "awzq-jca3-va",
"verify": "Verify",
"back": "Back",
"useRecoveryCode": "Use a recovery code instead",
"useAuthenticator": "Use your authenticator app instead",
"codeRequired": "Enter your authentication code",
"invalidCode": "Invalid code. Please try again.",
"sessionExpired": "Your verification session expired. Please sign in again.",
"locked": "Account temporarily locked due to too many attempts. Try again later.",
"lockedRetry": "Account temporarily locked. Try again in {{seconds}} seconds."
}
},
"slideshow": {
"adminTitle": "Live Slideshow",
"adminDescription": "A separate fullscreen link for projectors at live events. It shows all published photos and automatically picks up new uploads while running.",
"generateLink": "Generate slideshow link",
"linkLabel": "Slideshow link",
"linkGenerated": "Slideshow link generated",
"linkDisabled": "Slideshow link disabled",
"regenerate": "Regenerate",
"disable": "Disable",
"disableConfirm": "Disable this slideshow link? The current link will stop working.",
"intervalLabel": "Display time (sec)",
"transitionLabel": "Transition",
"transitionSpeedLabel": "Transition speed (ms)",
"transition": {
"crossfade": "Crossfade",
"cut": "Cut",
"slide": "Slide",
"kenburns": "Ken Burns",
"dipwhite": "Dip to white",
"dipblack": "Dip to black"
},
"colorfilterLabel": "Color filter",
"colorfilter": {
"none": "None",
"bw": "Black & White",
"sepia": "Sepia",
"warm": "Warm",
"cool": "Cool",
"vignette": "Vignette"
},
"watermarkToggle": "Show logo watermark",
"watermarkDescription": "Overlay a white, semi-transparent logo in a corner (like a TV station ident).",
"watermarkSourceLabel": "Logo",
"watermarkSource": {
"logo": "Light logo",
"logo_dark": "Dark-mode logo",
"favicon": "Favicon",
"event": "Event logo",
"notSet": "Not set"
},
"watermarkPositionLabel": "Position",
"watermarkPosition": {
"top-left": "Top left",
"top-right": "Top right",
"bottom-left": "Bottom left",
"bottom-right": "Bottom right"
},
"watermarkOpacityLabel": "Opacity (%)",
"watermarkStyleLabel": "Logo style",
"watermarkSizeLabel": "Size (% of screen)",
"watermarkModeHint": "The logo, position, opacity, style and size are configured under Settings → Slideshow.",
"watermarkStyle": {
"white": "White (recolor)",
"original": "Original colors"
},
"watermarkMode": {
"inherit": "Use global default",
"on": "On",
"off": "Off"
},
"globalTitle": "Global slideshow settings",
"globalDescription": "Defaults for every slideshow. Events can override the watermark on or off.",
"fitLabel": "Image fit",
"fit": {
"cover": "Fill screen (crop)",
"contain": "Black bars (no crop)"
},
"fitHint": "\"Fill\" crops to fill the screen; \"Black bars\" shows the whole photo — better for portrait images.",
"presetTitle": "Default style for new slideshows",
"presetHint": "Applied to events created from now on; each event can still override it.",
"defaultsSaved": "Slideshow defaults saved",
"liveHint": "Changes apply to a running slideshow within a few seconds — no need to regenerate the link.",
"saveSettings": "Save slideshow settings",
"settingsSaved": "Slideshow settings saved",
"start": "Start slideshow",
"waiting": "Waiting for photos…",
"ended": "Slideshow ended.",
"notFound": "This slideshow link is not active.",
"startFailed": "Could not start the slideshow. Please try again."
},
"clientAccess": {
"title": "Client Access",
"description": "Review photos and manage visibility before the gallery is shared with guests.",
"pinLabel": "Client PIN",
"pinPlaceholder": "Enter your client PIN",
"enterPin": "Please enter your client PIN",
"invalidPin": "Invalid PIN. Please try again.",
"loginFailed": "Authentication failed. Please try again.",
"loginButton": "Access Gallery",
"guestHint": "Looking for the guest gallery?",
"guestLink": "Go to guest view",
"banner": "Client Mode",
"visibleCount": "{{visible}} of {{total}} photos visible to guests",
"hideSelected": "Hide Selected",
"showSelected": "Show Selected",
"adminTitle": "Client Access",
"enableToggle": "Enable Client Access",
"enableDescription": "Allow clients to review and hide photos before the gallery is shared with guests.",
"pinHelperText": "The client will use this PIN to access the review page.",
"pinUpdated": "Client PIN updated",
"setPin": "Set PIN",
"linkLabel": "Client Access Link",
"regenerateToken": "Regenerate link",
"tokenRegenerated": "Client access link regenerated"
},
"export": {
"button": "Export",
"success": "Export downloaded successfully",
"error": "Export failed: ",
"exportFiltered": "Export filtered photos",
"hint": "Select photos or apply filters to export",
"exportSelected_one": "Export {{count}} selected",
"exportSelected_other": "Export {{count}} selected",
"preview": {
"titleTxt": "Lightroom filename list",
"titleCsv": "CSV export",
"helpTxt": "Paste into Lightroom's filename search. The list is comma-separated with no extension so it matches a catalog that holds RAW files.",
"helpCsv": "Paste into a spreadsheet (Google Sheets, Excel, Numbers) — the first row is the column header.",
"copyButton": "Copy to clipboard",
"copiedShort": "Copied",
"copied": "Copied to clipboard.",
"copyFailed": "Clipboard write blocked. Select the text and copy manually.",
"download": "Download as file"
}
},
"photoSort": {
"defaultSort": "Default Photo Sort",
"uploadDateNewest": "Upload Date (Newest First)",
"uploadDateOldest": "Upload Date (Oldest First)",
"captureDateNewest": "Date Taken (Newest First)",
"captureDateOldest": "Date Taken (Oldest First)",
"filenameAZ": "Filename (A-Z)",
"filenameZA": "Filename (Z-A)",
"dateTaken": "Date Taken"
},
"photos": {
"moveToCategory_one": "Move {{count}} photos to category",
"moveToCategory_other": "Move {{count}} photos to category",
"selectCategory": "Select category",
"uncategorized": "Uncategorized",
"movePhotos": "Move Photos",
"selectedCategory": "selected category",
"movedToCategory_one": "{{count}} photos moved to {{category}}",
"movedToCategory_other": "{{count}} photos moved to {{category}}",
"moveToCategoryFailed": "Failed to move photos to category",
"moveToCategory": "Move to Category"
},
"customer": {
"login": {
"title": "Customer login",
"subtitle": "Access all of your photo galleries in one place.",
"email": "Email",
"password": "Password",
"emailRequired": "Email is required",
"invalidEmail": "Please enter a valid email",
"passwordRequired": "Password is required",
"showPassword": "Show password",
"hidePassword": "Hide password",
"signIn": "Sign in",
"loginSuccess": "Welcome back!",
"invalidCredentials": "Invalid email or password",
"tooManyAttempts": "Too many attempts — please try again later.",
"networkError": "Could not reach the server. Please try again.",
"generalError": "Login failed. Please try again.",
"acceptedToast": "Account ready — please log in.",
"adminHint": "Looking for the admin panel? Visit /admin/login.",
"emailPlaceholder": "[email protected]",
"passwordPlaceholder": "Your password",
"needHelp": "Need help?",
"poweredBy": "Powered by PicPeak"
},
"acceptInvite": {
"title": "Set up your account",
"invalidToken": "This invitation link is invalid or has expired. Please contact your photographer for a new invitation.",
"emailWillBe": "Your account email will be ",
"invitedBy": ", invited by ",
"name": "Your name",
"nameRequired": "Please enter your name",
"password": "Choose a password",
"confirm": "Confirm password",
"passwordTooShort": "Password must be at least 8 characters",
"passwordsMismatch": "Passwords do not match",
"passwordHint": "At least 8 characters, with one uppercase letter and one number.",
"submit": "Create account",
"successToast": "Account created — please log in.",
"alreadyExists": "An account with this email already exists. Please log in instead.",
"invalidSubmission": "Could not create your account.",
"generalError": "Could not create your account. Please try again.",
"subtitle": "Confirm or fill in your details. You can edit anything from the profile page later.",
"displayName": "Display name",
"section": {
"personal": "Personal",
"contact": "Contact & business (optional)",
"address": "Billing address (optional)",
"password": "Choose a password"
}
},
"dashboard": {
"title": "Your galleries",
"subtitle": "Click a gallery to open it. The Download button bundles every photo as a zip.",
"loadError": "Could not load your galleries. Please try again.",
"emptyTitle": "No galleries yet",
"emptyBody": "Once your photographer assigns you to a gallery, it will appear here.",
"openAria": "Open gallery {{name}}",
"opening": "Opening…",
"expiresOn": "Expires {{date}}",
"expiredOn": "Expired {{date}}",
"eventExpired": "This gallery has expired.",
"eventForbidden": "You no longer have access to this gallery.",
"openError": "Could not open this gallery. Please try again.",
"download": "Download",
"preparingDownload": "Preparing…",
"quickDownloadAria": "Download all photos for {{name}}",
"downloadStarted": "Download started for {{name}}",
"downloadError": "Could not start the download. Please try again.",
"sortLabel": "Sort by",
"sortNewest": "Newest first",
"sortOldest": "Oldest first",
"sortName": "By name",
"open": "Open"
},
"layout": {
"greeting": "Hi, {{name}}"
},
"nav": {
"galleries": "Galleries",
"calendar": "Calendar",
"quotes": "Quotes",
"bills": "Invoices",
"profile": "Profile",
"soon": "Soon",
"new": "New"
},
"comingSoon": {
"tag": "Coming soon"
},
"calendar": {
"title": "Calendar",
"body": "Upcoming sessions, gallery delivery dates, and other shoot-related events will land here. We'll let you know when it's ready."
},
"quotes": {
"title": "Quotes",
"subtitle": "Every quote your photographer has sent you. Click an open quote to accept or decline.",
"empty": "No quotes yet.",
"disabled": "This feature is currently disabled for your account. Please contact your photographer if you expected to see quotes here.",
"loadError": "Could not load quotes.",
"noEventName": "—",
"openToRespond": "Open to respond",
"viewPdf": "View PDF",
"popupBlocked": "Allow pop-ups for this site to view the quote PDF."
},
"bills": {
"title": "Invoices",
"subtitle": "Every invoice your photographer has sent you. Click \"View PDF\" to download.",
"empty": "No invoices yet.",
"disabled": "This feature is currently disabled for your account. Please contact your photographer if you expected to see invoices here.",
"loadError": "Could not load invoices.",
"viewPdf": "View PDF",
"popupBlocked": "Allow pop-ups for this site to view the invoice PDF.",
"field": {
"issueDate": "Issued",
"dueDate": "Due",
"paidAt": "Paid",
"outstanding": "Outstanding",
"lateFee": "Late fee"
},
"filter": {
"outstanding": "Outstanding (sent + overdue)"
},
"kind": {
"storno": "Cancellation invoice"
},
"cancelsLabel": "Cancels invoice",
"cancelledByLabel": "Cancelled by cancellation invoice"
},
"sort": {
"label": "Sort",
"newest": "Newest first",
"oldest": "Oldest first",
"priceDesc": "Price (high to low)",
"priceAsc": "Price (low to high)"
},
"filter": {
"label": "Filter",
"all": "All",
"countAll": "{{count}} total",
"countFiltered": "{{visible}} of {{total}}"
},
"profile": {
"title": "Customer profile",
"subtitle": "Keep your contact and billing details up to date — they're shown on quotes and invoices once those features go live.",
"savedToast": "Profile saved",
"saveError": "Could not save profile.",
"loadError": "Could not load your profile.",
"save": "Save changes",
"salutation": {
"none": "— Not specified —",
"herr": "Mr.",
"frau": "Ms.",
"mx": "Mx",
"dr": "Dr."
},
"section": {
"personal": "Personal information",
"contact": "Contact & business",
"address": "Billing address",
"password": "Change password"
},
"field": {
"email": "Email (login)",
"emailHint": "Contact your photographer if you need to change your login email.",
"salutation": "Salutation",
"firstName": "First name",
"lastName": "Last name",
"displayName": "Display name",
"displayNameHint": "How we greet you in the dashboard.",
"phone": "Phone",
"companyName": "Company name",
"vatId": "VAT ID",
"addressLine1": "Address line 1",
"addressLine2": "Address line 2",
"postalCode": "Postal code",
"city": "City",
"state": "State / region",
"countryCode": "Country"
},
"password": {
"current": "Current password",
"next": "New password",
"confirm": "Confirm new password",
"submit": "Update password",
"hint": "At least 8 characters with one uppercase letter and one number.",
"currentRequired": "Enter your current password",
"tooShort": "At least 8 characters",
"mismatch": "Passwords do not match",
"wrong": "Current password is incorrect",
"savedToast": "Password updated",
"error": "Could not change password"
}
},
"resetPassword": {
"title": "Reset your password",
"invalidToken": "This reset link is invalid or has expired. Please ask your photographer to send a new one.",
"forEmail": "Setting a new password for ",
"password": "New password",
"confirm": "Confirm new password",
"submit": "Update password",
"hint": "At least 8 characters with one uppercase letter and one number.",
"tooShort": "Password must be at least 8 characters",
"mismatch": "Passwords do not match",
"successToast": "Password updated. Please log in.",
"invalidSubmission": "Could not update your password.",
"generalError": "Could not update your password. Please try again."
}
},
"customers": {
"field": {
"featureHoursLogging": "Hours logging",
"hourlyRate": "Default hourly rate",
"hourlyRateHint": "Major units (e.g. 150.00 for {{currency}} 150). Leave blank to require a per-entry override on every block."
},
"hours": {
"section": "Hours",
"monthlyHint": "Entries auto-append to the current monthly draft. Edit / delete remains possible until the scheduler arms the draft for send.",
"perEventHint": "Logged entries stay unbilled until you click \"Create draft invoice\" — one scheduled invoice is generated with a line per entry and opened in the editor, so you can add other items before it ships.",
"form": {
"title": "Log new entry",
"date": "Date",
"start": "Start",
"end": "End",
"duration": "Duration",
"durationPlaceholder": "1h · 1.5 · 1:30",
"durationHint": "Type a duration to auto-fill End: 1h, 1.5, 1,5 or 1:30",
"rateOverride": "Rate override",
"note": "Note / description",
"notePlaceholder": "What was worked on?",
"bookToProject": "Book to project",
"save": "Add entry",
"needRate": "Set a rate or enter an override to log time."
},
"col": {
"date": "Date",
"range": "Time",
"hours": "Hours",
"rate": "Rate",
"total": "Total",
"note": "Note",
"status": "Status"
},
"status": {
"unbilled": "Unbilled",
"billed": "Billed",
"billedOn": "Billed: {{number}}"
},
"billButton": "Create draft invoice",
"unbilledCount": "{{count}} unbilled entries totaling {{total}}",
"empty": "No entries logged yet.",
"confirmDelete": "Delete this entry? If it has been billed onto a draft, the matching invoice line will also be removed.",
"locked": "Locked: invoice already armed for send",
"toast": {
"created": "Entry logged",
"deleted": "Entry deleted",
"billed": "Hours billed"
},
"noRate": {
"title": "No hourly rate configured",
"body": "Logging needs a rate. Set one for this customer, type a per-entry override below, or configure an install-wide default.",
"setForCustomer": "Set a rate for this customer",
"setInstallDefault": "Set an install-wide default"
},
"rateSource": {
"customer": "from this customer",
"installDefault": "install-wide default"
},
"error": {
"noRate": "No hourly rate set for this customer. Enter a rate override, set a rate on the customer, or configure an install-wide default in Settings.",
"createFailed": "Failed to log entry"
}
},
"create": {
"openButton": "Create passive customer",
"openLink": "+ Create new customer",
"title": "Create new customer",
"subtitle": "Fill in the details below. Choose \"Save as passive customer\" to create an admin-only record, or \"Save & send portal invitation\" to also email the customer a sign-up link.",
"passiveSubtitle": "Adds an admin-only customer record. The customer is not notified and cannot log in until you send them an invitation later.",
"saveAsPassive": "Save as passive customer",
"saveAndInvite": "Save & send portal invitation",
"savedPassiveToast": "Passive customer created.",
"savedActiveToast": "Customer created and portal invitation sent.",
"inviteFailedToast": "Customer saved (passive). Invitation email failed — retry from the customer detail page.",
"emailRequired": "A valid email is required.",
"nameRequired": "Enter at least a company name or a contact name."
},
"passive": {
"badge": "Passive — admin only",
"activeLabel": "Has portal access",
"sendInvite": "Send portal invitation",
"sendInviteToast": "Portal invitation sent.",
"alreadyActive": "Customer already has portal access — no invitation needed.",
"detailHint": "This customer has no portal access (admin-only record). Click below to email them a portal sign-up link. The customer's existing invoices, quotes, and gallery assignments are preserved when they claim the invitation.",
"deactivatedHint": "Reactivate the customer before sending the invitation."
},
"hoursLoggingDisabled": {
"badge": "Hour logging disabled"
},
"pageTitle": "Customers",
"pageSubtitle": "Recurring customer accounts that can log in at /customer/login.",
"unnamed": "Unnamed",
"empty": "No customers yet. Click \"Invite customer\" to add one.",
"loadError": "Could not load customers",
"loadInvitationsError": "Could not load invitations",
"tabs": {
"customers": "Customers",
"invitations": "Invitations"
},
"search": {
"placeholder": "Search by email, name, or company"
},
"table": {
"name": "Name",
"email": "Email",
"company": "Company",
"eventCount": "Events",
"lastLogin": "Last login",
"status": "Status"
},
"status": {
"active": "Active",
"inactive": "Deactivated"
},
"invite": {
"button": "Invite customer",
"title": "Invite a customer",
"description": "They will receive an email with a link to set up their account. Once they have accepted, you can assign them to events.",
"email": "Email",
"submit": "Send invitation",
"success": "Invitation sent",
"error": "Could not send invitation.",
"conflict": "A customer with this email already exists or has a pending invitation.",
"invalidEmail": "Please enter a valid email",
"showPrefill": "+ Add contact details (optional)",
"hidePrefill": " Hide contact details",
"prefillHint": "Anything you fill in will be pre-populated on the customer's sign-up page — they can still edit it."
},
"invitations": {
"empty": "No pending invitations.",
"email": "Email",
"invitedBy": "Invited by",
"expiresAt": "Expires",
"createdAt": "Created",
"cancel": "Cancel"
},
"deactivate": {
"button": "Deactivate",
"title": "Deactivate customer?",
"body": "They will no longer be able to log in. You can re-invite them later.",
"success": "Customer deactivated",
"error": "Could not deactivate customer"
},
"cancelInvitation": {
"title": "Cancel invitation?",
"body": "The invitation link will stop working immediately.",
"success": "Invitation cancelled",
"error": "Could not cancel invitation"
},
"detail": {
"loadError": "Could not load customer",
"saved": "Customer saved",
"saveError": "Could not save changes.",
"emailConflict": "That email is already in use by another customer.",
"save": "Save changes",
"expires": "expires",
"accountSection": "Account",
"personalSection": "Personal information",
"billingSection": "Address & billing",
"notesSection": "Internal notes",
"eventsSection": "Assigned events",
"noEvents": "Not assigned to any events yet. Add this customer to an event from the event form.",
"email": "Email",
"preferredLanguage": "Preferred language",
"preferredLanguageHint": "Drives portal UI, quote/invoice PDFs, and billing emails (reminders/dunning). New customers default to the business-profile language ({{lang}}); override here per customer.",
"salutation": "Salutation",
"salutationNone": "—",
"firstName": "First name",
"lastName": "Last name",
"displayName": "Display name",
"phone": "Phone",
"company": "Company",
"billingEmail": "Billing email",
"vatId": "VAT / tax ID",
"addressLine1": "Address line 1",
"addressLine2": "Address line 2",
"postalCode": "Postal code",
"city": "City",
"state": "State / region",
"countryCode": "Country (ISO 2)",
"country": "Country",
"countryName": "Country (full name)",
"notesHint": "Visible only to admins. Never shown to the customer.",
"featuresSection": "Customer features",
"featuresHint": "Per-customer overrides for the customer-surface tabs. The global toggles in Settings → Customer Surface are the master switch — when global is OFF nobody sees the tab, regardless of what you set here. Defaults are ON, so flip a switch OFF to hide a tab for this specific customer.",
"passwordSection": "Account actions",
"passwordHint": "Sends a 7-day single-use reset link to the customer's email. The customer's current password keeps working until they click the link and set a new one.",
"passwordReset": {
"button": "Send password reset email",
"success": "Password reset email sent",
"error": "Could not send password reset",
"inactive": "Reactivate the customer before sending a reset."
},
"quotesSection": "Quotes",
"noQuotes": "No quotes for this customer yet.",
"contractsSection": "Contracts",
"noContracts": "No contracts for this customer yet.",
"billsSection": "Invoices",
"noBills": "No invoices for this customer yet.",
"manageEvents": "Manage galleries"
},
"billing": {
"section": "Billing cadence",
"hint": "Per-event (default): every invoice is sent on its own schedule. Monthly: all invoices issued in the period accumulate into one bill that fires on the configured day. Manual: items accumulate the same way, but the bill ships only when you trigger it.",
"cadence": "Billing cadence",
"perEvent": "Per event",
"monthly": "Monthly",
"quarterly": "Quarterly",
"manual": "Manual (trigger only)",
"cycleDay": "Cycle day",
"cycleDayHint": "1..28 = day of month. Use negative -1..-15 for \"N days before month end\" (so -3 fires on the 28th of a 31-day month).",
"skontoDisabled": "No Skonto for this customer",
"skontoDisabledHint": "Disables the early-payment discount on all of this customers invoices, regardless of template or global defaults.",
"triggerNow": "Trigger invoice now",
"triggerConfirm": "Issue this customer's monthly bill now? The customer receives the email immediately.",
"triggerConfirmManual": "Issue this customer's accumulated bill now? The customer receives the email immediately.",
"triggerHint": "Bypasses the cadence day and issues the running draft immediately. Refuses when nothing has been queued for the current period.",
"triggerHintManual": "Issues the running draft immediately. Manual-cadence drafts never ship automatically — this is the only way to send them. Refuses when nothing has been queued.",
"triggered": "Monthly bill issued: {{number}}",
"triggerError": "Could not trigger the monthly bill.",
"draftPreview": {
"title": "Pending in this month's bill",
"titleManual": "Pending — ships on manual trigger",
"periodRange": "{{number}} · {{from}} {{to}}"
}
},
"reactivate": {
"button": "Reactivate",
"success": "Customer reactivated",
"error": "Could not reactivate customer"
},
"erase": {
"button": "Erase customer data",
"title": "Erase customer data?",
"body": "Removes the customer's name, email, phone, address, company and credentials. The account row stays so historical event-access records and audit logs still reference it. This is irreversible — you cannot restore the data afterwards.",
"confirm": "Erase permanently",
"confirmInFlight": "Erasing…",
"success": "Customer erased",
"error": "Could not erase customer"
},
"assignedEvents": {
"title": "Manage assigned galleries",
"subtitle": "Pick every gallery this customer should be able to access from their dashboard. Removing a gallery here revokes access immediately on the customer's next request.",
"currentLabel": "Assigned galleries",
"empty": "No galleries assigned yet. Search below to add one.",
"searchLabel": "Add a gallery",
"searchPlaceholder": "Search by event name",
"searchHint": "Start typing to find galleries.",
"noResults": "No matching galleries.",
"removeAria": "Remove {{name}}",
"save": "Save assignments",
"saved": "Assignments updated",
"savedDiff": "Assignments updated: {{parts}}",
"addedN": "{{count}} added",
"removedN": "{{count}} removed",
"error": "Could not update assignments",
"clearSearchAria": "Clear search"
}
},
"clients": {
"title": "CRM",
"subtitle": "Customer accounts, scheduling, quotes and billing — everything for recurring clients in one place.",
"navAriaLabel": "CRM navigation",
"empty": {
"title": "No CRM features enabled",
"body": "Enable Accounts (or another CRM sub-feature) under Settings → Features to get started."
},
"subnav": {
"overview": "Overview",
"accounts": "Accounts",
"quotes": "Quotes",
"contracts": "Contracts",
"hours": "Hours",
"calendar": "Calendar",
"bills": "Invoices",
"taxReport": "Tax",
"development": "Development"
}
},
"accounting": {
"title": "Accounting",
"subtitle": "Inbound supplier invoices, expenses and reporting.",
"navAriaLabel": "Accounting navigation",
"empty": {
"title": "No accounting features enabled",
"body": "Enable the Tax report (or another accounting sub-feature) under Settings → Features to get started."
},
"subnav": {
"incomingInvoices": "Incoming invoices",
"expenses": "Expenses",
"taxReport": "Tax",
"export": "Treuhänder export",
"chartOfAccounts": "Chart of accounts"
},
"taxTreatment": {
"domestic": "Domestic (with VAT)",
"reverse_charge_service": "Reverse charge (services)",
"foreign_vat_non_reclaimable": "Foreign VAT (non-reclaimable)",
"import_goods": "Import of goods"
},
"disposition": {
"rebill": "Re-bill to client",
"durchlaufend": "Pass-through",
"eigener_aufwand": "Company expense",
"duplikat": "Duplicate",
"abgelehnt": "Declined",
"help": {
"rebill": "Your own supplier cost that you invoice on to a client — usually with a markup. Booked as both a cost and re-billed revenue.",
"durchlaufend": "An amount you only front on behalf of a client and pass through at the exact figure — no markup, VAT-neutral (durchlaufender Posten). Attach the client to re-bill it at cost.",
"eigener_aufwand": "Your own cost, not re-billed to anyone. Pick a category so it lands in the right place in your P&L.",
"duplikat": "A duplicate of an invoice you already captured — excluded from the books.",
"abgelehnt": "Decline this document — excluded from the books."
}
},
"markup": {
"none": "None / from contract",
"percent": "Percent",
"flat": "Flat"
},
"inbox": {
"captureTitle": "Capture a supplier invoice",
"captureBody": "Photograph a paper invoice with your device camera, or upload a PDF / image.",
"scanCamera": "Scan with camera",
"uploadFile": "Upload file",
"capturedToast": "Document captured.",
"categorizedToast": "Document categorized.",
"triageTitle": "Categorize document",
"saveCategorize": "Save",
"saveCategorizePay": "Save & mark paid",
"categorize": "Categorize",
"recategorize": "Re-categorize",
"view": "View",
"empty": "No documents yet — capture one above.",
"untitled": "Untitled document",
"noAmount": "amount not entered",
"rebillHint": "Creates an editable scheduled invoice on the client. VAT/tax handling is v1 — verify with your Treuhänder.",
"previewLoading": "Loading preview…",
"previewError": "Preview unavailable — enter the fields manually.",
"qrHint": "Showing the last page — the Swiss QR-bill usually sits at the bottom.",
"prevPage": "Prev",
"nextPage": "Next",
"pageOf": "Page {{n}} / {{total}}",
"status": {
"label": "Status",
"unsorted": "New",
"categorized": "Categorized",
"declined": "Declined",
"duplicate": "Duplicate"
},
"field": {
"supplier": "Supplier",
"total": "Total",
"currency": "Currency",
"invoiceDate": "Invoice date",
"disposition": "Disposition",
"category": "Category",
"categoryNone": "— none —",
"declineReason": "Reason",
"customer": "Client",
"eventId": "Event ID (optional)",
"markup": "Markup",
"reference": "Payment reference",
"referenceHint": "QR / ESR reference or message",
"note": "Note",
"noteHint": "Internal note for this invoice (optional)",
"passthroughCustomerHint": "Optional — attach a client to re-bill this passthrough; leave empty to only book it to the event.",
"supplierCountry": "Supplier country",
"supplierCountryNone": "— unknown —",
"supplierCountryHint": "Sets the tax treatment automatically: outside your VAT-reclaim countries → foreign VAT (not reclaimable)."
}
},
"expenseStatus": {
"open": "Open",
"parked": "Parked",
"billed": "Billed",
"declined": "Declined"
},
"paymentMethod": {
"bank_transfer": "Bank transfer",
"cash": "Cash",
"twint": "TWINT",
"paypal": "PayPal",
"card": "Card",
"other": "Other"
},
"booking": {
"label": "Book to",
"company": "Company",
"event": "Event",
"eventId": "Event ID",
"inboundHint": "Which event carries this cost in your reports & tax export (Company = general overhead). This is separate from who you re-bill it to."
},
"incoming": {
"triageTitle": "Categorize incoming invoice",
"payTitle": "Mark supplier paid",
"outstanding": "Outstanding",
"markPaid": "Mark paid",
"markUnpaid": "Mark unpaid",
"confirmPaid": "Mark paid",
"paid": "Paid",
"paidToast": "Marked as paid.",
"categorizedToast": "Categorized.",
"categorizedPaidToast": "Categorized and marked paid.",
"pendingRebill": "Pending re-bill",
"pendingTitle": "Pending re-bills",
"pendingBody": "Categorized invoices waiting to be re-billed. Bundle a clients items into one invoice.",
"pendingCount": "{{count}} item",
"pendingCount_other": "{{count}} items",
"billPending": "Bill these",
"bundledToast": "Bundled {{count}} re-bill into one invoice.",
"bundledToast_other": "Bundled {{count}} re-bills into one invoice."
},
"expense": {
"kind": "Type",
"km": "Kilometres",
"days": "Days",
"rate": "Rate",
"rateDefault": "Default {{rate}} — leave blank to use it",
"computed": "Amount",
"who": "Paid by / vendor",
"whoHint": "e.g. coworker name or shop",
"proof": "Proof",
"proofRequired": "A proof file is required.",
"proofExisting": "A proof file is already attached — upload a new one to replace it.",
"viewProof": "Proof"
},
"expenseKind": {
"amount": "Amount",
"mileage": "Mileage (km)",
"per_diem": "Daily allowance"
},
"category": {
"infrastructure": "Infrastructure & rent",
"equipment": "Equipment & hardware",
"software": "Software & licenses",
"material": "Materials & supplies",
"travel": "Travel & expenses",
"marketing": "Advertising & marketing",
"services": "Services / subcontracting",
"insurance": "Insurance & fees",
"training": "Training",
"other": "Other"
},
"ledger": {
"allStatuses": "All statuses",
"allDispositions": "All dispositions",
"allKinds": "All types",
"empty": "No expenses yet — add one above.",
"untitled": "Expense",
"invoiceLink": "Invoice",
"paid": "Paid",
"markPaid": "Mark paid",
"markPaidTitle": "Mark supplier paid",
"paidDate": "Payment date",
"method": "Method",
"reference": "Reference (optional)",
"confirmPaid": "Mark paid",
"paidToast": "Marked as paid.",
"unpaidToast": "Marked as not paid.",
"addExpense": "Add expense",
"addTitle": "Add expense",
"editTitle": "Edit expense",
"updatedToast": "Expense updated.",
"payTitle": "Mark expense paid",
"invoiced": "Invoiced",
"invoicedToast": "Added to a client invoice.",
"invoiceTitle": "Add to client invoice",
"invoiceHint": "This creates a billable line on the clients next scheduled invoice and locks the expense from further edits.",
"addToInvoice": "Add to invoice",
"locked": "Locked",
"lockedHint": "Locked — this expense is on a client invoice.",
"description": "Description",
"descriptionHint": "e.g. mileage, per-diem, cash receipt",
"createdToast": "Expense added."
}
},
"projects": {
"title": "Project Overview",
"subtitle": "Group events into projects and see every email, document, gallery and hour in one cockpit.",
"search": "Search by name or customer…",
"empty": "No projects yet. Create one above, or events you already have were grouped automatically.",
"notFound": "Project not found",
"backToList": "All projects",
"noCustomer": "No single customer",
"rename": "Rename",
"timeline": "Milestones",
"eventCount": "{{count}} events",
"totalHours": "{{hours}} logged",
"create": {
"label": "New project name",
"placeholder": "e.g. Müller wedding 2026",
"button": "Create project"
},
"col": {
"name": "Project",
"customer": "Customer",
"events": "Events",
"value": "Value",
"status": "Status",
"updated": "Updated"
},
"picker": {
"label": "Project",
"none": "No project"
},
"value": {
"label": "Project value",
"paid": "paid"
},
"events": {
"title": "Events",
"none": "No events grouped under this project yet.",
"searchPlaceholder": "Attach an event — search by name…",
"attached": "Event attached",
"attachFailed": "Could not attach event"
},
"feed": {
"title": "Activity",
"empty": "Nothing rolled up to this project yet.",
"email": "Email",
"quote": "Quote",
"contract": "Contract",
"invoice": "Invoice",
"gallery": "Gallery",
"hours": "Hours"
},
"email": {
"preview": "Preview",
"resend": "Resend",
"sendNow": "Send now",
"cancel": "Cancel",
"retry": "Retry",
"previewTitle": "Email preview",
"noPreview": "No stored preview for this email — it was sent before previews were captured.",
"reRendered": "Re-rendered from the current template — this email was sent before previews were captured, so it may differ slightly from what the recipient received.",
"reRenderedTag": "≈ re-rendered"
},
"toast": {
"created": "Project created",
"createFailed": "Could not create project",
"saved": "Project saved",
"saveFailed": "Save failed",
"emailAction": "Done",
"emailActionFailed": "Action failed",
"previewFailed": "Could not load preview"
},
"error": {
"customerMismatch": "That belongs to a different customer than this project."
}
},
"calendar": {
"pageTitle": "Calendar",
"subtitle": "Events, logged hours, and pending quotes/contracts in one view.",
"viewMonth": "Month",
"viewWeek": "Week",
"loading": "Loading items…",
"legend": {
"events": "Events",
"hours": "Hours",
"pendingQuotes": "Pending quotes",
"pendingContracts": "Pending contracts",
"hoursLocked": "Locked (billed)"
},
"hourEntry": {
"createTitle": "Log hours",
"customerLabel": "Customer",
"customerSearch": "Search by email or company…",
"descriptionLabel": "Description (optional)",
"descriptionPlaceholder": "Editing / shoot / travel…",
"startLabel": "Start",
"endLabel": "End",
"submit": "Save hours",
"saving": "Saving…",
"cancel": "Cancel",
"close": "Close",
"delete": "Delete",
"confirmDelete": "Delete these logged hours? This cannot be undone.",
"created": "Hours logged.",
"createFailed": "Couldn't save: {{message}}",
"saved": "Hours updated.",
"saveFailed": "Couldn't save: {{message}}",
"movedToast": "Hours updated.",
"moveFailed": "Couldn't move: {{message}}",
"resizedToast": "Hours updated.",
"resizeFailed": "Couldn't resize: {{message}}",
"deleted": "Hours deleted.",
"deleteFailed": "Couldn't delete: {{message}}",
"lockedBadge": "Locked",
"featureOffToast": "Hour logging is disabled.",
"lockedTooltip": "Already billed — Storno the invoice to edit.",
"noDescription": "No description.",
"untitledCustomer": "Hours"
}
},
"hoursLogging": {
"title": "Hours logging",
"subtitle": "Pick a customer and log billable time blocks. Entries flow into the next monthly bill or are billed on demand for per-event customers.",
"pickCustomer": "Customer",
"pickPlaceholder": "— Select customer —",
"emptyList": "No customers have hours logging enabled yet. Flip \"Hours logging\" on a customer's detail page first.",
"searchPlaceholder": "Search by email or company…",
"customerLoggingDisabled": "This customer has hour logging disabled. Enable it on the customer's detail page to log hours.",
"openHours": {
"title": "Open hours across all customers",
"subtitle": "Unbilled time blocks waiting to be billed. Pick a customer above, or click a row to drill in.",
"empty": "No unbilled hours right now — everything is billed or no time has been logged yet.",
"entryLine_one": "{{count}} entry · {{hours}}h",
"entryLine_other": "{{count}} entries · {{hours}}h",
"passive": "Passive",
"needsRate": "Rate not set"
}
},
"crmDev": {
"title": "CRM Development",
"subtitle": "Internal tools for verifying CRM flows. Hidden by default — enabled via Settings → Features → Development.",
"warning": "These tools fire real side effects (emails, status changes). Use against test data.",
"envDisabled": {
"title": "Dev tools blocked by env gate",
"body": "Set PICPEAK_ENABLE_DEV_TOOLS=1 in your backend env file and restart the server. The feature flag alone is not enough — this is a deliberate production-safety guard.",
"cardHint": "Email templates can't be listed until the dev-tools env gate is opened."
},
"paymentCheck": {
"title": "Test payment-check email (real invoice)",
"help": "Fires the admin payment-check email for a real sent/overdue invoice, bypassing the 24h throttle. The three buttons in the email are real signed tokens — clicking them will affect the invoice status.",
"selectInvoice": "Sent or overdue invoice",
"selectPlaceholder": "— Pick an invoice —",
"noneAvailable": "No sent or overdue invoices in the database.",
"send": "Send test email",
"sending": "Queuing…",
"sentToast": "Test email queued. Check the recipient's inbox."
},
"templates": {
"title": "Send any CRM email to me (mock data)",
"help": "Queues the chosen template to your own admin email with placeholder values. When the install has a real quote / invoice on file, the appropriate PDF is attached so you can verify the full output.",
"notSeeded": "Not seeded",
"send": "Send to me",
"sending": "Queuing…",
"sentToast": "Queued to {{to}}.",
"empty": "No CRM email templates found — run migrations to seed them.",
"label": {
"quote_sent": {
"title": "Quote sent (to customer)",
"description": "Fires when a quote is sent — includes the PDF attachment + accept/decline links."
},
"quote_accepted_customer": {
"title": "Quote accepted — customer confirmation",
"description": "Sent to the customer after they (or the admin on their behalf) accept the quote. Includes PDF."
},
"quote_accepted_admin": {
"title": "Quote accepted — admin notification",
"description": "Notifies the admin that a quote was accepted by the customer."
},
"quote_declined_admin": {
"title": "Quote declined — admin notification",
"description": "Notifies the admin that a quote was declined by the customer."
},
"invoice_sent": {
"title": "Invoice sent (to customer)",
"description": "Fires when an invoice is sent — includes the PDF attachment."
},
"invoice_reminder_first": {
"title": "Invoice reminder — first",
"description": "First reminder to the customer. No late fee yet."
},
"invoice_reminder_second": {
"title": "Invoice reminder — second (with Mahngebühr)",
"description": "Second reminder with the late fee surcharge added."
},
"invoice_payment_check_admin": {
"title": "Payment-check (admin)",
"description": "Admin email with the three signed-token action buttons."
},
"contract_sent": {
"title": "Contract sent (to customer)",
"description": "Admin → customer send with the unsigned contract PDF attached."
},
"contract_signed_admin_notification": {
"title": "Contract signed — admin notification",
"description": "Customer-signed ping back to the admin. No attachment."
},
"contract_fully_signed": {
"title": "Contract fully signed (dual-party)",
"description": "Dual-party send once both signatures are in. Stamped contract PDF attached."
},
"event_reminder_default": {
"title": "Event reminder — default (catch-all)",
"description": "Pre-event nudge used when no per-category template matches."
},
"event_reminder_wedding": {
"title": "Event reminder — wedding",
"description": "Pre-event nudge for events with event_type=wedding."
},
"event_reminder_birthday": {
"title": "Event reminder — birthday",
"description": "Pre-event nudge for events with event_type=birthday."
},
"event_reminder_corporate": {
"title": "Event reminder — corporate",
"description": "Pre-event nudge for events with event_type=corporate."
},
"event_reminder_other": {
"title": "Event reminder — other",
"description": "Pre-event nudge for events with event_type=other."
}
}
}
},
"ledger": {
"intro": "Used only to produce the Treuhänder export — picpeak does not keep double-entry books. The seeded chart + VAT codes follow the Swiss/LI KMU-Kontenrahmen; adjust them to match your Treuhänders setup.",
"settingsSaved": "Mappings saved.",
"saveDefaults": "Save mappings",
"accountType": {
"asset": "Asset",
"liability": "Liability",
"equity": "Equity",
"revenue": "Revenue",
"expense": "Expense"
},
"vatDirection": {
"input": "Input (Vorsteuer)",
"output": "Output (Umsatzsteuer)"
},
"defaults": {
"title": "Default & system accounts",
"none": "— none —",
"ledger_account_debitoren": "Accounts receivable (Debitoren)",
"ledger_account_kreditoren": "Accounts payable (Kreditoren)",
"ledger_account_bank": "Bank",
"ledger_account_cash": "Cash",
"ledger_account_default_revenue": "Default revenue account",
"ledger_account_default_expense": "Default expense account",
"ledger_account_mileage": "Mileage account",
"ledger_account_per_diem": "Per-diem account",
"ledger_account_rebilled_revenue": "Re-billed expenses (revenue)"
},
"vatMap": {
"title": "VAT code by tax treatment (costs)"
},
"outputVatMap": {
"title": "VAT code by revenue rate",
"empty": "Add output VAT codes above to configure a code per revenue rate."
},
"categoryMap": {
"title": "Expense category → account"
},
"accounts": {
"title": "Chart of accounts"
},
"vatCodes": {
"title": "VAT codes"
},
"account": {
"addTitle": "Add account",
"editTitle": "Edit account",
"number": "Account number",
"name": "Name",
"type": "Type",
"confirmDelete": "Delete this account?"
},
"vat": {
"addTitle": "Add VAT code",
"editTitle": "Edit VAT code",
"code": "Code",
"name": "Name",
"rate": "Rate %",
"direction": "Direction",
"account": "VAT account",
"noAccount": "— none —",
"confirmDelete": "Delete this VAT code?",
"legacyRate": "{{rate}}% (not configured)"
},
"export": {
"title": "Treuhänder export",
"intro": "Download the collective journal (revenue + costs as accrual postings with account and VAT codes) for import into your Treuhänders accounting software.",
"format": "Target tool",
"format_generic": "Generic (all columns)",
"format_banana": "Banana — double-entry",
"format_banana_ie": "Banana — income & expense",
"format_bexio": "bexio",
"download": "Download CSV",
"failed": "Export failed.",
"disclaimer": "Accrual basis only (document dates) — payments/bank movements are not included. Account + VAT codes follow your Chart-of-accounts mapping. Always review the import with your Treuhänder before filing."
}
},
"taxReport": {
"title": "Tax report",
"intro": "Period-scoped revenue list with net + VAT breakdown grouped by VAT rate. Cancelled invoices stay visible for audit-trail continuity but are excluded from totals.",
"filters": {
"period": "Period",
"thisYear": "This year",
"lastYear": "Last year",
"thisQuarter": "This quarter",
"lastQuarter": "Last quarter",
"custom": "Custom range",
"from": "From",
"to": "To",
"currency": "Currency"
},
"exportPdf": "Export PDF",
"ledgerExport": "Accountant export",
"ledgerExportHint": "Bookkeeping entries for your accountant, mapped via your Chart of accounts.",
"ledgerExportConfigure": "Configure →",
"export": {
"reportTitle": "Report",
"reportHint": "Readable list — for your own records.",
"scopeLabel": "Scope",
"scopeAll": "Complete",
"scopeIncome": "Income only",
"scopeCost": "Cost only",
"journalTitle": "Accounting journal"
},
"exportCsv": "Export CSV",
"exportFailed": "Export failed. Please try again.",
"errorTitle": "Could not load tax report",
"empty": "No invoices in this period.",
"statusCancelled": "Cancelled",
"totalsByVatRate": "Totals by VAT rate",
"grandTotalNet": "Total net",
"grandTotalVat": "Total VAT",
"grandTotalGross": "Total gross",
"cancelledFootnote": "{{count}} cancelled invoice(s) — amounts excluded from totals (shown for audit-trail continuity).",
"skontoTooltip": "Paid with Skonto",
"costsTitle": "Costs (incoming invoices + expenses)",
"costsDisclaimer": "This income/expense overview is a guideline for your records (Einnahmen-Ausgaben-Rechnung). VAT reclaimability and the result figure depend on each costs tax treatment — verify with your Treuhänder / tax authority before filing.",
"summary": {
"outgoingTitle": "Outgoing invoices",
"title": "Income / costs",
"income": "Income",
"costs": "Costs",
"result": "Result",
"vatPayable": "VAT payable (output input)",
"vatUnconfigured": "VAT registration isnt configured, so VAT payable cant be computed. Set it under Settings → Accounting."
},
"cost": {
"source": "Type",
"sourceIncoming": "Incoming",
"sourceExpense": "Expense",
"supplier": "Supplier / description",
"taxTreatment": "Tax treatment",
"company": "Company",
"total": "Total costs"
},
"type": {
"outgoing": "Outgoing invoice",
"incoming": "Incoming invoice",
"expense": "Expense"
},
"col": {
"date": "Date",
"invoice": "Invoice",
"customer": "Customer",
"event": "Event",
"vatRate": "VAT %",
"net": "Net",
"vat": "VAT",
"total": "Gross",
"skonto": "Skonto",
"type": "Type",
"reference": "Reference",
"party": "Customer / supplier",
"tax": "Tax"
}
},
"quotes": {
"errors": {
"popupBlocked": "Allow pop-ups for this site to preview the PDF.",
"customerRequired": "Pick a customer first.",
"customerFeatureDisabled": "This customer has Quotes disabled. Enable Quotes on the customer detail page first."
},
"confirmSend": "Send this quote to the customer now?",
"sentToast": "Quote sent to customer.",
"confirmConvert": "Convert this accepted quote into an event + scheduled invoices?",
"convertedToast": "Quote converted to event #{{id}}",
"send": "Send",
"resend": "Resend",
"convert": "Convert to event",
"declineOnBehalf": "Decline on behalf",
"declineReasonPrompt": "Mark this quote as declined on behalf of the customer? Optionally note why (leave blank to skip).",
"declinedOnBehalfToast": "Quote marked as declined.",
"field": {
"issueDate": "Issued",
"validUntil": "Valid until",
"eventName": "Event",
"eventDate": "Event date",
"sentAt": "Sent at",
"acceptedAt": "Accepted at",
"declinedAt": "Declined at",
"declineReason": "Decline reason",
"responseWindow": "Response window",
"eventTimeStart": "Start time",
"eventTimeEnd": "End time",
"expectedDuration": "Expected duration (h)",
"vatRate": "VAT rate %",
"shipping": "Shipping amount",
"currency": "Currency",
"selectPaymentTerm": "— Select payment terms —",
"paymentNetDays": "Net days",
"paymentTiming": "Payment schedule",
"selectNetDays": "— Select net days —",
"selectTiming": "— Select schedule —",
"introText": "Intro text",
"outroText": "Outro text",
"ccPdfEmail": "CC PDF to (extra recipient)",
"ccPdfEmailPlaceholder": "[email protected]",
"ccPdfPickFromAdmins": "Pick from admins:",
"ccPdfCustom": "Custom email",
"internalNotes": "Internal notes (not on PDF)"
},
"responseWindowOpen": "Open until {{at}}",
"section": {
"lineItems": "Line items",
"internalNotes": "Internal notes",
"customer": "Customer",
"event": "Event details",
"payment": "Payment conditions",
"extras": "Intro / outro / extras"
},
"savedToast": "Quote saved as draft.",
"edit": "Edit quote",
"new": "New quote",
"preview": "Preview PDF",
"saveAndSend": "Save & send",
"customerSearch": "Search customer by email or company…",
"title": "Quotes",
"subtitle": "Send, track and convert quotes into events.",
"searchPlaceholder": "Search by number, customer, event…",
"sort": {
"newest": "Newest first",
"oldest": "Oldest first",
"customerAsc": "Customer A→Z",
"valueAsc": "Value low→high",
"valueDesc": "Value high→low"
},
"empty": "No quotes yet.",
"table": {
"customer": "Customer",
"event": "Event",
"issueDate": "Issued",
"total": "Total",
"status": "Status"
},
"pagination": "Page {{page}} of {{total}} · {{count}} quotes",
"status": {
"draft": "Draft",
"sent": "Sent",
"accepted": "Accepted",
"declined": "Declined",
"expired": "Expired",
"converted": "Converted"
},
"trigger": {
"quote_accepted": "on acceptance",
"before_event": "before event",
"after_event": "after event",
"after_delivery": "after delivery",
"fixed_date": "on fixed date"
}
},
"crm": {
"lineItems": {
"quantity": "Qty",
"description": "Description",
"unitPrice": "Unit",
"total": "Total",
"subtotal": "Subtotal",
"vat": "VAT",
"rounding": "Rounding",
"position": "Pos.",
"discount": "Rabatt %",
"descriptionPlaceholder": "Description (multi-line OK)",
"saveAsPreset": "Save as preset",
"empty": "No line items yet — add one to get started.",
"addRow": "Add row",
"addFromPreset": "Add from preset…",
"shipping": "Shipping",
"addSubItem": "Add sub-item",
"detailsAdd": "+ Add details / notes",
"detailsFilled": "Details",
"detailsPlaceholder": "Optional notes — fine print, package inclusions, conditions…",
"autoTotaledHint": "Total auto-computed from sub-items below",
"autoTotaledNote": "= sum of sub-items"
}
},
"bills": {
"title": "Invoices",
"subtitle": "Schedule, send, track payments and chase late invoices.",
"new": "New invoice",
"searchPlaceholder": "Search by number or customer…",
"sort": {
"newest": "Newest first",
"dueAsc": "Due soon first",
"dueDesc": "Due latest first",
"customerAsc": "Customer A→Z",
"valueAsc": "Value low→high",
"valueDesc": "Value high→low"
},
"filter": {
"unpaidOnly": "Unpaid only"
},
"empty": "No invoices yet.",
"table": {
"customer": "Customer",
"event": "Event",
"installment": "Installment",
"issueDate": "Issued",
"dueDate": "Due",
"total": "Total",
"status": "Status"
},
"errors": {
"popupBlocked": "Allow pop-ups for this site to preview the PDF.",
"customerRequired": "Pick a customer first."
},
"confirmSend": "Send invoice to customer now?",
"sentToast": "Invoice sent.",
"confirmRelease": "Mark the photos as delivered and send this invoice to the customer now?",
"releaseForDelivery": "Mark delivered & send",
"releasedToast": "Delivery invoice sent.",
"monthlyDraftBadge": "Monthly draft — ships via the customer's cadence day or \"Trigger invoice now\"",
"confirmReminder": "Send a reminder now?",
"reminderToast": "Reminder sent.",
"confirmCancel": "Cancel this invoice?",
"confirmCancelDraft": "Cancel this draft invoice? No document goes out.",
"confirmCancelIssued": "A Stornorechnung will be generated and emailed to the customer immediately. This cannot be undone. Continue?",
"cancelledToast": "Invoice cancelled.",
"cancelledWithStornoToast": "Invoice cancelled — Stornorechnung issued to the customer.",
"confirmReissue": "A Stornorechnung will be issued to the customer for this invoice, and a new scheduled draft will be created with the same line items. The new invoice will reference this one as \"Replaces R-XXXX\". Continue?",
"confirmReissueCancelled": "Create a new scheduled invoice with the same line items, linked back to this cancelled one?",
"reissuedToast": "Invoice reissued — opening the new draft.",
"reissuedWithStornoToast": "Stornorechnung issued — opening the new draft.",
"reissue": "Reissue",
"cancelAndReissue": "Cancel & reissue",
"kind": {
"storno": "Stornorechnung",
"reissue": "Reissue"
},
"stornoCancelsLabel": "This Stornorechnung cancels invoice",
"cancelledByStornoLabel": "This invoice was cancelled by Stornorechnung",
"paymentRecordedToast": "Payment recorded.",
"sendNow": "Send now",
"resend": "Resend",
"markPaid": "Mark paid",
"sendReminder": "Send reminder",
"field": {
"issueDate": "Issued",
"dueDate": "Due",
"dueDateOverrideOn": "Manual due date — untick to auto-set from send date + payment term",
"dueDateOverrideOff": "Auto from send date + payment term — tick to set manually",
"scheduledSendAt": "Scheduled send",
"installment": "Installment",
"total": "Total",
"paid": "Paid",
"outstanding": "Outstanding",
"lateFee": "Late fee",
"qrFormat": "Payment QR format",
"vatRate": "VAT rate %",
"shipping": "Shipping",
"currency": "Currency",
"ccPdfEmail": "CC PDF to (extra recipient)",
"sourceQuote": "From quote",
"businessBankAccount": "Payment account",
"bankAccountProfileDefault": "Use business profile default for {{currency}}",
"bankAccountDefaultBadge": "(default)",
"bankAccountHelp": "Overrides the profile default for this invoice only. Leave on default to inherit the currency-matched account from Settings → Business profile.",
"paymentNetDays": "Net days",
"paymentTiming": "Payment schedule",
"selectNetDays": "— Select net days —",
"selectTiming": "— Select schedule —",
"eventName": "Event",
"eventDate": "Event date",
"eventNamePlaceholder": "e.g. Smith wedding 2024",
"eventTimeStart": "Start time",
"eventTimeEnd": "End time",
"customer": "Customer",
"invoiceNumber": "Invoice number",
"paidAmount": "Paid amount (optional)",
"pdfFile": "PDF file",
"ccPdfCustom": "Custom email",
"ccPdfEmailPlaceholder": "[email protected]",
"ccPdfPickFromAdmins": "Pick from admins:",
"paymentTermHelp": "Net days + Skonto for this invoice. Leave blank to inherit from the source quote or the global CRM defaults.",
"skontoDisabled": "Disable Skonto for this invoice (suppresses the early-payment-discount block on the PDF and the \"Paid with Skonto\" buttons in the admin email / record-payment dialog).",
"sourceContract": "From contract"
},
"import": "Import historical",
"importTitle": "Import historical invoice",
"importHelp": "Attach a PDF from a previous billing system. The customer sees this original document in their portal — picpeak does not regenerate it.",
"importedToast": "Invoice imported.",
"importError": "Import failed",
"importing": "Importing…",
"noMatch": "No matches",
"section": {
"lineItems": "Line items",
"paymentLog": "Payment log",
"customer": "Customer",
"event": "Event details",
"details": "Details",
"payment": "Payment conditions"
},
"noPayments": "No payments recorded yet.",
"payment": {
"paidAt": "Date",
"amount": "Amount",
"date": "Payment date",
"method": "Method",
"reference": "Reference",
"notes": "Notes",
"methodPlaceholder": "Select method…",
"methods": {
"bankTransfer": "Bank transfer",
"card": "Card",
"cash": "Cash",
"paypal": "PayPal",
"twint": "TWINT"
},
"withSkonto": "Paid with Skonto ({{percent}}% discount)"
},
"recordPayment": "Record payment",
"savedToast": "Invoice saved.",
"savedToastMulti": "Created {{n}} invoices.",
"saveAndSpawn": "Create {{n}} invoices",
"edit": "Edit invoice",
"customerSearch": "Search by email or company…",
"qrFormat": {
"none": "None",
"swiss": "Swiss QR-bill",
"epc": "EPC QR (SEPA)",
"profileDefault": "Use business profile default"
},
"status": {
"scheduled": "Scheduled",
"draft": "Draft",
"pending_delivery": "Awaiting delivery",
"sent": "Sent",
"paid": "Paid",
"overdue": "Overdue",
"cancelled": "Cancelled",
"skipped": "Skipped (empty month)"
}
},
"businessProfile": {
"savedToast": "Business profile saved.",
"movedToAccounting": "The VAT rate, VAT label and default hourly rate now live under Settings → Accounting.",
"title": "Business profile",
"subtitle": "Issuer block shown on every quote and invoice PDF.",
"businessHours": {
"title": "Business hours",
"subtitle": "Set opening hours per weekday — add a second block for a lunch break. Interpreted in the timezone above ({{tz}}).",
"closed": "Closed",
"addHours": "Add hours",
"addBlock": "Add another block",
"copyToAll": "Copy to all days",
"startTime": "Opening time",
"endTime": "Closing time",
"floorToggle": "Hold scheduled emails until business hours",
"floorToggleHelp": "When on, an automated email scheduled outside the hours above is delivered at the next opening instead of at an odd hour. When off, scheduled emails send at their exact time.",
"weekday": {
"1": "Monday",
"2": "Tuesday",
"3": "Wednesday",
"4": "Thursday",
"5": "Friday",
"6": "Saturday",
"7": "Sunday"
}
},
"section": {
"company": "Company",
"contact": "Contact",
"defaults": "Defaults",
"banks": "Bank accounts"
},
"field": {
"companyName": "Company name",
"vatId": "VAT ID (USt-IdNr.)",
"taxId": "Tax number (Steuernummer)",
"addressLine1": "Address line 1",
"addressLine2": "Address line 2",
"postalCode": "Postal code",
"city": "City",
"state": "State / Region",
"countryCode": "Country (ISO 3166)",
"phone": "Phone",
"mobile": "Mobile",
"email": "Email",
"website": "Website",
"defaultCurrency": "Default currency",
"defaultLocale": "Default locale",
"timezone": "Timezone (IANA)",
"defaultQrFormat": "Default invoice QR",
"footerLine": "PDF footer line"
},
"qrFormat": {
"none": "None",
"swiss": "Swiss QR-bill (CH / LI)",
"epc": "EPC QR (SEPA / EUR)"
},
"bankCreatedToast": "Bank account added.",
"bankDeletedToast": "Bank account removed.",
"addBank": "Add account",
"bank": {
"label": "Label",
"accountHolder": "Account holder",
"currency": "Currency",
"isDefault": "Default for this currency",
"makeDefault": "Make default",
"confirmDelete": "Remove this bank account?"
},
"noBanks": "No bank accounts configured yet."
},
"crmSettings": {
"savedToast": "CRM settings saved.",
"title": "CRM settings",
"subtitle": "Fine-tune quote and invoice behaviour.",
"emptyState": "No CRM features are currently enabled. Turn on Quotes, Invoices, or Contracts in Settings → Features to see the matching configuration here.",
"section": {
"quotes": "Quotes",
"invoices": "Invoices",
"paymentDefaults": "Default payment conditions",
"installmentDefaults": "Default installment triggers",
"contracts": "Contracts",
"quotesTos": "Terms of Service / AGB step",
"dashboardOverview": "Dashboard CRM overview",
"dashboardOverviewHint": "Hide CRM overview tiles on the admin dashboard. All tiles render by default; uncheck to hide."
},
"paymentDefaults": {
"help": "Pre-filled on every new quote and invoice. The editor still lets you pick a different combination per document.",
"none": "— No default —"
},
"installmentDefaults": {
"help": "Pre-fill the trigger for fresh rows in the Installments panel. Per-document edits override; existing documents keep their snapshotted plan.",
"trigger": {
"quote_accepted": "At signing / creation",
"before_event": "Before event",
"after_event": "After event",
"after_delivery": "On delivery (manual release)",
"fixed_date": "Fixed date"
}
},
"crm_invoices_default_payment_net_days_template_id": {
"label": "Default net days"
},
"crm_invoices_default_payment_timing_template_id": {
"label": "Default payment schedule"
},
"crm_quotes_accept_window_minutes": {
"label": "Accept window (minutes)"
},
"crm_quotes_default_valid_days": {
"label": "Default validity (days)"
},
"crm_quotes_number_format": {
"label": "Quote number format"
},
"crm_invoices_reminder_first_days": {
"label": "First reminder after (days past due)"
},
"crm_invoices_reminder_second_days": {
"label": "Second reminder after (days past due)"
},
"crm_invoices_late_fee_minor": {
"label": "Late fee (minor units / Rappen)"
},
"crm_invoices_late_fee_type": {
"label": "Late fee type"
},
"crm_invoices_late_fee_percent": {
"label": "Late fee (% of invoice)"
},
"lateFeeType": {
"flat": "Flat amount (Rappen)",
"percent": "Percentage of invoice"
},
"lateFeeAgb": {
"title": "Late fees must be itemised in your terms (AGB)",
"body": "A contractual duty: phrases like “late fees apply” aren't enough. Your terms must state the concrete fee (e.g. “CHF 20 from the 2nd reminder”). Verify with your Treuhänder."
},
"dunningMoved": {
"title": "Reminder schedule is now in Workflows",
"body": "When and how often overdue reminders go out is configured in the “Invoice dunning” workflow. The late-fee amounts below still apply.",
"link": "Open Workflows"
},
"crm_invoices_late_fee_label": {
"label": "Late fee label"
},
"crm_invoices_skonto_percent_default": {
"label": "Skonto rate (default %)"
},
"crm_invoices_skonto_business_days": {
"label": "Skonto window (business days)"
},
"crm_invoices_installment_trigger_first": {
"label": "First installment trigger"
},
"crm_invoices_installment_days_before_event": {
"label": "Days before event (middle installment)"
},
"crm_invoices_installment_days_after_event": {
"label": "Days after event (final installment)"
},
"crm_invoices_number_format": {
"label": "Invoice number format"
},
"crm_quotes_pdf_attachment_enabled": {
"label": "Attach quote PDF to email"
},
"crm_quotes_skonto_enabled": {
"label": "Allow early-payment discount (Skonto) on quotes"
},
"crm_invoices_qr_enabled": {
"label": "Render payment QR on invoice PDFs"
},
"crm_invoice_round_total": {
"label": "Reconcile sub-cent rounding to a clean total (adds a \"Rundung\" row when per-line rounding drifts from qty × rate)"
},
"crm_invoices_reminders_enabled": {
"label": "Send automatic reminders for overdue invoices"
},
"crm_invoices_late_fee_enabled": {
"label": "Add a late fee on every reminder after the first"
},
"crm_quotes_tos_required": {
"label": "Require customers to tick \"I accept the Terms of Service\" before accepting"
},
"crm_quotes_tos_url": {
"label": "Terms of Service URL (optional)"
},
"crm_quotes_tos_text": {
"label": "Inline Terms text shown on the quote page",
"placeholder": "Paste the contract terms here. Plain text. Leave empty to only show the checkbox + URL."
},
"crm_contracts_pdf_attachment_enabled": {
"label": "Attach contract PDF to email"
},
"crm_contracts_require_drawn_signature": {
"label": "Require drawn signature (typed name alone is not enough)"
},
"crm_contracts_allow_pdf_upload": {
"label": "Allow customer to upload a wet-signed PDF"
},
"crm_contracts_store_ip": {
"label": "Store signer's IP address (recommended — corroborating evidence in civil disputes)",
"help": "When off, the customer's and admin's IP at signing time is NOT recorded into the contract row or the public sign-page audit confirmation. Per GDPR data-minimisation principle some operators prefer this — but IP is corroborating identity evidence if the contract is challenged, so we recommend keeping it on."
},
"crm_contracts_default_valid_days": {
"label": "Signing window (days)"
},
"crm_contracts_number_format": {
"label": "Contract number format",
"help": "Supported tokens: {YEAR}, {MONTH}, {SEQ:04d}. Example: LBM-C-{YEAR}-{SEQ:04d} → LBM-C-2026-0001."
},
"crm_overview_show_revenue": {
"label": "Revenue tiles (30 / 90 / 365 days)"
},
"crm_overview_show_outstanding": {
"label": "Outstanding payments tile"
},
"crm_overview_show_quotes": {
"label": "Quotes pipeline (per-status)"
},
"crm_overview_show_invoices": {
"label": "Invoices pipeline (per-status)"
}
},
"quoteResponse": {
"notFound": "Quote not found",
"notFoundBody": "This link may have expired or been revoked. Please contact the photographer for a new quote.",
"locked": "Your response window has closed and the decision is now final.",
"title": "Quote",
"event": "Event",
"issueDate": "Issued",
"validUntil": "valid until",
"description": "Description",
"qty": "Qty",
"unit": "Unit",
"total": "Total",
"subtotal": "Subtotal",
"vat": "VAT",
"acceptedLocked": "You accepted this quote on {{date}}. The decision is final.",
"declinedLocked": "You declined this quote on {{date}}. The decision is final.",
"cannotRespond": "This quote can no longer be responded to via this link.",
"changeWithin": "You can change your response until {{at}}.",
"intro": "Please accept or decline this quote:",
"accept": "Accept quote",
"decline": "Decline"
},
"reminderTemplates": {
"title": "Pre-event reminder emails",
"scheduleMoved": {
"title": "The reminder schedule is now in Workflows",
"body": "Whether pre-event reminders are sent, and how many days before the event, is configured in the “Pre-event reminder” workflow. This page edits the email templates; per-event overrides stay on each events detail page.",
"link": "Open Workflows"
},
"globalSection": "Global behaviour",
"globalHelp": "Off by default — turn on to start sending pre-event reminders. The offset below is the default; each event can override on its detail page.",
"enableLabel": "Send pre-event reminder emails",
"daysBeforeLabel": "Days before the event",
"saveSettings": "Save global settings",
"settingsSaved": "Reminder settings saved.",
"settingsSaveError": "Could not save reminder settings.",
"sidebarHeading": "Templates",
"sectionTemplates": "Templates",
"defaultLabel": "Default (catch-all)",
"usesDefault": "Default",
"usesDefaultTooltip": "No dedicated template yet — this event type falls back to the default. Edit + save here to create one.",
"willCreateOnSave": "This event type uses the default template. The fields below are pre-filled from the default; saving will create a dedicated template for this event type.",
"variablesHint": "Available variables: {{customer_name}}, {{event_name}}, {{event_date}}, {{event_type}}, {{days_before}}, {{business_name}} — substituted when the email is rendered.",
"subjectLabel": "Subject",
"subjectPlaceholder": "Email subject",
"bodyLabel": "Body",
"noTranslation": "No translation yet",
"saveTemplate": "Save template",
"saved": "Template saved.",
"saveError": "Could not save template."
},
"eventReminderOverride": {
"title": "Pre-event reminder",
"save": "Save override",
"help": "Per-event override for the customer reminder. Global on-off + default offset live under Settings → Reminder emails. Anything left blank here inherits the global setting / resolved template.",
"disabledLabel": "Disable the reminder for this event (no email goes out)",
"offsetLabel": "Days before the event (override) — leave blank to inherit",
"offsetPlaceholder": "Leave blank to use the global default",
"bodyOverrideLabel": "Custom body for this event (overrides the resolved template body)",
"bodyOverridePlaceholder": "Leave blank to use the template body. Variables like {{customer_name}}, {{event_name}}, {{event_date}} still work here.",
"saved": "Reminder override saved.",
"saveError": "Could not save reminder override.",
"invalidOffset": "Offset must be a non-negative integer or blank."
},
"dealLineage": {
"title": "Related documents",
"quotes": "Quotes",
"contracts": "Contracts",
"invoices": "Invoices",
"loading": "Loading related documents…",
"error": "Could not load related documents.",
"empty": "No other documents share this deal yet. New invoices, contracts, or installments will show up here once created.",
"editPlan": "Edit plan",
"editPlanModalTitle": "Edit installment plan",
"editPlanHelp": "Atomically reshape this plan: change percents, labels, triggers, add or remove rows. The plan total stays fixed; existing invoice numbers are kept where possible. Refused once any invoice has shipped.",
"editPlanWarning": "Trimming rows deletes their invoice numbers (the sequence cannot release them — a §14 UStG continuity rule). Adding rows claims fresh numbers.",
"editPlanSave": "Save plan",
"editPlanSuccess": "Installment plan updated.",
"editPlanLocked": "Plan can no longer be edited — at least one invoice has shipped or been cancelled.",
"editPlanPercentSumError": "Percents must sum to 100.",
"editPlanGeneralError": "Could not update plan.",
"editPlanEmpty": "Plan cannot be empty."
},
"installments": {
"enableLabel": "Split into installments",
"advancedToggle": "Use dynamic triggers",
"simpleToggle": "Use simple date picker",
"simpleHint": "Each row fires on a specific date. Switch to dynamic triggers for plans tied to event date or delivery.",
"advancedHint": "Pick a trigger (quote accepted, before/after event, on delivery, fixed date) plus offset in days. Triggers re-resolve if the event date later shifts.",
"percent": "%",
"label": "Label",
"labelPlaceholder": "Anzahlung / before event …",
"sendOn": "Send on",
"trigger": "Trigger",
"offsetDays": "Offset (days)",
"onDeliveryHint": "On delivery — admin releases manually. Switch to advanced to change.",
"addRow": "Add installment",
"removeRow": "Remove row",
"total": "Total",
"mustSumTo100": "must sum to 100%",
"firstRowDefaultLabel": "Down payment",
"triggerOption": {
"quote_accepted": "At signing / creation",
"before_event": "Before event",
"after_event": "After event",
"after_delivery": "On delivery (manual release)",
"fixed_date": "Fixed date"
}
},
"contracts": {
"title": "Contracts",
"subtitle": "Compose contracts from reusable blocks and have customers sign in-browser or upload a wet-signed PDF.",
"sections": {
"basics": "Basics",
"scope": "Scope",
"privacy": "Privacy",
"commercial": "Commercial",
"nda": "Confidentiality",
"closing": "Closing provisions"
},
"status": {
"draft": "Draft",
"sent": "Sent",
"signed_by_customer": "Signed by customer",
"signed_by_admin": "Counter-signed",
"fully_signed": "Fully signed",
"cancelled": "Cancelled"
},
"list": {
"new": "New contract"
},
"detail": {
"previewPdf": "Preview PDF",
"integrity": {
"title": "PDF integrity check",
"help": "Re-hashes the unsigned + signed PDFs on disk and compares them to the SHA-256 stored when the document was issued. Catches backup corruption or manual edits since the customer received their copy.",
"verify": "Verify",
"reverify": "Re-verify",
"unsignedTitle": "Unsigned PDF",
"signedTitle": "Signed PDF",
"match": "Hash matches",
"mismatch": "Hash mismatch — file altered",
"missing": "File missing from disk",
"notIssued": "Not yet issued",
"expected": "expected",
"actual": "actual",
"error": "Integrity check failed."
},
"alreadyEventToast": "Already linked to an event.",
"auditEmpty": "No audit-log entries yet.",
"auditTrail": "Audit trail",
"auditTrailHelp": "Every event recorded on this contract. The list is append-only and is the source of truth if the contract is challenged.",
"back": "Back to list",
"blocks": "Included blocks",
"cancel": "Cancel",
"cancelConfirm": "Cancel this contract? Customer signing link will be invalidated.",
"cancelError": "Cancel failed",
"cancelledToast": "Contract cancelled.",
"clearSignature": "Clear",
"confirmConvertEvent": "Convert this contract into an event + scheduled invoices?",
"confirmConvertInvoice": "Convert this contract into invoice(s) only? No gallery / event will be created.",
"confirmCountersign": "Counter-sign",
"confirmResendSigned": "Re-send the signed contract PDF to both parties?",
"confirmRestamp": "Re-stamp & re-render PDF",
"convertError": "Convert failed",
"convertToEvent": "Convert to event",
"convertToInvoice": "Convert to invoice only",
"convertedToEvent": "Converted to event",
"convertedToEventToast": "Contract converted to event #{{id}}",
"convertedToInvoiceToast": "{{count}} invoice(s) created from this contract",
"countersignError": "Counter-sign failed",
"countersignHelp": "Type your name AND draw your signature below — both are stamped onto the re-rendered PDF. IP and timestamp are recorded for audit.",
"countersignSignaturePrompt": "Draw your signature",
"countersignTitle": "Counter-sign to make it binding",
"countersignedToast": "Counter-signed.",
"customer": "Customer",
"dates": "Dates",
"downloadPdf": "Download PDF",
"downloadSignedPdf": "Download signed PDF",
"edit": "Edit",
"fromQuote": "From quote",
"issued": "Issued",
"linkedInvoice": "Invoice",
"newInvoice": "New invoice",
"noBlocks": "No blocks included.",
"noSignatureImage": "No signature image captured — use \"Re-stamp signatures\" below to add one.",
"notFound": "Contract not found.",
"parties": "Parties",
"popupBlocked": "Allow pop-ups for this site to preview the PDF.",
"renderFailedBody": "The signature evidence is recorded, but the stamped PDF was not generated on the last attempt. Click \"Re-send signed PDF\" above to re-stamp from the original document and resend.",
"renderFailedTitle": "Signed PDF stamp failed — re-stamp required",
"resendError": "Resend failed",
"resendSigned": "Re-send signed PDF",
"resentSignedToast": "Signed contract re-sent to both parties.",
"restampAdmin": "Admin signature",
"restampCustomer": "Customer signature",
"restampError": "Re-stamp failed",
"restampHelp": "One or both signatures didn't capture an image. Draw the missing signature(s) here and we'll re-render the PDF. The typed names, timestamps, and IPs already on file stay untouched.",
"restampTitle": "Re-stamp missing signatures",
"restampedToast": "Signatures re-stamped and PDF re-rendered.",
"send": "Send to customer",
"sendError": "Send failed",
"sentAt": "Sent at",
"sentToast": "Contract sent.",
"signBy": "Sign by",
"signatures": "Signatures",
"signedByAdmin": "Counter-signed",
"signedByCustomer": "Signed by customer",
"signedNamePlaceholder": "Your full name",
"uploadError": "Upload failed",
"uploadSigned": "Upload signed PDF",
"uploadedToast": "Signed PDF uploaded."
},
"editor": {
"back": "Back to list",
"backToDetail": "Back to contract",
"create": "Create draft",
"createdToast": "Contract created.",
"customer": "Customer",
"disclaimerBody": "The seeded block bodies are EXAMPLES ONLY — written by the maintainer, not by a lawyer. Have your own lawyer review every block you include before sending the contract. See docs/crm-disclaimers.md.",
"disclaimerTitle": "Lawyer review required",
"eventDate": "Event date",
"eventHelp": "Snapshotted onto the contract and propagated to any event / invoice generated from it. Set this so the customer portal and dunning emails show the right \"Wedding Doe / Müller\" label.",
"eventName": "Event name",
"eventNamePlaceholder": "e.g. Wedding Doe / Müller",
"eventType": "Event type",
"eventTypeNone": "— Use default —",
"eventTypeHint": "Used for the event created when this quote is accepted.",
"bookingWorkflow": "Booking workflow (on acceptance)",
"bookingWorkflowNone": "— None —",
"bookingWorkflowDisabled": "(disabled)",
"bookingWorkflowHint": "The flow that runs when the customer accepts. Leave as None to run no booking flow. The flow must be enabled to fire.",
"eventSection": "Event (optional)",
"eventTimeEnd": "End",
"eventTimeStart": "Start",
"intro": "Intro text (optional)",
"issueDate": "Issue date",
"language": "Language",
"locked": "Sent contracts cannot be edited. Cancel and create a fresh one for amendments.",
"noBlocksInSection": "No blocks for this section yet.",
"outro": "Closing text (optional)",
"popupBlocked": "Allow pop-ups for this site to preview the PDF.",
"preview": "Preview PDF",
"previewAfterSave": "Save the draft first, then preview.",
"previewError": "Preview failed",
"save": "Save",
"saveError": "Save failed",
"savedToast": "Contract saved.",
"schriftformWarning": "Signature type: simple electronic signature (SES). Sufficient for routine photography contracts in CH / DE / AT / FL. NOT sufficient for documents that legally require Schriftform / form qualifiée: Bürgschaft (DE § 766 BGB), Verbraucherdarlehensvertrag (DE § 492 BGB), befristete Arbeitsverträge (DE § 14 Abs. 4 TzBfG), and similar. For those, a qualified electronic signature (QES) from a Trust Service Provider is required — picpeak does not provide QES.",
"searchCustomer": "Search by email…",
"systemBadge": "System",
"titleEdit": "Edit contract",
"titleField": "Contract title",
"titleNew": "New contract",
"titlePlaceholder": "e.g. Wedding contract Doe / Müller",
"validUntil": "Sign by (optional)"
}
},
"messages": {
"title": "Messages",
"subtitle": "Sent, automated and incoming mail — one place.",
"sync": "Sync",
"newMessage": "New message",
"searchPlaceholder": "Search this folder…",
"account": {
"all": "All mail",
"customers": "Customers",
"accounting": "Accounting",
"automated": "Automated"
},
"folder": {
"inbox": "Inbox",
"sent": "Sent",
"archived": "Archived",
"deleted": "Deleted"
},
"unified": "Unified across accounts",
"systemGenerated": "System-generated",
"acrossAccounts": "Across all accounts",
"selectPrompt": "Select a message to read",
"noMessages": "No messages",
"noSearchResults": "No matches",
"noSubject": "(no subject)",
"from": "from",
"to": "To",
"reply": "Reply",
"replyAll": "Reply all",
"forward": "Forward",
"archive": "Archive",
"delete": "Delete",
"deleteForever": "Delete permanently",
"restore": "Restore",
"bookExpense": "Book as expense",
"rebill": "Re-bill to client",
"createQuote": "Quote",
"createContract": "Contract",
"createGallery": "Gallery",
"createInvoice": "Invoice",
"doc": {
"quote": "Quote",
"contract": "Contract",
"invoice": "Invoice",
"gallery": "Gallery"
},
"soon": "Available in a later phase",
"viewDocument": "View document",
"openInAccounting": "Open in Accounting inbox",
"noInboundBody": "No message body was captured for this email.",
"noBody": "This message was sent before body capture was added, so no preview is available.",
"loadError": "Could not load this message.",
"attachments": "attachment(s)",
"notArchived": "not archived yet",
"sentAttachHint": "Sent attachments are not archived yet — Phase 2.",
"document": "Document",
"previewUnavailable": "Preview unavailable",
"rasterNote": "Server-rendered preview — the raw file never reaches the browser.",
"close": "Close",
"compose": "Compose message",
"cancel": "Cancel",
"send": "Send",
"subject": "Subject",
"optional": "optional",
"bodyHint": "Edit the message freely — add a note anywhere before sending.",
"sendsFromHint": "Sends from your configured outgoing address.",
"sentToast": "Message sent.",
"sendFailed": "Failed to send message.",
"onWrote": "On",
"customer": "Customer",
"resolvingCustomer": "Matching the sender to a customer…",
"noCustomerMatch": "No customer matched this sender — search for one or create a new customer above.",
"createNewDoc": "Create new {{label}}",
"existingDocs": "Or reference an existing one",
"noExistingDocs": "No existing documents for this customer yet.",
"galleryCreateOnly": "Galleries are event-based — this opens the event editor, where you can assign the customer.",
"syncOk": "Checked mailboxes — {{count}} new.",
"syncDisabled": "Incoming mail is off — enable it under Settings → Features.",
"syncUnconfigured": "Configure a mailbox under Settings → Email first.",
"syncBusy": "A sync is already running.",
"syncFailed": "Sync failed.",
"actionFailed": "Action failed."
}
}